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