KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > tree > FixedHeightLayoutCache


1 /*
2  * @(#)FixedHeightLayoutCache.java 1.24 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing.tree;
9
10 import javax.swing.event.TreeModelEvent JavaDoc;
11 import java.awt.Dimension JavaDoc;
12 import java.awt.Rectangle JavaDoc;
13 import java.util.Enumeration JavaDoc;
14 import java.util.Hashtable JavaDoc;
15 import java.util.NoSuchElementException JavaDoc;
16 import java.util.Stack JavaDoc;
17
18 /**
19  * NOTE: This will become more open in a future release.
20  * <p>
21  * <strong>Warning:</strong>
22  * Serialized objects of this class will not be compatible with
23  * future Swing releases. The current serialization support is
24  * appropriate for short term storage or RMI between applications running
25  * the same version of Swing. As of 1.4, support for long term storage
26  * of all JavaBeans<sup><font size="-2">TM</font></sup>
27  * has been added to the <code>java.beans</code> package.
28  * Please see {@link java.beans.XMLEncoder}.
29  *
30  * @version 1.24 05/05/04
31  * @author Scott Violet
32  */

33
34 public class FixedHeightLayoutCache extends AbstractLayoutCache JavaDoc {
35     /** Root node. */
36     private FHTreeStateNode root;
37
38     /** Number of rows currently visible. */
39     private int rowCount;
40
41     /**
42      * Used in getting sizes for nodes to avoid creating a new Rectangle
43      * every time a size is needed.
44      */

45     private Rectangle JavaDoc boundsBuffer;
46
47     /**
48      * Maps from TreePath to a FHTreeStateNode.
49      */

50     private Hashtable JavaDoc treePathMapping;
51
52     /**
53      * Used for getting path/row information.
54      */

55     private SearchInfo info;
56
57     private Stack JavaDoc tempStacks;
58
59
60     public FixedHeightLayoutCache() {
61     super();
62     tempStacks = new Stack JavaDoc();
63     boundsBuffer = new Rectangle JavaDoc();
64     treePathMapping = new Hashtable JavaDoc();
65     info = new SearchInfo();
66     setRowHeight(1);
67     }
68
69     /**
70      * Sets the TreeModel that will provide the data.
71      *
72      * @param newModel the TreeModel that is to provide the data
73      */

74     public void setModel(TreeModel JavaDoc newModel) {
75     super.setModel(newModel);
76     rebuild(false);
77     }
78
79     /**
80      * Determines whether or not the root node from
81      * the TreeModel is visible.
82      *
83      * @param rootVisible true if the root node of the tree is to be displayed
84      * @see #rootVisible
85      */

86     public void setRootVisible(boolean rootVisible) {
87     if(isRootVisible() != rootVisible) {
88         super.setRootVisible(rootVisible);
89         if(root != null) {
90         if(rootVisible) {
91             rowCount++;
92             root.adjustRowBy(1);
93         }
94         else {
95             rowCount--;
96             root.adjustRowBy(-1);
97         }
98         visibleNodesChanged();
99         }
100     }
101     }
102
103     /**
104      * Sets the height of each cell. If rowHeight is less than or equal to
105      * 0 this will throw an IllegalArgumentException.
106      *
107      * @param rowHeight the height of each cell, in pixels
108      */

109     public void setRowHeight(int rowHeight) {
110     if(rowHeight <= 0)
111         throw new IllegalArgumentException JavaDoc("FixedHeightLayoutCache only supports row heights greater than 0");
112     if(getRowHeight() != rowHeight) {
113         super.setRowHeight(rowHeight);
114         visibleNodesChanged();
115     }
116     }
117
118     /**
119      * Returns the number of visible rows.
120      */

121     public int getRowCount() {
122     return rowCount;
123     }
124
125     /**
126      * Does nothing, FixedHeightLayoutCache doesn't cache width, and that
127      * is all that could change.
128      */

129     public void invalidatePathBounds(TreePath JavaDoc path) {
130     }
131
132
133     /**
134      * Informs the TreeState that it needs to recalculate all the sizes
135      * it is referencing.
136      */

137     public void invalidateSizes() {
138     // Nothing to do here, rowHeight still same, which is all
139
// this is interested in, visible region may have changed though.
140
visibleNodesChanged();
141     }
142
143     /**
144       * Returns true if the value identified by row is currently expanded.
145       */

146     public boolean isExpanded(TreePath JavaDoc path) {
147     if(path != null) {
148         FHTreeStateNode lastNode = getNodeForPath(path, true, false);
149
150         return (lastNode != null && lastNode.isExpanded());
151     }
152     return false;
153     }
154
155     /**
156      * Returns a rectangle giving the bounds needed to draw path.
157      *
158      * @param path a TreePath specifying a node
159      * @param placeIn a Rectangle object giving the available space
160      * @return a Rectangle object specifying the space to be used
161      */

162     public Rectangle JavaDoc getBounds(TreePath JavaDoc path, Rectangle JavaDoc placeIn) {
163     if(path == null)
164         return null;
165
166     FHTreeStateNode node = getNodeForPath(path, true, false);
167
168     if(node != null)
169         return getBounds(node, -1, placeIn);
170
171     // node hasn't been created yet.
172
TreePath JavaDoc parentPath = path.getParentPath();
173
174     node = getNodeForPath(parentPath, true, false);
175     if(node != null) {
176         int childIndex = treeModel.getIndexOfChild
177                          (parentPath.getLastPathComponent(),
178                   path.getLastPathComponent());
179
180         if(childIndex != -1)
181         return getBounds(node, childIndex, placeIn);
182     }
183     return null;
184     }
185
186     /**
187       * Returns the path for passed in row. If row is not visible
188       * null is returned.
189       */

190     public TreePath JavaDoc getPathForRow(int row) {
191     if(row >= 0 && row < getRowCount()) {
192         if(root.getPathForRow(row, getRowCount(), info)) {
193         return info.getPath();
194         }
195     }
196     return null;
197     }
198
199     /**
200       * Returns the row that the last item identified in path is visible
201       * at. Will return -1 if any of the elements in path are not
202       * currently visible.
203       */

204     public int getRowForPath(TreePath JavaDoc path) {
205     if(path == null || root == null)
206         return -1;
207
208     FHTreeStateNode node = getNodeForPath(path, true, false);
209
210     if(node != null)
211         return node.getRow();
212
213     TreePath JavaDoc parentPath = path.getParentPath();
214
215     node = getNodeForPath(parentPath, true, false);
216     if(node != null && node.isExpanded()) {
217         return node.getRowToModelIndex(treeModel.getIndexOfChild
218                        (parentPath.getLastPathComponent(),
219                         path.getLastPathComponent()));
220     }
221     return -1;
222     }
223
224     /**
225       * Returns the path to the node that is closest to x,y. If
226       * there is nothing currently visible this will return null, otherwise
227       * it'll always return a valid path. If you need to test if the
228       * returned object is exactly at x, y you should get the bounds for
229       * the returned path and test x, y against that.
230       */

231     public TreePath JavaDoc getPathClosestTo(int x, int y) {
232     if(getRowCount() == 0)
233         return null;
234
235     int row = getRowContainingYLocation(y);
236
237     return getPathForRow(row);
238     }
239
240     /**
241      * Returns the number of visible children for row.
242      */

243     public int getVisibleChildCount(TreePath JavaDoc path) {
244     FHTreeStateNode node = getNodeForPath(path, true, false);
245
246     if(node == null)
247         return 0;
248     return node.getTotalChildCount();
249     }
250
251     /**
252      * Returns an Enumerator that increments over the visible paths
253      * starting at the passed in location. The ordering of the enumeration
254      * is based on how the paths are displayed.
255      */

256     public Enumeration JavaDoc<TreePath JavaDoc> getVisiblePathsFrom(TreePath JavaDoc path) {
257     if(path == null)
258         return null;
259
260     FHTreeStateNode node = getNodeForPath(path, true, false);
261
262     if(node != null) {
263         return new VisibleFHTreeStateNodeEnumeration(node);
264     }
265     TreePath JavaDoc parentPath = path.getParentPath();
266
267     node = getNodeForPath(parentPath, true, false);
268     if(node != null && node.isExpanded()) {
269         return new VisibleFHTreeStateNodeEnumeration(node,
270           treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
271                         path.getLastPathComponent()));
272     }
273     return null;
274     }
275
276     /**
277      * Marks the path <code>path</code> expanded state to
278      * <code>isExpanded</code>.
279      */

