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   
   36   /**
   37    * NOTE: This will become more open in a future release.
   38    * <p>
   39    * <strong>Warning:</strong>
   40    * Serialized objects of this class will not be compatible with
   41    * future Swing releases. The current serialization support is
   42    * appropriate for short term storage or RMI between applications running
   43    * the same version of Swing.  As of 1.4, support for long term storage
   44    * of all JavaBeans<sup><font size="-2">TM</font></sup>
   45    * has been added to the <code>java.beans</code> package.
   46    * Please see {@link java.beans.XMLEncoder}.
   47    *
   48    * @author Scott Violet
   49    */
   50   
   51   public class FixedHeightLayoutCache extends AbstractLayoutCache {
   52       /** Root node. */
   53       private FHTreeStateNode    root;
   54   
   55       /** Number of rows currently visible. */
   56       private int                rowCount;
   57   
   58       /**
   59        * Used in getting sizes for nodes to avoid creating a new Rectangle
   60        * every time a size is needed.
   61        */
   62       private Rectangle          boundsBuffer;
   63   
   64       /**
   65        * Maps from TreePath to a FHTreeStateNode.
   66        */
   67       private Hashtable<TreePath, FHTreeStateNode> treePathMapping;
   68   
   69       /**
   70        * Used for getting path/row information.
   71        */
   72       private SearchInfo         info;
   73   
   74       private Stack<Stack<TreePath>> tempStacks;
   75   
   76   
   77       public FixedHeightLayoutCache() {
   78           super();
   79           tempStacks = new Stack<Stack<TreePath>>();
   80           boundsBuffer = new Rectangle();
   81           treePathMapping = new Hashtable<TreePath, FHTreeStateNode>();
   82           info = new SearchInfo();
   83           setRowHeight(1);
   84       }
   85   
   86       /**
   87        * Sets the TreeModel that will provide the data.
   88        *
   89        * @param newModel the TreeModel that is to provide the data
   90        */
   91       public void setModel(TreeModel newModel) {
   92           super.setModel(newModel);
   93           rebuild(false);
   94       }
   95   
   96       /**
   97        * Determines whether or not the root node from
   98        * the TreeModel is visible.
   99        *
  100        * @param rootVisible true if the root node of the tree is to be displayed
  101        * @see #rootVisible
  102        */
  103       public void setRootVisible(boolean rootVisible) {
  104           if(isRootVisible() != rootVisible) {
  105               super.setRootVisible(rootVisible);
  106               if(root != null) {
  107                   if(rootVisible) {
  108                       rowCount++;
  109                       root.adjustRowBy(1);
  110                   }
  111                   else {
  112                       rowCount--;
  113                       root.adjustRowBy(-1);
  114                   }
  115                   visibleNodesChanged();
  116               }
  117           }
  118       }
  119   
  120       /**
  121        * Sets the height of each cell. If rowHeight is less than or equal to
  122        * 0 this will throw an IllegalArgumentException.
  123        *
  124        * @param rowHeight the height of each cell, in pixels
  125        */
  126       public void setRowHeight(int rowHeight) {
  127           if(rowHeight <= 0)
  128               throw new IllegalArgumentException("FixedHeightLayoutCache only supports row heights greater than 0");
  129           if(getRowHeight() != rowHeight) {
  130               super.setRowHeight(rowHeight);
  131               visibleNodesChanged();
  132           }
  133       }
  134   
  135       /**
  136        * Returns the number of visible rows.
  137        */
  138       public int getRowCount() {
  139           return rowCount;
  140       }
  141   
  142       /**
  143        * Does nothing, FixedHeightLayoutCache doesn't cache width, and that
  144        * is all that could change.
  145        */
  146       public void invalidatePathBounds(TreePath path) {
  147       }
  148   
  149   
  150       /**
  151        * Informs the TreeState that it needs to recalculate all the sizes
  152        * it is referencing.
  153        */
  154       public void invalidateSizes() {
  155           // Nothing to do here, rowHeight still same, which is all
  156           // this is interested in, visible region may have changed though.
  157           visibleNodesChanged();
  158       }
  159   
  160       /**
  161         * Returns true if the value identified by row is currently expanded.
  162         */
  163       public boolean isExpanded(TreePath path) {
  164           if(path != null) {
  165               FHTreeStateNode     lastNode = getNodeForPath(path, true, false);
  166   
  167               return (lastNode != null && lastNode.isExpanded());
  168           }
  169           return false;
  170       }
  171   
  172       /**
  173        * Returns a rectangle giving the bounds needed to draw path.
  174        *
  175        * @param path     a TreePath specifying a node
  176        * @param placeIn  a Rectangle object giving the available space
  177        * @return a Rectangle object specifying the space to be used
  178        */
  179       public Rectangle getBounds(TreePath path, Rectangle placeIn) {
  180           if(path == null)
  181               return null;
  182   
  183           FHTreeStateNode      node = getNodeForPath(path, true, false);
  184   
  185           if(node != null)
  186               return getBounds(node, -1, placeIn);
  187   
  188           // node hasn't been created yet.
  189           TreePath       parentPath = path.getParentPath();
  190   
  191           node = getNodeForPath(parentPath, true, false);
  192           if (node != null && node.isExpanded()) {
  193               int              childIndex = treeModel.getIndexOfChild
  194                                    (parentPath.getLastPathComponent(),
  195                                     path.getLastPathComponent());
  196   
  197               if(childIndex != -1)
  198                   return getBounds(node, childIndex, placeIn);
  199           }
  200           return null;
  201       }
  202   
  203       /**
  204         * Returns the path for passed in row.  If row is not visible
  205         * null is returned.
  206         */
  207       public TreePath getPathForRow(int row) {
  208           if(row >= 0 && row < getRowCount()) {
  209               if(root.getPathForRow(row, getRowCount(), info)) {
  210                   return info.getPath();
  211               }
  212           }
  213           return null;
  214       }
  215   
  216       /**
  217         * Returns the row that the last item identified in path is visible
  218         * at.  Will return -1 if any of the elements in path are not
  219         * currently visible.
  220         */
  221       public int getRowForPath(TreePath path) {
  222           if(path == null || root == null)
  223               return -1;
  224   
  225           FHTreeStateNode         node = getNodeForPath(path, true, false);
  226   
  227           if(node != null)
  228               return node.getRow();
  229   
  230           TreePath       parentPath = path.getParentPath();
  231   
  232           node = getNodeForPath(parentPath, true, false);
  233           if(node != null && node.isExpanded()) {
  234               return node.getRowToModelIndex(treeModel.getIndexOfChild
  235                                              (parentPath.getLastPathComponent(),
  236                                               path.getLastPathComponent()));
  237           }
  238           return -1;
  239       }
  240   
  241       /**
  242         * Returns the path to the node that is closest to x,y.  If
  243         * there is nothing currently visible this will return null, otherwise
  244         * it'll always return a valid path.  If you need to test if the
  245         * returned object is exactly at x, y you should get the bounds for
  246         * the returned path and test x, y against that.
  247         */
  248       public TreePath getPathClosestTo(int x, int y) {
  249           if(getRowCount() == 0)
  250               return null;
  251   
  252           int                row = getRowContainingYLocation(y);
  253   
  254           return getPathForRow(row);
  255       }
  256   
  257       /**
  258        * Returns the number of visible children for row.
  259        */
  260       public int getVisibleChildCount(TreePath path) {
  261           FHTreeStateNode         node = getNodeForPath(path, true, false);
  262   
  263           if(node == null)
  264               return 0;
  265           return node.getTotalChildCount();
  266       }
  267   
  268       /**
  269        * Returns an Enumerator that increments over the visible paths
  270        * starting at the passed in location. The ordering of the enumeration
  271        * is based on how the paths are displayed.
  272        */
  273       public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
  274           if(path == null)
  275               return null;
  276   
  277           FHTreeStateNode         node = getNodeForPath(path, true, false);
  278   
  279           if(node != null) {
  280               return new VisibleFHTreeStateNodeEnumeration(node);
  281           }
  282           TreePath            parentPath = path.getParentPath();
  283   
  284           node = getNodeForPath(parentPath, true, false);
  285           if(node != null && node.isExpanded()) {
  286               return new VisibleFHTreeStateNodeEnumeration(node,
  287                     treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
  288                                               path.getLastPathComponent()));
  289           }
  290           return null;
  291       }
  292   
  293       /**
  294        * Marks the path <code>path</code> expanded state to
  295        * <code>isExpanded</code>.
  296        */
  297       public void setExpandedState(TreePath path, boolean isExpanded) {
  298           if(isExpanded)
  299               ensurePathIsExpanded(path, true);
  300           else if(path != null) {
  301               TreePath              parentPath = path.getParentPath();
  302   
  303               // YECK! Make the parent expanded.
  304               if(parentPath != null) {
  305                   FHTreeStateNode     parentNode = getNodeForPath(parentPath,
  306                                                                   false, true);
  307                   if(parentNode != null)
  308                       parentNode.makeVisible();
  309               }
  310               // And collapse the child.
  311               FHTreeStateNode         childNode = getNodeForPath(path, true,
  312                                                                  false);
  313   
  314               if(childNode != null)
  315                   childNode.collapse(true);
  316           }
  317       }
  318   
  319       /**
  320        * Returns true if the path is expanded, and visible.
  321        */
  322       public boolean getExpandedState(TreePath path) {
  323           FHTreeStateNode       node = getNodeForPath(path, true, false);
  324   
  325           return (node != null) ? (node.isVisible() && node.isExpanded()) :
  326                                    false;
  327       }
  328   
  329       //
  330       // TreeModelListener methods
  331       //
  332   
  333       /**
  334        * <p>Invoked after a node (or a set of siblings) has changed in some
  335        * way. The node(s) have not changed locations in the tree or
  336        * altered their children arrays, but other attributes have
  337        * changed and may affect presentation. Example: the name of a
  338        * file has changed, but it is in the same location in the file
  339        * system.</p>
  340        *
  341        * <p>e.path() returns the path the parent of the changed node(s).</p>
  342        *
  343        * <p>e.childIndices() returns the index(es) of the changed node(s).</p>
  344        */
  345       public void treeNodesChanged(TreeModelEvent e) {
  346           if(e != null) {
  347               int                 changedIndexs[];
  348               FHTreeStateNode     changedParent = getNodeForPath
  349                                     (e.getTreePath(), false, false);
  350               int                 maxCounter;
  351   
  352               changedIndexs = e.getChildIndices();
  353               /* Only need to update the children if the node has been
  354                  expanded once. */
  355               // PENDING(scott): make sure childIndexs is sorted!
  356               if (changedParent != null) {
  357                   if (changedIndexs != null &&
  358                       (maxCounter = changedIndexs.length) > 0) {
  359                       Object       parentValue = changedParent.getUserObject();
  360   
  361                       for(int counter = 0; counter < maxCounter; counter++) {
  362                           FHTreeStateNode    child = changedParent.
  363                                    getChildAtModelIndex(changedIndexs[counter]);
  364   
  365                           if(child != null) {
  366                               child.setUserObject(treeModel.getChild(parentValue,
  367                                                        changedIndexs[counter]));
  368                           }
  369                       }
  370                       if(changedParent.isVisible() && changedParent.isExpanded())
  371                           visibleNodesChanged();
  372                   }
  373                   // Null for root indicates it changed.
  374                   else if (changedParent == root && changedParent.isVisible() &&
  375                            changedParent.isExpanded()) {
  376                       visibleNodesChanged();
  377                   }
  378               }
  379           }
  380       }
  381   
  382       /**
  383        * <p>Invoked after nodes have been inserted into the tree.</p>
  384        *
  385        * <p>e.path() returns the parent of the new nodes
  386        * <p>e.childIndices() returns the indices of the new nodes in
  387        * ascending order.
  388        */
  389       public void treeNodesInserted(TreeModelEvent e) {
  390           if(e != null) {
  391               int                 changedIndexs[];
  392               FHTreeStateNode     changedParent = getNodeForPath
  393                                     (e.getTreePath(), false, false);
  394               int                 maxCounter;
  395   
  396               changedIndexs = e.getChildIndices();
  397               /* Only need to update the children if the node has been
  398                  expanded once. */
  399               // PENDING(scott): make sure childIndexs is sorted!
  400               if(changedParent != null && changedIndexs != null &&
  401                  (maxCounter = changedIndexs.length) > 0) {
  402                   boolean          isVisible =
  403                       (changedParent.isVisible() &&
  404                        changedParent.isExpanded());
  405   
  406                   for(int counter = 0; counter < maxCounter; counter++) {
  407                       changedParent.childInsertedAtModelIndex
  408                           (changedIndexs[counter], isVisible);
  409                   }
  410                   if(isVisible && treeSelectionModel != null)
  411                       treeSelectionModel.resetRowSelection();
  412                   if(changedParent.isVisible())
  413                       this.visibleNodesChanged();
  414               }
  415           }
  416       }
  417   
  418       /**
  419        * <p>Invoked after nodes have been removed from the tree.  Note that
  420        * if a subtree is removed from the tree, this method may only be
  421        * invoked once for the root of the removed subtree, not once for
  422        * each individual set of siblings removed.</p>
  423        *
  424        * <p>e.path() returns the former parent of the deleted nodes.</p>
  425        *
  426        * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p>
  427        */
  428       public void treeNodesRemoved(TreeModelEvent e) {
  429           if(e != null) {
  430               int                  changedIndexs[];
  431               int                  maxCounter;
  432               TreePath             parentPath = e.getTreePath();
  433               FHTreeStateNode      changedParentNode = getNodeForPath
  434                                          (parentPath, false, false);
  435   
  436               changedIndexs = e.getChildIndices();
  437               // PENDING(scott): make sure that changedIndexs are sorted in
  438               // ascending order.
  439               if(changedParentNode != null && changedIndexs != null &&
  440                  (maxCounter = changedIndexs.length) > 0) {
  441                   Object[]           children = e.getChildren();
  442                   boolean            isVisible =
  443                       (changedParentNode.isVisible() &&
  444                        changedParentNode.isExpanded());
  445   
  446                   for(int counter = maxCounter - 1; counter >= 0; counter--) {
  447                       changedParentNode.removeChildAtModelIndex
  448                                        (changedIndexs[counter], isVisible);
  449                   }
  450                   if(isVisible) {
  451                       if(treeSelectionModel != null)
  452                           treeSelectionModel.resetRowSelection();
  453                       if (treeModel.getChildCount(changedParentNode.
  454                                                   getUserObject()) == 0 &&
  455                                     changedParentNode.isLeaf()) {
  456                           // Node has become a leaf, collapse it.
  457                           changedParentNode.collapse(false);
  458                       }
  459                       visibleNodesChanged();
  460                   }
  461                   else if(changedParentNode.isVisible())
  462                       visibleNodesChanged();
  463               }
  464           }
  465       }
  466   
  467       /**
  468        * <p>Invoked after the tree has drastically changed structure from a
  469        * given node down.  If the path returned by e.getPath() is of length
  470        * one and the first element does not identify the current root node
  471        * the first element should become the new root of the tree.<p>
  472        *
  473        * <p>e.path() holds the path to the node.</p>
  474        * <p>e.childIndices() returns null.</p>
  475        */
  476       public void treeStructureChanged(TreeModelEvent e) {
  477           if(e != null) {
  478               TreePath          changedPath = e.getTreePath();
  479               FHTreeStateNode   changedNode = getNodeForPath
  480                                                   (changedPath, false, false);
  481   
  482               // Check if root has changed, either to a null root, or
  483               // to an entirely new root.
  484               if (changedNode == root ||
  485                   (changedNode == null &&
  486                    ((changedPath == null && treeModel != null &&
  487                      treeModel.getRoot() == null) ||
  488                     (changedPath != null && changedPath.getPathCount() <= 1)))) {
  489                   rebuild(true);
  490               }
  491               else if(changedNode != null) {
  492                   boolean             wasExpanded, wasVisible;
  493                   FHTreeStateNode     parent = (FHTreeStateNode)
  494                                                 changedNode.getParent();
  495   
  496                   wasExpanded = changedNode.isExpanded();
  497                   wasVisible = changedNode.isVisible();
  498   
  499                   int index = parent.getIndex(changedNode);
  500                   changedNode.collapse(false);
  501                   parent.remove(index);
  502   
  503                   if(wasVisible && wasExpanded) {
  504                       int row = changedNode.getRow();
  505                       parent.resetChildrenRowsFrom(row, index,
  506                                                    changedNode.getChildIndex());
  507                       changedNode = getNodeForPath(changedPath, false, true);
  508                       changedNode.expand();
  509                   }
  510                   if(treeSelectionModel != null && wasVisible && wasExpanded)
  511                       treeSelectionModel.resetRowSelection();
  512                   if(wasVisible)
  513                       this.visibleNodesChanged();
  514               }
  515           }
  516       }
  517   
  518   
  519       //
  520       // Local methods
  521       //
  522   
  523       private void visibleNodesChanged() {
  524       }
  525   
  526       /**
  527        * Returns the bounds for the given node. If <code>childIndex</code>
  528        * is -1, the bounds of <code>parent</code> are returned, otherwise
  529        * the bounds of the node at <code>childIndex</code> are returned.
  530        */
  531       private Rectangle getBounds(FHTreeStateNode parent, int childIndex,
  532                                     Rectangle placeIn) {
  533           boolean              expanded;
  534           int                  level;
  535           int                  row;
  536           Object               value;
  537   
  538           if(childIndex == -1) {
  539               // Getting bounds for parent
  540               row = parent.getRow();
  541               value = parent.getUserObject();
  542               expanded = parent.isExpanded();
  543               level = parent.getLevel();
  544           }
  545           else {
  546               row = parent.getRowToModelIndex(childIndex);
  547               value = treeModel.getChild(parent.getUserObject(), childIndex);
  548               expanded = false;
  549               level = parent.getLevel() + 1;
  550           }
  551   
  552           Rectangle      bounds = getNodeDimensions(value, row, level,
  553                                                     expanded, boundsBuffer);
  554           // No node dimensions, bail.
  555           if(bounds == null)
  556               return null;
  557   
  558           if(placeIn == null)
  559               placeIn = new Rectangle();
  560   
  561           placeIn.x = bounds.x;
  562           placeIn.height = getRowHeight();
  563           placeIn.y = row * placeIn.height;
  564           placeIn.width = bounds.width;
  565           return placeIn;
  566       }
  567   
  568       /**
  569        * Adjust the large row count of the AbstractTreeUI the receiver was
  570        * created with.
  571        */
  572       private void adjustRowCountBy(int changeAmount) {
  573           rowCount += changeAmount;
  574       }
  575   
  576       /**
  577        * Adds a mapping for node.
  578        */
  579       private void addMapping(FHTreeStateNode node) {
  580           treePathMapping.put(node.getTreePath(), node);
  581       }
  582   
  583       /**
  584        * Removes the mapping for a previously added node.
  585        */
  586       private void removeMapping(FHTreeStateNode node) {
  587           treePathMapping.remove(node.getTreePath());
  588       }
  589   
  590       /**
  591        * Returns the node previously added for <code>path</code>. This may
  592        * return null, if you to create a node use getNodeForPath.
  593        */
  594       private FHTreeStateNode getMapping(TreePath path) {
  595           return treePathMapping.get(path);
  596       }
  597   
  598       /**
  599        * Sent to completely rebuild the visible tree. All nodes are collapsed.
  600        */
  601       private void rebuild(boolean clearSelection) {
  602           Object            rootUO;
  603   
  604           treePathMapping.clear();
  605           if(treeModel != null && (rootUO = treeModel.getRoot()) != null) {
  606               root = createNodeForValue(rootUO, 0);
  607               root.path = new TreePath(rootUO);
  608               addMapping(root);
  609               if(isRootVisible()) {
  610                   rowCount = 1;
  611                   root.row = 0;
  612               }
  613               else {
  614                   rowCount = 0;
  615                   root.row = -1;
  616               }
  617               root.expand();
  618           }
  619           else {
  620               root = null;
  621               rowCount = 0;
  622           }
  623           if(clearSelection && treeSelectionModel != null) {
  624               treeSelectionModel.clearSelection();
  625           }
  626           this.visibleNodesChanged();
  627       }
  628   
  629       /**
  630         * Returns the index of the row containing location.  If there
  631         * are no rows, -1 is returned.  If location is beyond the last
  632         * row index, the last row index is returned.
  633         */
  634       private int getRowContainingYLocation(int location) {
  635           if(getRowCount() == 0)
  636               return -1;
  637           return Math.max(0, Math.min(getRowCount() - 1,
  638                                       location / getRowHeight()));
  639       }
  640   
  641       /**
  642        * Ensures that all the path components in path are expanded, accept
  643        * for the last component which will only be expanded if expandLast
  644        * is true.
  645        * Returns true if succesful in finding the path.
  646        */
  647       private boolean ensurePathIsExpanded(TreePath aPath,
  648                                              boolean expandLast) {
  649           if(aPath != null) {
  650               // Make sure the last entry isn't a leaf.
  651               if(treeModel.isLeaf(aPath.getLastPathComponent())) {
  652                   aPath = aPath.getParentPath();
  653                   expandLast = true;
  654               }
  655               if(aPath != null) {
  656                   FHTreeStateNode     lastNode = getNodeForPath(aPath, false,
  657                                                                 true);
  658   
  659                   if(lastNode != null) {
  660                       lastNode.makeVisible();
  661                       if(expandLast)
  662                           lastNode.expand();
  663                       return true;
  664                   }
  665               }
  666           }
  667           return false;
  668       }
  669   
  670       /**
  671        * Creates and returns an instance of FHTreeStateNode.
  672        */
  673       private FHTreeStateNode createNodeForValue(Object value,int childIndex) {
  674           return new FHTreeStateNode(value, childIndex, -1);
  675       }
  676   
  677       /**
  678        * Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate,
  679        * path.length) as long as path is non-null and the length is > 0.
  680        * Otherwise returns null.
  681        */
  682       private FHTreeStateNode getNodeForPath(TreePath path,
  683                                                boolean onlyIfVisible,
  684                                                boolean shouldCreate) {
  685           if(path != null) {
  686               FHTreeStateNode      node;
  687   
  688               node = getMapping(path);
  689               if(node != null) {
  690                   if(onlyIfVisible && !node.isVisible())
  691                       return null;
  692                   return node;
  693               }
  694               if(onlyIfVisible)
  695                   return null;
  696   
  697               // Check all the parent paths, until a match is found.
  698               Stack<TreePath> paths;
  699   
  700               if(tempStacks.size() == 0) {
  701                   paths = new Stack<TreePath>();
  702               }
  703               else {
  704                   paths = tempStacks.pop();
  705               }
  706   
  707               try {
  708                   paths.push(path);
  709                   path = path.getParentPath();
  710                   node = null;
  711                   while(path != null) {
  712                       node = getMapping(path);
  713                       if(node != null) {
  714                           // Found a match, create entries for all paths in
  715                           // paths.
  716                           while(node != null && paths.size() > 0) {
  717                               path = paths.pop();
  718                               node = node.createChildFor(path.
  719                                                          getLastPathComponent());
  720                           }
  721                           return node;
  722                       }
  723                       paths.push(path);
  724                       path = path.getParentPath();
  725                   }
  726               }
  727               finally {
  728                   paths.removeAllElements();
  729                   tempStacks.push(paths);
  730               }
  731               // If we get here it means they share a different root!
  732               return null;
  733           }
  734           return null;
  735       }
  736   
  737       /**
  738        * FHTreeStateNode is used to track what has been expanded.
  739        * FHTreeStateNode differs from VariableHeightTreeState.TreeStateNode
  740        * in that it is highly model intensive. That is almost all queries to a
  741        * FHTreeStateNode result in the TreeModel being queried. And it
  742        * obviously does not support variable sized row heights.
  743        */
  744       private class FHTreeStateNode extends DefaultMutableTreeNode {
  745           /** Is this node expanded? */
  746           protected boolean         isExpanded;
  747   
  748           /** Index of this node from the model. */
  749           protected int             childIndex;
  750   
  751           /** Child count of the receiver. */
  752           protected int             childCount;
  753   
  754           /** Row of the receiver. This is only valid if the row is expanded.
  755            */
  756           protected int             row;
  757   
  758           /** Path of this node. */
  759           protected TreePath        path;
  760   
  761   
  762           public FHTreeStateNode(Object userObject, int childIndex, int row) {
  763               super(userObject);
  764               this.childIndex = childIndex;
  765               this.row = row;
  766           }
  767   
  768           //
  769           // Overriden DefaultMutableTreeNode methods
  770           //
  771   
  772           /**
  773            * Messaged when this node is added somewhere, resets the path
  774            * and adds a mapping from path to this node.
  775            */
  776           public void setParent(MutableTreeNode parent) {
  777               super.setParent(parent);
  778               if(parent != null) {
  779                   path = ((FHTreeStateNode)parent).getTreePath().
  780                               pathByAddingChild(getUserObject());
  781                   addMapping(this);
  782               }
  783           }
  784   
  785           /**
  786            * Messaged when this node is removed from its parent, this messages
  787            * <code>removedFromMapping</code> to remove all the children.
  788            */
  789           public void remove(int childIndex) {
  790               FHTreeStateNode     node = (FHTreeStateNode)getChildAt(childIndex);
  791   
  792               node.removeFromMapping();
  793               super.remove(childIndex);
  794           }
  795   
  796           /**
  797            * Messaged to set the user object. This resets the path.
  798            */
  799           public void setUserObject(Object o) {
  800               super.setUserObject(o);
  801               if(path != null) {
  802                   FHTreeStateNode      parent = (FHTreeStateNode)getParent();
  803   
  804                   if(parent != null)
  805                       resetChildrenPaths(parent.getTreePath());
  806                   else
  807                       resetChildrenPaths(null);
  808               }
  809           }
  810   
  811           //
  812           //
  813   
  814           /**
  815            * Returns the index of the receiver in the model.
  816            */
  817           public int getChildIndex() {
  818               return childIndex;
  819           }
  820   
  821           /**
  822            * Returns the <code>TreePath</code> of the receiver.
  823            */
  824           public TreePath getTreePath() {
  825               return path;
  826           }
  827   
  828           /**
  829            * Returns the child for the passed in model index, this will
  830            * return <code>null</code> if the child for <code>index</code>
  831            * has not yet been created (expanded).
  832            */
  833           public FHTreeStateNode getChildAtModelIndex(int index) {
  834               // PENDING: Make this a binary search!
  835               for(int counter = getChildCount() - 1; counter >= 0; counter--)
  836                   if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
  837                       return (FHTreeStateNode)getChildAt(counter);
  838               return null;
  839           }
  840   
  841           /**
  842            * Returns true if this node is visible. This is determined by
  843            * asking all the parents if they are expanded.
  844            */
  845           public boolean isVisible() {
  846               FHTreeStateNode         parent = (FHTreeStateNode)getParent();
  847   
  848               if(parent == null)
  849                   return true;
  850               return (parent.isExpanded() && parent.isVisible());
  851           }
  852   
  853           /**
  854            * Returns the row of the receiver.
  855            */
  856           public int getRow() {
  857               return row;
  858           }
  859   
  860           /**
  861            * Returns the row of the child with a model index of
  862            * <code>index</code>.
  863            */
  864           public int getRowToModelIndex(int index) {
  865               FHTreeStateNode      child;
  866               int                  lastRow = getRow() + 1;
  867               int                  retValue = lastRow;
  868   
  869               // This too could be a binary search!
  870               for(int counter = 0, maxCounter = getChildCount();
  871                   counter < maxCounter; counter++) {
  872                   child = (FHTreeStateNode)getChildAt(counter);
  873                   if(child.childIndex >= index) {
  874                       if(child.childIndex == index)
  875                           return child.row;
  876                       if(counter == 0)
  877                           return getRow() + 1 + index;
  878                       return child.row - (child.childIndex - index);
  879                   }
  880               }
  881               // YECK!
  882               return getRow() + 1 + getTotalChildCount() -
  883                                (childCount - index);
  884           }
  885   
  886           /**
  887            * Returns the number of children in the receiver by descending all
  888            * expanded nodes and messaging them with getTotalChildCount.
  889            */
  890           public int getTotalChildCount() {
  891               if(isExpanded()) {
  892                   FHTreeStateNode      parent = (FHTreeStateNode)getParent();
  893                   int                  pIndex;
  894   
  895                   if(parent != null && (pIndex = parent.getIndex(this)) + 1 <
  896                      parent.getChildCount()) {
  897                       // This node has a created sibling, to calc total
  898                       // child count directly from that!
  899                       FHTreeStateNode  nextSibling = (FHTreeStateNode)parent.
  900                                              getChildAt(pIndex + 1);
  901   
  902                       return nextSibling.row - row -
  903                              (nextSibling.childIndex - childIndex);
  904                   }
  905                   else {
  906                       int retCount = childCount;
  907   
  908                       for(int counter = getChildCount() - 1; counter >= 0;
  909                           counter--) {
  910                           retCount += ((FHTreeStateNode)getChildAt(counter))
  911                                                     .getTotalChildCount();
  912                       }
  913                       return retCount;
  914                   }
  915               }
  916               return 0;
  917           }
  918   
  919           /**
  920            * Returns true if this node is expanded.
  921            */
  922           public boolean isExpanded() {
  923               return isExpanded;
  924           }
  925   
  926           /**
  927            * The highest visible nodes have a depth of 0.
  928            */
  929           public int getVisibleLevel() {
  930               if (isRootVisible()) {
  931                   return getLevel();
  932               } else {
  933                   return getLevel() - 1;
  934               }
  935           }
  936   
  937           /**
  938            * Recreates the receivers path, and all its childrens paths.
  939            */
  940           protected void resetChildrenPaths(TreePath parentPath) {
  941               removeMapping(this);
  942               if(parentPath == null)
  943                   path = new TreePath(getUserObject());
  944               else
  945                   path = parentPath.pathByAddingChild(getUserObject());
  946               addMapping(this);
  947               for(int counter = getChildCount() - 1; counter >= 0; counter--)
  948                   ((FHTreeStateNode)getChildAt(counter)).
  949                                  resetChildrenPaths(path);
  950           }
  951   
  952           /**
  953            * Removes the receiver, and all its children, from the mapping
  954            * table.
  955            */
  956           protected void removeFromMapping() {
  957               if(path != null) {
  958                   removeMapping(this);
  959                   for(int counter = getChildCount() - 1; counter >= 0; counter--)
  960                       ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
  961               }
  962           }
  963   
  964           /**
  965            * Creates a new node to represent <code>userObject</code>.
  966            * This does NOT check to ensure there isn't already a child node
  967            * to manage <code>userObject</code>.
  968            */
  969           protected FHTreeStateNode createChildFor(Object userObject) {
  970               int      newChildIndex = treeModel.getIndexOfChild
  971                                        (getUserObject(), userObject);
  972   
  973               if(newChildIndex < 0)
  974                   return null;
  975   
  976               FHTreeStateNode     aNode;
  977               FHTreeStateNode     child = createNodeForValue(userObject,
  978                                                              newChildIndex);
  979               int                 childRow;
  980   
  981               if(isVisible()) {
  982                   childRow = getRowToModelIndex(newChildIndex);
  983               }
  984               else {
  985                   childRow = -1;
  986               }
  987               child.row = childRow;
  988               for(int counter = 0, maxCounter = getChildCount();
  989                   counter < maxCounter; counter++) {
  990                   aNode = (FHTreeStateNode)getChildAt(counter);
  991                   if(aNode.childIndex > newChildIndex) {
  992                       insert(child, counter);
  993                       return child;
  994                   }
  995               }
  996               add(child);
  997               return child;
  998           }
  999   
 1000           /**
 1001            * Adjusts the receiver, and all its children rows by
 1002            * <code>amount</code>.
 1003            */
 1004           protected void adjustRowBy(int amount) {
 1005               row += amount;
 1006               if(isExpanded) {
 1007                   for(int counter = getChildCount() - 1; counter >= 0;
 1008                       counter--)
 1009                       ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
 1010               }
 1011           }
 1012   
 1013           /**
 1014            * Adjusts this node, its child, and its parent starting at
 1015            * an index of <code>index</code> index is the index of the child
 1016            * to start adjusting from, which is not necessarily the model
 1017            * index.
 1018            */
 1019           protected void adjustRowBy(int amount, int startIndex) {
 1020               // Could check isVisible, but probably isn't worth it.
 1021               if(isExpanded) {
 1022                   // children following startIndex.
 1023                   for(int counter = getChildCount() - 1; counter >= startIndex;
 1024                       counter--)
 1025                       ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
 1026               }
 1027               // Parent
 1028               FHTreeStateNode        parent = (FHTreeStateNode)getParent();
 1029   
 1030               if(parent != null) {
 1031                   parent.adjustRowBy(amount, parent.getIndex(this) + 1);
 1032               }
 1033           }
 1034   
 1035           /**
 1036            * Messaged when the node has expanded. This updates all of
 1037            * the receivers children rows, as well as the total row count.
 1038            */
 1039           protected void didExpand() {
 1040               int               nextRow = setRowAndChildren(row);
 1041               FHTreeStateNode   parent = (FHTreeStateNode)getParent();
 1042               int               childRowCount = nextRow - row - 1;
 1043   
 1044               if(parent != null) {
 1045                   parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
 1046               }
 1047               adjustRowCountBy(childRowCount);
 1048           }
 1049   
 1050           /**
 1051            * Sets the receivers row to <code>nextRow</code> and recursively
 1052            * updates all the children of the receivers rows. The index the
 1053            * next row is to be placed as is returned.
 1054            */
 1055           protected int setRowAndChildren(int nextRow) {
 1056               row = nextRow;
 1057   
 1058               if(!isExpanded())
 1059                   return row + 1;
 1060   
 1061               int              lastRow = row + 1;
 1062               int              lastModelIndex = 0;
 1063               FHTreeStateNode  child;
 1064               int              maxCounter = getChildCount();
 1065   
 1066               for(int counter = 0; counter < maxCounter; counter++) {
 1067                   child = (FHTreeStateNode)getChildAt(counter);
 1068                   lastRow += (child.childIndex - lastModelIndex);
 1069                   lastModelIndex = child.childIndex + 1;
 1070                   if(child.isExpanded) {
 1071                       lastRow = child.setRowAndChildren(lastRow);
 1072                   }
 1073                   else {
 1074                       child.row = lastRow++;
 1075                   }
 1076               }
 1077               return lastRow + childCount - lastModelIndex;
 1078           }
 1079   
 1080           /**
 1081            * Resets the receivers childrens rows. Starting with the child
 1082            * at <code>childIndex</code> (and <code>modelIndex</code>) to
 1083            * <code>newRow</code>. This uses <code>setRowAndChildren</code>
 1084            * to recursively descend children, and uses
 1085            * <code>resetRowSelection</code> to ascend parents.
 1086            */
 1087           // This can be rather expensive, but is needed for the collapse
 1088           // case this is resulting from a remove (although I could fix
 1089           // that by having instances of FHTreeStateNode hold a ref to
 1090           // the number of children). I prefer this though, making determing
 1091           // the row of a particular node fast is very nice!
 1092           protected void resetChildrenRowsFrom(int newRow, int childIndex,
 1093                                               int modelIndex) {
 1094               int              lastRow = newRow;
 1095               int              lastModelIndex = modelIndex;
 1096               FHTreeStateNode  node;
 1097               int              maxCounter = getChildCount();
 1098   
 1099               for(int counter = childIndex; counter < maxCounter; counter++) {
 1100                   node = (FHTreeStateNode)getChildAt(counter);
 1101                   lastRow += (node.childIndex - lastModelIndex);
 1102                   lastModelIndex = node.childIndex + 1;
 1103                   if(node.isExpanded) {
 1104                       lastRow = node.setRowAndChildren(lastRow);
 1105                   }
 1106                   else {
 1107                       node.row = lastRow++;
 1108                   }
 1109               }
 1110               lastRow += childCount - lastModelIndex;
 1111               node = (FHTreeStateNode)getParent();
 1112               if(node != null) {
 1113                   node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
 1114                                              this.childIndex + 1);
 1115               }
 1116               else { // This is the root, reset total ROWCOUNT!
 1117                   rowCount = lastRow;
 1118               }
 1119           }
 1120   
 1121           /**
 1122            * Makes the receiver visible, but invoking
 1123            * <code>expandParentAndReceiver</code> on the superclass.
 1124            */
 1125           protected void makeVisible() {
 1126               FHTreeStateNode       parent = (FHTreeStateNode)getParent();
 1127   
 1128               if(parent != null)
 1129                   parent.expandParentAndReceiver();
 1130           }
 1131   
 1132           /**
 1133            * Invokes <code>expandParentAndReceiver</code> on the parent,
 1134            * and expands the receiver.
 1135            */
 1136           protected void expandParentAndReceiver() {
 1137               FHTreeStateNode       parent = (FHTreeStateNode)getParent();
 1138   
 1139               if(parent != null)
 1140                   parent.expandParentAndReceiver();
 1141               expand();
 1142           }
 1143   
 1144           /**
 1145            * Expands the receiver.
 1146            */
 1147           protected void expand() {
 1148               if(!isExpanded && !isLeaf()) {
 1149                   boolean            visible = isVisible();
 1150   
 1151                   isExpanded = true;
 1152                   childCount = treeModel.getChildCount(getUserObject());
 1153   
 1154                   if(visible) {
 1155                       didExpand();
 1156                   }
 1157   
 1158                   // Update the selection model.
 1159                   if(visible && treeSelectionModel != null) {
 1160                       treeSelectionModel.resetRowSelection();
 1161                   }
 1162               }
 1163           }
 1164   
 1165           /**
 1166            * Collapses the receiver. If <code>adjustRows</code> is true,
 1167            * the rows of nodes after the receiver are adjusted.
 1168            */
 1169           protected void collapse(boolean adjustRows) {
 1170               if(isExpanded) {
 1171                   if(isVisible() && adjustRows) {
 1172                       int              childCount = getTotalChildCount();
 1173   
 1174                       isExpanded = false;
 1175                       adjustRowCountBy(-childCount);
 1176                       // We can do this because adjustRowBy won't descend
 1177                       // the children.
 1178                       adjustRowBy(-childCount, 0);
 1179                   }
 1180                   else
 1181                       isExpanded = false;
 1182   
 1183                   if(adjustRows && isVisible() && treeSelectionModel != null)
 1184                       treeSelectionModel.resetRowSelection();
 1185               }
 1186           }
 1187   
 1188           /**
 1189            * Returns true if the receiver is a leaf.
 1190            */
 1191           public boolean isLeaf() {
 1192               TreeModel model = getModel();
 1193   
 1194               return (model != null) ? model.isLeaf(this.getUserObject()) :
 1195                      true;
 1196           }
 1197   
 1198           /**
 1199            * Adds newChild to this nodes children at the appropriate location.
 1200            * The location is determined from the childIndex of newChild.
 1201            */
 1202           protected void addNode(FHTreeStateNode newChild) {
 1203               boolean         added = false;
 1204               int             childIndex = newChild.getChildIndex();
 1205   
 1206               for(int counter = 0, maxCounter = getChildCount();
 1207                   counter < maxCounter; counter++) {
 1208                   if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
 1209                      childIndex) {
 1210                       added = true;
 1211                       insert(newChild, counter);
 1212                       counter = maxCounter;
 1213                   }
 1214               }
 1215               if(!added)
 1216                   add(newChild);
 1217           }
 1218   
 1219           /**
 1220            * Removes the child at <code>modelIndex</code>.
 1221            * <code>isChildVisible</code> should be true if the receiver
 1222            * is visible and expanded.
 1223            */
 1224           protected void removeChildAtModelIndex(int modelIndex,
 1225                                                  boolean isChildVisible) {
 1226               FHTreeStateNode     childNode = getChildAtModelIndex(modelIndex);
 1227   
 1228               if(childNode != null) {
 1229                   int          row = childNode.getRow();
 1230                   int          index = getIndex(childNode);
 1231   
 1232                   childNode.collapse(false);
 1233                   remove(index);
 1234                   adjustChildIndexs(index, -1);
 1235                   childCount--;
 1236                   if(isChildVisible) {
 1237                       // Adjust the rows.
 1238                       resetChildrenRowsFrom(row, index, modelIndex);
 1239                   }
 1240               }
 1241               else {
 1242                   int                  maxCounter = getChildCount();
 1243                   FHTreeStateNode      aChild;
 1244   
 1245                   for(int counter = 0; counter < maxCounter; counter++) {
 1246                       aChild = (FHTreeStateNode)getChildAt(counter);
 1247                       if(aChild.childIndex >= modelIndex) {
 1248                           if(isChildVisible) {
 1249                               adjustRowBy(-1, counter);
 1250                               adjustRowCountBy(-1);
 1251                           }
 1252                           // Since matched and children are always sorted by
 1253                           // index, no need to continue testing with the
 1254                           // above.
 1255                           for(; counter < maxCounter; counter++)
 1256                               ((FHTreeStateNode)getChildAt(counter)).
 1257                                                 childIndex--;
 1258                           childCount--;
 1259                           return;
 1260                       }
 1261                   }
 1262                   // No children to adjust, but it was a child, so we still need
 1263                   // to adjust nodes after this one.
 1264                   if(isChildVisible) {
 1265                       adjustRowBy(-1, maxCounter);
 1266                       adjustRowCountBy(-1);
 1267                   }
 1268                   childCount--;
 1269               }
 1270           }
 1271   
 1272           /**
 1273            * Adjusts the child indexs of the receivers children by
 1274            * <code>amount</code>, starting at <code>index</code>.
 1275            */
 1276           protected void adjustChildIndexs(int index, int amount) {
 1277               for(int counter = index, maxCounter = getChildCount();
 1278                   counter < maxCounter; counter++) {
 1279                   ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
 1280               }
 1281           }
 1282   
 1283           /**
 1284            * Messaged when a child has been inserted at index. For all the
 1285            * children that have a childIndex >= index their index is incremented
 1286            * by one.
 1287            */
 1288           protected void childInsertedAtModelIndex(int index,
 1289                                                  boolean isExpandedAndVisible) {
 1290               FHTreeStateNode                aChild;
 1291               int                            maxCounter = getChildCount();
 1292   
 1293               for(int counter = 0; counter < maxCounter; counter++) {
 1294                   aChild = (FHTreeStateNode)getChildAt(counter);
 1295                   if(aChild.childIndex >= index) {
 1296                       if(isExpandedAndVisible) {
 1297                           adjustRowBy(1, counter);
 1298                           adjustRowCountBy(1);
 1299                       }
 1300                       /* Since matched and children are always sorted by
 1301                          index, no need to continue testing with the above. */
 1302                       for(; counter < maxCounter; counter++)
 1303                           ((FHTreeStateNode)getChildAt(counter)).childIndex++;
 1304                       childCount++;
 1305                       return;
 1306                   }
 1307               }
 1308               // No children to adjust, but it was a child, so we still need
 1309               // to adjust nodes after this one.
 1310               if(isExpandedAndVisible) {
 1311                   adjustRowBy(1, maxCounter);
 1312                   adjustRowCountBy(1);
 1313               }
 1314               childCount++;
 1315           }
 1316   
 1317           /**
 1318            * Returns true if there is a row for <code>row</code>.
 1319            * <code>nextRow</code> gives the bounds of the receiver.
 1320            * Information about the found row is returned in <code>info</code>.
 1321            * This should be invoked on root with <code>nextRow</code> set
 1322            * to <code>getRowCount</code>().
 1323            */
 1324           protected boolean getPathForRow(int row, int nextRow,
 1325                                           SearchInfo info) {
 1326               if(this.row == row) {
 1327                   info.node = this;
 1328                   info.isNodeParentNode = false;
 1329                   info.childIndex = childIndex;
 1330                   return true;
 1331               }
 1332   
 1333               FHTreeStateNode            child;
 1334               FHTreeStateNode            lastChild = null;
 1335   
 1336               for(int counter = 0, maxCounter = getChildCount();
 1337                   counter < maxCounter; counter++) {
 1338                   child = (FHTreeStateNode)getChildAt(counter);
 1339                   if(child.row > row) {
 1340                       if(counter == 0) {
 1341                           // No node exists for it, and is first.
 1342                           info.node = this;
 1343                           info.isNodeParentNode = true;
 1344                           info.childIndex = row - this.row - 1;
 1345                           return true;
 1346                       }
 1347                       else {
 1348                           // May have been in last childs bounds.
 1349                           int          lastChildEndRow = 1 + child.row -
 1350                                        (child.childIndex - lastChild.childIndex);
 1351   
 1352                           if(row < lastChildEndRow) {
 1353                               return lastChild.getPathForRow(row,
 1354                                                          lastChildEndRow, info);
 1355                           }
 1356                           // Between last child and child, but not in last child
 1357                           info.node = this;
 1358                           info.isNodeParentNode = true;
 1359                           info.childIndex = row - lastChildEndRow +
 1360                                                   lastChild.childIndex + 1;
 1361                           return true;
 1362                       }
 1363                   }
 1364                   lastChild = child;
 1365               }
 1366   
 1367               // Not in children, but we should have it, offset from
 1368               // nextRow.
 1369               if(lastChild != null) {
 1370                   int        lastChildEndRow = nextRow -
 1371                                     (childCount - lastChild.childIndex) + 1;
 1372   
 1373                   if(row < lastChildEndRow) {
 1374                       return lastChild.getPathForRow(row, lastChildEndRow, info);
 1375                   }
 1376                   // Between last child and child, but not in last child
 1377                   info.node = this;
 1378                   info.isNodeParentNode = true;
 1379                   info.childIndex = row - lastChildEndRow +
 1380                                                lastChild.childIndex + 1;
 1381                   return true;
 1382               }
 1383               else {
 1384                   // No children.
 1385                   int         retChildIndex = row - this.row - 1;
 1386   
 1387                   if(retChildIndex >= childCount) {
 1388                       return false;
 1389                   }
 1390                   info.node = this;
 1391                   info.isNodeParentNode = true;
 1392                   info.childIndex = retChildIndex;
 1393                   return true;
 1394               }
 1395           }
 1396   
 1397           /**
 1398            * Asks all the children of the receiver for their totalChildCount
 1399            * and returns this value (plus stopIndex).
 1400            */
 1401           protected int getCountTo(int stopIndex) {
 1402               FHTreeStateNode    aChild;
 1403               int                retCount = stopIndex + 1;
 1404   
 1405               for(int counter = 0, maxCounter = getChildCount();
 1406                   counter < maxCounter; counter++) {
 1407                   aChild = (FHTreeStateNode)getChildAt(counter);
 1408                   if(aChild.childIndex >= stopIndex)
 1409                       counter = maxCounter;
 1410                   else
 1411                       retCount += aChild.getTotalChildCount();
 1412               }
 1413               if(parent != null)
 1414                   return retCount + ((FHTreeStateNode)getParent())
 1415                                      .getCountTo(childIndex);
 1416               if(!isRootVisible())
 1417                   return (retCount - 1);
 1418               return retCount;
 1419           }
 1420   
 1421           /**
 1422            * Returns the number of children that are expanded to
 1423            * <code>stopIndex</code>. This does not include the number
 1424            * of children that the child at <code>stopIndex</code> might
 1425            * have.
 1426            */
 1427           protected int getNumExpandedChildrenTo(int stopIndex) {
 1428               FHTreeStateNode    aChild;
 1429               int                retCount = stopIndex;
 1430   
 1431               for(int counter = 0, maxCounter = getChildCount();
 1432                   counter < maxCounter; counter++) {
 1433                   aChild = (FHTreeStateNode)getChildAt(counter);
 1434                   if(aChild.childIndex >= stopIndex)
 1435                       return retCount;
 1436                   else {
 1437                       retCount += aChild.getTotalChildCount();
 1438                   }
 1439               }
 1440               return retCount;
 1441           }
 1442   
 1443           /**
 1444            * Messaged when this node either expands or collapses.
 1445            */
 1446           protected void didAdjustTree() {
 1447           }
 1448   
 1449       } // FixedHeightLayoutCache.FHTreeStateNode
 1450   
 1451   
 1452       /**
 1453        * Used as a placeholder when getting the path in FHTreeStateNodes.
 1454        */
 1455       private class SearchInfo {
 1456           protected FHTreeStateNode   node;
 1457           protected boolean           isNodeParentNode;
 1458           protected int               childIndex;
 1459   
 1460           protected TreePath getPath() {
 1461               if(node == null)
 1462                   return null;
 1463   
 1464               if(isNodeParentNode)
 1465                   return node.getTreePath().pathByAddingChild(treeModel.getChild
 1466                                               (node.getUserObject(),
 1467                                                childIndex));
 1468               return node.path;
 1469           }
 1470       } // FixedHeightLayoutCache.SearchInfo
 1471   
 1472   
 1473       /**
 1474        * An enumerator to iterate through visible nodes.
 1475        */
 1476       // This is very similiar to
 1477       // VariableHeightTreeState.VisibleTreeStateNodeEnumeration
 1478       private class VisibleFHTreeStateNodeEnumeration
 1479           implements Enumeration<TreePath>
 1480       {
 1481           /** Parent thats children are being enumerated. */
 1482           protected FHTreeStateNode     parent;
 1483           /** Index of next child. An index of -1 signifies parent should be
 1484            * visibled next. */
 1485           protected int                 nextIndex;
 1486           /** Number of children in parent. */
 1487           protected int                 childCount;
 1488   
 1489           protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode node) {
 1490               this(node, -1);
 1491           }
 1492   
 1493           protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode parent,
 1494                                                       int startIndex) {
 1495               this.parent = parent;
 1496               this.nextIndex = startIndex;
 1497               this.childCount = treeModel.getChildCount(this.parent.
 1498                                                         getUserObject());
 1499           }
 1500   
 1501           /**
 1502            * @return true if more visible nodes.
 1503            */
 1504           public boolean hasMoreElements() {
 1505               return (parent != null);
 1506           }
 1507   
 1508           /**
 1509            * @return next visible TreePath.
 1510            */
 1511           public TreePath nextElement() {
 1512               if(!hasMoreElements())
 1513                   throw new NoSuchElementException("No more visible paths");
 1514   
 1515               TreePath                retObject;
 1516   
 1517               if(nextIndex == -1)
 1518                   retObject = parent.getTreePath();
 1519               else {
 1520                   FHTreeStateNode  node = parent.getChildAtModelIndex(nextIndex);
 1521   
 1522                   if(node == null)
 1523                       retObject = parent.getTreePath().pathByAddingChild
 1524                                     (treeModel.getChild(parent.getUserObject(),
 1525                                                         nextIndex));
 1526                   else
 1527                       retObject = node.getTreePath();
 1528               }
 1529               updateNextObject();
 1530               return retObject;
 1531           }
 1532   
 1533           /**
 1534            * Determines the next object by invoking <code>updateNextIndex</code>
 1535            * and if not succesful <code>findNextValidParent</code>.
 1536            */
 1537           protected void updateNextObject() {
 1538               if(!updateNextIndex()) {
 1539                   findNextValidParent();
 1540               }
 1541           }
 1542   
 1543           /**
 1544            * Finds the next valid parent, this should be called when nextIndex
 1545            * is beyond the number of children of the current parent.
 1546            */
 1547           protected boolean findNextValidParent() {
 1548               if(parent == root) {
 1549                   // mark as invalid!
 1550                   parent = null;
 1551                   return false;
 1552               }
 1553               while(parent != null) {
 1554                   FHTreeStateNode      newParent = (FHTreeStateNode)parent.
 1555                                                     getParent();
 1556   
 1557                   if(newParent != null) {
 1558                       nextIndex = parent.childIndex;
 1559                       parent = newParent;
 1560                       childCount = treeModel.getChildCount
 1561                                               (parent.getUserObject());
 1562                       if(updateNextIndex())
 1563                           return true;
 1564                   }
 1565                   else
 1566                       parent = null;
 1567               }
 1568               return false;
 1569           }
 1570   
 1571           /**
 1572            * Updates <code>nextIndex</code> returning false if it is beyond
 1573            * the number of children of parent.
 1574            */
 1575           protected boolean updateNextIndex() {
 1576               // nextIndex == -1 identifies receiver, make sure is expanded
 1577               // before descend.
 1578               if(nextIndex == -1 && !parent.isExpanded()) {
 1579                   return false;
 1580               }
 1581   
 1582               // Check that it can have kids
 1583               if(childCount == 0) {
 1584                   return false;
 1585               }
 1586               // Make sure next index not beyond child count.
 1587               else if(++nextIndex >= childCount) {
 1588                   return false;
 1589               }
 1590   
 1591               FHTreeStateNode    child = parent.getChildAtModelIndex(nextIndex);
 1592   
 1593               if(child != null && child.isExpanded()) {
 1594                   parent = child;
 1595                   nextIndex = -1;
 1596                   childCount = treeModel.getChildCount(child.getUserObject());
 1597               }
 1598               return true;
 1599           }
 1600       } // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration
 1601   }

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