KickJava   Java API By Example, From Geeks To Geeks.

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


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

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

36
37 public class VariableHeightLayoutCache extends AbstractLayoutCache JavaDoc {
38     /**
39      * The array of nodes that are currently visible, in the order they
40      * are displayed.
41      */

42     private Vector JavaDoc visibleNodes;
43
44     /**
45      * This is set to true if one of the entries has an invalid size.
46      */

47     private boolean updateNodeSizes;
48
49     /**
50      * The root node of the internal cache of nodes that have been shown.
51      * If the treeModel is vending a network rather than a true tree,
52      * there may be one cached node for each path to a modeled node.
53      */

54     private TreeStateNode root;
55
56     /**
57      * Used in getting sizes for nodes to avoid creating a new Rectangle
58      * every time a size is needed.
59      */

60     private Rectangle JavaDoc boundsBuffer;
61
62     /**
63      * Maps from <code>TreePath</code> to a <code>TreeStateNode</code>.
64      */

65     private Hashtable JavaDoc treePathMapping;
66
67     /**
68      * A stack of stacks.
69      */

70     private Stack JavaDoc tempStacks;
71
72
73     public VariableHeightLayoutCache() {
74     super();
75     tempStacks = new Stack JavaDoc();
76     visibleNodes = new Vector JavaDoc();
77     boundsBuffer = new Rectangle JavaDoc();
78     treePathMapping = new Hashtable JavaDoc();
79     }
80
81     /**
82      * Sets the <code>TreeModel</code> that will provide the data.
83      *
84      * @param newModel the <code>TreeModel</code> that is to provide the data
85      * @beaninfo
86      * bound: true
87      * description: The TreeModel that will provide the data.
88      */

89     public void setModel(TreeModel JavaDoc newModel) {
90     super.setModel(newModel);
91     rebuild(false);
92     }
93
94     /**
95      * Determines whether or not the root node from
96      * the <code>TreeModel</code> is visible.
97      *
98      * @param rootVisible true if the root node of the tree is to be displayed
99      * @see #rootVisible
100      * @beaninfo
101      * bound: true
102      * description: Whether or not the root node
103      * from the TreeModel is visible.
104      */

105     public void setRootVisible(boolean rootVisible) {
106     if(isRootVisible() != rootVisible && root != null) {
107         if(rootVisible) {
108         root.updatePreferredSize(0);
109         visibleNodes.insertElementAt(root, 0);
110         }
111         else if(visibleNodes.size() > 0) {
112         visibleNodes.removeElementAt(0);
113         if(treeSelectionModel != null)
114             treeSelectionModel.removeSelectionPath
115             (root.getTreePath());
116         }
117         if(treeSelectionModel != null)
118         treeSelectionModel.resetRowSelection();
119         if(getRowCount() > 0)
120         getNode(0).setYOrigin(0);
121         updateYLocationsFrom(0);
122         visibleNodesChanged();
123     }
124     super.setRootVisible(rootVisible);
125     }
126
127     /**
128      * Sets the height of each cell. If the specified value
129      * is less than or equal to zero the current cell renderer is
130      * queried for each row's height.
131      *
132      * @param rowHeight the height of each cell, in pixels
133      * @beaninfo
134      * bound: true
135      * description: The height of each cell.
136      */

137     public void setRowHeight(int rowHeight) {
138     if(rowHeight != getRowHeight()) {
139         super.setRowHeight(rowHeight);
140         invalidateSizes();
141         this.visibleNodesChanged();
142     }
143     }
144
145     /**
146      * Sets the renderer that is responsible for drawing nodes in the tree.
147      * @param nd the renderer
148      */

149     public void setNodeDimensions(NodeDimensions nd) {
150     super.setNodeDimensions(nd);
151     invalidateSizes();
152     visibleNodesChanged();
153     }
154
155     /**
156      * Marks the path <code>path</code> expanded state to
157      * <code>isExpanded</code>.
158      * @param path the <code>TreePath</code> of interest
159      * @param isExpanded true if the path should be expanded, otherwise false
160      */

161     public void setExpandedState(TreePath JavaDoc path, boolean isExpanded) {
162     if(path != null) {
163         if(isExpanded)
164         ensurePathIsExpanded(path, true);
165         else {
166         TreeStateNode node = getNodeForPath(path, false, true);
167
168         if(node != null) {
169             node.makeVisible();
170             node.collapse();
171         }
172         }
173     }
174     }
175
176     /**
177      * Returns true if the path is expanded, and visible.
178      * @return true if the path is expanded and visible, otherwise false
179      */

180     public boolean getExpandedState(TreePath JavaDoc path) {
181     TreeStateNode node = getNodeForPath(path, true, false);
182
183     return (node != null) ? (node.isVisible() && node.isExpanded()) :
184                              false;
185     }
186
187     /**
188       * Returns the <code>Rectangle</code> enclosing the label portion
189       * into which the item identified by <code>path</code> will be drawn.
190       *
191       * @param path the path to be drawn
192       * @param placeIn the bounds of the enclosing rectangle
193       * @return the bounds of the enclosing rectangle or <code>null</code>
194       * if the node could not be ascertained
195       */

196     public Rectangle JavaDoc getBounds(TreePath JavaDoc path, Rectangle JavaDoc placeIn) {
197     TreeStateNode node = getNodeForPath(path, true, false);
198
199     if(node != null) {
200         if(updateNodeSizes)
201         updateNodeSizes(false);
202         return node.getNodeBounds(placeIn);
203     }
204     return null;
205     }
206
207     /**
208       * Returns the path for <code>row</code>. If <code>row</code>
209       * is not visible, <code>null</code> is returned.
210       *
211       * @param row the location of interest
212       * @return the path for <code>row</code>, or <code>null</code>
213       * if <code>row</code> is not visible
214       */

215     public TreePath JavaDoc getPathForRow(int row) {
216     if(row >= 0 && row < getRowCount()) {
217         return getNode(row).getTreePath();
218     }
219     return null;
220     }
221
222     /**
223       * Returns the row where the last item identified in path is visible.
224       * Will return -1 if any of the elements in path are not
225       * currently visible.
226       *
227       * @param path the <code>TreePath</code> of interest
228       * @return the row where the last item in path is visible
229       */

230     public int getRowForPath(TreePath JavaDoc path) {
231     if(path == null)
232         return -1;
233
234     TreeStateNode visNode = getNodeForPath(path, true, false);
235
236     if(visNode != null)
237         return visNode.getRow();
238     return -1;
239     }
240
241     /**
242      * Returns the number of visible rows.
243      * @return the number of visible rows
244      */

245     public int getRowCount() {
246     return visibleNodes.size();
247     }
248
249     /**
250      * Instructs the <code>LayoutCache</code> that the bounds for
251      * <code>path</code> are invalid, and need to be updated.
252      *
253      * @param path the <code>TreePath</code> which is now invalid
254      */

255     public void invalidatePathBounds(TreePath JavaDoc path) {
256     TreeStateNode node = getNodeForPath(path, true, false);
257
258     if(node != null) {
259         node.markSizeInvalid();
260         if(node.isVisible())
261         updateYLocationsFrom(node.getRow());
262     }
263     }
264
265     /**
266      * Returns the preferred height.
267      * @return the preferred height
268      */

269     public int getPreferredHeight() {
270     // Get the height
271
int rowCount = getRowCount();
272
273     if(rowCount > 0) {
274         TreeStateNode node = getNode(rowCount - 1);
275
276         return node.getYOrigin() + node.getPreferredHeight();
277     }
278     return 0;
279     }
280
281     /**
282      * Returns the preferred width and height for the region in
283      * <code>visibleRegion</code>.
284      *
285      * @param bounds the region being queried
286      */

287     public int getPreferredWidth(Rectangle JavaDoc bounds) {
288     if(updateNodeSizes)
289         updateNodeSizes(false);
290
291     return getMaxNodeWidth();
292     }
293
294     /**
295       * Returns the path to the node that is closest to x,y. If
296       * there is nothing currently visible this will return <code>null</code>,
297       * otherwise it will always return a valid path.
298       * If you need to test if the
299       * returned object is exactly at x, y you should get the bounds for
300       * the returned path and test x, y against that.
301       *
302       * @param x the x-coordinate
303       * @param y the y-coordinate
304       * @return the path to the node that is closest to x, y
305       */

306     public TreePath JavaDoc getPathClosestTo(int x, int y) {
307     if(getRowCount() == 0)
308         return null;
309
310     if(updateNodeSizes)
311         updateNodeSizes(false);
312
313     int row = getRowContainingYLocation(y);
314
315     return getNode(row).getTreePath();
316     }
317
318     /**
319      * Returns an <code>Enumerator</code> that increments over the visible paths
320      * starting at the passed in location. The ordering of the enumeration
321      * is based on how the paths are displayed.
322      *
323      * @param path the location in the <code>TreePath</code> to start
324      * @return an <code>Enumerator</code> that increments over the visible
325      * paths
326      */

327     public Enumeration JavaDoc<TreePath JavaDoc> getVisiblePathsFrom(TreePath JavaDoc path) {
328     TreeStateNode node = getNodeForPath(path, true, false);
329
330     if(node != null) {
331         return new VisibleTreeStateNodeEnumeration(node);
332     }
333     return null;
334     }
335
336     /**
337      * Returns the number of visible children for <code>path</code>.
338      * @return the number of visible children for <code>path</code>
339      */

340     public int getVisibleChildCount(TreePath JavaDoc path) {
341     TreeStateNode node = getNodeForPath(path, true, false);
342
343     return (node != null) ? node.getVisibleChildCount() : 0;
344     }
345
346     /**
347      * Informs the <code>TreeState</code> that it needs to recalculate
348      * all the sizes it is referencing.
349      */

350     public void invalidateSizes() {
351     if(root != null)
352         root.deepMarkSizeInvalid();
353     if(!isFixedRowHeight() && visibleNodes.size() > 0) {
354         updateNodeSizes(true);
355     }
356     }
357
358     /**
359       * Returns true if the value identified by <code>path</code> is
360       * currently expanded.
361       * @return true if the value identified by <code>path</code> is
362       * currently expanded
363       */

364     public boolean isExpanded(TreePath JavaDoc path) {
365     if(path != null) {
366         TreeStateNode lastNode = getNodeForPath(path, true, false);
367
368         return (lastNode != null && lastNode.isExpanded());
369     }
370     return false;
371     }
372
373     //
374
// TreeModelListener methods
375
//
376

377     /**
378      * Invoked after a node (or a set of siblings) has changed in some
379      * way. The node(s) have not changed locations in the tree or
380      * altered their children arrays, but other attributes have
381      * changed and may affect presentation. Example: the name of a
382      * file has changed, but it is in the same location in the file
383      * system.
384      *
385      * <p><code>e.path</code> returns the path the parent of the
386      * changed node(s).
387      *
388      * <p><code>e.childIndices</code> returns the index(es) of the
389      * changed node(s).
390      *
391      * @param e the <code>TreeModelEvent</code> of interest
392      */

393     public void treeNodesChanged(TreeModelEvent JavaDoc e) {
394     if(e != null) {
395         int changedIndexs[];
396         TreeStateNode changedNode;
397
398         changedIndexs = e.getChildIndices();
399         changedNode = getNodeForPath(e.getTreePath(), false, false);
400         if(changedNode != null) {
401         Object JavaDoc changedValue = changedNode.getValue();
402
403         /* Update the size of the changed node, as well as all the
404            child indexs that are passed in. */

405         changedNode.updatePreferredSize();
406         if(changedNode.hasBeenExpanded() && changedIndexs != null) {
407             int counter;
408             TreeStateNode changedChildNode;
409
410             for(counter = 0; counter < changedIndexs.length;
411             counter++) {
412             changedChildNode = (TreeStateNode)changedNode
413                     .getChildAt(changedIndexs[counter]);
414             /* Reset the user object. */
415             changedChildNode.setUserObject
416                     (treeModel.getChild(changedValue,
417                              changedIndexs[counter]));
418             changedChildNode.updatePreferredSize();
419             }
420         }
421         else if (changedNode == root) {
422             // Null indicies for root indicates it changed.
423
changedNode.updatePreferredSize();
424         }
425         if(!isFixedRowHeight()) {
426             int aRow = changedNode.getRow();
427
428             if(aRow != -1)
429             this.updateYLocationsFrom(aRow);
430         }
431         this.visibleNodesChanged();
432         }
433     }
434     }
435
436
437     /**
438      * Invoked after nodes have been inserted into the tree.
439      *
440      * <p><code>e.path</code> returns the parent of the new nodes.
441      * <p><code>e.childIndices</code> returns the indices of the new nodes in
442      * ascending order.
443      *
444      * @param e the <code>TreeModelEvent</code> of interest
445      */

446     public void treeNodesInserted(TreeModelEvent JavaDoc e) {
447     if(e != null) {
448         int changedIndexs[];
449         TreeStateNode changedParentNode;
450
451         changedIndexs = e.getChildIndices();
452         changedParentNode = getNodeForPath(e.getTreePath(), false, false);
453         /* Only need to update the children if the node has been
454            expanded once. */

455         // PENDING(scott): make sure childIndexs is sorted!
456
if(changedParentNode != null && changedIndexs != null &&
457            changedIndexs.length > 0) {
458         if(changedParentNode.hasBeenExpanded()) {
459             boolean makeVisible;
460             int counter;
461             Object JavaDoc changedParent;
462             TreeStateNode newNode;
463             int oldChildCount = changedParentNode.
464                               getChildCount();
465
466             changedParent = changedParentNode.getValue();
467             makeVisible = ((changedParentNode == root &&
468                     !rootVisible) ||
469                    (changedParentNode.getRow() != -1 &&
470                     changedParentNode.isExpanded()));
471             for(counter = 0;counter < changedIndexs.length;counter++)
472             {
473             newNode = this.createNodeAt(changedParentNode,
474                             changedIndexs[counter]);
475             }
476             if(oldChildCount == 0) {
477             // Update the size of the parent.
478
changedParentNode.updatePreferredSize();
479             }
480             if(treeSelectionModel != null)
481             treeSelectionModel.resetRowSelection();
482             /* Update the y origins from the index of the parent
483                to the end of the visible rows. */

484             if(!isFixedRowHeight() && (makeVisible ||
485                            (oldChildCount == 0 &&
486                     changedParentNode.isVisible()))) {
487             if(changedParentNode == root)
488                 this.updateYLocationsFrom(0);
489             else
490                 this.updateYLocationsFrom(changedParentNode.
491                               getRow());
492             this.visibleNodesChanged();
493             }
494             else if(makeVisible)
495             this.visibleNodesChanged();
496         }
497         else if(treeModel.getChildCount(changedParentNode.getValue())
498             - changedIndexs.length == 0) {
499             changedParentNode.updatePreferredSize();
500             if(!isFixedRowHeight() && changedParentNode.isVisible())
501             updateYLocationsFrom(changedParentNode.getRow());
502         }
503         }
504     }
505     }
506
507     /**
508      * Invoked after nodes have been removed from the tree. Note that
509      * if a subtree is removed from the tree, this method may only be
510      * invoked once for the root of the removed subtree, not once for
511      * each individual set of siblings removed.
512      *
513      * <p><code>e.path</code> returns the former parent of the deleted nodes.
514      *
515      * <p><code>e.childIndices</code> returns the indices the nodes had
516      * before they were deleted in ascending order.
517      *
518      * @param e the <code>TreeModelEvent</code> of interest
519      */

520     public void treeNodesRemoved(TreeModelEvent JavaDoc e) {
521     if(e != null) {
522         int changedIndexs[];
523         TreeStateNode changedParentNode;
524
525         changedIndexs = e.getChildIndices();
526         changedParentNode = getNodeForPath(e.getTreePath(), false, false);
527         // PENDING(scott): make sure that changedIndexs are sorted in
528
// ascending order.
529
if(changedParentNode != null && changedIndexs != null &&
530            changedIndexs.length > 0) {
531         if(changedParentNode.hasBeenExpanded()) {
532             boolean makeInvisible;
533             int counter;
534             int removedRow;
535             TreeStateNode removedNode;
536
537             makeInvisible = ((changedParentNode == root &&
538                       !rootVisible) ||
539                      (changedParentNode.getRow() != -1 &&
540                       changedParentNode.isExpanded()));
541             for(counter = changedIndexs.length - 1;counter >= 0;
542             counter--) {
543             removedNode = (TreeStateNode)changedParentNode.
544                 getChildAt(changedIndexs[counter]);
545             if(removedNode.isExpanded()) {
546                 removedNode.collapse(false);
547             }
548
549             /* Let the selection model now. */
550             if(makeInvisible) {
551                 removedRow = removedNode.getRow();
552                 if(removedRow != -1) {
553                 visibleNodes.removeElementAt(removedRow);
554                 }
555             }
556             changedParentNode.remove(changedIndexs[counter]);
557             }
558             if(changedParentNode.getChildCount() == 0) {
559             // Update the size of the parent.
560
changedParentNode.updatePreferredSize();
561                         if (changedParentNode.isExpanded() &&
562                                    changedParentNode.isLeaf()) {
563                             // Node has become a leaf, collapse it.
564
changedParentNode.collapse(false);
565                         }
566             }
567             if(treeSelectionModel != null)
568             treeSelectionModel.resetRowSelection();
569             /* Update the y origins from the index of the parent
570                to the end of the visible rows. */

571             if(!isFixedRowHeight() && (makeInvisible ||
572                    (changedParentNode.getChildCount() == 0 &&
573                 changedParentNode.isVisible()))) {
574             if(changedParentNode == root) {
575                 /* It is possible for first row to have been
576                    removed if the root isn't visible, in which
577                    case ylocations will be off! */

578                 if(getRowCount() > 0)
579                 getNode(0).setYOrigin(0);
580                 updateYLocationsFrom(0);
581             }
582             else
583                 updateYLocationsFrom(changedParentNode.getRow());
584             this.visibleNodesChanged();
585             }
586             else if(makeInvisible)
587             this.visibleNodesChanged();
588         }
589         else if(treeModel.getChildCount(changedParentNode.getValue())
590             == 0) {
591             changedParentNode.updatePreferredSize();
592             if(!isFixedRowHeight() && changedParentNode.isVisible())
593             this.updateYLocationsFrom(changedParentNode.getRow());
594         }
595         }
596     }
597     }
598
599     /**
600      * Invoked after the tree has drastically changed structure from a
601      * given node down. If the path returned by <code>e.getPath</code>
602      * is of length one and the first element does not identify the
603      * current root node the first element should become the new root
604      * of the tree.
605      *
606      * <p><code>e.path</code> holds the path to the node.
607      * <p><code>e.childIndices</code> returns <code>null</code>.
608      *
609      * @param e the <code>TreeModelEvent</code> of interest
610      */

611     public void treeStructureChanged(TreeModelEvent JavaDoc e) {
612     if(e != null)
613     {
614         TreePath JavaDoc changedPath = e.getTreePath();
615         TreeStateNode changedNode;
616
617         changedNode = getNodeForPath(changedPath, false, false);
618
619         // Check if root has changed, either to a null root, or
620
// to an entirely new root.
621
if(changedNode == root ||
622                (changedNode == null &&
623                 ((changedPath == null && treeModel != null &&
624                   treeModel.getRoot() == null) ||
625                  (changedPath != null && changedPath.getPathCount() == 1)))) {
626                 rebuild(true);
627             }
628         else if(changedNode != null) {
629                 int nodeIndex, oldRow;
630                 TreeStateNode newNode, parent;
631                 boolean wasExpanded, wasVisible;
632                 int newIndex;
633
634                 wasExpanded = changedNode.isExpanded();
635                 wasVisible = (changedNode.getRow() != -1);
636                 /* Remove the current node and recreate a new one. */
637                 parent = (TreeStateNode)changedNode.getParent();
638                 nodeIndex = parent.getIndex(changedNode);
639                 if(wasVisible && wasExpanded) {
640                     changedNode.collapse(false);
641                 }
642                 if(wasVisible)
643                     visibleNodes.removeElement(changedNode);
644                 changedNode.removeFromParent();
645                 createNodeAt(parent, nodeIndex);
646                 newNode = (TreeStateNode)parent.getChildAt(nodeIndex);
647                 if(wasVisible && wasExpanded)
648                     newNode.expand(false);
649                 newIndex = newNode.getRow();
650                 if(!isFixedRowHeight() && wasVisible) {
651                     if(newIndex == 0)
652                         updateYLocationsFrom(newIndex);
653                     else
654                         updateYLocationsFrom(newIndex - 1);
655                     this.visibleNodesChanged();
656                 }
657                 else if(wasVisible)
658                     this.visibleNodesChanged();
659         }
660     }
661     }
662
663
664     //
665
// Local methods
666
//
667

668     private void visibleNodesChanged() {
669     }
670
671     /**
672      * Adds a mapping for node.
673      */

674     private void addMapping(TreeStateNode node) {
675     treePathMapping.put(node.getTreePath(), node);
676     }
677
678     /**
679      * Removes the mapping for a previously added node.
680      */

681     private void removeMapping(TreeStateNode node) {
682     treePathMapping.remove(node.getTreePath());
683     }
684
685     /**
686      * Returns the node previously added for <code>path</code>. This may
687      * return null, if you to create a node use getNodeForPath.
688      */

689     private TreeStateNode getMapping(TreePath JavaDoc path) {
690     return (TreeStateNode)treePathMapping.get(path);
691     }
692
693     /**
694      * Retursn the bounds for row, <code>row</code> by reference in
695      * <code>placeIn</code>. If <code>placeIn</code> is null a new
696      * Rectangle will be created and returned.
697      */

698     private Rectangle JavaDoc getBounds(int row, Rectangle JavaDoc placeIn) {
699     if(updateNodeSizes)
700         updateNodeSizes(false);
701
702     if(row >= 0 && row < getRowCount()) {
703         return getNode(row).getNodeBounds(placeIn);
704     }
705     return null;
706     }
707
708     /**
709      * Completely rebuild the tree, all expanded state, and node caches are
710      * removed. All nodes are collapsed, except the root.
711      */

712     private void rebuild(boolean clearSelection) {
713         Object JavaDoc rootObject;
714
715     treePathMapping.clear();
716     if(treeModel != null && (rootObject = treeModel.getRoot()) != null) {
717         root = createNodeForValue(rootObject);
718         root.path = new TreePath JavaDoc(rootObject);
719         addMapping(root);
720         root.updatePreferredSize(0);
721         visibleNodes.removeAllElements();
722         if (isRootVisible())
723         visibleNodes.addElement(root);
724         if(!root.isExpanded())
725         root.expand();
726         else {
727         Enumeration JavaDoc cursor = root.children();
728         while(cursor.hasMoreElements()) {
729             visibleNodes.addElement(cursor.nextElement());
730         }
731         if(!isFixedRowHeight())
732             updateYLocationsFrom(0);
733         }
734     }
735     else {
736         visibleNodes.removeAllElements();
737         root = null;
738     }
739     if(clearSelection && treeSelectionModel != null) {
740         treeSelectionModel.clearSelection();
741     }
742     this.visibleNodesChanged();
743     }
744
745     /**
746       * Creates a new node to represent the node at <I>childIndex</I> in
747       * <I>parent</I>s children. This should be called if the node doesn't
748       * already exist and <I>parent</I> has been expanded at least once.
749       * The newly created node will be made visible if <I>parent</I> is
750       * currently expanded. This does not update the position of any
751       * cells, nor update the selection if it needs to be. If succesful
752       * in creating the new TreeStateNode, it is returned, otherwise
753       * null is returned.
754       */

755     private TreeStateNode createNodeAt(TreeStateNode parent,
756                      int childIndex) {
757     boolean isParentRoot;
758     Object JavaDoc newValue;
759     TreeStateNode newChildNode;
760
761     newValue = treeModel.getChild(parent.getValue(), childIndex);
762     newChildNode = createNodeForValue(newValue);
763     parent.insert(newChildNode, childIndex);
764     newChildNode.updatePreferredSize(-1);
765     isParentRoot = (parent == root);
766     if(newChildNode != null && parent.isExpanded() &&
767        (parent.getRow() != -1 || isParentRoot)) {
768         int newRow;
769
770         /* Find the new row to insert this newly visible node at. */
771         if(childIndex == 0) {
772         if(isParentRoot && !isRootVisible())
773             newRow = 0;
774         else
775             newRow = parent.getRow() + 1;
776         }
777         else if(childIndex == parent.getChildCount())
778         newRow = parent.getLastVisibleNode().getRow() + 1;
779         else {
780         TreeStateNode previousNode;
781
782         previousNode = (TreeStateNode)parent.
783             getChildAt(childIndex - 1);
784         newRow = previousNode.getLastVisibleNode().getRow() + 1;
785         }
786         visibleNodes.insertElementAt(newChildNode, newRow);
787     }
788     return newChildNode;
789     }
790
791     /**
792       * Returns the TreeStateNode identified by path. This mirrors
793       * the behavior of getNodeForPath, but tries to take advantage of
794       * path if it is an instance of AbstractTreePath.
795       */

796     private TreeStateNode getNodeForPath(TreePath JavaDoc path,
797                        boolean onlyIfVisible,
798                        boolean shouldCreate) {
799     if(path != null) {
800         TreeStateNode node;
801
802         node = getMapping(path);
803         if(node != null) {
804         if(onlyIfVisible && !node.isVisible())
805             return null;
806         return node;
807         }
808
809         // Check all the parent paths, until a match is found.
810
Stack JavaDoc paths;
811
812         if(tempStacks.size() == 0) {
813         paths = new Stack JavaDoc();
814         }
815         else {
816         paths = (Stack JavaDoc)tempStacks.pop();
817         }
818
819         try {
820         paths.push(path);
821         path = path.getParentPath();
822         node = null;
823         while(path != null) {
824             node = getMapping(path);
825             if(node != null) {
826             // Found a match, create entries for all paths in
827
// paths.
828
while(node != null && paths.size() > 0) {
829                 path = (TreePath JavaDoc)paths.pop();
830                 node.getLoadedChildren(shouldCreate);
831
832                 int childIndex = treeModel.
833                       getIndexOfChild(node.getUserObject(),
834                           path.getLastPathComponent());
835
836                 if(childIndex == -1 ||
837                    childIndex >= node.getChildCount() ||
838                    (onlyIfVisible && !node.isVisible())) {
839                 node = null;
840                 }
841                 else
842                 node = (TreeStateNode)node.getChildAt
843                                (childIndex);
844             }
845             return node;
846             }
847             paths.push(path);
848             path = path.getParentPath();
849         }
850         }
851         finally {
852         paths.removeAllElements();
853         tempStacks.push(paths);
854         }
855         // If we get here it means they share a different root!
856
// We could throw an exception...
857
}
858     return null;
859     }
860
861     /**
862       * Updates the y locations of all of the visible nodes after
863       * location.
864       */

865     private void updateYLocationsFrom(int location) {
866     if(location >= 0 && location < getRowCount()) {
867         int counter, maxCounter, newYOrigin;
868         TreeStateNode aNode;
869
870         aNode = getNode(location);
871         newYOrigin = aNode.getYOrigin() + aNode.getPreferredHeight();
872         for(counter = location + 1, maxCounter = visibleNodes.size();
873         counter < maxCounter;counter++) {
874         aNode = (TreeStateNode)visibleNodes.
875             elementAt(counter);
876         aNode.setYOrigin(newYOrigin);
877         newYOrigin += aNode.getPreferredHeight();
878         }
879     }
880     }
881
882     /**
883       * Resets the y origin of all the visible nodes as well as messaging
884       * all the visible nodes to updatePreferredSize(). You should not
885       * normally have to call this. Expanding and contracting the nodes
886       * automaticly adjusts the locations.
887       * updateAll determines if updatePreferredSize() is call on all nodes
888       * or just those that don't have a valid size.
889       */

890     private void updateNodeSizes(boolean updateAll) {
891     int aY, counter, maxCounter;
892     TreeStateNode node;
893
894     updateNodeSizes = false;
895     for(aY = counter = 0, maxCounter = visibleNodes.size();
896         counter < maxCounter; counter++) {
897         node = (TreeStateNode)visibleNodes.elementAt(counter);
898         node.setYOrigin(aY);
899         if(updateAll || !node.hasValidSize())
900         node.updatePreferredSize(counter);
901         aY += node.getPreferredHeight();
902     }
903     }
904
905     /**
906       * Returns the index of the row containing location. If there
907       * are no rows, -1 is returned. If location is beyond the last
908       * row index, the last row index is returned.
909       */

910     private int getRowContainingYLocation(int location) {
911     if(isFixedRowHeight()) {
912         if(getRowCount() == 0)
913         return -1;
914         return Math.max(0, Math.min(getRowCount() - 1,
915                     location / getRowHeight()));
916     }
917
918     int max, maxY, mid, min, minY;
919     TreeStateNode node;
920
921     if((max = getRowCount()) <= 0)
922         return -1;
923     mid = min = 0;
924     while(min < max) {
925         mid = (max - min) / 2 + min;
926         node = (TreeStateNode)visibleNodes.elementAt(mid);
927         minY = node.getYOrigin();
928         maxY = minY + node.getPreferredHeight();
929         if(location < minY) {
930         max = mid - 1;
931         }
932         else if(location >= maxY) {
933         min = mid + 1;
934         }
935         else
936         break;
937     }
938     if(min == max) {
939         mid = min;
940         if(mid >= getRowCount())
941         mid = getRowCount() - 1;
942     }
943     return mid;
944     }
945
946     /**
947      * Ensures that all the path components in path are expanded, accept
948      * for the last component which will only be expanded if expandLast
949      * is true.
950      * Returns true if succesful in finding the path.
951      */

952     private void ensurePathIsExpanded(TreePath JavaDoc aPath, boolean expandLast) {
953     if(aPath != null) {
954         // Make sure the last entry isn't a leaf.
955
if(treeModel.isLeaf(aPath.getLastPathComponent())) {
956         aPath = aPath.getParentPath();
957         expandLast = true;
958         }
959         if(aPath != null) {
960         TreeStateNode lastNode = getNodeForPath(aPath, false,
961                                 true);
962
963         if(lastNode != null) {
964             lastNode.makeVisible();
965             if(expandLast)
966             lastNode.expand();
967         }
968         }
969     }
970     }
971
972     /**
973      * Returns the AbstractTreeUI.VisibleNode displayed at the given row
974      */

975     private TreeStateNode getNode(int row) {
976     return (TreeStateNode)visibleNodes.elementAt(row);
977     }
978
979     /**
980       * Returns the maximum node width.
981       */

982     private int getMaxNodeWidth() {
983     int maxWidth = 0;
984     int nodeWidth;
985     int counter;
986     TreeStateNode node;
987
988     for(counter = getRowCount() - 1;counter >= 0;counter--) {
989         node = this.getNode(counter);
990         nodeWidth = node.getPreferredWidth() + node.getXOrigin();
991         if(nodeWidth > maxWidth)
992         maxWidth = nodeWidth;
993     }
994     return maxWidth;
995     }
996
997     /**
998       * Responsible for creating a TreeStateNode that will be used
999       * to track display information about value.
1000      */

1001    private TreeStateNode createNodeForValue(Object JavaDoc value) {
1002    return new TreeStateNode(value);
1003    }
1004
1005
1006    /**
1007     * TreeStateNode is used to keep track of each of
1008     * the nodes that have been expanded. This will also cache the preferred
1009     * size of the value it represents.
1010     */

1011    private class TreeStateNode extends DefaultMutableTreeNode JavaDoc {
1012    /** Preferred size needed to draw the user object. */
1013    protected int preferredWidth;
1014    protected int preferredHeight;
1015
1016    /** X location that the user object will be drawn at. */
1017    protected int xOrigin;
1018
1019    /** Y location that the user object will be drawn at. */
1020    protected int yOrigin;
1021
1022    /** Is this node currently expanded? */
1023    protected boolean expanded;
1024
1025    /** Has this node been expanded at least once? */
1026    protected boolean hasBeenExpanded;
1027
1028    /** Path of this node. */
1029    protected TreePath JavaDoc path;
1030
1031
1032    public TreeStateNode(Object JavaDoc value) {
1033        super(value);
1034    }
1035
1036    //
1037
// Overriden DefaultMutableTreeNode methods
1038
//
1039

1040    /**
1041     * Messaged when this node is added somewhere, resets the path
1042     * and adds a mapping from path to this node.
1043     */

1044    public void setParent(MutableTreeNode JavaDoc parent) {
1045        super.setParent(parent);
1046        if(parent != null) {
1047        path = ((TreeStateNode)parent).getTreePath().
1048                               pathByAddingChild(getUserObject());
1049        addMapping(this);
1050        }
1051    }
1052
1053    /**
1054     * Messaged when this node is removed from its parent, this messages
1055     * <code>removedFromMapping</code> to remove all the children.
1056     */

1057    public void remove(int childIndex) {
1058        TreeStateNode node = (TreeStateNode)getChildAt(childIndex);
1059
1060        node.removeFromMapping();
1061        super.remove(childIndex);
1062    }
1063
1064    /**
1065     * Messaged to set the user object. This resets the path.
1066     */

1067    public void setUserObject(Object JavaDoc o) {
1068        super.setUserObject(o);
1069        if(path != null) {
1070        TreeStateNode parent = (TreeStateNode)getParent();
1071
1072        if(parent != null)
1073            resetChildrenPaths(parent.getTreePath());
1074        else
1075            resetChildrenPaths(null);
1076        }
1077    }
1078
1079    /**
1080     * Returns the children of the receiver.
1081     * If the receiver is not currently expanded, this will return an
1082     * empty enumeration.
1083     */

1084    public Enumeration JavaDoc children() {
1085        if (!this.isExpanded()) {
1086        return DefaultMutableTreeNode.EMPTY_ENUMERATION;
1087        } else {
1088        return super.children();
1089        }
1090    }
1091
1092    /**
1093     * Returns true if the receiver is a leaf.
1094     */

1095    public boolean isLeaf() {
1096        return getModel().isLeaf(this.getValue());
1097    }
1098
1099    //
1100
// VariableHeightLayoutCache
1101
//
1102

1103    /**
1104     * Returns the location and size of this node.
1105     */

1106    public Rectangle JavaDoc getNodeBounds(Rectangle JavaDoc placeIn) {
1107        if(placeIn == null)
1108        placeIn = new Rectangle JavaDoc(getXOrigin(), getYOrigin(),
1109                    getPreferredWidth(),
1110                    getPreferredHeight());
1111        else {
1112        placeIn.x = getXOrigin();
1113        placeIn.y = getYOrigin();
1114        placeIn.width = getPreferredWidth();
1115        placeIn.height = getPreferredHeight();
1116        }
1117        return placeIn;
1118    }
1119
1120    /**
1121     * @return x location to draw node at.
1122     */

1123    public int getXOrigin() {
1124        if(!hasValidSize())
1125        updatePreferredSize(getRow());
1126        return xOrigin;
1127    }
1128
1129    /**
1130     * Returns the y origin the user object will be drawn at.
1131     */

1132    public int getYOrigin() {
1133        if(isFixedRowHeight()) {
1134        int aRow = getRow();
1135
1136        if(aRow == -1)
1137            return -1;
1138        return getRowHeight() * aRow;
1139        }
1140        return yOrigin;
1141    }
1142
1143    /**
1144     * Returns the preferred height of the receiver.
1145     */

1146    public int getPreferredHeight() {
1147        if(isFixedRowHeight())
1148        return getRowHeight();
1149        else if(!hasValidSize())
1150        updatePreferredSize(getRow());
1151        return preferredHeight;
1152    }
1153
1154    /**
1155     * Returns the preferred width of the receiver.
1156     */

1157    public int getPreferredWidth() {
1158        if(!hasValidSize())
1159        updatePreferredSize(getRow());
1160        return preferredWidth;
1161    }
1162
1163    /**
1164     * Returns true if this node has a valid size.
1165     */

1166    public boolean hasValidSize() {
1167        return (preferredHeight != 0);
1168    }
1169
1170    /**
1171     * Returns the row of the receiver.
1172     */

1173    public int getRow() {
1174        return visibleNodes.indexOf(this);
1175    }
1176
1177    /**
1178     * Returns true if this node has been expanded at least once.
1179     */

1180    public boolean hasBeenExpanded() {
1181        return hasBeenExpanded;
1182    }
1183
1184    /**
1185     * Returns true if the receiver has been expanded.
1186     */

1187    public boolean isExpanded() {
1188        return expanded;
1189    }
1190
1191    /**
1192     * Returns the last visible node that is a child of this
1193     * instance.
1194     */

1195    public TreeStateNode getLastVisibleNode() {
1196        TreeStateNode node = this;
1197
1198        while(node.isExpanded() && node.getChildCount() > 0)
1199        node = (TreeStateNode)node.getLastChild();
1200        return node;
1201    }
1202
1203    /**
1204     * Returns true if the receiver is currently visible.
1205     */

1206    public boolean isVisible() {
1207        if(this == root)
1208        return true;
1209
1210        TreeStateNode parent = (TreeStateNode)getParent();
1211
1212        return (parent != null && parent.isExpanded() &&
1213            parent.isVisible());
1214    }
1215
1216    /**
1217     * Returns the number of children this will have. If the children
1218     * have not yet been loaded, this messages the model.
1219     */

1220    public int getModelChildCount() {
1221        if(hasBeenExpanded)
1222        return super.getChildCount();
1223        return getModel().getChildCount(getValue());
1224    }
1225
1226    /**
1227     * Returns the number of visible children, that is the number of
1228     * children that are expanded, or leafs.
1229     */

1230    public int getVisibleChildCount() {
1231        int childCount = 0;
1232
1233        if(isExpanded()) {
1234        int maxCounter = getChildCount();
1235
1236        childCount += maxCounter;
1237        for(int counter = 0; counter < maxCounter; counter++)
1238            childCount += ((TreeStateNode)getChildAt(counter)).
1239                        getVisibleChildCount();
1240        }
1241        return childCount;
1242    }
1243
1244    /**
1245     * Toggles the receiver between expanded and collapsed.
1246     */

1247    public void toggleExpanded() {
1248        if (isExpanded()) {
1249        collapse();
1250        } else {
1251        expand();
1252        }
1253    }
1254
1255    /**
1256     * Makes the receiver visible, but invoking
1257     * <code>expandParentAndReceiver</code> on the superclass.
1258     */

1259    public void makeVisible() {
1260        TreeStateNode parent = (TreeStateNode)getParent();
1261
1262        if(parent != null)
1263        parent.expandParentAndReceiver();
1264    }
1265
1266    /**
1267     * Expands the receiver.
1268     */

1269    public void expand() {
1270        expand(true);
1271    }
1272
1273    /**
1274     * Collapses the receiver.
1275     */

1276    public void collapse() {
1277        collapse(true);
1278    }
1279
1280    /**
1281     * Returns the value the receiver is representing. This is a cover
1282     * for getUserObject.
1283     */

1284    public Object JavaDoc getValue() {
1285        return getUserObject();
1286    }
1287
1288    /**
1289     * Returns a TreePath instance for this node.
1290     */

1291    public TreePath JavaDoc getTreePath() {
1292        return path;
1293    }
1294
1295    //
1296
// Local methods
1297
//
1298

1299    /**
1300     * Recreates the receivers path, and all its childrens paths.
1301     */

1302    protected void resetChildrenPaths(TreePath JavaDoc parentPath) {
1303        removeMapping(this);
1304        if(parentPath == null)
1305        path = new TreePath JavaDoc(getUserObject());
1306        else
1307        path = parentPath.pathByAddingChild(getUserObject());
1308        addMapping(this);
1309        for(int counter = getChildCount() - 1; counter >= 0; counter--)
1310        ((TreeStateNode)getChildAt(counter)).resetChildrenPaths(path);
1311    }
1312
1313    /**
1314     * Sets y origin the user object will be drawn at to
1315     * <I>newYOrigin</I>.
1316     */

1317    protected void setYOrigin(int newYOrigin) {
1318        yOrigin = newYOrigin;
1319    }
1320
1321    /**
1322     * Shifts the y origin by <code>offset</code>.
1323     */

1324    protected void shiftYOriginBy(int offset) {
1325        yOrigin += offset;
1326    }
1327
1328    /**
1329     * Updates the receivers preferredSize by invoking
1330     * <code>updatePreferredSize</code> with an argument of -1.
1331     */

1332    protected void updatePreferredSize() {
1333        updatePreferredSize(getRow());
1334    }
1335
1336    /**
1337     * Updates the preferred size by asking the current renderer
1338     * for the Dimension needed to draw the user object this
1339     * instance represents.
1340     */

1341    protected void updatePreferredSize(int index) {
1342        Rectangle JavaDoc bounds = getNodeDimensions(this.getUserObject(),
1343                               index, getLevel(),
1344                               isExpanded(),
1345                               boundsBuffer);
1346
1347        if(bounds == null) {
1348        xOrigin = 0;
1349        preferredWidth = preferredHeight = 0;
1350        updateNodeSizes = true;
1351        }
1352        else if(bounds.height == 0) {
1353        xOrigin = 0;
1354        preferredWidth = preferredHeight = 0;
1355        updateNodeSizes = true;
1356        }
1357        else {
1358        xOrigin = bounds.x;
1359        preferredWidth = bounds.width;
1360        if(isFixedRowHeight())
1361            preferredHeight = getRowHeight();
1362        else
1363            preferredHeight = bounds.height;
1364        }
1365    }
1366
1367    /**
1368     * Marks the receivers size as invalid. Next time the size, location
1369     * is asked for it will be obtained.
1370     */

1371    protected void markSizeInvalid() {
1372        preferredHeight = 0;
1373    }
1374
1375    /**
1376     * Marks the receivers size, and all its descendants sizes, as invalid.
1377     */

1378    protected void deepMarkSizeInvalid() {
1379        markSizeInvalid();
1380        for(int counter = getChildCount() - 1; counter >= 0; counter--)
1381        ((TreeStateNode)getChildAt(counter)).deepMarkSizeInvalid();
1382    }
1383
1384    /**
1385     * Returns the children of the receiver. If the children haven't
1386     * been loaded from the model and
1387     * <code>createIfNeeded</code> is true, the children are first
1388     * loaded.
1389     */

1390    protected Enumeration JavaDoc getLoadedChildren(boolean createIfNeeded) {
1391        if(!createIfNeeded || hasBeenExpanded)
1392        return super.children();
1393
1394        TreeStateNode newNode;
1395        Object JavaDoc realNode = getValue();
1396        TreeModel JavaDoc treeModel = getModel();
1397        int count = treeModel.getChildCount(realNode);
1398
1399        hasBeenExpanded = true;
1400
1401        int childRow = getRow();
1402
1403        if(childRow == -1) {
1404        for (int i = 0; i < count; i++) {
1405            newNode = createNodeForValue
1406            (treeModel.getChild(realNode, i));
1407            this.add(newNode);
1408            newNode.updatePreferredSize(-1);
1409        }
1410        }
1411        else {
1412        childRow++;
1413        for (int i = 0; i < count; i++) {
1414            newNode = createNodeForValue
1415            (treeModel.getChild(realNode, i));
1416            this.add(newNode);
1417            newNode.updatePreferredSize(childRow++);
1418        }
1419        }
1420        return super.children();
1421    }
1422
1423    /**
1424     * Messaged from expand and collapse. This is meant for subclassers
1425     * that may wish to do something interesting with this.
1426     */

1427    protected void didAdjustTree() {
1428    }
1429
1430    /**
1431     * Invokes <code>expandParentAndReceiver</code> on the parent,
1432     * and expands the receiver.
1433     */

1434    protected void expandParentAndReceiver() {
1435        TreeStateNode parent = (TreeStateNode)getParent();
1436
1437        if(parent != null)
1438        parent.expandParentAndReceiver();
1439        expand();
1440    }
1441
1442    /**
1443     * Expands this node in the tree. This will load the children
1444     * from the treeModel if this node has not previously been
1445     * expanded. If <I>adjustTree</I> is true the tree and selection
1446     * are updated accordingly.
1447     */

1448    protected void expand(boolean adjustTree) {
1449        if (!isExpanded() && !isLeaf()) {
1450        boolean isFixed = isFixedRowHeight();
1451        int startHeight = getPreferredHeight();
1452        int originalRow = getRow();
1453
1454        expanded = true;
1455        updatePreferredSize(originalRow);
1456
1457        if (!hasBeenExpanded) {
1458            TreeStateNode newNode;
1459            Object JavaDoc realNode = getValue();
1460            TreeModel JavaDoc treeModel = getModel();
1461            int count = treeModel.getChildCount(realNode);
1462
1463            hasBeenExpanded = true;
1464            if(originalRow == -1) {
1465            for (int i = 0; i < count; i++) {
1466                newNode = createNodeForValue(treeModel.getChild
1467                                (realNode, i));
1468                this.add(newNode);
1469                newNode.updatePreferredSize(-1);
1470            }
1471            }
1472            else {
1473            int offset = originalRow + 1;
1474            for (int i = 0; i < count; i++) {
1475                newNode = createNodeForValue(treeModel.getChild
1476                               (realNode, i));
1477                this.add(newNode);
1478                newNode.updatePreferredSize(offset);
1479            }
1480            }
1481        }
1482
1483        int i = originalRow;
1484        Enumeration JavaDoc cursor = preorderEnumeration();
1485        cursor.nextElement(); // don't add me, I'm already in
1486

1487        int newYOrigin;
1488
1489        if(isFixed)
1490            newYOrigin = 0;
1491        else if(this == root && !isRootVisible())
1492            newYOrigin = 0;
1493        else
1494            newYOrigin = getYOrigin() + this.getPreferredHeight();
1495        TreeStateNode aNode;
1496        if(!isFixed) {
1497            while (cursor.hasMoreElements()) {
1498            aNode = (TreeStateNode)cursor.nextElement();
1499            if(!updateNodeSizes && !aNode.hasValidSize())
1500                aNode.updatePreferredSize(i + 1);
1501            aNode.setYOrigin(newYOrigin);
1502            newYOrigin += aNode.getPreferredHeight();
1503            visibleNodes.insertElementAt(aNode, ++i);
1504            }
1505        }
1506        else {
1507            while (cursor.hasMoreElements()) {
1508            aNode = (TreeStateNode)cursor.nextElement();
1509            visibleNodes.insertElementAt(aNode, ++i);
1510            }
1511        }
1512
1513        if(adjustTree && (originalRow != i ||
1514                  getPreferredHeight() != startHeight)) {
1515            // Adjust the Y origin of any nodes following this row.
1516
if(!isFixed && ++i < getRowCount()) {
1517            int counter;
1518            int heightDiff = newYOrigin -
1519                (getYOrigin() + getPreferredHeight()) +
1520                (getPreferredHeight() - startHeight);
1521
1522            for(counter = visibleNodes.size() - 1;counter >= i;
1523                counter--)
1524                ((TreeStateNode)visibleNodes.elementAt(counter)).
1525                shiftYOriginBy(heightDiff);
1526            }
1527            didAdjustTree();
1528            visibleNodesChanged();
1529        }
1530
1531        // Update the rows in the selection
1532
if(treeSelectionModel != null) {
1533            treeSelectionModel.resetRowSelection();
1534        }
1535        }
1536    }
1537
1538    /**
1539     * Collapses this node in the tree. If <I>adjustTree</I> is
1540     * true the tree and selection are updated accordingly.
1541     */

1542    protected void collapse(boolean adjustTree) {
1543        if (isExpanded()) {
1544        Enumeration JavaDoc cursor = preorderEnumeration();
1545        cursor.nextElement(); // don't remove me, I'm still visible
1546
int rowsDeleted = 0;
1547        boolean isFixed = isFixedRowHeight();
1548        int lastYEnd;
1549        if(isFixed)
1550            lastYEnd = 0;
1551        else
1552            lastYEnd = getPreferredHeight() + getYOrigin();
1553        int startHeight = getPreferredHeight();
1554        int startYEnd = lastYEnd;
1555        int myRow = getRow();
1556
1557        if(!isFixed) {
1558            while(cursor.hasMoreElements()) {
1559            TreeStateNode node = (TreeStateNode)cursor.
1560                nextElement();
1561            if (node.isVisible()) {
1562                rowsDeleted++;
1563                //visibleNodes.removeElement(node);
1564
lastYEnd = node.getYOrigin() +
1565                node.getPreferredHeight();
1566            }
1567            }
1568        }
1569        else {
1570            while(cursor.hasMoreElements()) {
1571            TreeStateNode node = (TreeStateNode)cursor.
1572                nextElement();
1573            if (node.isVisible()) {
1574                rowsDeleted++;
1575                //visibleNodes.removeElement(node);
1576
}
1577            }
1578        }
1579
1580        // Clean up the visible nodes.
1581
for (int counter = rowsDeleted + myRow; counter > myRow;
1582             counter--) {
1583            visibleNodes.removeElementAt(counter);
1584        }
1585
1586        expanded = false;
1587
1588        if(myRow == -1)
1589            markSizeInvalid();
1590        else if (adjustTree)
1591            updatePreferredSize(myRow);
1592
1593        if(myRow != -1 && adjustTree &&
1594           (rowsDeleted > 0 || startHeight != getPreferredHeight())) {
1595            // Adjust the Y origin of any rows following this one.
1596
startYEnd += (getPreferredHeight() - startHeight);
1597            if(!isFixed && (myRow + 1) < getRowCount() &&
1598               startYEnd != lastYEnd) {
1599            int counter, maxCounter, shiftAmount;
1600
1601            shiftAmount = startYEnd - lastYEnd;
1602            for(counter = myRow + 1, maxCounter =
1603                visibleNodes.size();
1604                counter < maxCounter;counter++)
1605                ((TreeStateNode)visibleNodes.elementAt(counter))
1606                .shiftYOriginBy(shiftAmount);
1607            }
1608            didAdjustTree();
1609            visibleNodesChanged();
1610        }
1611        if(treeSelectionModel != null && rowsDeleted > 0 &&
1612           myRow != -1) {
1613            treeSelectionModel.resetRowSelection();
1614        }
1615        }
1616    }
1617
1618    /**
1619     * Removes the receiver, and all its children, from the mapping
1620     * table.
1621     */

1622    protected void removeFromMapping() {
1623        if(path != null) {
1624        removeMapping(this);
1625        for(int counter = getChildCount() - 1; counter >= 0; counter--)
1626            ((TreeStateNode)getChildAt(counter)).removeFromMapping();
1627        }
1628    }
1629    } // End of VariableHeightLayoutCache.TreeStateNode
1630

1631
1632    /**
1633     * An enumerator to iterate through visible nodes.
1634     */

1635    private class VisibleTreeStateNodeEnumeration implements
1636                 Enumeration JavaDoc<TreePath JavaDoc> {
1637    /** Parent thats children are being enumerated. */
1638    protected TreeStateNode parent;
1639    /** Index of next child. An index of -1 signifies parent should be
1640     * visibled next. */

1641    protected int nextIndex;
1642    /** Number of children in parent. */
1643    protected int childCount;
1644
1645    protected VisibleTreeStateNodeEnumeration(TreeStateNode node) {
1646        this(node, -1);
1647    }
1648
1649    protected VisibleTreeStateNodeEnumeration(TreeStateNode parent,
1650                          int startIndex) {
1651        this.parent = parent;
1652        this.nextIndex = startIndex;
1653        this.childCount = this.parent.getChildCount();
1654    }
1655
1656    /**
1657     * @return true if more visible nodes.
1658     */

1659    public boolean hasMoreElements() {
1660        return (parent != null);
1661    }
1662
1663    /**
1664     * @return next visible TreePath.
1665     */

1666    public TreePath JavaDoc nextElement() {
1667        if(!hasMoreElements())
1668        throw new NoSuchElementException JavaDoc("No more visible paths");
1669
1670        TreePath JavaDoc retObject;
1671
1672        if(nextIndex == -1) {
1673        retObject = parent.getTreePath();
1674        }
1675        else {
1676        TreeStateNode node = (TreeStateNode)parent.
1677                                getChildAt(nextIndex);
1678
1679        retObject = node.getTreePath();
1680        }
1681        updateNextObject();
1682        return retObject;
1683    }
1684
1685    /**
1686     * Determines the next object by invoking <code>updateNextIndex</code>
1687     * and if not succesful <code>findNextValidParent</code>.
1688     */

1689    protected void updateNextObject() {
1690        if(!updateNextIndex()) {
1691        findNextValidParent();
1692        }
1693    }
1694
1695    /**
1696     * Finds the next valid parent, this should be called when nextIndex
1697     * is beyond the number of children of the current parent.
1698     */

1699    protected boolean findNextValidParent() {
1700        if(parent == root) {
1701        // mark as invalid!
1702
parent = null;
1703        return false;
1704        }
1705        while(parent != null) {
1706        TreeStateNode newParent = (TreeStateNode)parent.
1707                                          getParent();
1708
1709        if(newParent != null) {
1710            nextIndex = newParent.getIndex(parent);
1711            parent = newParent;
1712            childCount = parent.getChildCount();
1713            if(updateNextIndex())
1714            return true;
1715        }
1716        else
1717            parent = null;
1718        }
1719        return false;
1720    }
1721
1722    /**
1723     * Updates <code>nextIndex</code> returning false if it is beyond
1724     * the number of children of parent.
1725     */

1726    protected boolean updateNextIndex() {
1727        // nextIndex == -1 identifies receiver, make sure is expanded
1728
// before descend.
1729
if(nextIndex == -1 && !parent.isExpanded())
1730        return false;
1731
1732        // Check that it can have kids
1733
if(childCount == 0)
1734        return false;
1735        // Make sure next index not beyond child count.
1736
else if(++nextIndex >= childCount)
1737        return false;
1738
1739        TreeStateNode child = (TreeStateNode)parent.
1740                                getChildAt(nextIndex);
1741
1742        if(child != null && child.isExpanded()) {
1743        parent = child;
1744        nextIndex = -1;
1745        childCount = child.getChildCount();
1746        }
1747        return true;
1748    }
1749    } // VariableHeightLayoutCache.VisibleTreeStateNodeEnumeration
1750
}
1751
Popular Tags