280     public void setExpandedState(TreePath JavaDoc path, boolean isExpanded) {
281     if(isExpanded)
282         ensurePathIsExpanded(path, true);
283     else if(path != null) {
284         TreePath JavaDoc parentPath = path.getParentPath();
285
286         // YECK! Make the parent expanded.
287
if(parentPath != null) {
288         FHTreeStateNode parentNode = getNodeForPath(parentPath,
289                                 false, true);
290         if(parentNode != null)
291             parentNode.makeVisible();
292         }
293         // And collapse the child.
294
FHTreeStateNode childNode = getNodeForPath(path, true,
295                                    false);
296
297         if(childNode != null)
298         childNode.collapse(true);
299     }
300     }
301
302     /**
303      * Returns true if the path is expanded, and visible.
304      */

305     public boolean getExpandedState(TreePath JavaDoc path) {
306     FHTreeStateNode node = getNodeForPath(path, true, false);
307
308     return (node != null) ? (node.isVisible() && node.isExpanded()) :
309                              false;
310     }
311
312     //
313
// TreeModelListener methods
314
//
315

316     /**
317      * <p>Invoked after a node (or a set of siblings) has changed in some
318      * way. The node(s) have not changed locations in the tree or
319      * altered their children arrays, but other attributes have
320      * changed and may affect presentation. Example: the name of a
321      * file has changed, but it is in the same location in the file
322      * system.</p>
323      *
324      * <p>e.path() returns the path the parent of the changed node(s).</p>
325      *
326      * <p>e.childIndices() returns the index(es) of the changed node(s).</p>
327      */

328     public void treeNodesChanged(TreeModelEvent JavaDoc e) {
329     if(e != null) {
330         int changedIndexs[];
331         FHTreeStateNode changedParent = getNodeForPath
332                           (e.getTreePath(), false, false);
333         int maxCounter;
334
335         changedIndexs = e.getChildIndices();
336         /* Only need to update the children if the node has been
337            expanded once. */

338         // PENDING(scott): make sure childIndexs is sorted!
339
if (changedParent != null) {
340         if (changedIndexs != null &&
341             (maxCounter = changedIndexs.length) > 0) {
342             Object JavaDoc parentValue = changedParent.getUserObject();
343
344             for(int counter = 0; counter < maxCounter; counter++) {
345             FHTreeStateNode child = changedParent.
346                      getChildAtModelIndex(changedIndexs[counter]);
347
348             if(child != null) {
349                 child.setUserObject(treeModel.getChild(parentValue,
350                              changedIndexs[counter]));
351             }
352             }
353             if(changedParent.isVisible() && changedParent.isExpanded())
354             visibleNodesChanged();
355         }
356         // Null for root indicates it changed.
357
else if (changedParent == root && changedParent.isVisible() &&
358              changedParent.isExpanded()) {
359             visibleNodesChanged();
360         }
361         }
362     }
363     }
364
365     /**
366      * <p>Invoked after nodes have been inserted into the tree.</p>
367      *
368      * <p>e.path() returns the parent of the new nodes
369      * <p>e.childIndices() returns the indices of the new nodes in
370      * ascending order.
371      */

372     public void treeNodesInserted(TreeModelEvent JavaDoc e) {
373     if(e != null) {
374         int changedIndexs[];
375         FHTreeStateNode changedParent = getNodeForPath
376                           (e.getTreePath(), false, false);
377         int maxCounter;
378
379         changedIndexs = e.getChildIndices();
380         /* Only need to update the children if the node has been
381            expanded once. */

382         // PENDING(scott): make sure childIndexs is sorted!
383
if(changedParent != null && changedIndexs != null &&
384            (maxCounter = changedIndexs.length) > 0) {
385         boolean isVisible =
386             (changedParent.isVisible() &&
387              changedParent.isExpanded());
388
389         for(int counter = 0; counter < maxCounter; counter++) {
390             changedParent.childInsertedAtModelIndex
391             (changedIndexs[counter], isVisible);
392         }
393         if(isVisible && treeSelectionModel != null)
394             treeSelectionModel.resetRowSelection();
395         if(changedParent.isVisible())
396             this.visibleNodesChanged();
397         }
398     }
399     }
400
401     /**
402      * <p>Invoked after nodes have been removed from the tree. Note that
403      * if a subtree is removed from the tree, this method may only be
404      * invoked once for the root of the removed subtree, not once for
405      * each individual set of siblings removed.</p>
406      *
407      * <p>e.path() returns the former parent of the deleted nodes.</p>
408      *
409      * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p>
410      */

