27
28 import javax.swing.event.TreeModelEvent;
29 import java.awt.Rectangle;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 import java.util.NoSuchElementException;
33 import java.util.Stack;
34 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™
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 */
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 * @beaninfo
107 * bound: true
108 * description: The TreeModel that will provide the data.
109 */
110 public void setModel(TreeModel newModel) {
111 super.setModel(newModel);
112 rebuild(false);
113 }
114
115 /**
116 * Determines whether or not the root node from
117 * the <code>TreeModel</code> is visible.
118 *
119 * @param rootVisible true if the root node of the tree is to be displayed
120 * @see #rootVisible
121 * @beaninfo
122 * bound: true
123 * description: Whether or not the root node
124 * from the TreeModel is visible.
125 */
126 public void setRootVisible(boolean rootVisible) {
127 if(isRootVisible() != rootVisible && root != null) {
128 if(rootVisible) {
129 root.updatePreferredSize(0);
130 visibleNodes.insertElementAt(root, 0);
131 }
132 else if(visibleNodes.size() > 0) {
133 visibleNodes.removeElementAt(0);
134 if(treeSelectionModel != null)
135 treeSelectionModel.removeSelectionPath
136 (root.getTreePath());
137 }
157 */
158 public void setRowHeight(int rowHeight) {
159 if(rowHeight != getRowHeight()) {
160 super.setRowHeight(rowHeight);
161 invalidateSizes();
162 this.visibleNodesChanged();
163 }
164 }
165
166 /**
167 * Sets the renderer that is responsible for drawing nodes in the tree.
168 * @param nd the renderer
169 */
170 public void setNodeDimensions(NodeDimensions nd) {
171 super.setNodeDimensions(nd);
172 invalidateSizes();
173 visibleNodesChanged();
174 }
175
176 /**
177 * Marks the path <code>path</code> expanded state to
178 * <code>isExpanded</code>.
179 * @param path the <code>TreePath</code> of interest
180 * @param isExpanded true if the path should be expanded, otherwise false
181 */
182 public void setExpandedState(TreePath path, boolean isExpanded) {
183 if(path != null) {
184 if(isExpanded)
185 ensurePathIsExpanded(path, true);
186 else {
187 TreeStateNode node = getNodeForPath(path, false, true);
188
189 if(node != null) {
190 node.makeVisible();
191 node.collapse();
192 }
193 }
194 }
195 }
196
197 /**
198 * Returns true if the path is expanded, and visible.
199 * @return true if the path is expanded and visible, otherwise false
200 */
201 public boolean getExpandedState(TreePath path) {
202 TreeStateNode node = getNodeForPath(path, true, false);
203
204 return (node != null) ? (node.isVisible() && node.isExpanded()) :
205 false;
206 }
207
208 /**
209 * Returns the <code>Rectangle</code> enclosing the label portion
210 * into which the item identified by <code>path</code> will be drawn.
211 *
212 * @param path the path to be drawn
213 * @param placeIn the bounds of the enclosing rectangle
214 * @return the bounds of the enclosing rectangle or <code>null</code>
215 * if the node could not be ascertained
216 */
217 public Rectangle getBounds(TreePath path, Rectangle placeIn) {
218 TreeStateNode node = getNodeForPath(path, true, false);
219
220 if(node != null) {
221 if(updateNodeSizes)
222 updateNodeSizes(false);
223 return node.getNodeBounds(placeIn);
224 }
225 return null;
226 }
227
228 /**
229 * Returns the path for <code>row</code>. If <code>row</code>
230 * is not visible, <code>null</code> is returned.
231 *
232 * @param row the location of interest
233 * @return the path for <code>row</code>, or <code>null</code>
234 * if <code>row</code> is not visible
235 */
236 public TreePath getPathForRow(int row) {
237 if(row >= 0 && row < getRowCount()) {
238 return getNode(row).getTreePath();
239 }
240 return null;
241 }
242
243 /**
244 * Returns the row where the last item identified in path is visible.
245 * Will return -1 if any of the elements in path are not
246 * currently visible.
247 *
248 * @param path the <code>TreePath</code> of interest
249 * @return the row where the last item in path is visible
250 */
251 public int getRowForPath(TreePath path) {
252 if(path == null)
253 return -1;
254
255 TreeStateNode visNode = getNodeForPath(path, true, false);
256
257 if(visNode != null)
258 return visNode.getRow();
259 return -1;
260 }
261
262 /**
263 * Returns the number of visible rows.
264 * @return the number of visible rows
265 */
266 public int getRowCount() {
267 return visibleNodes.size();
268 }
269
270 /**
271 * Instructs the <code>LayoutCache</code> that the bounds for
272 * <code>path</code> are invalid, and need to be updated.
273 *
274 * @param path the <code>TreePath</code> which is now invalid
275 */
276 public void invalidatePathBounds(TreePath path) {
277 TreeStateNode node = getNodeForPath(path, true, false);
278
279 if(node != null) {
280 node.markSizeInvalid();
281 if(node.isVisible())
282 updateYLocationsFrom(node.getRow());
283 }
284 }
285
286 /**
287 * Returns the preferred height.
288 * @return the preferred height
289 */
290 public int getPreferredHeight() {
291 // Get the height
292 int rowCount = getRowCount();
293
294 if(rowCount > 0) {
295 TreeStateNode node = getNode(rowCount - 1);
296
297 return node.getYOrigin() + node.getPreferredHeight();
298 }
299 return 0;
300 }
301
302 /**
303 * Returns the preferred width and height for the region in
304 * <code>visibleRegion</code>.
305 *
306 * @param bounds the region being queried
307 */
308 public int getPreferredWidth(Rectangle bounds) {
309 if(updateNodeSizes)
310 updateNodeSizes(false);
311
312 return getMaxNodeWidth();
313 }
314
315 /**
316 * Returns the path to the node that is closest to x,y. If
317 * there is nothing currently visible this will return <code>null</code>,
318 * otherwise it will always return a valid path.
319 * If you need to test if the
320 * returned object is exactly at x, y you should get the bounds for
321 * the returned path and test x, y against that.
322 *
323 * @param x the x-coordinate
324 * @param y the y-coordinate
325 * @return the path to the node that is closest to x, y
326 */
327 public TreePath getPathClosestTo(int x, int y) {
328 if(getRowCount() == 0)
329 return null;
330
331 if(updateNodeSizes)
332 updateNodeSizes(false);
333
334 int row = getRowContainingYLocation(y);
335
336 return getNode(row).getTreePath();
337 }
338
339 /**
340 * Returns an <code>Enumerator</code> that increments over the visible paths
341 * starting at the passed in location. The ordering of the enumeration
342 * is based on how the paths are displayed.
343 *
344 * @param path the location in the <code>TreePath</code> to start
345 * @return an <code>Enumerator</code> that increments over the visible
346 * paths
347 */
348 public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
349 TreeStateNode node = getNodeForPath(path, true, false);
350
351 if(node != null) {
352 return new VisibleTreeStateNodeEnumeration(node);
353 }
354 return null;
355 }
356
357 /**
358 * Returns the number of visible children for <code>path</code>.
359 * @return the number of visible children for <code>path</code>
360 */
361 public int getVisibleChildCount(TreePath path) {
362 TreeStateNode node = getNodeForPath(path, true, false);
363
364 return (node != null) ? node.getVisibleChildCount() : 0;
365 }
366
367 /**
368 * Informs the <code>TreeState</code> that it needs to recalculate
369 * all the sizes it is referencing.
370 */
371 public void invalidateSizes() {
372 if(root != null)
373 root.deepMarkSizeInvalid();
374 if(!isFixedRowHeight() && visibleNodes.size() > 0) {
375 updateNodeSizes(true);
376 }
377 }
378
379 /**
380 * Returns true if the value identified by <code>path</code> is
381 * currently expanded.
382 * @return true if the value identified by <code>path</code> is
383 * currently expanded
384 */
385 public boolean isExpanded(TreePath path) {
386 if(path != null) {
387 TreeStateNode lastNode = getNodeForPath(path, true, false);
388
389 return (lastNode != null && lastNode.isExpanded());
390 }
391 return false;
392 }
393
394 //
395 // TreeModelListener methods
396 //
397
398 /**
399 * Invoked after a node (or a set of siblings) has changed in some
400 * way. The node(s) have not changed locations in the tree or
401 * altered their children arrays, but other attributes have
402 * changed and may affect presentation. Example: the name of a
403 * file has changed, but it is in the same location in the file
404 * system.
405 *
406 * <p><code>e.path</code> returns the path the parent of the
407 * changed node(s).
408 *
409 * <p><code>e.childIndices</code> returns the index(es) of the
410 * changed node(s).
411 *
412 * @param e the <code>TreeModelEvent</code> of interest
413 */
414 public void treeNodesChanged(TreeModelEvent e) {
415 if(e != null) {
416 int changedIndexs[];
417 TreeStateNode changedNode;
418
419 changedIndexs = e.getChildIndices();
420 changedNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
421 if(changedNode != null) {
422 Object changedValue = changedNode.getValue();
423
424 /* Update the size of the changed node, as well as all the
425 child indexs that are passed in. */
426 changedNode.updatePreferredSize();
427 if(changedNode.hasBeenExpanded() && changedIndexs != null) {
428 int counter;
429 TreeStateNode changedChildNode;
430
431 for(counter = 0; counter < changedIndexs.length;
432 counter++) {
441 }
442 else if (changedNode == root) {
443 // Null indicies for root indicates it changed.
444 changedNode.updatePreferredSize();
445 }
446 if(!isFixedRowHeight()) {
447 int aRow = changedNode.getRow();
448
449 if(aRow != -1)
450 this.updateYLocationsFrom(aRow);
451 }
452 this.visibleNodesChanged();
453 }
454 }
455 }
456
457
458 /**
459 * Invoked after nodes have been inserted into the tree.
460 *
461 * <p><code>e.path</code> returns the parent of the new nodes.
462 * <p><code>e.childIndices</code> returns the indices of the new nodes in
463 * ascending order.
464 *
465 * @param e the <code>TreeModelEvent</code> of interest
466 */
467 public void treeNodesInserted(TreeModelEvent e) {
468 if(e != null) {
469 int changedIndexs[];
470 TreeStateNode changedParentNode;
471
472 changedIndexs = e.getChildIndices();
473 changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
474 /* Only need to update the children if the node has been
475 expanded once. */
476 // PENDING(scott): make sure childIndexs is sorted!
477 if(changedParentNode != null && changedIndexs != null &&
478 changedIndexs.length > 0) {
479 if(changedParentNode.hasBeenExpanded()) {
480 boolean makeVisible;
481 int counter;
482 Object changedParent;
483 TreeStateNode newNode;
484 int oldChildCount = changedParentNode.
485 getChildCount();
514 }
515 else if(makeVisible)
516 this.visibleNodesChanged();
517 }
518 else if(treeModel.getChildCount(changedParentNode.getValue())
519 - changedIndexs.length == 0) {
520 changedParentNode.updatePreferredSize();
521 if(!isFixedRowHeight() && changedParentNode.isVisible())
522 updateYLocationsFrom(changedParentNode.getRow());
523 }
524 }
525 }
526 }
527
528 /**
529 * Invoked after nodes have been removed from the tree. Note that
530 * if a subtree is removed from the tree, this method may only be
531 * invoked once for the root of the removed subtree, not once for
532 * each individual set of siblings removed.
533 *
534 * <p><code>e.path</code> returns the former parent of the deleted nodes.
535 *
536 * <p><code>e.childIndices</code> returns the indices the nodes had
537 * before they were deleted in ascending order.
538 *
539 * @param e the <code>TreeModelEvent</code> of interest
540 */
541 public void treeNodesRemoved(TreeModelEvent e) {
542 if(e != null) {
543 int changedIndexs[];
544 TreeStateNode changedParentNode;
545
546 changedIndexs = e.getChildIndices();
547 changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
548 // PENDING(scott): make sure that changedIndexs are sorted in
549 // ascending order.
550 if(changedParentNode != null && changedIndexs != null &&
551 changedIndexs.length > 0) {
552 if(changedParentNode.hasBeenExpanded()) {
553 boolean makeInvisible;
554 int counter;
555 int removedRow;
556 TreeStateNode removedNode;
557
558 makeInvisible = ((changedParentNode == root &&
559 !rootVisible) ||
602 }
603 else
604 updateYLocationsFrom(changedParentNode.getRow());
605 this.visibleNodesChanged();
606 }
607 else if(makeInvisible)
608 this.visibleNodesChanged();
609 }
610 else if(treeModel.getChildCount(changedParentNode.getValue())
611 == 0) {
612 changedParentNode.updatePreferredSize();
613 if(!isFixedRowHeight() && changedParentNode.isVisible())
614 this.updateYLocationsFrom(changedParentNode.getRow());
615 }
616 }
617 }
618 }
619
620 /**
621 * Invoked after the tree has drastically changed structure from a
622 * given node down. If the path returned by <code>e.getPath</code>
623 * is of length one and the first element does not identify the
624 * current root node the first element should become the new root
625 * of the tree.
626 *
627 * <p><code>e.path</code> holds the path to the node.
628 * <p><code>e.childIndices</code> returns <code>null</code>.
629 *
630 * @param e the <code>TreeModelEvent</code> of interest
631 */
632 public void treeStructureChanged(TreeModelEvent e) {
633 if(e != null)
634 {
635 TreePath changedPath = SwingUtilities2.getTreePath(e, getModel());
636 TreeStateNode changedNode;
637
638 changedNode = getNodeForPath(changedPath, false, false);
639
640 // Check if root has changed, either to a null root, or
641 // to an entirely new root.
642 if(changedNode == root ||
643 (changedNode == null &&
644 ((changedPath == null && treeModel != null &&
645 treeModel.getRoot() == null) ||
646 (changedPath != null && changedPath.getPathCount() == 1)))) {
647 rebuild(true);
648 }
649 else if(changedNode != null) {
650 int nodeIndex, oldRow;
687 //
688
689 private void visibleNodesChanged() {
690 }
691
692 /**
693 * Adds a mapping for node.
694 */
695 private void addMapping(TreeStateNode node) {
696 treePathMapping.put(node.getTreePath(), node);
697 }
698
699 /**
700 * Removes the mapping for a previously added node.
701 */
702 private void removeMapping(TreeStateNode node) {
703 treePathMapping.remove(node.getTreePath());
704 }
705
706 /**
707 * Returns the node previously added for <code>path</code>. This may
708 * return null, if you to create a node use getNodeForPath.
709 */
710 private TreeStateNode getMapping(TreePath path) {
711 return treePathMapping.get(path);
712 }
713
714 /**
715 * Retursn the bounds for row, <code>row</code> by reference in
716 * <code>placeIn</code>. If <code>placeIn</code> is null a new
717 * Rectangle will be created and returned.
718 */
719 private Rectangle getBounds(int row, Rectangle placeIn) {
720 if(updateNodeSizes)
721 updateNodeSizes(false);
722
723 if(row >= 0 && row < getRowCount()) {
724 return getNode(row).getNodeBounds(placeIn);
725 }
726 return null;
727 }
728
729 /**
730 * Completely rebuild the tree, all expanded state, and node caches are
731 * removed. All nodes are collapsed, except the root.
732 */
733 private void rebuild(boolean clearSelection) {
734 Object rootObject;
735
736 treePathMapping.clear();
1056
1057 //
1058 // Overriden DefaultMutableTreeNode methods
1059 //
1060
1061 /**
1062 * Messaged when this node is added somewhere, resets the path
1063 * and adds a mapping from path to this node.
1064 */
1065 public void setParent(MutableTreeNode parent) {
1066 super.setParent(parent);
1067 if(parent != null) {
1068 path = ((TreeStateNode)parent).getTreePath().
1069 pathByAddingChild(getUserObject());
1070 addMapping(this);
1071 }
1072 }
1073
1074 /**
1075 * Messaged when this node is removed from its parent, this messages
1076 * <code>removedFromMapping</code> to remove all the children.
1077 */
1078 public void remove(int childIndex) {
1079 TreeStateNode node = (TreeStateNode)getChildAt(childIndex);
1080
1081 node.removeFromMapping();
1082 super.remove(childIndex);
1083 }
1084
1085 /**
1086 * Messaged to set the user object. This resets the path.
1087 */
1088 public void setUserObject(Object o) {
1089 super.setUserObject(o);
1090 if(path != null) {
1091 TreeStateNode parent = (TreeStateNode)getParent();
1092
1093 if(parent != null)
1094 resetChildrenPaths(parent.getTreePath());
1095 else
1096 resetChildrenPaths(null);
1259 for(int counter = 0; counter < maxCounter; counter++)
1260 childCount += ((TreeStateNode)getChildAt(counter)).
1261 getVisibleChildCount();
1262 }
1263 return childCount;
1264 }
1265
1266 /**
1267 * Toggles the receiver between expanded and collapsed.
1268 */
1269 public void toggleExpanded() {
1270 if (isExpanded()) {
1271 collapse();
1272 } else {
1273 expand();
1274 }
1275 }
1276
1277 /**
1278 * Makes the receiver visible, but invoking
1279 * <code>expandParentAndReceiver</code> on the superclass.
1280 */
1281 public void makeVisible() {
1282 TreeStateNode parent = (TreeStateNode)getParent();
1283
1284 if(parent != null)
1285 parent.expandParentAndReceiver();
1286 }
1287
1288 /**
1289 * Expands the receiver.
1290 */
1291 public void expand() {
1292 expand(true);
1293 }
1294
1295 /**
1296 * Collapses the receiver.
1297 */
1298 public void collapse() {
1299 collapse(true);
1324 protected void resetChildrenPaths(TreePath parentPath) {
1325 removeMapping(this);
1326 if(parentPath == null)
1327 path = new TreePath(getUserObject());
1328 else
1329 path = parentPath.pathByAddingChild(getUserObject());
1330 addMapping(this);
1331 for(int counter = getChildCount() - 1; counter >= 0; counter--)
1332 ((TreeStateNode)getChildAt(counter)).resetChildrenPaths(path);
1333 }
1334
1335 /**
1336 * Sets y origin the user object will be drawn at to
1337 * <I>newYOrigin</I>.
1338 */
1339 protected void setYOrigin(int newYOrigin) {
1340 yOrigin = newYOrigin;
1341 }
1342
1343 /**
1344 * Shifts the y origin by <code>offset</code>.
1345 */
1346 protected void shiftYOriginBy(int offset) {
1347 yOrigin += offset;
1348 }
1349
1350 /**
1351 * Updates the receivers preferredSize by invoking
1352 * <code>updatePreferredSize</code> with an argument of -1.
1353 */
1354 protected void updatePreferredSize() {
1355 updatePreferredSize(getRow());
1356 }
1357
1358 /**
1359 * Updates the preferred size by asking the current renderer
1360 * for the Dimension needed to draw the user object this
1361 * instance represents.
1362 */
1363 protected void updatePreferredSize(int index) {
1364 Rectangle bounds = getNodeDimensions(this.getUserObject(),
1365 index, getLevel(),
1366 isExpanded(),
1367 boundsBuffer);
1368
1369 if(bounds == null) {
1370 xOrigin = 0;
1371 preferredWidth = preferredHeight = 0;
1372 updateNodeSizes = true;
1389 /**
1390 * Marks the receivers size as invalid. Next time the size, location
1391 * is asked for it will be obtained.
1392 */
1393 protected void markSizeInvalid() {
1394 preferredHeight = 0;
1395 }
1396
1397 /**
1398 * Marks the receivers size, and all its descendants sizes, as invalid.
1399 */
1400 protected void deepMarkSizeInvalid() {
1401 markSizeInvalid();
1402 for(int counter = getChildCount() - 1; counter >= 0; counter--)
1403 ((TreeStateNode)getChildAt(counter)).deepMarkSizeInvalid();
1404 }
1405
1406 /**
1407 * Returns the children of the receiver. If the children haven't
1408 * been loaded from the model and
1409 * <code>createIfNeeded</code> is true, the children are first
1410 * loaded.
1411 */
1412 protected Enumeration<TreeNode> getLoadedChildren(boolean createIfNeeded) {
1413 if(!createIfNeeded || hasBeenExpanded)
1414 return super.children();
1415
1416 TreeStateNode newNode;
1417 Object realNode = getValue();
1418 TreeModel treeModel = getModel();
1419 int count = treeModel.getChildCount(realNode);
1420
1421 hasBeenExpanded = true;
1422
1423 int childRow = getRow();
1424
1425 if(childRow == -1) {
1426 for (int i = 0; i < count; i++) {
1427 newNode = createNodeForValue
1428 (treeModel.getChild(realNode, i));
1429 this.add(newNode);
1433 else {
1434 childRow++;
1435 for (int i = 0; i < count; i++) {
1436 newNode = createNodeForValue
1437 (treeModel.getChild(realNode, i));
1438 this.add(newNode);
1439 newNode.updatePreferredSize(childRow++);
1440 }
1441 }
1442 return super.children();
1443 }
1444
1445 /**
1446 * Messaged from expand and collapse. This is meant for subclassers
1447 * that may wish to do something interesting with this.
1448 */
1449 protected void didAdjustTree() {
1450 }
1451
1452 /**
1453 * Invokes <code>expandParentAndReceiver</code> on the parent,
1454 * and expands the receiver.
1455 */
1456 protected void expandParentAndReceiver() {
1457 TreeStateNode parent = (TreeStateNode)getParent();
1458
1459 if(parent != null)
1460 parent.expandParentAndReceiver();
1461 expand();
1462 }
1463
1464 /**
1465 * Expands this node in the tree. This will load the children
1466 * from the treeModel if this node has not previously been
1467 * expanded. If <I>adjustTree</I> is true the tree and selection
1468 * are updated accordingly.
1469 */
1470 protected void expand(boolean adjustTree) {
1471 if (!isExpanded() && !isLeaf()) {
1472 boolean isFixed = isFixedRowHeight();
1473 int startHeight = getPreferredHeight();
1688 public TreePath nextElement() {
1689 if(!hasMoreElements())
1690 throw new NoSuchElementException("No more visible paths");
1691
1692 TreePath retObject;
1693
1694 if(nextIndex == -1) {
1695 retObject = parent.getTreePath();
1696 }
1697 else {
1698 TreeStateNode node = (TreeStateNode)parent.
1699 getChildAt(nextIndex);
1700
1701 retObject = node.getTreePath();
1702 }
1703 updateNextObject();
1704 return retObject;
1705 }
1706
1707 /**
1708 * Determines the next object by invoking <code>updateNextIndex</code>
1709 * and if not succesful <code>findNextValidParent</code>.
1710 */
1711 protected void updateNextObject() {
1712 if(!updateNextIndex()) {
1713 findNextValidParent();
1714 }
1715 }
1716
1717 /**
1718 * Finds the next valid parent, this should be called when nextIndex
1719 * is beyond the number of children of the current parent.
1720 */
1721 protected boolean findNextValidParent() {
1722 if(parent == root) {
1723 // mark as invalid!
1724 parent = null;
1725 return false;
1726 }
1727 while(parent != null) {
1728 TreeStateNode newParent = (TreeStateNode)parent.
1729 getParent();
1730
1731 if(newParent != null) {
1732 nextIndex = newParent.getIndex(parent);
1733 parent = newParent;
1734 childCount = parent.getChildCount();
1735 if(updateNextIndex())
1736 return true;
1737 }
1738 else
1739 parent = null;
1740 }
1741 return false;
1742 }
1743
1744 /**
1745 * Updates <code>nextIndex</code> returning false if it is beyond
1746 * the number of children of parent.
1747 */
1748 protected boolean updateNextIndex() {
1749 // nextIndex == -1 identifies receiver, make sure is expanded
1750 // before descend.
1751 if(nextIndex == -1 && !parent.isExpanded())
1752 return false;
1753
1754 // Check that it can have kids
1755 if(childCount == 0)
1756 return false;
1757 // Make sure next index not beyond child count.
1758 else if(++nextIndex >= childCount)
1759 return false;
1760
1761 TreeStateNode child = (TreeStateNode)parent.
1762 getChildAt(nextIndex);
1763
1764 if(child != null && child.isExpanded()) {
1765 parent = child;
|
27
28 import javax.swing.event.TreeModelEvent;
29 import java.awt.Rectangle;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 import java.util.NoSuchElementException;
33 import java.util.Stack;
34 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™
47 * has been added to the {@code java.beans} 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} to a {@code TreeStateNode}.
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 */
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} that will provide the data.
104 *
105 * @param newModel the {@code TreeModel} that is to provide the data
106 * @beaninfo
107 * bound: true
108 * description: The TreeModel that will provide the data.
109 */
110 public void setModel(TreeModel newModel) {
111 super.setModel(newModel);
112 rebuild(false);
113 }
114
115 /**
116 * Determines whether or not the root node from
117 * the {@code TreeModel} is visible.
118 *
119 * @param rootVisible true if the root node of the tree is to be displayed
120 * @see #rootVisible
121 * @beaninfo
122 * bound: true
123 * description: Whether or not the root node
124 * from the TreeModel is visible.
125 */
126 public void setRootVisible(boolean rootVisible) {
127 if(isRootVisible() != rootVisible && root != null) {
128 if(rootVisible) {
129 root.updatePreferredSize(0);
130 visibleNodes.insertElementAt(root, 0);
131 }
132 else if(visibleNodes.size() > 0) {
133 visibleNodes.removeElementAt(0);
134 if(treeSelectionModel != null)
135 treeSelectionModel.removeSelectionPath
136 (root.getTreePath());
137 }
157 */
158 public void setRowHeight(int rowHeight) {
159 if(rowHeight != getRowHeight()) {
160 super.setRowHeight(rowHeight);
161 invalidateSizes();
162 this.visibleNodesChanged();
163 }
164 }
165
166 /**
167 * Sets the renderer that is responsible for drawing nodes in the tree.
168 * @param nd the renderer
169 */
170 public void setNodeDimensions(NodeDimensions nd) {
171 super.setNodeDimensions(nd);
172 invalidateSizes();
173 visibleNodesChanged();
174 }
175
176 /**
177 * Marks the path {@code path} expanded state to
178 * {@code isExpanded}.
179 * @param path the {@code TreePath} of interest
180 * @param isExpanded true if the path should be expanded, otherwise false
181 */
182 public void setExpandedState(TreePath path, boolean isExpanded) {
183 if(path != null) {
184 if(isExpanded)
185 ensurePathIsExpanded(path, true);
186 else {
187 TreeStateNode node = getNodeForPath(path, false, true);
188
189 if(node != null) {
190 node.makeVisible();
191 node.collapse();
192 }
193 }
194 }
195 }
196
197 /**
198 * Returns true if the path is expanded, and visible.
199 * @return true if the path is expanded and visible, otherwise false
200 */
201 public boolean getExpandedState(TreePath path) {
202 TreeStateNode node = getNodeForPath(path, true, false);
203
204 return (node != null) ? (node.isVisible() && node.isExpanded()) :
205 false;
206 }
207
208 /**
209 * Returns the {@code Rectangle} enclosing the label portion
210 * into which the item identified by {@code path} will be drawn.
211 *
212 * @param path the path to be drawn
213 * @param placeIn the bounds of the enclosing rectangle
214 * @return the bounds of the enclosing rectangle or {@code null}
215 * if the node could not be ascertained
216 */
217 public Rectangle getBounds(TreePath path, Rectangle placeIn) {
218 TreeStateNode node = getNodeForPath(path, true, false);
219
220 if(node != null) {
221 if(updateNodeSizes)
222 updateNodeSizes(false);
223 return node.getNodeBounds(placeIn);
224 }
225 return null;
226 }
227
228 /**
229 * Returns the path for {@code row}. If {@code row}
230 * is not visible, {@code null} is returned.
231 *
232 * @param row the location of interest
233 * @return the path for {@code row}, or {@code null}
234 * if {@code row} is not visible
235 */
236 public TreePath getPathForRow(int row) {
237 if(row >= 0 && row < getRowCount()) {
238 return getNode(row).getTreePath();
239 }
240 return null;
241 }
242
243 /**
244 * Returns the row where the last item identified in path is visible.
245 * Will return -1 if any of the elements in path are not
246 * currently visible.
247 *
248 * @param path the {@code TreePath} of interest
249 * @return the row where the last item in path is visible
250 */
251 public int getRowForPath(TreePath path) {
252 if(path == null)
253 return -1;
254
255 TreeStateNode visNode = getNodeForPath(path, true, false);
256
257 if(visNode != null)
258 return visNode.getRow();
259 return -1;
260 }
261
262 /**
263 * Returns the number of visible rows.
264 * @return the number of visible rows
265 */
266 public int getRowCount() {
267 return visibleNodes.size();
268 }
269
270 /**
271 * Instructs the {@code LayoutCache} that the bounds for
272 * {@code path} are invalid, and need to be updated.
273 *
274 * @param path the {@code TreePath} which is now invalid
275 */
276 public void invalidatePathBounds(TreePath path) {
277 TreeStateNode node = getNodeForPath(path, true, false);
278
279 if(node != null) {
280 node.markSizeInvalid();
281 if(node.isVisible())
282 updateYLocationsFrom(node.getRow());
283 }
284 }
285
286 /**
287 * Returns the preferred height.
288 * @return the preferred height
289 */
290 public int getPreferredHeight() {
291 // Get the height
292 int rowCount = getRowCount();
293
294 if(rowCount > 0) {
295 TreeStateNode node = getNode(rowCount - 1);
296
297 return node.getYOrigin() + node.getPreferredHeight();
298 }
299 return 0;
300 }
301
302 /**
303 * Returns the preferred width and height for the region in
304 * {@code visibleRegion}.
305 *
306 * @param bounds the region being queried
307 */
308 public int getPreferredWidth(Rectangle bounds) {
309 if(updateNodeSizes)
310 updateNodeSizes(false);
311
312 return getMaxNodeWidth();
313 }
314
315 /**
316 * Returns the path to the node that is closest to x,y. If
317 * there is nothing currently visible this will return {@code null},
318 * otherwise it will always return a valid path.
319 * If you need to test if the
320 * returned object is exactly at x, y you should get the bounds for
321 * the returned path and test x, y against that.
322 *
323 * @param x the x-coordinate
324 * @param y the y-coordinate
325 * @return the path to the node that is closest to x, y
326 */
327 public TreePath getPathClosestTo(int x, int y) {
328 if(getRowCount() == 0)
329 return null;
330
331 if(updateNodeSizes)
332 updateNodeSizes(false);
333
334 int row = getRowContainingYLocation(y);
335
336 return getNode(row).getTreePath();
337 }
338
339 /**
340 * Returns an {@code Enumerator} that increments over the visible paths
341 * starting at the passed in location. The ordering of the enumeration
342 * is based on how the paths are displayed.
343 *
344 * @param path the location in the {@code TreePath} to start
345 * @return an {@code Enumerator} that increments over the visible
346 * paths
347 */
348 public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
349 TreeStateNode node = getNodeForPath(path, true, false);
350
351 if(node != null) {
352 return new VisibleTreeStateNodeEnumeration(node);
353 }
354 return null;
355 }
356
357 /**
358 * Returns the number of visible children for {@code path}.
359 * @return the number of visible children for {@code path}
360 */
361 public int getVisibleChildCount(TreePath path) {
362 TreeStateNode node = getNodeForPath(path, true, false);
363
364 return (node != null) ? node.getVisibleChildCount() : 0;
365 }
366
367 /**
368 * Informs the {@code TreeState} that it needs to recalculate
369 * all the sizes it is referencing.
370 */
371 public void invalidateSizes() {
372 if(root != null)
373 root.deepMarkSizeInvalid();
374 if(!isFixedRowHeight() && visibleNodes.size() > 0) {
375 updateNodeSizes(true);
376 }
377 }
378
379 /**
380 * Returns true if the value identified by {@code path} is
381 * currently expanded.
382 * @return true if the value identified by {@code path} is
383 * currently expanded
384 */
385 public boolean isExpanded(TreePath path) {
386 if(path != null) {
387 TreeStateNode lastNode = getNodeForPath(path, true, false);
388
389 return (lastNode != null && lastNode.isExpanded());
390 }
391 return false;
392 }
393
394 //
395 // TreeModelListener methods
396 //
397
398 /**
399 * Invoked after a node (or a set of siblings) has changed in some
400 * way. The node(s) have not changed locations in the tree or
401 * altered their children arrays, but other attributes have
402 * changed and may affect presentation. Example: the name of a
403 * file has changed, but it is in the same location in the file
404 * system.
405 *
406 * <p>{@code e.path} returns the path the parent of the
407 * changed node(s).
408 *
409 * <p>{@code e.childIndices} returns the index(es) of the
410 * changed node(s).
411 *
412 * @param e the {@code TreeModelEvent} of interest
413 */
414 public void treeNodesChanged(TreeModelEvent e) {
415 if(e != null) {
416 int changedIndexs[];
417 TreeStateNode changedNode;
418
419 changedIndexs = e.getChildIndices();
420 changedNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
421 if(changedNode != null) {
422 Object changedValue = changedNode.getValue();
423
424 /* Update the size of the changed node, as well as all the
425 child indexs that are passed in. */
426 changedNode.updatePreferredSize();
427 if(changedNode.hasBeenExpanded() && changedIndexs != null) {
428 int counter;
429 TreeStateNode changedChildNode;
430
431 for(counter = 0; counter < changedIndexs.length;
432 counter++) {
441 }
442 else if (changedNode == root) {
443 // Null indicies for root indicates it changed.
444 changedNode.updatePreferredSize();
445 }
446 if(!isFixedRowHeight()) {
447 int aRow = changedNode.getRow();
448
449 if(aRow != -1)
450 this.updateYLocationsFrom(aRow);
451 }
452 this.visibleNodesChanged();
453 }
454 }
455 }
456
457
458 /**
459 * Invoked after nodes have been inserted into the tree.
460 *
461 * <p>{@code e.path} returns the parent of the new nodes.
462 * <p>{@code e.childIndices} returns the indices of the new nodes in
463 * ascending order.
464 *
465 * @param e the {@code TreeModelEvent} of interest
466 */
467 public void treeNodesInserted(TreeModelEvent e) {
468 if(e != null) {
469 int changedIndexs[];
470 TreeStateNode changedParentNode;
471
472 changedIndexs = e.getChildIndices();
473 changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
474 /* Only need to update the children if the node has been
475 expanded once. */
476 // PENDING(scott): make sure childIndexs is sorted!
477 if(changedParentNode != null && changedIndexs != null &&
478 changedIndexs.length > 0) {
479 if(changedParentNode.hasBeenExpanded()) {
480 boolean makeVisible;
481 int counter;
482 Object changedParent;
483 TreeStateNode newNode;
484 int oldChildCount = changedParentNode.
485 getChildCount();
514 }
515 else if(makeVisible)
516 this.visibleNodesChanged();
517 }
518 else if(treeModel.getChildCount(changedParentNode.getValue())
519 - changedIndexs.length == 0) {
520 changedParentNode.updatePreferredSize();
521 if(!isFixedRowHeight() && changedParentNode.isVisible())
522 updateYLocationsFrom(changedParentNode.getRow());
523 }
524 }
525 }
526 }
527
528 /**
529 * Invoked after nodes have been removed from the tree. Note that
530 * if a subtree is removed from the tree, this method may only be
531 * invoked once for the root of the removed subtree, not once for
532 * each individual set of siblings removed.
533 *
534 * <p>{@code e.path} returns the former parent of the deleted nodes.
535 *
536 * <p>{@code e.childIndices} returns the indices the nodes had
537 * before they were deleted in ascending order.
538 *
539 * @param e the {@code TreeModelEvent} of interest
540 */
541 public void treeNodesRemoved(TreeModelEvent e) {
542 if(e != null) {
543 int changedIndexs[];
544 TreeStateNode changedParentNode;
545
546 changedIndexs = e.getChildIndices();
547 changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
548 // PENDING(scott): make sure that changedIndexs are sorted in
549 // ascending order.
550 if(changedParentNode != null && changedIndexs != null &&
551 changedIndexs.length > 0) {
552 if(changedParentNode.hasBeenExpanded()) {
553 boolean makeInvisible;
554 int counter;
555 int removedRow;
556 TreeStateNode removedNode;
557
558 makeInvisible = ((changedParentNode == root &&
559 !rootVisible) ||
602 }
603 else
604 updateYLocationsFrom(changedParentNode.getRow());
605 this.visibleNodesChanged();
606 }
607 else if(makeInvisible)
608 this.visibleNodesChanged();
609 }
610 else if(treeModel.getChildCount(changedParentNode.getValue())
611 == 0) {
612 changedParentNode.updatePreferredSize();
613 if(!isFixedRowHeight() && changedParentNode.isVisible())
614 this.updateYLocationsFrom(changedParentNode.getRow());
615 }
616 }
617 }
618 }
619
620 /**
621 * Invoked after the tree has drastically changed structure from a
622 * given node down. If the path returned by {@code e.getPath}
623 * is of length one and the first element does not identify the
624 * current root node the first element should become the new root
625 * of the tree.
626 *
627 * <p>{@code e.path} holds the path to the node.
628 * <p>{@code e.childIndices} returns {@code null}.
629 *
630 * @param e the {@code TreeModelEvent} of interest
631 */
632 public void treeStructureChanged(TreeModelEvent e) {
633 if(e != null)
634 {
635 TreePath changedPath = SwingUtilities2.getTreePath(e, getModel());
636 TreeStateNode changedNode;
637
638 changedNode = getNodeForPath(changedPath, false, false);
639
640 // Check if root has changed, either to a null root, or
641 // to an entirely new root.
642 if(changedNode == root ||
643 (changedNode == null &&
644 ((changedPath == null && treeModel != null &&
645 treeModel.getRoot() == null) ||
646 (changedPath != null && changedPath.getPathCount() == 1)))) {
647 rebuild(true);
648 }
649 else if(changedNode != null) {
650 int nodeIndex, oldRow;
687 //
688
689 private void visibleNodesChanged() {
690 }
691
692 /**
693 * Adds a mapping for node.
694 */
695 private void addMapping(TreeStateNode node) {
696 treePathMapping.put(node.getTreePath(), node);
697 }
698
699 /**
700 * Removes the mapping for a previously added node.
701 */
702 private void removeMapping(TreeStateNode node) {
703 treePathMapping.remove(node.getTreePath());
704 }
705
706 /**
707 * Returns the node previously added for {@code path}. This may
708 * return null, if you to create a node use getNodeForPath.
709 */
710 private TreeStateNode getMapping(TreePath path) {
711 return treePathMapping.get(path);
712 }
713
714 /**
715 * Retursn the bounds for row, {@code row} by reference in
716 * {@code placeIn}. If {@code placeIn} is null a new
717 * Rectangle will be created and returned.
718 */
719 private Rectangle getBounds(int row, Rectangle placeIn) {
720 if(updateNodeSizes)
721 updateNodeSizes(false);
722
723 if(row >= 0 && row < getRowCount()) {
724 return getNode(row).getNodeBounds(placeIn);
725 }
726 return null;
727 }
728
729 /**
730 * Completely rebuild the tree, all expanded state, and node caches are
731 * removed. All nodes are collapsed, except the root.
732 */
733 private void rebuild(boolean clearSelection) {
734 Object rootObject;
735
736 treePathMapping.clear();
1056
1057 //
1058 // Overriden DefaultMutableTreeNode methods
1059 //
1060
1061 /**
1062 * Messaged when this node is added somewhere, resets the path
1063 * and adds a mapping from path to this node.
1064 */
1065 public void setParent(MutableTreeNode parent) {
1066 super.setParent(parent);
1067 if(parent != null) {
1068 path = ((TreeStateNode)parent).getTreePath().
1069 pathByAddingChild(getUserObject());
1070 addMapping(this);
1071 }
1072 }
1073
1074 /**
1075 * Messaged when this node is removed from its parent, this messages
1076 * {@code removedFromMapping} to remove all the children.
1077 */
1078 public void remove(int childIndex) {
1079 TreeStateNode node = (TreeStateNode)getChildAt(childIndex);
1080
1081 node.removeFromMapping();
1082 super.remove(childIndex);
1083 }
1084
1085 /**
1086 * Messaged to set the user object. This resets the path.
1087 */
1088 public void setUserObject(Object o) {
1089 super.setUserObject(o);
1090 if(path != null) {
1091 TreeStateNode parent = (TreeStateNode)getParent();
1092
1093 if(parent != null)
1094 resetChildrenPaths(parent.getTreePath());
1095 else
1096 resetChildrenPaths(null);
1259 for(int counter = 0; counter < maxCounter; counter++)
1260 childCount += ((TreeStateNode)getChildAt(counter)).
1261 getVisibleChildCount();
1262 }
1263 return childCount;
1264 }
1265
1266 /**
1267 * Toggles the receiver between expanded and collapsed.
1268 */
1269 public void toggleExpanded() {
1270 if (isExpanded()) {
1271 collapse();
1272 } else {
1273 expand();
1274 }
1275 }
1276
1277 /**
1278 * Makes the receiver visible, but invoking
1279 * {@code expandParentAndReceiver} on the superclass.
1280 */
1281 public void makeVisible() {
1282 TreeStateNode parent = (TreeStateNode)getParent();
1283
1284 if(parent != null)
1285 parent.expandParentAndReceiver();
1286 }
1287
1288 /**
1289 * Expands the receiver.
1290 */
1291 public void expand() {
1292 expand(true);
1293 }
1294
1295 /**
1296 * Collapses the receiver.
1297 */
1298 public void collapse() {
1299 collapse(true);
1324 protected void resetChildrenPaths(TreePath parentPath) {
1325 removeMapping(this);
1326 if(parentPath == null)
1327 path = new TreePath(getUserObject());
1328 else
1329 path = parentPath.pathByAddingChild(getUserObject());
1330 addMapping(this);
1331 for(int counter = getChildCount() - 1; counter >= 0; counter--)
1332 ((TreeStateNode)getChildAt(counter)).resetChildrenPaths(path);
1333 }
1334
1335 /**
1336 * Sets y origin the user object will be drawn at to
1337 * <I>newYOrigin</I>.
1338 */
1339 protected void setYOrigin(int newYOrigin) {
1340 yOrigin = newYOrigin;
1341 }
1342
1343 /**
1344 * Shifts the y origin by {@code offset}.
1345 */
1346 protected void shiftYOriginBy(int offset) {
1347 yOrigin += offset;
1348 }
1349
1350 /**
1351 * Updates the receivers preferredSize by invoking
1352 * {@code updatePreferredSize} with an argument of -1.
1353 */
1354 protected void updatePreferredSize() {
1355 updatePreferredSize(getRow());
1356 }
1357
1358 /**
1359 * Updates the preferred size by asking the current renderer
1360 * for the Dimension needed to draw the user object this
1361 * instance represents.
1362 */
1363 protected void updatePreferredSize(int index) {
1364 Rectangle bounds = getNodeDimensions(this.getUserObject(),
1365 index, getLevel(),
1366 isExpanded(),
1367 boundsBuffer);
1368
1369 if(bounds == null) {
1370 xOrigin = 0;
1371 preferredWidth = preferredHeight = 0;
1372 updateNodeSizes = true;
1389 /**
1390 * Marks the receivers size as invalid. Next time the size, location
1391 * is asked for it will be obtained.
1392 */
1393 protected void markSizeInvalid() {
1394 preferredHeight = 0;
1395 }
1396
1397 /**
1398 * Marks the receivers size, and all its descendants sizes, as invalid.
1399 */
1400 protected void deepMarkSizeInvalid() {
1401 markSizeInvalid();
1402 for(int counter = getChildCount() - 1; counter >= 0; counter--)
1403 ((TreeStateNode)getChildAt(counter)).deepMarkSizeInvalid();
1404 }
1405
1406 /**
1407 * Returns the children of the receiver. If the children haven't
1408 * been loaded from the model and
1409 * {@code createIfNeeded} is true, the children are first
1410 * loaded.
1411 */
1412 protected Enumeration<TreeNode> getLoadedChildren(boolean createIfNeeded) {
1413 if(!createIfNeeded || hasBeenExpanded)
1414 return super.children();
1415
1416 TreeStateNode newNode;
1417 Object realNode = getValue();
1418 TreeModel treeModel = getModel();
1419 int count = treeModel.getChildCount(realNode);
1420
1421 hasBeenExpanded = true;
1422
1423 int childRow = getRow();
1424
1425 if(childRow == -1) {
1426 for (int i = 0; i < count; i++) {
1427 newNode = createNodeForValue
1428 (treeModel.getChild(realNode, i));
1429 this.add(newNode);
1433 else {
1434 childRow++;
1435 for (int i = 0; i < count; i++) {
1436 newNode = createNodeForValue
1437 (treeModel.getChild(realNode, i));
1438 this.add(newNode);
1439 newNode.updatePreferredSize(childRow++);
1440 }
1441 }
1442 return super.children();
1443 }
1444
1445 /**
1446 * Messaged from expand and collapse. This is meant for subclassers
1447 * that may wish to do something interesting with this.
1448 */
1449 protected void didAdjustTree() {
1450 }
1451
1452 /**
1453 * Invokes {@code expandParentAndReceiver} on the parent,
1454 * and expands the receiver.
1455 */
1456 protected void expandParentAndReceiver() {
1457 TreeStateNode parent = (TreeStateNode)getParent();
1458
1459 if(parent != null)
1460 parent.expandParentAndReceiver();
1461 expand();
1462 }
1463
1464 /**
1465 * Expands this node in the tree. This will load the children
1466 * from the treeModel if this node has not previously been
1467 * expanded. If <I>adjustTree</I> is true the tree and selection
1468 * are updated accordingly.
1469 */
1470 protected void expand(boolean adjustTree) {
1471 if (!isExpanded() && !isLeaf()) {
1472 boolean isFixed = isFixedRowHeight();
1473 int startHeight = getPreferredHeight();
1688 public TreePath nextElement() {
1689 if(!hasMoreElements())
1690 throw new NoSuchElementException("No more visible paths");
1691
1692 TreePath retObject;
1693
1694 if(nextIndex == -1) {
1695 retObject = parent.getTreePath();
1696 }
1697 else {
1698 TreeStateNode node = (TreeStateNode)parent.
1699 getChildAt(nextIndex);
1700
1701 retObject = node.getTreePath();
1702 }
1703 updateNextObject();
1704 return retObject;
1705 }
1706
1707 /**
1708 * Determines the next object by invoking {@code updateNextIndex}
1709 * and if not succesful {@code findNextValidParent}.
1710 */
1711 protected void updateNextObject() {
1712 if(!updateNextIndex()) {
1713 findNextValidParent();
1714 }
1715 }
1716
1717 /**
1718 * Finds the next valid parent, this should be called when nextIndex
1719 * is beyond the number of children of the current parent.
1720 */
1721 protected boolean findNextValidParent() {
1722 if(parent == root) {
1723 // mark as invalid!
1724 parent = null;
1725 return false;
1726 }
1727 while(parent != null) {
1728 TreeStateNode newParent = (TreeStateNode)parent.
1729 getParent();
1730
1731 if(newParent != null) {
1732 nextIndex = newParent.getIndex(parent);
1733 parent = newParent;
1734 childCount = parent.getChildCount();
1735 if(updateNextIndex())
1736 return true;
1737 }
1738 else
1739 parent = null;
1740 }
1741 return false;
1742 }
1743
1744 /**
1745 * Updates {@code nextIndex} returning false if it is beyond
1746 * the number of children of parent.
1747 */
1748 protected boolean updateNextIndex() {
1749 // nextIndex == -1 identifies receiver, make sure is expanded
1750 // before descend.
1751 if(nextIndex == -1 && !parent.isExpanded())
1752 return false;
1753
1754 // Check that it can have kids
1755 if(childCount == 0)
1756 return false;
1757 // Make sure next index not beyond child count.
1758 else if(++nextIndex >= childCount)
1759 return false;
1760
1761 TreeStateNode child = (TreeStateNode)parent.
1762 getChildAt(nextIndex);
1763
1764 if(child != null && child.isExpanded()) {
1765 parent = child;
|