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