411     public void treeNodesRemoved(TreeModelEvent JavaDoc e) {
412     if(e != null) {
413         int changedIndexs[];
414         int maxCounter;
415         TreePath JavaDoc parentPath = e.getTreePath();
416         FHTreeStateNode changedParentNode = getNodeForPath
417                                (parentPath, false, false);
418
419         changedIndexs = e.getChildIndices();
420         // PENDING(scott): make sure that changedIndexs are sorted in
421
// ascending order.
422
if(changedParentNode != null && changedIndexs != null &&
423            (maxCounter = changedIndexs.length) > 0) {
424         Object JavaDoc[] children = e.getChildren();
425         boolean isVisible =
426             (changedParentNode.isVisible() &&
427              changedParentNode.isExpanded());
428
429         for(int counter = maxCounter - 1; counter >= 0; counter--) {
430             changedParentNode.removeChildAtModelIndex
431                          (changedIndexs[counter], isVisible);
432         }
433         if(isVisible) {
434             if(treeSelectionModel != null)
435             treeSelectionModel.resetRowSelection();
436                     if (treeModel.getChildCount(changedParentNode.
437                                                 getUserObject()) == 0 &&
438                                   changedParentNode.isLeaf()) {
439                         // Node has become a leaf, collapse it.
440
changedParentNode.collapse(false);
441                     }
442             visibleNodesChanged();
443         }
444         else if(changedParentNode.isVisible())
445             visibleNodesChanged();
446         }
447     }
448     }
449
450     /**
451      * <p>Invoked after the tree has drastically changed structure from a
452      * given node down. If the path returned by e.getPath() is of length
453      * one and the first element does not identify the current root node
454      * the first element should become the new root of the tree.<p>
455      *
456      * <p>e.path() holds the path to the node.</p>
457      * <p>e.childIndices() returns null.</p>
458      */

459     public void treeStructureChanged(TreeModelEvent JavaDoc e) {
460     if(e != null) {
461         TreePath JavaDoc changedPath = e.getTreePath();
462             FHTreeStateNode changedNode = getNodeForPath
463                                     (changedPath, false, false);
464
465         // Check if root has changed, either to a null root, or
466
// to an entirely new root.
467
if (changedNode == root ||
468                 (changedNode == null &&
469                  ((changedPath == null && treeModel != null &&
470                    treeModel.getRoot() == null) ||
471                   (changedPath != null && changedPath.getPathCount() <= 1)))) {
472                 rebuild(true);
473             }
474             else if(changedNode != null) {
475                 boolean wasExpanded, wasVisible;
476                 FHTreeStateNode parent = (FHTreeStateNode)
477                                               changedNode.getParent();
478
479                 wasExpanded = changedNode.isExpanded();
480                 wasVisible = changedNode.isVisible();
481
482         int index = parent.getIndex(changedNode);
483         changedNode.collapse(false);
484         parent.remove(index);
485
486                 if(wasVisible && wasExpanded) {
487             int row = changedNode.getRow();
488             parent.resetChildrenRowsFrom(row, index,
489                          changedNode.getChildIndex());
490                     changedNode = getNodeForPath(changedPath, false, true);
491                     changedNode.expand();
492                 }
493                 if(treeSelectionModel != null && wasVisible && wasExpanded)
494                     treeSelectionModel.resetRowSelection();
495                 if(wasVisible)
496                     this.visibleNodesChanged();
497         }
498     }
499     }
500
501
502     //
503
// Local methods
504
//
505

506     private void visibleNodesChanged() {
507     }
508
509     /**
510      * Returns the bounds for the given node. If <code>childIndex</code>
511      * is -1, the bounds of <code>parent</code> are returned, otherwise
512      * the bounds of the node at <code>childIndex</code> are returned.
513      */

514     private Rectangle JavaDoc getBounds(FHTreeStateNode parent, int childIndex,
515                   Rectangle JavaDoc placeIn) {
516     boolean expanded;
517     int level;
518     int row;
519     Object JavaDoc value;
520
521     if(childIndex == -1) {
522         // Getting bounds for parent
523
row = parent.getRow();
524         value = parent.getUserObject();
525         expanded = parent.isExpanded();
526         level = parent.getLevel();
527     }
528     else {
529         row = parent.getRowToModelIndex(childIndex);
530         value = treeModel.getChild(parent.getUserObject(), childIndex);
531         expanded = false;
532         level = parent.getLevel() + 1;
533     }
534
535     Rectangle JavaDoc bounds = getNodeDimensions(value, row, level,
536                           expanded, boundsBuffer);
537     // No node dimensions, bail.
538
if(bounds == null)
539         return null;
540
541     if(placeIn == null)
542         placeIn = new Rectangle JavaDoc();
543
544     placeIn.x = bounds.x;
545     placeIn.height = getRowHeight();
546     placeIn.y = row * placeIn.height;
547     placeIn.width = bounds.width;
548     return placeIn;
549     }
550
551     /**
552      * Adjust the large row count of the AbstractTreeUI the receiver was
553      * created with.
554      */

555     private void adjustRowCountBy(int changeAmount) {
556     rowCount += changeAmount;
557     }
558
559     /**
560      * Adds a mapping for node.
561      */

562     private void addMapping(FHTreeStateNode node) {
563     treePathMapping.put(node.getTreePath(), node);
564     }
565
566     /**
567      * Removes the mapping for a previously added node.
568      */

569     private void removeMapping(FHTreeStateNode node) {
570     treePathMapping.remove(node.getTreePath());
571     }
572
573     /**
574      * Returns the node previously added for <code>path</code>. This may
575      * return null, if you to create a node use getNodeForPath.
576      */

