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