1 /*
2  * Copyright (c) 1998, 2018, 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 package javax.swing.tree;
26 
27 import javax.swing.event.TreeModelEvent;
28 import java.awt.Rectangle;
29 import java.beans.BeanProperty;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 import java.util.NoSuchElementException;
33 import java.util.Stack;
34 import java.util.Vector;
35 
36 import sun.swing.SwingUtilities2;
37 
38 /**
39  * NOTE: This will become more open in a future release.
40  * <p>
41  * <strong>Warning:</strong>
42  * Serialized objects of this class will not be compatible with
43  * future Swing releases. The current serialization support is
44  * appropriate for short term storage or RMI between applications running
45  * the same version of Swing.  As of 1.4, support for long term storage
46  * of all JavaBeans&trade;
47  * has been added to the <code>java.beans</code> package.
48  * Please see {@link java.beans.XMLEncoder}.
49  *
50  * @author Rob Davis
51  * @author Ray Ryan
52  * @author Scott Violet
53  */
54 @SuppressWarnings("serial") // Same-version serialization only
55 public class VariableHeightLayoutCache extends AbstractLayoutCache {
56     /**
57      * The array of nodes that are currently visible, in the order they
58      * are displayed.
59      */
60     private Vector<Object> visibleNodes;
61 
62     /**
63      * This is set to true if one of the entries has an invalid size.
64      */
65     private boolean           updateNodeSizes;
66 
67     /**
68      * The root node of the internal cache of nodes that have been shown.
69      * If the treeModel is vending a network rather than a true tree,
70      * there may be one cached node for each path to a modeled node.
71      */
72     private TreeStateNode     root;
73 
74     /**
75      * Used in getting sizes for nodes to avoid creating a new Rectangle
76      * every time a size is needed.
77      */
78     private Rectangle         boundsBuffer;
79 
80     /**
81      * Maps from <code>TreePath</code> to a <code>TreeStateNode</code>.
82      */
83     private Hashtable<TreePath, TreeStateNode> treePathMapping;
84 
85     /**
86      * A stack of stacks.
87      */
88     private Stack<Stack<TreePath>> tempStacks;
89 
90 
91     /**
92      * Constructs a {@code VariableHeightLayoutCache}.
93      */
VariableHeightLayoutCache()94     public VariableHeightLayoutCache() {
95         super();
96         tempStacks = new Stack<Stack<TreePath>>();
97         visibleNodes = new Vector<Object>();
98         boundsBuffer = new Rectangle();
99         treePathMapping = new Hashtable<TreePath, TreeStateNode>();
100     }
101 
102     /**
103      * Sets the <code>TreeModel</code> that will provide the data.
104      *
105      * @param newModel the <code>TreeModel</code> that is to provide the data
106      */
107     @BeanProperty(description
108             = "The TreeModel that will provide the data.")
setModel(TreeModel newModel)109     public void setModel(TreeModel newModel) {
110         super.setModel(newModel);
111         rebuild(false);
112     }
113 
114     /**
115      * Determines whether or not the root node from
116      * the <code>TreeModel</code> is visible.
117      *
118      * @param rootVisible true if the root node of the tree is to be displayed
119      * @see #rootVisible
120      */
121     @BeanProperty(description
122             = "Whether or not the root node from the TreeModel is visible.")
setRootVisible(boolean rootVisible)123     public void setRootVisible(boolean rootVisible) {
124         if(isRootVisible() != rootVisible && root != null) {
125             if(rootVisible) {
126                 root.updatePreferredSize(0);
127                 visibleNodes.insertElementAt(root, 0);
128             }
129             else if(visibleNodes.size() > 0) {
130                 visibleNodes.removeElementAt(0);
131                 if(treeSelectionModel != null)
132                     treeSelectionModel.removeSelectionPath
133                         (root.getTreePath());
134             }
135             if(treeSelectionModel != null)
136                 treeSelectionModel.resetRowSelection();
137             if(getRowCount() > 0)
138                 getNode(0).setYOrigin(0);
139             updateYLocationsFrom(0);
140             visibleNodesChanged();
141         }
142         super.setRootVisible(rootVisible);
143     }
144 
145     /**
146      * Sets the height of each cell.  If the specified value
147      * is less than or equal to zero the current cell renderer is
148      * queried for each row's height.
149      *
150      * @param rowHeight the height of each cell, in pixels
151      */
152     @BeanProperty(description
153             = "The height of each cell.")
setRowHeight(int rowHeight)154     public void setRowHeight(int rowHeight) {
155         if(rowHeight != getRowHeight()) {
156             super.setRowHeight(rowHeight);
157             invalidateSizes();
158             this.visibleNodesChanged();
159         }
160     }
161 
162     /**
163      * Sets the renderer that is responsible for drawing nodes in the tree.
164      * @param nd the renderer
165      */
setNodeDimensions(NodeDimensions nd)166     public void setNodeDimensions(NodeDimensions nd) {
167         super.setNodeDimensions(nd);
168         invalidateSizes();
169         visibleNodesChanged();
170     }
171 
172     /**
173      * Marks the path <code>path</code> expanded state to
174      * <code>isExpanded</code>.
175      * @param path the <code>TreePath</code> of interest
176      * @param isExpanded true if the path should be expanded, otherwise false
177      */
setExpandedState(TreePath path, boolean isExpanded)178     public void setExpandedState(TreePath path, boolean isExpanded) {
179         if(path != null) {
180             if(isExpanded)
181                 ensurePathIsExpanded(path, true);
182             else {
183                 TreeStateNode        node = getNodeForPath(path, false, true);
184 
185                 if(node != null) {
186                     node.makeVisible();
187                     node.collapse();
188                 }
189             }
190         }
191     }
192 
193     /**
194      * Returns true if the path is expanded, and visible.
195      * @return true if the path is expanded and visible, otherwise false
196      */
getExpandedState(TreePath path)197     public boolean getExpandedState(TreePath path) {
198         TreeStateNode       node = getNodeForPath(path, true, false);
199 
200         return (node != null) ? (node.isVisible() && node.isExpanded()) :
201                                  false;
202     }
203 
204     /**
205       * Returns the <code>Rectangle</code> enclosing the label portion
206       * into which the item identified by <code>path</code> will be drawn.
207       *
208       * @param path  the path to be drawn
209       * @param placeIn the bounds of the enclosing rectangle
210       * @return the bounds of the enclosing rectangle or <code>null</code>
211       *    if the node could not be ascertained
212       */
getBounds(TreePath path, Rectangle placeIn)213     public Rectangle getBounds(TreePath path, Rectangle placeIn) {
214         TreeStateNode       node = getNodeForPath(path, true, false);
215 
216         if(node != null) {
217             if(updateNodeSizes)
218                 updateNodeSizes(false);
219             return node.getNodeBounds(placeIn);
220         }
221         return null;
222     }
223 
224     /**
225       * Returns the path for <code>row</code>.  If <code>row</code>
226       * is not visible, <code>null</code> is returned.
227       *
228       * @param row the location of interest
229       * @return the path for <code>row</code>, or <code>null</code>
230       * if <code>row</code> is not visible
231       */
getPathForRow(int row)232     public TreePath getPathForRow(int row) {
233         if(row >= 0 && row < getRowCount()) {
234             return getNode(row).getTreePath();
235         }
236         return null;
237     }
238 
239     /**
240       * Returns the row where the last item identified in path is visible.
241       * Will return -1 if any of the elements in path are not
242       * currently visible.
243       *
244       * @param path the <code>TreePath</code> of interest
245       * @return the row where the last item in path is visible
246       */
getRowForPath(TreePath path)247     public int getRowForPath(TreePath path) {
248         if(path == null)
249             return -1;
250 
251         TreeStateNode    visNode = getNodeForPath(path, true, false);
252 
253         if(visNode != null)
254             return visNode.getRow();
255         return -1;
256     }
257 
258     /**
259      * Returns the number of visible rows.
260      * @return the number of visible rows
261      */
getRowCount()262     public int getRowCount() {
263         return visibleNodes.size();
264     }
265 
266     /**
267      * Instructs the <code>LayoutCache</code> that the bounds for
268      * <code>path</code> are invalid, and need to be updated.
269      *
270      * @param path the <code>TreePath</code> which is now invalid
271      */
invalidatePathBounds(TreePath path)272     public void invalidatePathBounds(TreePath path) {
273         TreeStateNode       node = getNodeForPath(path, true, false);
274 
275         if(node != null) {
276             node.markSizeInvalid();
277             if(node.isVisible())
278                 updateYLocationsFrom(node.getRow());
279         }
280     }
281 
282     /**
283      * Returns the preferred height.
284      * @return the preferred height
285      */
getPreferredHeight()286     public int getPreferredHeight() {
287         // Get the height
288         int           rowCount = getRowCount();
289 
290         if(rowCount > 0) {
291             TreeStateNode  node = getNode(rowCount - 1);
292 
293             return node.getYOrigin() + node.getPreferredHeight();
294         }
295         return 0;
296     }
297 
298     /**
299      * Returns the preferred width and height for the region in
300      * <code>visibleRegion</code>.
301      *
302      * @param bounds  the region being queried
303      */
getPreferredWidth(Rectangle bounds)304     public int getPreferredWidth(Rectangle bounds) {
305         if(updateNodeSizes)
306             updateNodeSizes(false);
307 
308         return getMaxNodeWidth();
309     }
310 
311     /**
312       * Returns the path to the node that is closest to x,y.  If
313       * there is nothing currently visible this will return <code>null</code>,
314       * otherwise it will always return a valid path.
315       * If you need to test if the
316       * returned object is exactly at x, y you should get the bounds for
317       * the returned path and test x, y against that.
318       *
319       * @param x  the x-coordinate
320       * @param y  the y-coordinate
321       * @return the path to the node that is closest to x, y
322       */
getPathClosestTo(int x, int y)323     public TreePath getPathClosestTo(int x, int y) {
324         if(getRowCount() == 0)
325             return null;
326 
327         if(updateNodeSizes)
328             updateNodeSizes(false);
329 
330         int                row = getRowContainingYLocation(y);
331 
332         return getNode(row).getTreePath();
333     }
334 
335     /**
336      * Returns an <code>Enumerator</code> that increments over the visible paths
337      * starting at the passed in location. The ordering of the enumeration
338      * is based on how the paths are displayed.
339      *
340      * @param path the location in the <code>TreePath</code> to start
341      * @return an <code>Enumerator</code> that increments over the visible
342      *     paths
343      */
getVisiblePathsFrom(TreePath path)344     public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
345         TreeStateNode       node = getNodeForPath(path, true, false);
346 
347         if(node != null) {
348             return new VisibleTreeStateNodeEnumeration(node);
349         }
350         return null;
351     }
352 
353     /**
354      * Returns the number of visible children for <code>path</code>.
355      * @return the number of visible children for <code>path</code>
356      */
getVisibleChildCount(TreePath path)357     public int getVisibleChildCount(TreePath path) {
358         TreeStateNode         node = getNodeForPath(path, true, false);
359 
360         return (node != null) ? node.getVisibleChildCount() : 0;
361     }
362 
363     /**
364      * Informs the <code>TreeState</code> that it needs to recalculate
365      * all the sizes it is referencing.
366      */
invalidateSizes()367     public void invalidateSizes() {
368         if(root != null)
369             root.deepMarkSizeInvalid();
370         if(!isFixedRowHeight() && visibleNodes.size() > 0) {
371             updateNodeSizes(true);
372         }
373     }
374 
375     /**
376       * Returns true if the value identified by <code>path</code> is
377       * currently expanded.
378       * @return true if the value identified by <code>path</code> is
379       *    currently expanded
380       */
isExpanded(TreePath path)381     public boolean isExpanded(TreePath path) {
382         if(path != null) {
383             TreeStateNode     lastNode = getNodeForPath(path, true, false);
384 
385             return (lastNode != null && lastNode.isExpanded());
386         }
387         return false;
388     }
389 
390     //
391     // TreeModelListener methods
392     //
393 
394     /**
395      * Invoked after a node (or a set of siblings) has changed in some
396      * way. The node(s) have not changed locations in the tree or
397      * altered their children arrays, but other attributes have
398      * changed and may affect presentation. Example: the name of a
399      * file has changed, but it is in the same location in the file
400      * system.
401      *
402      * <p><code>e.path</code> returns the path the parent of the
403      * changed node(s).
404      *
405      * <p><code>e.childIndices</code> returns the index(es) of the
406      * changed node(s).
407      *
408      * @param e the <code>TreeModelEvent</code> of interest
409      */
treeNodesChanged(TreeModelEvent e)410     public void treeNodesChanged(TreeModelEvent e) {
411         if(e != null) {
412             int[]             changedIndexs  = e.getChildIndices();
413             TreeStateNode     changedNode = getNodeForPath(
414                     SwingUtilities2.getTreePath(e, getModel()), false, false);
415 
416             if(changedNode != null) {
417                 Object            changedValue = changedNode.getValue();
418 
419                 /* Update the size of the changed node, as well as all the
420                    child indexs that are passed in. */
421                 changedNode.updatePreferredSize();
422                 if(changedNode.hasBeenExpanded() && changedIndexs != null) {
423                     for(int index : changedIndexs) {
424                         TreeStateNode changedChildNode = (TreeStateNode)changedNode
425                                     .getChildAt(index);
426                         /* Reset the user object. */
427                         changedChildNode.setUserObject
428                                     (treeModel.getChild(changedValue, index));
429                         changedChildNode.updatePreferredSize();
430                     }
431                 }
432                 else if (changedNode == root) {
433                     // Null indicies for root indicates it changed.
434                     changedNode.updatePreferredSize();
435                 }
436                 if(!isFixedRowHeight()) {
437                     int          aRow = changedNode.getRow();
438 
439                     if(aRow != -1)
440                         this.updateYLocationsFrom(aRow);
441                 }
442                 this.visibleNodesChanged();
443             }
444         }
445     }
446 
447 
448     /**
449      * Invoked after nodes have been inserted into the tree.
450      *
451      * <p><code>e.path</code> returns the parent of the new nodes.
452      * <p><code>e.childIndices</code> returns the indices of the new nodes in
453      * ascending order.
454      *
455      * @param e the <code>TreeModelEvent</code> of interest
456      */
treeNodesInserted(TreeModelEvent e)457     public void treeNodesInserted(TreeModelEvent e) {
458         if(e != null) {
459             int[]             changedIndexs = e.getChildIndices();
460             TreeStateNode     changedParentNode = getNodeForPath(
461                     SwingUtilities2.getTreePath(e, getModel()), false, false);
462             /* Only need to update the children if the node has been
463                expanded once. */
464             // PENDING(scott): make sure childIndexs is sorted!
465             if(changedParentNode != null && changedIndexs != null &&
466                changedIndexs.length > 0) {
467                 if(changedParentNode.hasBeenExpanded()) {
468                     boolean   makeVisible =((changedParentNode == root &&
469                                             !rootVisible) ||
470                                             (changedParentNode.getRow() != -1 &&
471                                                changedParentNode.isExpanded()));
472                     int  oldChildCount = changedParentNode.getChildCount();
473 
474                     for(int index : changedIndexs)
475                     {
476                         this.createNodeAt(changedParentNode, index);
477                     }
478 
479                     if(oldChildCount == 0) {
480                         // Update the size of the parent.
481                         changedParentNode.updatePreferredSize();
482                     }
483                     if(treeSelectionModel != null)
484                         treeSelectionModel.resetRowSelection();
485                     /* Update the y origins from the index of the parent
486                        to the end of the visible rows. */
487                     if(!isFixedRowHeight() && (makeVisible ||
488                                                (oldChildCount == 0 &&
489                                         changedParentNode.isVisible()))) {
490                         if(changedParentNode == root)
491                             this.updateYLocationsFrom(0);
492                         else
493                             this.updateYLocationsFrom(changedParentNode.
494                                                       getRow());
495                         this.visibleNodesChanged();
496                     }
497                     else if(makeVisible)
498                         this.visibleNodesChanged();
499                 }
500                 else if(treeModel.getChildCount(changedParentNode.getValue())
501                         - changedIndexs.length == 0) {
502                     changedParentNode.updatePreferredSize();
503                     if(!isFixedRowHeight() && changedParentNode.isVisible())
504                         updateYLocationsFrom(changedParentNode.getRow());
505                 }
506             }
507         }
508     }
509 
510     /**
511      * Invoked after nodes have been removed from the tree.  Note that
512      * if a subtree is removed from the tree, this method may only be
513      * invoked once for the root of the removed subtree, not once for
514      * each individual set of siblings removed.
515      *
516      * <p><code>e.path</code> returns the former parent of the deleted nodes.
517      *
518      * <p><code>e.childIndices</code> returns the indices the nodes had
519      * before they were deleted in ascending order.
520      *
521      * @param e the <code>TreeModelEvent</code> of interest
522      */
treeNodesRemoved(TreeModelEvent e)523     public void treeNodesRemoved(TreeModelEvent e) {
524         if(e != null) {
525             int[]             changedIndexs;
526             TreeStateNode     changedParentNode;
527 
528             changedIndexs = e.getChildIndices();
529             changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
530             // PENDING(scott): make sure that changedIndexs are sorted in
531             // ascending order.
532             if(changedParentNode != null && changedIndexs != null &&
533                changedIndexs.length > 0) {
534                 if(changedParentNode.hasBeenExpanded()) {
535                     boolean            makeInvisible;
536                     int                counter;
537                     int                removedRow;
538                     TreeStateNode      removedNode;
539 
540                     makeInvisible = ((changedParentNode == root &&
541                                       !rootVisible) ||
542                                      (changedParentNode.getRow() != -1 &&
543                                       changedParentNode.isExpanded()));
544                     for(counter = changedIndexs.length - 1;counter >= 0;
545                         counter--) {
546                         removedNode = (TreeStateNode)changedParentNode.
547                                 getChildAt(changedIndexs[counter]);
548                         if(removedNode.isExpanded()) {
549                             removedNode.collapse(false);
550                         }
551 
552                         /* Let the selection model now. */
553                         if(makeInvisible) {
554                             removedRow = removedNode.getRow();
555                             if(removedRow != -1) {
556                                 visibleNodes.removeElementAt(removedRow);
557                             }
558                         }
559                         changedParentNode.remove(changedIndexs[counter]);
560                     }
561                     if(changedParentNode.getChildCount() == 0) {
562                         // Update the size of the parent.
563                         changedParentNode.updatePreferredSize();
564                         if (changedParentNode.isExpanded() &&
565                                    changedParentNode.isLeaf()) {
566                             // Node has become a leaf, collapse it.
567                             changedParentNode.collapse(false);
568                         }
569                     }
570                     if(treeSelectionModel != null)
571                         treeSelectionModel.resetRowSelection();
572                     /* Update the y origins from the index of the parent
573                        to the end of the visible rows. */
574                     if(!isFixedRowHeight() && (makeInvisible ||
575                                (changedParentNode.getChildCount() == 0 &&
576                                 changedParentNode.isVisible()))) {
577                         if(changedParentNode == root) {
578                             /* It is possible for first row to have been
579                                removed if the root isn't visible, in which
580                                case ylocations will be off! */
581                             if(getRowCount() > 0)
582                                 getNode(0).setYOrigin(0);
583                             updateYLocationsFrom(0);
584                         }
585                         else
586                             updateYLocationsFrom(changedParentNode.getRow());
587                         this.visibleNodesChanged();
588                     }
589                     else if(makeInvisible)
590                         this.visibleNodesChanged();
591                 }
592                 else if(treeModel.getChildCount(changedParentNode.getValue())
593                         == 0) {
594                     changedParentNode.updatePreferredSize();
595                     if(!isFixedRowHeight() && changedParentNode.isVisible())
596                         this.updateYLocationsFrom(changedParentNode.getRow());
597                 }
598             }
599         }
600     }
601 
602     /**
603      * Invoked after the tree has drastically changed structure from a
604      * given node down.  If the path returned by <code>e.getPath</code>
605      * is of length one and the first element does not identify the
606      * current root node the first element should become the new root
607      * of the tree.
608      *
609      * <p><code>e.path</code> holds the path to the node.
610      * <p><code>e.childIndices</code> returns <code>null</code>.
611      *
612      * @param e the <code>TreeModelEvent</code> of interest
613      */
treeStructureChanged(TreeModelEvent e)614     public void treeStructureChanged(TreeModelEvent e) {
615         if(e != null)
616         {
617             TreePath          changedPath = SwingUtilities2.getTreePath(e, getModel());
618             TreeStateNode     changedNode;
619 
620             changedNode = getNodeForPath(changedPath, false, false);
621 
622             // Check if root has changed, either to a null root, or
623             // to an entirely new root.
624             if(changedNode == root ||
625                (changedNode == null &&
626                 ((changedPath == null && treeModel != null &&
627                   treeModel.getRoot() == null) ||
628                  (changedPath != null && changedPath.getPathCount() == 1)))) {
629                 rebuild(true);
630             }
631             else if(changedNode != null) {
632                 int                              nodeIndex;
633                 TreeStateNode                    newNode, parent;
634                 boolean                          wasExpanded, wasVisible;
635                 int                              newIndex;
636 
637                 wasExpanded = changedNode.isExpanded();
638                 wasVisible = (changedNode.getRow() != -1);
639                 /* Remove the current node and recreate a new one. */
640                 parent = (TreeStateNode)changedNode.getParent();
641                 nodeIndex = parent.getIndex(changedNode);
642                 if(wasVisible && wasExpanded) {
643                     changedNode.collapse(false);
644                 }
645                 if(wasVisible)
646                     visibleNodes.removeElement(changedNode);
647                 changedNode.removeFromParent();
648                 createNodeAt(parent, nodeIndex);
649                 newNode = (TreeStateNode)parent.getChildAt(nodeIndex);
650                 if(wasVisible && wasExpanded)
651                     newNode.expand(false);
652                 newIndex = newNode.getRow();
653                 if(!isFixedRowHeight() && wasVisible) {
654                     if(newIndex == 0)
655                         updateYLocationsFrom(newIndex);
656                     else
657                         updateYLocationsFrom(newIndex - 1);
658                     this.visibleNodesChanged();
659                 }
660                 else if(wasVisible)
661                     this.visibleNodesChanged();
662             }
663         }
664     }
665 
666 
667     //
668     // Local methods
669     //
670 
visibleNodesChanged()671     private void visibleNodesChanged() {
672     }
673 
674     /**
675      * Adds a mapping for node.
676      */
addMapping(TreeStateNode node)677     private void addMapping(TreeStateNode node) {
678         treePathMapping.put(node.getTreePath(), node);
679     }
680 
681     /**
682      * Removes the mapping for a previously added node.
683      */
removeMapping(TreeStateNode node)684     private void removeMapping(TreeStateNode node) {
685         treePathMapping.remove(node.getTreePath());
686     }
687 
688     /**
689      * Returns the node previously added for <code>path</code>. This may
690      * return null, if you to create a node use getNodeForPath.
691      */
getMapping(TreePath path)692     private TreeStateNode getMapping(TreePath path) {
693         return treePathMapping.get(path);
694     }
695 
696     /**
697      * Retursn the bounds for row, <code>row</code> by reference in
698      * <code>placeIn</code>. If <code>placeIn</code> is null a new
699      * Rectangle will be created and returned.
700      */
getBounds(int row, Rectangle placeIn)701     private Rectangle getBounds(int row, Rectangle placeIn) {
702         if(updateNodeSizes)
703             updateNodeSizes(false);
704 
705         if(row >= 0 && row < getRowCount()) {
706             return getNode(row).getNodeBounds(placeIn);
707         }
708         return null;
709     }
710 
711     /**
712      * Completely rebuild the tree, all expanded state, and node caches are
713      * removed. All nodes are collapsed, except the root.
714      */
rebuild(boolean clearSelection)715     private void rebuild(boolean clearSelection) {
716         Object rootObject;
717 
718         treePathMapping.clear();
719         if(treeModel != null && (rootObject = treeModel.getRoot()) != null) {
720             root = createNodeForValue(rootObject);
721             root.path = new TreePath(rootObject);
722             addMapping(root);
723             root.updatePreferredSize(0);
724             visibleNodes.removeAllElements();
725             if (isRootVisible())
726                 visibleNodes.addElement(root);
727             if(!root.isExpanded())
728                 root.expand();
729             else {
730                 Enumeration<?> cursor = root.children();
731                 while(cursor.hasMoreElements()) {
732                     visibleNodes.addElement(cursor.nextElement());
733                 }
734                 if(!isFixedRowHeight())
735                     updateYLocationsFrom(0);
736             }
737         }
738         else {
739             visibleNodes.removeAllElements();
740             root = null;
741         }
742         if(clearSelection && treeSelectionModel != null) {
743             treeSelectionModel.clearSelection();
744         }
745         this.visibleNodesChanged();
746     }
747 
748     /**
749       * Creates a new node to represent the node at <I>childIndex</I> in
750       * <I>parent</I>s children.  This should be called if the node doesn't
751       * already exist and <I>parent</I> has been expanded at least once.
752       * The newly created node will be made visible if <I>parent</I> is
753       * currently expanded.  This does not update the position of any
754       * cells, nor update the selection if it needs to be.  If succesful
755       * in creating the new TreeStateNode, it is returned, otherwise
756       * null is returned.
757       */
createNodeAt(TreeStateNode parent, int childIndex)758     private TreeStateNode createNodeAt(TreeStateNode parent,
759                                          int childIndex) {
760         boolean                isParentRoot;
761         Object                 newValue;
762         TreeStateNode          newChildNode;
763 
764         newValue = treeModel.getChild(parent.getValue(), childIndex);
765         newChildNode = createNodeForValue(newValue);
766         parent.insert(newChildNode, childIndex);
767         newChildNode.updatePreferredSize(-1);
768         isParentRoot = (parent == root);
769         if(newChildNode != null && parent.isExpanded() &&
770            (parent.getRow() != -1 || isParentRoot)) {
771             int                 newRow;
772 
773             /* Find the new row to insert this newly visible node at. */
774             if(childIndex == 0) {
775                 if(isParentRoot && !isRootVisible())
776                     newRow = 0;
777                 else
778                     newRow = parent.getRow() + 1;
779             }
780             else if(childIndex == parent.getChildCount())
781                 newRow = parent.getLastVisibleNode().getRow() + 1;
782             else {
783                 TreeStateNode          previousNode;
784 
785                 previousNode = (TreeStateNode)parent.
786                     getChildAt(childIndex - 1);
787                 newRow = previousNode.getLastVisibleNode().getRow() + 1;
788             }
789             visibleNodes.insertElementAt(newChildNode, newRow);
790         }
791         return newChildNode;
792     }
793 
794     /**
795       * Returns the TreeStateNode identified by path.  This mirrors
796       * the behavior of getNodeForPath, but tries to take advantage of
797       * path if it is an instance of AbstractTreePath.
798       */
getNodeForPath(TreePath path, boolean onlyIfVisible, boolean shouldCreate)799     private TreeStateNode getNodeForPath(TreePath path,
800                                            boolean onlyIfVisible,
801                                            boolean shouldCreate) {
802         if(path != null) {
803             TreeStateNode      node;
804 
805             node = getMapping(path);
806             if(node != null) {
807                 if(onlyIfVisible && !node.isVisible())
808                     return null;
809                 return node;
810             }
811 
812             // Check all the parent paths, until a match is found.
813             Stack<TreePath> paths;
814 
815             if(tempStacks.size() == 0) {
816                 paths = new Stack<TreePath>();
817             }
818             else {
819                 paths = tempStacks.pop();
820             }
821 
822             try {
823                 paths.push(path);
824                 path = path.getParentPath();
825                 node = null;
826                 while(path != null) {
827                     node = getMapping(path);
828                     if(node != null) {
829                         // Found a match, create entries for all paths in
830                         // paths.
831                         while(node != null && paths.size() > 0) {
832                             path = paths.pop();
833                             node.getLoadedChildren(shouldCreate);
834 
835                             int            childIndex = treeModel.
836                                       getIndexOfChild(node.getUserObject(),
837                                                   path.getLastPathComponent());
838 
839                             if(childIndex == -1 ||
840                                childIndex >= node.getChildCount() ||
841                                (onlyIfVisible && !node.isVisible())) {
842                                 node = null;
843                             }
844                             else
845                                 node = (TreeStateNode)node.getChildAt
846                                                (childIndex);
847                         }
848                         return node;
849                     }
850                     paths.push(path);
851                     path = path.getParentPath();
852                 }
853             }
854             finally {
855                 paths.removeAllElements();
856                 tempStacks.push(paths);
857             }
858             // If we get here it means they share a different root!
859             // We could throw an exception...
860         }
861         return null;
862     }
863 
864     /**
865       * Updates the y locations of all of the visible nodes after
866       * location.
867       */
updateYLocationsFrom(int location)868     private void updateYLocationsFrom(int location) {
869         if(location >= 0 && location < getRowCount()) {
870             int                    counter, maxCounter, newYOrigin;
871             TreeStateNode          aNode;
872 
873             aNode = getNode(location);
874             newYOrigin = aNode.getYOrigin() + aNode.getPreferredHeight();
875             for(counter = location + 1, maxCounter = visibleNodes.size();
876                 counter < maxCounter;counter++) {
877                 aNode = (TreeStateNode)visibleNodes.
878                     elementAt(counter);
879                 aNode.setYOrigin(newYOrigin);
880                 newYOrigin += aNode.getPreferredHeight();
881             }
882         }
883     }
884 
885     /**
886       * Resets the y origin of all the visible nodes as well as messaging
887       * all the visible nodes to updatePreferredSize().  You should not
888       * normally have to call this.  Expanding and contracting the nodes
889       * automaticly adjusts the locations.
890       * updateAll determines if updatePreferredSize() is call on all nodes
891       * or just those that don't have a valid size.
892       */
updateNodeSizes(boolean updateAll)893     private void updateNodeSizes(boolean updateAll) {
894         int                      aY, counter, maxCounter;
895         TreeStateNode            node;
896 
897         updateNodeSizes = false;
898         for(aY = counter = 0, maxCounter = visibleNodes.size();
899             counter < maxCounter; counter++) {
900             node = (TreeStateNode)visibleNodes.elementAt(counter);
901             node.setYOrigin(aY);
902             if(updateAll || !node.hasValidSize())
903                 node.updatePreferredSize(counter);
904             aY += node.getPreferredHeight();
905         }
906     }
907 
908     /**
909       * Returns the index of the row containing location.  If there
910       * are no rows, -1 is returned.  If location is beyond the last
911       * row index, the last row index is returned.
912       */
getRowContainingYLocation(int location)913     private int getRowContainingYLocation(int location) {
914         final int rows = getRowCount();
915 
916         if(rows <= 0)
917             return -1;
918         if(isFixedRowHeight()) {
919             return Math.max(0, Math.min(rows - 1,
920                                         location / getRowHeight()));
921         }
922 
923         int max = rows, min = 0, mid = 0;
924 
925         while(min < max) {
926             mid = (max - min) / 2 + min;
927             TreeStateNode node = (TreeStateNode)visibleNodes.elementAt(mid);
928             int minY = node.getYOrigin();
929             int maxY = minY + node.getPreferredHeight();
930             if(location < minY) {
931                 max = mid - 1;
932             }
933             else if(location >= maxY) {
934                 min = mid + 1;
935             }
936             else
937                 break;
938         }
939         if(min == max) {
940             mid = min;
941             if(mid >= rows)
942                 mid = rows - 1;
943         }
944         return mid;
945     }
946 
947     /**
948      * Ensures that all the path components in path are expanded, accept
949      * for the last component which will only be expanded if expandLast
950      * is true.
951      * Returns true if succesful in finding the path.
952      */
ensurePathIsExpanded(TreePath aPath, boolean expandLast)953     private void ensurePathIsExpanded(TreePath aPath, boolean expandLast) {
954         if(aPath != null) {
955             // Make sure the last entry isn't a leaf.
956             if(treeModel.isLeaf(aPath.getLastPathComponent())) {
957                 aPath = aPath.getParentPath();
958                 expandLast = true;
959             }
960             if(aPath != null) {
961                 TreeStateNode     lastNode = getNodeForPath(aPath, false,
962                                                             true);
963 
964                 if(lastNode != null) {
965                     lastNode.makeVisible();
966                     if(expandLast)
967                         lastNode.expand();
968                 }
969             }
970         }
971     }
972 
973     /**
974      * Returns the AbstractTreeUI.VisibleNode displayed at the given row
975      */
getNode(int row)976     private TreeStateNode getNode(int row) {
977         return (TreeStateNode)visibleNodes.elementAt(row);
978     }
979 
980     /**
981       * Returns the maximum node width.
982       */
getMaxNodeWidth()983     private int getMaxNodeWidth() {
984         int                     maxWidth = 0;
985         int                     nodeWidth;
986         int                     counter;
987         TreeStateNode           node;
988 
989         for(counter = getRowCount() - 1;counter >= 0;counter--) {
990             node = this.getNode(counter);
991             nodeWidth = node.getPreferredWidth() + node.getXOrigin();
992             if(nodeWidth > maxWidth)
993                 maxWidth = nodeWidth;
994         }
995 
996         return maxWidth;
997     }
998     /**
999       * Responsible for creating a TreeStateNode that will be used
1000       * to track display information about value.
1001       */
createNodeForValue(Object value)1002     private TreeStateNode createNodeForValue(Object value) {
1003         return new TreeStateNode(value);
1004     }
1005 
1006 
1007     /**
1008      * TreeStateNode is used to keep track of each of
1009      * the nodes that have been expanded. This will also cache the preferred
1010      * size of the value it represents.
1011      */
1012     private class TreeStateNode extends DefaultMutableTreeNode {
1013         /** Preferred size needed to draw the user object. */
1014         protected int             preferredWidth;
1015         protected int             preferredHeight;
1016 
1017         /** X location that the user object will be drawn at. */
1018         protected int             xOrigin;
1019 
1020         /** Y location that the user object will be drawn at. */
1021         protected int             yOrigin;
1022 
1023         /** Is this node currently expanded? */
1024         protected boolean         expanded;
1025 
1026         /** Has this node been expanded at least once? */
1027         protected boolean         hasBeenExpanded;
1028 
1029         /** Path of this node. */
1030         protected TreePath        path;
1031 
1032 
TreeStateNode(Object value)1033         public TreeStateNode(Object value) {
1034             super(value);
1035         }
1036 
1037         //
1038         // Overriden DefaultMutableTreeNode methods
1039         //
1040 
1041         /**
1042          * Messaged when this node is added somewhere, resets the path
1043          * and adds a mapping from path to this node.
1044          */
setParent(MutableTreeNode parent)1045         public void setParent(MutableTreeNode parent) {
1046             super.setParent(parent);
1047             if(parent != null) {
1048                 path = ((TreeStateNode)parent).getTreePath().
1049                                        pathByAddingChild(getUserObject());
1050                 addMapping(this);
1051             }
1052         }
1053 
1054         /**
1055          * Messaged when this node is removed from its parent, this messages
1056          * <code>removedFromMapping</code> to remove all the children.
1057          */
remove(int childIndex)1058         public void remove(int childIndex) {
1059             TreeStateNode     node = (TreeStateNode)getChildAt(childIndex);
1060 
1061             node.removeFromMapping();
1062             super.remove(childIndex);
1063         }
1064 
1065         /**
1066          * Messaged to set the user object. This resets the path.
1067          */
setUserObject(Object o)1068         public void setUserObject(Object o) {
1069             super.setUserObject(o);
1070             if(path != null) {
1071                 TreeStateNode      parent = (TreeStateNode)getParent();
1072 
1073                 if(parent != null)
1074                     resetChildrenPaths(parent.getTreePath());
1075                 else
1076                     resetChildrenPaths(null);
1077             }
1078         }
1079 
1080         /**
1081          * Returns the children of the receiver.
1082          * If the receiver is not currently expanded, this will return an
1083          * empty enumeration.
1084          */
1085         @Override
children()1086         public Enumeration<TreeNode> children() {
1087             if (!this.isExpanded()) {
1088                 return DefaultMutableTreeNode.EMPTY_ENUMERATION;
1089             } else {
1090                 return super.children();
1091             }
1092         }
1093 
1094         /**
1095          * Returns true if the receiver is a leaf.
1096          */
isLeaf()1097         public boolean isLeaf() {
1098             return getModel().isLeaf(this.getValue());
1099         }
1100 
1101         //
1102         // VariableHeightLayoutCache
1103         //
1104 
1105         /**
1106          * Returns the location and size of this node.
1107          */
getNodeBounds(Rectangle placeIn)1108         public Rectangle getNodeBounds(Rectangle placeIn) {
1109             if(placeIn == null)
1110                 placeIn = new Rectangle(getXOrigin(), getYOrigin(),
1111                                         getPreferredWidth(),
1112                                         getPreferredHeight());
1113             else {
1114                 placeIn.x = getXOrigin();
1115                 placeIn.y = getYOrigin();
1116                 placeIn.width = getPreferredWidth();
1117                 placeIn.height = getPreferredHeight();
1118             }
1119             return placeIn;
1120         }
1121 
1122         /**
1123          * @return x location to draw node at.
1124          */
getXOrigin()1125         public int getXOrigin() {
1126             if(!hasValidSize())
1127                 updatePreferredSize(getRow());
1128             return xOrigin;
1129         }
1130 
1131         /**
1132          * Returns the y origin the user object will be drawn at.
1133          */
getYOrigin()1134         public int getYOrigin() {
1135             if(isFixedRowHeight()) {
1136                 int      aRow = getRow();
1137 
1138                 if(aRow == -1)
1139                     return -1;
1140                 return getRowHeight() * aRow;
1141             }
1142             return yOrigin;
1143         }
1144 
1145         /**
1146          * Returns the preferred height of the receiver.
1147          */
getPreferredHeight()1148         public int getPreferredHeight() {
1149             if(isFixedRowHeight())
1150                 return getRowHeight();
1151             else if(!hasValidSize())
1152                 updatePreferredSize(getRow());
1153             return preferredHeight;
1154         }
1155 
1156         /**
1157          * Returns the preferred width of the receiver.
1158          */
getPreferredWidth()1159         public int getPreferredWidth() {
1160             if(!hasValidSize())
1161                 updatePreferredSize(getRow());
1162             return preferredWidth;
1163         }
1164 
1165         /**
1166          * Returns true if this node has a valid size.
1167          */
hasValidSize()1168         public boolean hasValidSize() {
1169             return (preferredHeight != 0);
1170         }
1171 
1172         /**
1173          * Returns the row of the receiver.
1174          */
getRow()1175         public int getRow() {
1176             return visibleNodes.indexOf(this);
1177         }
1178 
1179         /**
1180          * Returns true if this node has been expanded at least once.
1181          */
hasBeenExpanded()1182         public boolean hasBeenExpanded() {
1183             return hasBeenExpanded;
1184         }
1185 
1186         /**
1187          * Returns true if the receiver has been expanded.
1188          */
isExpanded()1189         public boolean isExpanded() {
1190             return expanded;
1191         }
1192 
1193         /**
1194          * Returns the last visible node that is a child of this
1195          * instance.
1196          */
getLastVisibleNode()1197         public TreeStateNode getLastVisibleNode() {
1198             TreeStateNode                node = this;
1199 
1200             while(node.isExpanded() && node.getChildCount() > 0)
1201                 node = (TreeStateNode)node.getLastChild();
1202             return node;
1203         }
1204 
1205         /**
1206          * Returns true if the receiver is currently visible.
1207          */
isVisible()1208         public boolean isVisible() {
1209             if(this == root)
1210                 return true;
1211 
1212             TreeStateNode        parent = (TreeStateNode)getParent();
1213 
1214             return (parent != null && parent.isExpanded() &&
1215                     parent.isVisible());
1216         }
1217 
1218         /**
1219          * Returns the number of children this will have. If the children
1220          * have not yet been loaded, this messages the model.
1221          */
getModelChildCount()1222         public int getModelChildCount() {
1223             if(hasBeenExpanded)
1224                 return super.getChildCount();
1225             return getModel().getChildCount(getValue());
1226         }
1227 
1228         /**
1229          * Returns the number of visible children, that is the number of
1230          * children that are expanded, or leafs.
1231          */
getVisibleChildCount()1232         public int getVisibleChildCount() {
1233             int               childCount = 0;
1234 
1235             if(isExpanded()) {
1236                 int         maxCounter = getChildCount();
1237 
1238                 childCount += maxCounter;
1239                 for(int counter = 0; counter < maxCounter; counter++)
1240                     childCount += ((TreeStateNode)getChildAt(counter)).
1241                                     getVisibleChildCount();
1242             }
1243             return childCount;
1244         }
1245 
1246         /**
1247          * Toggles the receiver between expanded and collapsed.
1248          */
toggleExpanded()1249         public void toggleExpanded() {
1250             if (isExpanded()) {
1251                 collapse();
1252             } else {
1253                 expand();
1254             }
1255         }
1256 
1257         /**
1258          * Makes the receiver visible, but invoking
1259          * <code>expandParentAndReceiver</code> on the superclass.
1260          */
makeVisible()1261         public void makeVisible() {
1262             TreeStateNode       parent = (TreeStateNode)getParent();
1263 
1264             if(parent != null)
1265                 parent.expandParentAndReceiver();
1266         }
1267 
1268         /**
1269          * Expands the receiver.
1270          */
expand()1271         public void expand() {
1272             expand(true);
1273         }
1274 
1275         /**
1276          * Collapses the receiver.
1277          */
collapse()1278         public void collapse() {
1279             collapse(true);
1280         }
1281 
1282         /**
1283          * Returns the value the receiver is representing. This is a cover
1284          * for getUserObject.
1285          */
getValue()1286         public Object getValue() {
1287             return getUserObject();
1288         }
1289 
1290         /**
1291          * Returns a TreePath instance for this node.
1292          */
getTreePath()1293         public TreePath getTreePath() {
1294             return path;
1295         }
1296 
1297         //
1298         // Local methods
1299         //
1300 
1301         /**
1302          * Recreates the receivers path, and all its children's paths.
1303          */
resetChildrenPaths(TreePath parentPath)1304         protected void resetChildrenPaths(TreePath parentPath) {
1305             removeMapping(this);
1306             if(parentPath == null)
1307                 path = new TreePath(getUserObject());
1308             else
1309                 path = parentPath.pathByAddingChild(getUserObject());
1310             addMapping(this);
1311             for(int counter = getChildCount() - 1; counter >= 0; counter--)
1312                 ((TreeStateNode)getChildAt(counter)).resetChildrenPaths(path);
1313         }
1314 
1315         /**
1316          * Sets y origin the user object will be drawn at to
1317          * <I>newYOrigin</I>.
1318          */
setYOrigin(int newYOrigin)1319         protected void setYOrigin(int newYOrigin) {
1320             yOrigin = newYOrigin;
1321         }
1322 
1323         /**
1324          * Shifts the y origin by <code>offset</code>.
1325          */
shiftYOriginBy(int offset)1326         protected void shiftYOriginBy(int offset) {
1327             yOrigin += offset;
1328         }
1329 
1330         /**
1331          * Updates the receivers preferredSize by invoking
1332          * <code>updatePreferredSize</code> with an argument of -1.
1333          */
updatePreferredSize()1334         protected void updatePreferredSize() {
1335             updatePreferredSize(getRow());
1336         }
1337 
1338         /**
1339          * Updates the preferred size by asking the current renderer
1340          * for the Dimension needed to draw the user object this
1341          * instance represents.
1342          */
updatePreferredSize(int index)1343         protected void updatePreferredSize(int index) {
1344             Rectangle       bounds = getNodeDimensions(this.getUserObject(),
1345                                                        index, getLevel(),
1346                                                        isExpanded(),
1347                                                        boundsBuffer);
1348 
1349             if(bounds == null || bounds.height == 0) {
1350                 xOrigin = 0;
1351                 preferredWidth = preferredHeight = 0;
1352                 updateNodeSizes = true;
1353             } else {
1354                 xOrigin = bounds.x;
1355                 preferredWidth = bounds.width;
1356                 if(isFixedRowHeight())
1357                     preferredHeight = getRowHeight();
1358                 else
1359                     preferredHeight = bounds.height;
1360             }
1361         }
1362 
1363         /**
1364          * Marks the receivers size as invalid. Next time the size, location
1365          * is asked for it will be obtained.
1366          */
markSizeInvalid()1367         protected void markSizeInvalid() {
1368             preferredHeight = 0;
1369         }
1370 
1371         /**
1372          * Marks the receivers size, and all its descendants sizes, as invalid.
1373          */
deepMarkSizeInvalid()1374         protected void deepMarkSizeInvalid() {
1375             markSizeInvalid();
1376             for(int counter = getChildCount() - 1; counter >= 0; counter--)
1377                 ((TreeStateNode)getChildAt(counter)).deepMarkSizeInvalid();
1378         }
1379 
1380         /**
1381          * Returns the children of the receiver. If the children haven't
1382          * been loaded from the model and
1383          * <code>createIfNeeded</code> is true, the children are first
1384          * loaded.
1385          */
getLoadedChildren(boolean createIfNeeded)1386         protected Enumeration<TreeNode> getLoadedChildren(boolean createIfNeeded) {
1387             if(!createIfNeeded || hasBeenExpanded)
1388                 return super.children();
1389 
1390             TreeStateNode   newNode;
1391             Object          realNode = getValue();
1392             TreeModel       treeModel = getModel();
1393             int             count = treeModel.getChildCount(realNode);
1394 
1395             hasBeenExpanded = true;
1396 
1397             int    childRow = getRow();
1398 
1399             if(childRow == -1) {
1400                 for (int i = 0; i < count; i++) {
1401                     newNode = createNodeForValue
1402                         (treeModel.getChild(realNode, i));
1403                     this.add(newNode);
1404                     newNode.updatePreferredSize(-1);
1405                 }
1406             }
1407             else {
1408                 childRow++;
1409                 for (int i = 0; i < count; i++) {
1410                     newNode = createNodeForValue
1411                         (treeModel.getChild(realNode, i));
1412                     this.add(newNode);
1413                     newNode.updatePreferredSize(childRow++);
1414                 }
1415             }
1416             return super.children();
1417         }
1418 
1419         /**
1420          * Messaged from expand and collapse. This is meant for subclassers
1421          * that may wish to do something interesting with this.
1422          */
didAdjustTree()1423         protected void didAdjustTree() {
1424         }
1425 
1426         /**
1427          * Invokes <code>expandParentAndReceiver</code> on the parent,
1428          * and expands the receiver.
1429          */
expandParentAndReceiver()1430         protected void expandParentAndReceiver() {
1431             TreeStateNode       parent = (TreeStateNode)getParent();
1432 
1433             if(parent != null)
1434                 parent.expandParentAndReceiver();
1435             expand();
1436         }
1437 
1438         /**
1439          * Expands this node in the tree.  This will load the children
1440          * from the treeModel if this node has not previously been
1441          * expanded.  If <I>adjustTree</I> is true the tree and selection
1442          * are updated accordingly.
1443          */
expand(boolean adjustTree)1444         protected void expand(boolean adjustTree) {
1445             if (!isExpanded() && !isLeaf()) {
1446                 boolean         isFixed = isFixedRowHeight();
1447                 int             startHeight = getPreferredHeight();
1448                 int             originalRow = getRow();
1449 
1450                 expanded = true;
1451                 updatePreferredSize(originalRow);
1452 
1453                 if (!hasBeenExpanded) {
1454                     TreeStateNode  newNode;
1455                     Object         realNode = getValue();
1456                     TreeModel      treeModel = getModel();
1457                     int            count = treeModel.getChildCount(realNode);
1458                     int offset = originalRow == -1 ? -1 : originalRow + 1;
1459                     hasBeenExpanded = true;
1460 
1461                     for (int i = 0; i < count; i++) {
1462                         newNode = createNodeForValue(treeModel.getChild
1463                                                         (realNode, i));
1464                         this.add(newNode);
1465                         newNode.updatePreferredSize(offset);
1466                     }
1467                 }
1468 
1469                 int i = originalRow;
1470                 Enumeration<TreeNode> cursor = preorderEnumeration();
1471                 cursor.nextElement(); // don't add me, I'm already in
1472 
1473                 int newYOrigin = isFixed || (this == root && !isRootVisible()) ?
1474                                     0 : getYOrigin() + this.getPreferredHeight();
1475 
1476                 TreeStateNode   aNode;
1477                 if(!isFixed) {
1478                     while (cursor.hasMoreElements()) {
1479                         aNode = (TreeStateNode) cursor.nextElement();
1480                         if(!updateNodeSizes && !aNode.hasValidSize())
1481                             aNode.updatePreferredSize(i + 1);
1482                         aNode.setYOrigin(newYOrigin);
1483                         newYOrigin += aNode.getPreferredHeight();
1484                         visibleNodes.insertElementAt(aNode, ++i);
1485                     }
1486                 }
1487                 else {
1488                     while (cursor.hasMoreElements()) {
1489                         aNode = (TreeStateNode) cursor.nextElement();
1490                         visibleNodes.insertElementAt(aNode, ++i);
1491                     }
1492                 }
1493 
1494                 if(adjustTree && (originalRow != i ||
1495                                   getPreferredHeight() != startHeight)) {
1496                     // Adjust the Y origin of any nodes following this row.
1497                     if(!isFixed && ++i < getRowCount()) {
1498                         int              counter;
1499                         int              heightDiff = newYOrigin -
1500                             (getYOrigin() + getPreferredHeight()) +
1501                             (getPreferredHeight() - startHeight);
1502 
1503                         for(counter = visibleNodes.size() - 1;counter >= i;
1504                             counter--)
1505                             ((TreeStateNode)visibleNodes.elementAt(counter)).
1506                                 shiftYOriginBy(heightDiff);
1507                     }
1508                     didAdjustTree();
1509                     visibleNodesChanged();
1510                 }
1511 
1512                 // Update the rows in the selection
1513                 if(treeSelectionModel != null) {
1514                     treeSelectionModel.resetRowSelection();
1515                 }
1516             }
1517         }
1518 
1519         /**
1520          * Collapses this node in the tree.  If <I>adjustTree</I> is
1521          * true the tree and selection are updated accordingly.
1522          */
collapse(boolean adjustTree)1523         protected void collapse(boolean adjustTree) {
1524             if (isExpanded()) {
1525                 Enumeration<TreeNode> cursor = preorderEnumeration();
1526                 cursor.nextElement(); // don't remove me, I'm still visible
1527                 int rowsDeleted = 0;
1528                 boolean isFixed = isFixedRowHeight();
1529                 int lastYEnd;
1530                 if(isFixed)
1531                     lastYEnd = 0;
1532                 else
1533                     lastYEnd = getPreferredHeight() + getYOrigin();
1534                 int startHeight = getPreferredHeight();
1535                 int startYEnd = lastYEnd;
1536                 int myRow = getRow();
1537 
1538                 if(!isFixed) {
1539                     while(cursor.hasMoreElements()) {
1540                         TreeStateNode node = (TreeStateNode)cursor.
1541                             nextElement();
1542                         if (node.isVisible()) {
1543                             rowsDeleted++;
1544                             //visibleNodes.removeElement(node);
1545                             lastYEnd = node.getYOrigin() +
1546                                 node.getPreferredHeight();
1547                         }
1548                     }
1549                 }
1550                 else {
1551                     while(cursor.hasMoreElements()) {
1552                         TreeStateNode node = (TreeStateNode)cursor.
1553                             nextElement();
1554                         if (node.isVisible()) {
1555                             rowsDeleted++;
1556                             //visibleNodes.removeElement(node);
1557                         }
1558                     }
1559                 }
1560 
1561                 // Clean up the visible nodes.
1562                 for (int counter = rowsDeleted + myRow; counter > myRow;
1563                      counter--) {
1564                     visibleNodes.removeElementAt(counter);
1565                 }
1566 
1567                 expanded = false;
1568 
1569                 if(myRow == -1)
1570                     markSizeInvalid();
1571                 else if (adjustTree)
1572                     updatePreferredSize(myRow);
1573 
1574                 if(myRow != -1 && adjustTree &&
1575                    (rowsDeleted > 0 || startHeight != getPreferredHeight())) {
1576                     // Adjust the Y origin of any rows following this one.
1577                     startYEnd += (getPreferredHeight() - startHeight);
1578                     if(!isFixed && (myRow + 1) < getRowCount() &&
1579                        startYEnd != lastYEnd) {
1580                         int                 counter, maxCounter, shiftAmount;
1581 
1582                         shiftAmount = startYEnd - lastYEnd;
1583                         for(counter = myRow + 1, maxCounter =
1584                                 visibleNodes.size();
1585                             counter < maxCounter;counter++)
1586                             ((TreeStateNode)visibleNodes.elementAt(counter))
1587                                 .shiftYOriginBy(shiftAmount);
1588                     }
1589                     didAdjustTree();
1590                     visibleNodesChanged();
1591                 }
1592                 if(treeSelectionModel != null && rowsDeleted > 0 &&
1593                    myRow != -1) {
1594                     treeSelectionModel.resetRowSelection();
1595                 }
1596             }
1597         }
1598 
1599         /**
1600          * Removes the receiver, and all its children, from the mapping
1601          * table.
1602          */
removeFromMapping()1603         protected void removeFromMapping() {
1604             if(path != null) {
1605                 removeMapping(this);
1606                 for(int counter = getChildCount() - 1; counter >= 0; counter--)
1607                     ((TreeStateNode)getChildAt(counter)).removeFromMapping();
1608             }
1609         }
1610     } // End of VariableHeightLayoutCache.TreeStateNode
1611 
1612 
1613     /**
1614      * An enumerator to iterate through visible nodes.
1615      */
1616     private class VisibleTreeStateNodeEnumeration implements
1617                      Enumeration<TreePath> {
1618         /** Parent thats children are being enumerated. */
1619         protected TreeStateNode       parent;
1620         /** Index of next child. An index of -1 signifies parent should be
1621          * visibled next. */
1622         protected int                 nextIndex;
1623         /** Number of children in parent. */
1624         protected int                 childCount;
1625 
VisibleTreeStateNodeEnumeration(TreeStateNode node)1626         protected VisibleTreeStateNodeEnumeration(TreeStateNode node) {
1627             this(node, -1);
1628         }
1629 
VisibleTreeStateNodeEnumeration(TreeStateNode parent, int startIndex)1630         protected VisibleTreeStateNodeEnumeration(TreeStateNode parent,
1631                                                   int startIndex) {
1632             this.parent = parent;
1633             this.nextIndex = startIndex;
1634             this.childCount = this.parent.getChildCount();
1635         }
1636 
1637         /**
1638          * @return true if more visible nodes.
1639          */
hasMoreElements()1640         public boolean hasMoreElements() {
1641             return (parent != null);
1642         }
1643 
1644         /**
1645          * @return next visible TreePath.
1646          */
nextElement()1647         public TreePath nextElement() {
1648             if(!hasMoreElements())
1649                 throw new NoSuchElementException("No more visible paths");
1650 
1651             TreePath                retObject;
1652 
1653             if(nextIndex == -1) {
1654                 retObject = parent.getTreePath();
1655             }
1656             else {
1657                 TreeStateNode   node = (TreeStateNode)parent.
1658                                         getChildAt(nextIndex);
1659 
1660                 retObject = node.getTreePath();
1661             }
1662             updateNextObject();
1663             return retObject;
1664         }
1665 
1666         /**
1667          * Determines the next object by invoking <code>updateNextIndex</code>
1668          * and if not succesful <code>findNextValidParent</code>.
1669          */
updateNextObject()1670         protected void updateNextObject() {
1671             if(!updateNextIndex()) {
1672                 findNextValidParent();
1673             }
1674         }
1675 
1676         /**
1677          * Finds the next valid parent, this should be called when nextIndex
1678          * is beyond the number of children of the current parent.
1679          */
findNextValidParent()1680         protected boolean findNextValidParent() {
1681             if(parent == root) {
1682                 // mark as invalid!
1683                 parent = null;
1684                 return false;
1685             }
1686             while(parent != null) {
1687                 TreeStateNode      newParent = (TreeStateNode)parent.
1688                                                   getParent();
1689 
1690                 if(newParent != null) {
1691                     nextIndex = newParent.getIndex(parent);
1692                     parent = newParent;
1693                     childCount = parent.getChildCount();
1694                     if(updateNextIndex())
1695                         return true;
1696                 }
1697                 else
1698                     parent = null;
1699             }
1700             return false;
1701         }
1702 
1703         /**
1704          * Updates <code>nextIndex</code> returning false if it is beyond
1705          * the number of children of parent.
1706          */
updateNextIndex()1707         protected boolean updateNextIndex() {
1708             // nextIndex == -1 identifies receiver, make sure is expanded
1709             // before descend.
1710             if((nextIndex == -1 && !parent.isExpanded()) ||
1711                 childCount == 0 || // Check that it can have kids
1712                 ++nextIndex >= childCount) // Make sure next index not beyond
1713                                              // child count.
1714                 return false;
1715 
1716             TreeStateNode       child = (TreeStateNode)parent.
1717                                         getChildAt(nextIndex);
1718 
1719             if(child != null && child.isExpanded()) {
1720                 parent = child;
1721                 nextIndex = -1;
1722                 childCount = child.getChildCount();
1723             }
1724             return true;
1725         }
1726     } // VariableHeightLayoutCache.VisibleTreeStateNodeEnumeration
1727 }
1728