577     private FHTreeStateNode getMapping(TreePath JavaDoc path) {
578     return (FHTreeStateNode)treePathMapping.get(path);
579     }
580
581     /**
582      * Sent to completely rebuild the visible tree. All nodes are collapsed.
583      */

584     private void rebuild(boolean clearSelection) {
585         Object JavaDoc rootUO;
586
587     treePathMapping.clear();
588     if(treeModel != null && (rootUO = treeModel.getRoot()) != null) {
589         root = createNodeForValue(rootUO, 0);
590         root.path = new TreePath JavaDoc(rootUO);
591         addMapping(root);
592         if(isRootVisible()) {
593         rowCount = 1;
594         root.row = 0;
595         }
596         else {
597         rowCount = 0;
598         root.row = -1;
599         }
600         root.expand();
601     }
602     else {
603         root = null;
604         rowCount = 0;
605     }
606     if(clearSelection && treeSelectionModel != null) {
607         treeSelectionModel.clearSelection();
608     }
609     this.visibleNodesChanged();
610     }
611
612     /**
613       * Returns the index of the row containing location. If there
614       * are no rows, -1 is returned. If location is beyond the last
615       * row index, the last row index is returned.
616       */

617     private int getRowContainingYLocation(int location) {
618     if(getRowCount() == 0)
619         return -1;
620     return Math.max(0, Math.min(getRowCount() - 1,
621                     location / getRowHeight()));
622     }
623
624     /**
625      * Ensures that all the path components in path are expanded, accept
626      * for the last component which will only be expanded if expandLast
627      * is true.
628      * Returns true if succesful in finding the path.
629      */

630     private boolean ensurePathIsExpanded(TreePath JavaDoc aPath,
631                        boolean expandLast) {
632     if(aPath != null) {
633         // Make sure the last entry isn't a leaf.
634
if(treeModel.isLeaf(aPath.getLastPathComponent())) {
635         aPath = aPath.getParentPath();
636         expandLast = true;
637         }
638         if(aPath != null) {
639         FHTreeStateNode lastNode = getNodeForPath(aPath, false,
640                                   true);
641
642         if(lastNode != null) {
643             lastNode.makeVisible();
644             if(expandLast)
645             lastNode.expand();
646             return true;
647         }
648         }
649     }
650     return false;
651     }
652
653     /**
654      * Creates and returns an instance of FHTreeStateNode.
655      */

656     private FHTreeStateNode createNodeForValue(Object JavaDoc value,int childIndex) {
657     return new FHTreeStateNode(value, childIndex, -1);
658     }
659
660     /**
661      * Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate,
662      * path.length) as long as path is non-null and the length is > 0.
663      * Otherwise returns null.
664      */

665     private FHTreeStateNode getNodeForPath(TreePath JavaDoc path,
666                          boolean onlyIfVisible,
667                          boolean shouldCreate) {
668     if(path != null) {
669         FHTreeStateNode node;
670
671         node = getMapping(path);
672         if(node != null) {
673         if(onlyIfVisible && !node.isVisible())
674             return null;
675         return node;
676         }
677         if(onlyIfVisible)
678         return null;
679
680         // Check all the parent paths, until a match is found.
681
Stack JavaDoc paths;
682
683         if(tempStacks.size() == 0) {
684         paths = new Stack JavaDoc();
685         }
686         else {
687         paths = (Stack JavaDoc)tempStacks.pop();
688         }
689
690         try {
691         paths.push(path);
692         path = path.getParentPath();
693         node = null;
694         while(path != null) {
695             node = getMapping(path);
696             if(node != null) {
697             // Found a match, create entries for all paths in
698
// paths.
699
while(node != null && paths.size() > 0) {
700                 path = (TreePath JavaDoc)paths.pop();
701                 node = node.createChildFor(path.
702                                getLastPathComponent());
703             }
704             return node;
705             }
706             paths.push(path);
707             path = path.getParentPath();
708         }
709         }
710         finally {
711         paths.removeAllElements();
712         tempStacks.push(paths);
713         }
714         // If we get here it means they share a different root!
715
return null;
716     }
717     return null;
718     }
719
720     /**
721      * FHTreeStateNode is used to track what has been expanded.
722      * FHTreeStateNode differs from VariableHeightTreeState.TreeStateNode
723      * in that it is highly model intensive. That is almost all queries to a
724      * FHTreeStateNode result in the TreeModel being queried. And it
725      * obviously does not support variable sized row heights.
726      */

