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