727     private class FHTreeStateNode extends DefaultMutableTreeNode JavaDoc {
728     /** Is this node expanded? */
729     protected boolean isExpanded;
730
731     /** Index of this node from the model. */
732     protected int childIndex;
733
734     /** Child count of the receiver. */
735     protected int childCount;
736     
737     /** Row of the receiver. This is only valid if the row is expanded.
738      */

739     protected int row;
740
741     /** Path of this node. */
742     protected TreePath JavaDoc path;
743
744
745     public FHTreeStateNode(Object JavaDoc userObject, int childIndex, int row) {
746         super(userObject);
747         this.childIndex = childIndex;
748         this.row = row;
749     }
750
751     //
752
// Overriden DefaultMutableTreeNode methods
753
//
754

755     /**
756      * Messaged when this node is added somewhere, resets the path
757      * and adds a mapping from path to this node.
758      */

759     public void setParent(MutableTreeNode JavaDoc parent) {
760         super.setParent(parent);
761         if(parent != null) {
762         path = ((FHTreeStateNode)parent).getTreePath().
763                     pathByAddingChild(getUserObject());
764         addMapping(this);
765         }
766     }
767
768     /**
769      * Messaged when this node is removed from its parent, this messages
770      * <code>removedFromMapping</code> to remove all the children.
771      */

772     public void remove(int childIndex) {
773         FHTreeStateNode node = (FHTreeStateNode)getChildAt(childIndex);
774
775         node.removeFromMapping();
776         super.remove(childIndex);
777     }
778
779     /**
780      * Messaged to set the user object. This resets the path.
781      */

782     public void setUserObject(Object JavaDoc o) {
783         super.setUserObject(o);
784         if(path != null) {
785         FHTreeStateNode parent = (FHTreeStateNode)getParent();
786
787         if(parent != null)
788             resetChildrenPaths(parent.getTreePath());
789         else
790             resetChildrenPaths(null);
791         }
792     }
793
794     //
795
//
796

797     /**
798      * Returns the index of the receiver in the model.
799      */

800     public int getChildIndex() {
801         return childIndex;
802     }
803
804     /**
805      * Returns the <code>TreePath</code> of the receiver.
806      */

807     public TreePath JavaDoc getTreePath() {
808         return path;
809     }
810
811     /**
812      * Returns the child for the passed in model index, this will
813      * return <code>null</code> if the child for <code>index</code>
814          * has not yet been created (expanded).
815      */

816     public FHTreeStateNode getChildAtModelIndex(int index) {
817         // PENDING: Make this a binary search!
818
for(int counter = getChildCount() - 1; counter >= 0; counter--)
819         if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
820             return (FHTreeStateNode)getChildAt(counter);
821         return null;
822     }
823
824     /**
825      * Returns true if this node is visible. This is determined by
826      * asking all the parents if they are expanded.
827      */

828     public boolean isVisible() {
829         FHTreeStateNode parent = (FHTreeStateNode)getParent();
830
831         if(parent == null)
832         return true;
833         return (parent.isExpanded() && parent.isVisible());
834     }
835
836     /**
837      * Returns the row of the receiver.
838      */

839     public int getRow() {
840         return row;
841     }
842
843     /**
844      * Returns the row of the child with a model index of
845      * <code>index</code>.
846      */

847     public int getRowToModelIndex(int index) {
848         FHTreeStateNode child;
849         int lastRow = getRow() + 1;
850         int retValue = lastRow;
851
852         // This too could be a binary search!
853
for(int counter = 0, maxCounter = getChildCount();
854         counter < maxCounter; counter++) {
855         child = (FHTreeStateNode)getChildAt(counter);
856         if(child.childIndex >= index) {
857             if(child.childIndex == index)
858             return child.row;
859             if(counter == 0)
860             return getRow() + 1 + index;
861             return child.row - (child.childIndex - index);
862         }
863         }
864         // YECK!
865
return getRow() + 1 + getTotalChildCount() -
866                      (childCount - index);
867     }
868
869     /**
870      * Returns the number of children in the receiver by descending all
871      * expanded nodes and messaging them with getTotalChildCount.
872      */

873     public int getTotalChildCount() {
874         if(isExpanded()) {
875         FHTreeStateNode parent = (FHTreeStateNode)getParent();
876         int pIndex;
877
878         if(parent != null && (pIndex = parent.getIndex(this)) + 1 <
879            parent.getChildCount()) {
880             // This node has a created sibling, to calc total
881
// child count directly from that!
882
FHTreeStateNode nextSibling = (FHTreeStateNode)parent.
883                                getChildAt(pIndex + 1);
884
885             return nextSibling.row - row -
886                (nextSibling.childIndex - childIndex);
887         }
888         else {
889             int retCount = childCount;
890
891             for(int counter = getChildCount() - 1; counter >= 0;
892             counter--) {
893             retCount += ((FHTreeStateNode)getChildAt(counter))
894                                       .getTotalChildCount();
895             }
896             return retCount;
897         }
898         }
899         return 0;
900     }
901
902     /**
903      * Returns true if this node is expanded.
904      */

905     public boolean isExpanded() {
906         return isExpanded;
907     }
908
909     /**
910      * The highest visible nodes have a depth of 0.
911      */

912     public int getVisibleLevel() {
913         if (isRootVisible()) {
914         return getLevel();
915         } else {
916         return getLevel() - 1;
917         }
918     }
919
920     /**
921      * Recreates the receivers path, and all its childrens paths.
922      */

923     protected void resetChildrenPaths(TreePath JavaDoc parentPath) {
924         removeMapping(this);
925         if(parentPath == null)
926         path = new TreePath JavaDoc(getUserObject());
927         else
928         path = parentPath.pathByAddingChild(getUserObject());
929         addMapping(this);
930         for(int counter = getChildCount() - 1; counter >= 0; counter--)
931         ((FHTreeStateNode)getChildAt(counter)).
932                        resetChildrenPaths(path);
933     }
934
935     /**
936      * Removes the receiver, and all its children, from the mapping
937      * table.
938      */

939     protected void removeFromMapping() {
940         if(path != null) {
941         removeMapping(this);
942         for(int counter = getChildCount() - 1; counter >= 0; counter--)
943             ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
944         }
945     }
946
947     /**
948      * Creates a new node to represent <code>userObject</code>.
949      * This does NOT check to ensure there isn't already a child node
950      * to manage <code>userObject</code>.
951      */

952     protected FHTreeStateNode createChildFor(Object JavaDoc userObject) {
953         int newChildIndex = treeModel.getIndexOfChild
954                              (getUserObject(), userObject);
955
956         if(newChildIndex < 0)
957         return null;
958
959         FHTreeStateNode aNode;
960         FHTreeStateNode child = createNodeForValue(userObject,
961                                newChildIndex);
962         int childRow;
963
964         if(isVisible()) {
965         childRow = getRowToModelIndex(newChildIndex);
966         }
967         else {
968         childRow = -1;
969         }
970         child.row = childRow;
971         for(int counter = 0, maxCounter = getChildCount();
972         counter < maxCounter; counter++) {
973         aNode = (FHTreeStateNode)getChildAt(counter);
974         if(aNode.childIndex > newChildIndex) {
975             insert(child, counter);
976             return child;
977         }
978         }
979         add(child);
980         return child;
981     }
982
983     /**
984      * Adjusts the receiver, and all its children rows by
985      * <code>amount</code>.
986      */

987     protected void adjustRowBy(int amount) {
988         row += amount;
989         if(isExpanded) {
990         for(int counter = getChildCount() - 1; counter >= 0;
991             counter--)
992             ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
993         }
994     }
995
996     /**
997      * Adjusts this node, its child, and its parent starting at
998      * an index of <code>index</code> index is the index of the child
999      * to start adjusting from, which is not necessarily the model
1000     * index.
1001     */

1002    protected void adjustRowBy(int amount, int startIndex) {
1003        // Could check isVisible, but probably isn't worth it.
1004
if(isExpanded) {
1005        // children following startIndex.
1006
for(int counter = getChildCount() - 1; counter >= startIndex;
1007            counter--)
1008            ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1009        }
1010        // Parent
1011
FHTreeStateNode parent = (FHTreeStateNode)getParent();
1012
1013        if(parent != null) {
1014        parent.adjustRowBy(amount, parent.getIndex(this) + 1);
1015        }
1016    }
1017
1018    /**
1019     * Messaged when the node has expanded. This updates all of
1020     * the receivers children rows, as well as the total row count.
1021     */

1022    protected void didExpand() {
1023        int nextRow = setRowAndChildren(row);
1024        FHTreeStateNode parent = (FHTreeStateNode)getParent();
1025        int childRowCount = nextRow - row - 1;
1026
1027        if(parent != null) {
1028        parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
1029        }
1030        adjustRowCountBy(childRowCount);
1031    }
1032
1033    /**
1034     * Sets the receivers row to <code>nextRow</code> and recursively
1035     * updates all the children of the receivers rows. The index the
1036     * next row is to be placed as is returned.
1037     */

1038    protected int setRowAndChildren(int nextRow) {
1039        row = nextRow;
1040
1041        if(!isExpanded())
1042        return row + 1;
1043
1044        int lastRow = row + 1;
1045        int lastModelIndex = 0;
1046        FHTreeStateNode child;
1047        int maxCounter = getChildCount();
1048
1049        for(int counter = 0; counter < maxCounter; counter++) {
1050        child = (FHTreeStateNode)getChildAt(counter);
1051        lastRow += (child.childIndex - lastModelIndex);
1052        lastModelIndex = child.childIndex + 1;
1053        if(child.isExpanded) {
1054            lastRow = child.setRowAndChildren(lastRow);
1055        }
1056        else {
1057            child.row = lastRow++;
1058        }
1059        }
1060        return lastRow + childCount - lastModelIndex;
1061    }
1062
1063    /**
1064     * Resets the receivers childrens rows. Starting with the child
1065     * at <code>childIndex</code> (and <code>modelIndex</code>) to
1066     * <code>newRow</code>. This uses <code>setRowAndChildren</code>
1067     * to recursively descend children, and uses
1068     * <code>resetRowSelection</code> to ascend parents.
1069     */

1070    // This can be rather expensive, but is needed for the collapse
1071
// case this is resulting from a remove (although I could fix
1072
// that by having instances of FHTreeStateNode hold a ref to
1073
// the number of children). I prefer this though, making determing
1074
// the row of a particular node fast is very nice!
1075
protected void resetChildrenRowsFrom(int newRow, int childIndex,
1076                        int modelIndex) {
1077        int lastRow = newRow;
1078        int lastModelIndex = modelIndex;
1079        FHTreeStateNode node;
1080        int maxCounter = getChildCount();
1081
1082        for(int counter = childIndex; counter < maxCounter; counter++) {
1083        node = (FHTreeStateNode)getChildAt(counter);
1084        lastRow += (node.childIndex - lastModelIndex);
1085        lastModelIndex = node.childIndex + 1;
1086        if(node.isExpanded) {
1087            lastRow = node.setRowAndChildren(lastRow);
1088        }
1089        else {
1090            node.row = lastRow++;
1091        }
1092        }
1093        lastRow += childCount - lastModelIndex;
1094        node = (FHTreeStateNode)getParent();
1095        if(node != null) {
1096        node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
1097                       this.childIndex + 1);
1098        }
1099        else { // This is the root, reset total ROWCOUNT!
1100
rowCount = lastRow;
1101        }
1102    }
1103
1104    /**
1105     * Makes the receiver visible, but invoking
1106     * <code>expandParentAndReceiver</code> on the superclass.
1107     */

1108    protected void makeVisible() {
1109        FHTreeStateNode parent = (FHTreeStateNode)getParent();
1110
1111        if(parent != null)
1112        parent.expandParentAndReceiver();
1113    }
1114
1115    /**
1116     * Invokes <code>expandParentAndReceiver</code> on the parent,
1117     * and expands the receiver.
1118     */

1119    protected void expandParentAndReceiver() {
1120        FHTreeStateNode parent = (FHTreeStateNode)getParent();
1121
1122        if(parent != null)
1123        parent.expandParentAndReceiver();
1124        expand();
1125    }
1126
1127    /**
1128     * Expands the receiver.
1129     */

1130    protected void expand() {
1131        if(!isExpanded && !isLeaf()) {
1132        boolean visible = isVisible();
1133
1134        isExpanded = true;
1135        childCount = treeModel.getChildCount(getUserObject());
1136
1137        if(visible) {
1138            didExpand();
1139        }
1140
1141        // Update the selection model.
1142
if(visible && treeSelectionModel != null) {
1143            treeSelectionModel.resetRowSelection();
1144        }
1145        }
1146    }
1147
1148    /**
1149     * Collapses the receiver. If <code>adjustRows</code> is true,
1150     * the rows of nodes after the receiver are adjusted.
1151     */

1152    protected void collapse(boolean adjustRows) {
1153        if(isExpanded) {
1154        if(isVisible() && adjustRows) {
1155            int childCount = getTotalChildCount();
1156
1157            isExpanded = false;
1158            adjustRowCountBy(-childCount);
1159            // We can do this because adjustRowBy won't descend
1160
// the children.
1161
adjustRowBy(-childCount, 0);
1162        }
1163        else
1164            isExpanded = false;
1165
1166        if(adjustRows && isVisible() && treeSelectionModel != null)
1167            treeSelectionModel.resetRowSelection();
1168        }
1169    }
1170
1171    /**
1172     * Returns true if the receiver is a leaf.
1173     */

1174    public boolean isLeaf() {
1175            TreeModel JavaDoc model = getModel();
1176
1177            return (model != null) ? model.isLeaf(this.getUserObject()) :
1178                   true;
1179    }
1180
1181    /**
1182     * Adds newChild to this nodes children at the appropriate location.
1183     * The location is determined from the childIndex of newChild.
1184     */

1185    protected void addNode(FHTreeStateNode newChild) {
1186        boolean added = false;
1187        int childIndex = newChild.getChildIndex();
1188
1189        for(int counter = 0, maxCounter = getChildCount();
1190        counter < maxCounter; counter++) {
1191        if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
1192           childIndex) {
1193            added = true;
1194            insert(newChild, counter);
1195            counter = maxCounter;
1196        }
1197        }
1198        if(!added)
1199        add(newChild);
1200    }
1201
1202    /**
1203     * Removes the child at <code>modelIndex</code>.
1204     * <code>isChildVisible</code> should be true if the receiver
1205     * is visible and expanded.
1206     */

1207    protected void removeChildAtModelIndex(int modelIndex,
1208                           boolean isChildVisible) {
1209        FHTreeStateNode childNode = getChildAtModelIndex(modelIndex);
1210
1211        if(childNode != null) {
1212        int row = childNode.getRow();
1213        int index = getIndex(childNode);
1214
1215        childNode.collapse(false);
1216        remove(index);
1217        adjustChildIndexs(index, -1);
1218        childCount--;
1219        if(isChildVisible) {
1220            // Adjust the rows.
1221
resetChildrenRowsFrom(row, index, modelIndex);
1222        }
1223        }
1224        else {
1225        int maxCounter = getChildCount();
1226        FHTreeStateNode aChild;
1227
1228        for(int counter = 0; counter < maxCounter; counter++) {
1229            aChild = (FHTreeStateNode)getChildAt(counter);
1230            if(aChild.childIndex >= modelIndex) {
1231            if(isChildVisible) {
1232                adjustRowBy(-1, counter);
1233                adjustRowCountBy(-1);
1234            }
1235            // Since matched and children are always sorted by
1236
// index, no need to continue testing with the
1237
// above.
1238
for(; counter < maxCounter; counter++)
1239                ((FHTreeStateNode)getChildAt(counter)).
1240                              childIndex--;
1241            childCount--;
1242            return;
1243            }
1244        }
1245        // No children to adjust, but it was a child, so we still need
1246
// to adjust nodes after this one.
1247
if(isChildVisible) {
1248            adjustRowBy(-1, maxCounter);
1249            adjustRowCountBy(-1);
1250        }
1251        childCount--;
1252        }
1253    }
1254
1255    /**
1256     * Adjusts the child indexs of the receivers children by
1257     * <code>amount</code>, starting at <code>index</code>.
1258     */

1259    protected void adjustChildIndexs(int index, int amount) {
1260        for(int counter = index, maxCounter = getChildCount();
1261        counter < maxCounter; counter++) {
1262        ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
1263        }
1264    }
1265
1266    /**
1267     * Messaged when a child has been inserted at index. For all the
1268     * children that have a childIndex >= index their index is incremented
1269     * by one.
1270     */

1271    protected void childInsertedAtModelIndex(int index,
1272                           boolean isExpandedAndVisible) {
1273        FHTreeStateNode aChild;
1274        int maxCounter = getChildCount();
1275
1276        for(int counter = 0; counter < maxCounter; counter++) {
1277        aChild = (FHTreeStateNode)getChildAt(counter);
1278        if(aChild.childIndex >= index) {
1279            if(isExpandedAndVisible) {
1280            adjustRowBy(1, counter);
1281            adjustRowCountBy(1);
1282            }
1283            /* Since matched and children are always sorted by
1284               index, no need to continue testing with the above. */

1285            for(; counter < maxCounter; counter++)
1286            ((FHTreeStateNode)getChildAt(counter)).childIndex++;
1287            childCount++;
1288            return;
1289        }
1290        }
1291        // No children to adjust, but it was a child, so we still need
1292
// to adjust nodes after this one.
1293
if(isExpandedAndVisible) {
1294        adjustRowBy(1, maxCounter);
1295        adjustRowCountBy(1);
1296        }
1297        childCount++;
1298    }
1299
1300    /**
1301     * Returns true if there is a row for <code>row</code>.
1302     * <code>nextRow</code> gives the bounds of the receiver.
1303     * Information about the found row is returned in <code>info</code>.
1304     * This should be invoked on root with <code>nextRow</code> set
1305     * to <code>getRowCount</code>().
1306     */

1307    protected boolean getPathForRow(int row, int nextRow,
1308                    SearchInfo info) {
1309        if(this.row == row) {
1310        info.node = this;
1311        info.isNodeParentNode = false;
1312        info.childIndex = childIndex;
1313        return true;
1314        }
1315
1316        FHTreeStateNode child;
1317        FHTreeStateNode lastChild = null;
1318
1319        for(int counter = 0, maxCounter = getChildCount();
1320        counter < maxCounter; counter++) {
1321        child = (FHTreeStateNode)getChildAt(counter);
1322        if(child.row > row) {
1323            if(counter == 0) {
1324            // No node exists for it, and is first.
1325
info.node = this;
1326            info.isNodeParentNode = true;
1327            info.childIndex = row - this.row - 1;
1328            return true;
1329            }
1330            else {
1331            // May have been in last childs bounds.
1332
int lastChildEndRow = 1 + child.row -
1333                         (child.childIndex - lastChild.childIndex);
1334
1335            if(row < lastChildEndRow) {
1336                return lastChild.getPathForRow(row,
1337                               lastChildEndRow, info);
1338            }
1339            // Between last child and child, but not in last child
1340
info.node = this;
1341            info.isNodeParentNode = true;
1342            info.childIndex = row - lastChildEndRow +
1343                                    lastChild.childIndex + 1;
1344            return true;
1345            }
1346        }
1347        lastChild = child;
1348        }
1349
1350        // Not in children, but we should have it, offset from
1351
// nextRow.
1352
if(lastChild != null) {
1353        int lastChildEndRow = nextRow -
1354                          (childCount - lastChild.childIndex) + 1;
1355
1356        if(row < lastChildEndRow) {
1357            return lastChild.getPathForRow(row, 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        else {
1367        // No children.
1368
int retChildIndex = row - this.row - 1;
1369
1370        if(retChildIndex >= childCount) {
1371            return false;
1372        }
1373        info.node = this;
1374        info.isNodeParentNode = true;
1375        info.childIndex = retChildIndex;
1376        return true;
1377        }
1378    }
1379
1380    /**
1381     * Asks all the children of the receiver for their totalChildCount
1382     * and returns this value (plus stopIndex).
1383     */

1384    protected int getCountTo(int stopIndex) {
1385        FHTreeStateNode aChild;
1386        int retCount = stopIndex + 1;
1387
1388        for(int counter = 0, maxCounter = getChildCount();
1389        counter < maxCounter; counter++) {
1390        aChild = (FHTreeStateNode)getChildAt(counter);
1391        if(aChild.childIndex >= stopIndex)
1392            counter = maxCounter;
1393        else
1394            retCount += aChild.getTotalChildCount();
1395        }
1396        if(parent != null)
1397        return retCount + ((FHTreeStateNode)getParent())
1398                           .getCountTo(childIndex);
1399        if(!isRootVisible())
1400        return (retCount - 1);
1401        return retCount;
1402    }
1403
1404    /**
1405     * Returns the number of children that are expanded to
1406     * <code>stopIndex</code>. This does not include the number
1407     * of children that the child at <code>stopIndex</code> might
1408     * have.
1409     */

1410    protected int getNumExpandedChildrenTo(int stopIndex) {
1411        FHTreeStateNode aChild;
1412        int retCount = stopIndex;
1413
1414        for(int counter = 0, maxCounter = getChildCount();
1415        counter < maxCounter; counter++) {
1416        aChild = (FHTreeStateNode)getChildAt(counter);
1417        if(aChild.childIndex >= stopIndex)
1418            return retCount;
1419        else {
1420            retCount += aChild.getTotalChildCount();
1421        }
1422        }
1423        return retCount;
1424    }
1425
1426    /**
1427     * Messaged when this node either expands or collapses.
1428     */

1429    protected void didAdjustTree() {
1430    }
1431
1432    } // FixedHeightLayoutCache.FHTreeStateNode
1433

1434
1435    /**
1436     * Used as a placeholder when getting the path in FHTreeStateNodes.
1437     */

1438    private class SearchInfo {
1439    protected FHTreeStateNode node;
1440    protected boolean isNodeParentNode;
1441    protected int childIndex;
1442
1443    protected TreePath JavaDoc getPath() {
1444        if(node == null)
1445        return null;
1446
1447        if(isNodeParentNode)
1448        return node.getTreePath().pathByAddingChild(treeModel.getChild
1449                        (node.getUserObject(),
1450                         childIndex));
1451        return node.path;
1452    }
1453    } // FixedHeightLayoutCache.SearchInfo
1454

1455
1456    /**
1457     * An enumerator to iterate through visible nodes.
1458     */

1459    // This is very similiar to
1460
// VariableHeightTreeState.VisibleTreeStateNodeEnumeration
1461
private class VisibleFHTreeStateNodeEnumeration
1462    implements Enumeration JavaDoc<TreePath JavaDoc>
1463    {
1464    /** Parent thats children are being enumerated. */
1465    protected FHTreeStateNode parent;
1466    /** Index of next child. An index of -1 signifies parent should be
1467     * visibled next. */

1468    protected int nextIndex;
1469    /** Number of children in parent. */
1470    protected int childCount;
1471
1472    protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode node) {
1473        this(node, -1);
1474    }
1475
1476    protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode parent,
1477                            int startIndex) {
1478        this.parent = parent;
1479        this.nextIndex = startIndex;
1480        this.childCount = treeModel.getChildCount(this.parent.
1481                              getUserObject());
1482    }
1483
1484    /**
1485     * @return true if more visible nodes.
1486     */

1487    public boolean hasMoreElements() {
1488        return (parent != null);
1489    }
1490
1491    /**
1492     * @return next visible TreePath.
1493     */

1494    public TreePath JavaDoc nextElement() {
1495        if(!hasMoreElements())
1496        throw new NoSuchElementException JavaDoc("No more visible paths");
1497
1498        TreePath JavaDoc retObject;
1499
1500        if(nextIndex == -1)
1501        retObject = parent.getTreePath();
1502        else {
1503        FHTreeStateNode node = parent.getChildAtModelIndex(nextIndex);
1504
1505        if(node == null)
1506            retObject = parent.getTreePath().pathByAddingChild
1507                      (treeModel.getChild(parent.getUserObject(),
1508                              nextIndex));
1509        else
1510            retObject = node.getTreePath();
1511        }
1512        updateNextObject();
1513        return retObject;
1514    }
1515
1516    /**
1517     * Determines the next object by invoking <code>updateNextIndex</code>
1518     * and if not succesful <code>findNextValidParent</code>.
1519     */

1520    protected void updateNextObject() {
1521        if(!updateNextIndex()) {
1522        findNextValidParent();
1523        }
1524    }
1525
1526    /**
1527     * Finds the next valid parent, this should be called when nextIndex
1528     * is beyond the number of children of the current parent.
1529     */

1530    protected boolean findNextValidParent() {
1531        if(parent == root) {
1532        // mark as invalid!
1533
parent = null;
1534        return false;
1535        }
1536        while(parent != null) {
1537        FHTreeStateNode newParent = (FHTreeStateNode)parent.
1538                                          getParent();
1539
1540        if(newParent != null) {
1541            nextIndex = parent.childIndex;
1542            parent = newParent;
1543            childCount = treeModel.getChildCount
1544                                (parent.getUserObject());
1545            if(updateNextIndex())
1546            return true;
1547        }
1548        else
1549            parent = null;
1550        }
1551        return false;
1552    }
1553
1554    /**
1555     * Updates <code>nextIndex</code> returning false if it is beyond
1556     * the number of children of parent.
1557     */

1558    protected boolean updateNextIndex() {
1559        // nextIndex == -1 identifies receiver, make sure is expanded
1560
// before descend.
1561
if(nextIndex == -1 && !parent.isExpanded()) {
1562        return false;
1563        }
1564
1565        // Check that it can have kids
1566
if(childCount == 0) {
1567        return false;
1568        }
1569        // Make sure next index not beyond child count.
1570
else if(++nextIndex >= childCount) {
1571        return false;
1572        }
1573
1574        FHTreeStateNode child = parent.getChildAtModelIndex(nextIndex);
1575
1576        if(child != null && child.isExpanded()) {
1577        parent = child;
1578        nextIndex = -1;
1579        childCount = treeModel.getChildCount(child.getUserObject());
1580        }
1581        return true;
1582    }
1583    } // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration
1584
}
1585
Popular Tags