0001 /*
0002 * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package javax.swing.tree;
0027
0028 import javax.swing.event.TreeModelEvent;
0029 import java.awt.Dimension;
0030 import java.awt.Rectangle;
0031 import java.util.Enumeration;
0032 import java.util.Hashtable;
0033 import java.util.NoSuchElementException;
0034 import java.util.Stack;
0035 import java.util.Vector;
0036
0037 /**
0038 * NOTE: This will become more open in a future release.
0039 * <p>
0040 * <strong>Warning:</strong>
0041 * Serialized objects of this class will not be compatible with
0042 * future Swing releases. The current serialization support is
0043 * appropriate for short term storage or RMI between applications running
0044 * the same version of Swing. As of 1.4, support for long term storage
0045 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0046 * has been added to the <code>java.beans</code> package.
0047 * Please see {@link java.beans.XMLEncoder}.
0048 *
0049 * @version 1.28 05/05/07
0050 * @author Rob Davis
0051 * @author Ray Ryan
0052 * @author Scott Violet
0053 */
0054
0055 public class VariableHeightLayoutCache extends AbstractLayoutCache {
0056 /**
0057 * The array of nodes that are currently visible, in the order they
0058 * are displayed.
0059 */
0060 private Vector visibleNodes;
0061
0062 /**
0063 * This is set to true if one of the entries has an invalid size.
0064 */
0065 private boolean updateNodeSizes;
0066
0067 /**
0068 * The root node of the internal cache of nodes that have been shown.
0069 * If the treeModel is vending a network rather than a true tree,
0070 * there may be one cached node for each path to a modeled node.
0071 */
0072 private TreeStateNode root;
0073
0074 /**
0075 * Used in getting sizes for nodes to avoid creating a new Rectangle
0076 * every time a size is needed.
0077 */
0078 private Rectangle boundsBuffer;
0079
0080 /**
0081 * Maps from <code>TreePath</code> to a <code>TreeStateNode</code>.
0082 */
0083 private Hashtable treePathMapping;
0084
0085 /**
0086 * A stack of stacks.
0087 */
0088 private Stack tempStacks;
0089
0090 public VariableHeightLayoutCache() {
0091 super ();
0092 tempStacks = new Stack();
0093 visibleNodes = new Vector();
0094 boundsBuffer = new Rectangle();
0095 treePathMapping = new Hashtable();
0096 }
0097
0098 /**
0099 * Sets the <code>TreeModel</code> that will provide the data.
0100 *
0101 * @param newModel the <code>TreeModel</code> that is to provide the data
0102 * @beaninfo
0103 * bound: true
0104 * description: The TreeModel that will provide the data.
0105 */
0106 public void setModel(TreeModel newModel) {
0107 super .setModel(newModel);
0108 rebuild(false);
0109 }
0110
0111 /**
0112 * Determines whether or not the root node from
0113 * the <code>TreeModel</code> is visible.
0114 *
0115 * @param rootVisible true if the root node of the tree is to be displayed
0116 * @see #rootVisible
0117 * @beaninfo
0118 * bound: true
0119 * description: Whether or not the root node
0120 * from the TreeModel is visible.
0121 */
0122 public void setRootVisible(boolean rootVisible) {
0123 if (isRootVisible() != rootVisible && root != null) {
0124 if (rootVisible) {
0125 root.updatePreferredSize(0);
0126 visibleNodes.insertElementAt(root, 0);
0127 } else if (visibleNodes.size() > 0) {
0128 visibleNodes.removeElementAt(0);
0129 if (treeSelectionModel != null)
0130 treeSelectionModel.removeSelectionPath(root
0131 .getTreePath());
0132 }
0133 if (treeSelectionModel != null)
0134 treeSelectionModel.resetRowSelection();
0135 if (getRowCount() > 0)
0136 getNode(0).setYOrigin(0);
0137 updateYLocationsFrom(0);
0138 visibleNodesChanged();
0139 }
0140 super .setRootVisible(rootVisible);
0141 }
0142
0143 /**
0144 * Sets the height of each cell. If the specified value
0145 * is less than or equal to zero the current cell renderer is
0146 * queried for each row's height.
0147 *
0148 * @param rowHeight the height of each cell, in pixels
0149 * @beaninfo
0150 * bound: true
0151 * description: The height of each cell.
0152 */
0153 public void setRowHeight(int rowHeight) {
0154 if (rowHeight != getRowHeight()) {
0155 super .setRowHeight(rowHeight);
0156 invalidateSizes();
0157 this .visibleNodesChanged();
0158 }
0159 }
0160
0161 /**
0162 * Sets the renderer that is responsible for drawing nodes in the tree.
0163 * @param nd the renderer
0164 */
0165 public void setNodeDimensions(NodeDimensions nd) {
0166 super .setNodeDimensions(nd);
0167 invalidateSizes();
0168 visibleNodesChanged();
0169 }
0170
0171 /**
0172 * Marks the path <code>path</code> expanded state to
0173 * <code>isExpanded</code>.
0174 * @param path the <code>TreePath</code> of interest
0175 * @param isExpanded true if the path should be expanded, otherwise false
0176 */
0177 public void setExpandedState(TreePath path, boolean isExpanded) {
0178 if (path != null) {
0179 if (isExpanded)
0180 ensurePathIsExpanded(path, true);
0181 else {
0182 TreeStateNode node = getNodeForPath(path, false, true);
0183
0184 if (node != null) {
0185 node.makeVisible();
0186 node.collapse();
0187 }
0188 }
0189 }
0190 }
0191
0192 /**
0193 * Returns true if the path is expanded, and visible.
0194 * @return true if the path is expanded and visible, otherwise false
0195 */
0196 public boolean getExpandedState(TreePath path) {
0197 TreeStateNode node = getNodeForPath(path, true, false);
0198
0199 return (node != null) ? (node.isVisible() && node.isExpanded())
0200 : false;
0201 }
0202
0203 /**
0204 * Returns the <code>Rectangle</code> enclosing the label portion
0205 * into which the item identified by <code>path</code> will be drawn.
0206 *
0207 * @param path the path to be drawn
0208 * @param placeIn the bounds of the enclosing rectangle
0209 * @return the bounds of the enclosing rectangle or <code>null</code>
0210 * if the node could not be ascertained
0211 */
0212 public Rectangle getBounds(TreePath path, Rectangle placeIn) {
0213 TreeStateNode node = getNodeForPath(path, true, false);
0214
0215 if (node != null) {
0216 if (updateNodeSizes)
0217 updateNodeSizes(false);
0218 return node.getNodeBounds(placeIn);
0219 }
0220 return null;
0221 }
0222
0223 /**
0224 * Returns the path for <code>row</code>. If <code>row</code>
0225 * is not visible, <code>null</code> is returned.
0226 *
0227 * @param row the location of interest
0228 * @return the path for <code>row</code>, or <code>null</code>
0229 * if <code>row</code> is not visible
0230 */
0231 public TreePath getPathForRow(int row) {
0232 if (row >= 0 && row < getRowCount()) {
0233 return getNode(row).getTreePath();
0234 }
0235 return null;
0236 }
0237
0238 /**
0239 * Returns the row where the last item identified in path is visible.
0240 * Will return -1 if any of the elements in path are not
0241 * currently visible.
0242 *
0243 * @param path the <code>TreePath</code> of interest
0244 * @return the row where the last item in path is visible
0245 */
0246 public int getRowForPath(TreePath path) {
0247 if (path == null)
0248 return -1;
0249
0250 TreeStateNode visNode = getNodeForPath(path, true, false);
0251
0252 if (visNode != null)
0253 return visNode.getRow();
0254 return -1;
0255 }
0256
0257 /**
0258 * Returns the number of visible rows.
0259 * @return the number of visible rows
0260 */
0261 public int getRowCount() {
0262 return visibleNodes.size();
0263 }
0264
0265 /**
0266 * Instructs the <code>LayoutCache</code> that the bounds for
0267 * <code>path</code> are invalid, and need to be updated.
0268 *
0269 * @param path the <code>TreePath</code> which is now invalid
0270 */
0271 public void invalidatePathBounds(TreePath path) {
0272 TreeStateNode node = getNodeForPath(path, true, false);
0273
0274 if (node != null) {
0275 node.markSizeInvalid();
0276 if (node.isVisible())
0277 updateYLocationsFrom(node.getRow());
0278 }
0279 }
0280
0281 /**
0282 * Returns the preferred height.
0283 * @return the preferred height
0284 */
0285 public int getPreferredHeight() {
0286 // Get the height
0287 int rowCount = getRowCount();
0288
0289 if (rowCount > 0) {
0290 TreeStateNode node = getNode(rowCount - 1);
0291
0292 return node.getYOrigin() + node.getPreferredHeight();
0293 }
0294 return 0;
0295 }
0296
0297 /**
0298 * Returns the preferred width and height for the region in
0299 * <code>visibleRegion</code>.
0300 *
0301 * @param bounds the region being queried
0302 */
0303 public int getPreferredWidth(Rectangle bounds) {
0304 if (updateNodeSizes)
0305 updateNodeSizes(false);
0306
0307 return getMaxNodeWidth();
0308 }
0309
0310 /**
0311 * Returns the path to the node that is closest to x,y. If
0312 * there is nothing currently visible this will return <code>null</code>,
0313 * otherwise it will always return a valid path.
0314 * If you need to test if the
0315 * returned object is exactly at x, y you should get the bounds for
0316 * the returned path and test x, y against that.
0317 *
0318 * @param x the x-coordinate
0319 * @param y the y-coordinate
0320 * @return the path to the node that is closest to x, y
0321 */
0322 public TreePath getPathClosestTo(int x, int y) {
0323 if (getRowCount() == 0)
0324 return null;
0325
0326 if (updateNodeSizes)
0327 updateNodeSizes(false);
0328
0329 int row = getRowContainingYLocation(y);
0330
0331 return getNode(row).getTreePath();
0332 }
0333
0334 /**
0335 * Returns an <code>Enumerator</code> that increments over the visible paths
0336 * starting at the passed in location. The ordering of the enumeration
0337 * is based on how the paths are displayed.
0338 *
0339 * @param path the location in the <code>TreePath</code> to start
0340 * @return an <code>Enumerator</code> that increments over the visible
0341 * paths
0342 */
0343 public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
0344 TreeStateNode node = getNodeForPath(path, true, false);
0345
0346 if (node != null) {
0347 return new VisibleTreeStateNodeEnumeration(node);
0348 }
0349 return null;
0350 }
0351
0352 /**
0353 * Returns the number of visible children for <code>path</code>.
0354 * @return the number of visible children for <code>path</code>
0355 */
0356 public int getVisibleChildCount(TreePath path) {
0357 TreeStateNode node = getNodeForPath(path, true, false);
0358
0359 return (node != null) ? node.getVisibleChildCount() : 0;
0360 }
0361
0362 /**
0363 * Informs the <code>TreeState</code> that it needs to recalculate
0364 * all the sizes it is referencing.
0365 */
0366 public void invalidateSizes() {
0367 if (root != null)
0368 root.deepMarkSizeInvalid();
0369 if (!isFixedRowHeight() && visibleNodes.size() > 0) {
0370 updateNodeSizes(true);
0371 }
0372 }
0373
0374 /**
0375 * Returns true if the value identified by <code>path</code> is
0376 * currently expanded.
0377 * @return true if the value identified by <code>path</code> is
0378 * currently expanded
0379 */
0380 public boolean isExpanded(TreePath path) {
0381 if (path != null) {
0382 TreeStateNode lastNode = getNodeForPath(path, true, false);
0383
0384 return (lastNode != null && lastNode.isExpanded());
0385 }
0386 return false;
0387 }
0388
0389 //
0390 // TreeModelListener methods
0391 //
0392
0393 /**
0394 * Invoked after a node (or a set of siblings) has changed in some
0395 * way. The node(s) have not changed locations in the tree or
0396 * altered their children arrays, but other attributes have
0397 * changed and may affect presentation. Example: the name of a
0398 * file has changed, but it is in the same location in the file
0399 * system.
0400 *
0401 * <p><code>e.path</code> returns the path the parent of the
0402 * changed node(s).
0403 *
0404 * <p><code>e.childIndices</code> returns the index(es) of the
0405 * changed node(s).
0406 *
0407 * @param e the <code>TreeModelEvent</code> of interest
0408 */
0409 public void treeNodesChanged(TreeModelEvent e) {
0410 if (e != null) {
0411 int changedIndexs[];
0412 TreeStateNode changedNode;
0413
0414 changedIndexs = e.getChildIndices();
0415 changedNode = getNodeForPath(e.getTreePath(), false, false);
0416 if (changedNode != null) {
0417 Object changedValue = changedNode.getValue();
0418
0419 /* Update the size of the changed node, as well as all the
0420 child indexs that are passed in. */
0421 changedNode.updatePreferredSize();
0422 if (changedNode.hasBeenExpanded()
0423 && changedIndexs != null) {
0424 int counter;
0425 TreeStateNode changedChildNode;
0426
0427 for (counter = 0; counter < changedIndexs.length; counter++) {
0428 changedChildNode = (TreeStateNode) changedNode
0429 .getChildAt(changedIndexs[counter]);
0430 /* Reset the user object. */
0431 changedChildNode.setUserObject(treeModel
0432 .getChild(changedValue,
0433 changedIndexs[counter]));
0434 changedChildNode.updatePreferredSize();
0435 }
0436 } else if (changedNode == root) {
0437 // Null indicies for root indicates it changed.
0438 changedNode.updatePreferredSize();
0439 }
0440 if (!isFixedRowHeight()) {
0441 int aRow = changedNode.getRow();
0442
0443 if (aRow != -1)
0444 this .updateYLocationsFrom(aRow);
0445 }
0446 this .visibleNodesChanged();
0447 }
0448 }
0449 }
0450
0451 /**
0452 * Invoked after nodes have been inserted into the tree.
0453 *
0454 * <p><code>e.path</code> returns the parent of the new nodes.
0455 * <p><code>e.childIndices</code> returns the indices of the new nodes in
0456 * ascending order.
0457 *
0458 * @param e the <code>TreeModelEvent</code> of interest
0459 */
0460 public void treeNodesInserted(TreeModelEvent e) {
0461 if (e != null) {
0462 int changedIndexs[];
0463 TreeStateNode changedParentNode;
0464
0465 changedIndexs = e.getChildIndices();
0466 changedParentNode = getNodeForPath(e.getTreePath(), false,
0467 false);
0468 /* Only need to update the children if the node has been
0469 expanded once. */
0470 // PENDING(scott): make sure childIndexs is sorted!
0471 if (changedParentNode != null && changedIndexs != null
0472 && changedIndexs.length > 0) {
0473 if (changedParentNode.hasBeenExpanded()) {
0474 boolean makeVisible;
0475 int counter;
0476 Object changedParent;
0477 TreeStateNode newNode;
0478 int oldChildCount = changedParentNode
0479 .getChildCount();
0480
0481 changedParent = changedParentNode.getValue();
0482 makeVisible = ((changedParentNode == root && !rootVisible) || (changedParentNode
0483 .getRow() != -1 && changedParentNode
0484 .isExpanded()));
0485 for (counter = 0; counter < changedIndexs.length; counter++) {
0486 newNode = this .createNodeAt(changedParentNode,
0487 changedIndexs[counter]);
0488 }
0489 if (oldChildCount == 0) {
0490 // Update the size of the parent.
0491 changedParentNode.updatePreferredSize();
0492 }
0493 if (treeSelectionModel != null)
0494 treeSelectionModel.resetRowSelection();
0495 /* Update the y origins from the index of the parent
0496 to the end of the visible rows. */
0497 if (!isFixedRowHeight()
0498 && (makeVisible || (oldChildCount == 0 && changedParentNode
0499 .isVisible()))) {
0500 if (changedParentNode == root)
0501 this .updateYLocationsFrom(0);
0502 else
0503 this .updateYLocationsFrom(changedParentNode
0504 .getRow());
0505 this .visibleNodesChanged();
0506 } else if (makeVisible)
0507 this .visibleNodesChanged();
0508 } else if (treeModel.getChildCount(changedParentNode
0509 .getValue())
0510 - changedIndexs.length == 0) {
0511 changedParentNode.updatePreferredSize();
0512 if (!isFixedRowHeight()
0513 && changedParentNode.isVisible())
0514 updateYLocationsFrom(changedParentNode.getRow());
0515 }
0516 }
0517 }
0518 }
0519
0520 /**
0521 * Invoked after nodes have been removed from the tree. Note that
0522 * if a subtree is removed from the tree, this method may only be
0523 * invoked once for the root of the removed subtree, not once for
0524 * each individual set of siblings removed.
0525 *
0526 * <p><code>e.path</code> returns the former parent of the deleted nodes.
0527 *
0528 * <p><code>e.childIndices</code> returns the indices the nodes had
0529 * before they were deleted in ascending order.
0530 *
0531 * @param e the <code>TreeModelEvent</code> of interest
0532 */
0533 public void treeNodesRemoved(TreeModelEvent e) {
0534 if (e != null) {
0535 int changedIndexs[];
0536 TreeStateNode changedParentNode;
0537
0538 changedIndexs = e.getChildIndices();
0539 changedParentNode = getNodeForPath(e.getTreePath(), false,
0540 false);
0541 // PENDING(scott): make sure that changedIndexs are sorted in
0542 // ascending order.
0543 if (changedParentNode != null && changedIndexs != null
0544 && changedIndexs.length > 0) {
0545 if (changedParentNode.hasBeenExpanded()) {
0546 boolean makeInvisible;
0547 int counter;
0548 int removedRow;
0549 TreeStateNode removedNode;
0550
0551 makeInvisible = ((changedParentNode == root && !rootVisible) || (changedParentNode
0552 .getRow() != -1 && changedParentNode
0553 .isExpanded()));
0554 for (counter = changedIndexs.length - 1; counter >= 0; counter--) {
0555 removedNode = (TreeStateNode) changedParentNode
0556 .getChildAt(changedIndexs[counter]);
0557 if (removedNode.isExpanded()) {
0558 removedNode.collapse(false);
0559 }
0560
0561 /* Let the selection model now. */
0562 if (makeInvisible) {
0563 removedRow = removedNode.getRow();
0564 if (removedRow != -1) {
0565 visibleNodes
0566 .removeElementAt(removedRow);
0567 }
0568 }
0569 changedParentNode
0570 .remove(changedIndexs[counter]);
0571 }
0572 if (changedParentNode.getChildCount() == 0) {
0573 // Update the size of the parent.
0574 changedParentNode.updatePreferredSize();
0575 if (changedParentNode.isExpanded()
0576 && changedParentNode.isLeaf()) {
0577 // Node has become a leaf, collapse it.
0578 changedParentNode.collapse(false);
0579 }
0580 }
0581 if (treeSelectionModel != null)
0582 treeSelectionModel.resetRowSelection();
0583 /* Update the y origins from the index of the parent
0584 to the end of the visible rows. */
0585 if (!isFixedRowHeight()
0586 && (makeInvisible || (changedParentNode
0587 .getChildCount() == 0 && changedParentNode
0588 .isVisible()))) {
0589 if (changedParentNode == root) {
0590 /* It is possible for first row to have been
0591 removed if the root isn't visible, in which
0592 case ylocations will be off! */
0593 if (getRowCount() > 0)
0594 getNode(0).setYOrigin(0);
0595 updateYLocationsFrom(0);
0596 } else
0597 updateYLocationsFrom(changedParentNode
0598 .getRow());
0599 this .visibleNodesChanged();
0600 } else if (makeInvisible)
0601 this .visibleNodesChanged();
0602 } else if (treeModel.getChildCount(changedParentNode
0603 .getValue()) == 0) {
0604 changedParentNode.updatePreferredSize();
0605 if (!isFixedRowHeight()
0606 && changedParentNode.isVisible())
0607 this .updateYLocationsFrom(changedParentNode
0608 .getRow());
0609 }
0610 }
0611 }
0612 }
0613
0614 /**
0615 * Invoked after the tree has drastically changed structure from a
0616 * given node down. If the path returned by <code>e.getPath</code>
0617 * is of length one and the first element does not identify the
0618 * current root node the first element should become the new root
0619 * of the tree.
0620 *
0621 * <p><code>e.path</code> holds the path to the node.
0622 * <p><code>e.childIndices</code> returns <code>null</code>.
0623 *
0624 * @param e the <code>TreeModelEvent</code> of interest
0625 */
0626 public void treeStructureChanged(TreeModelEvent e) {
0627 if (e != null) {
0628 TreePath changedPath = e.getTreePath();
0629 TreeStateNode changedNode;
0630
0631 changedNode = getNodeForPath(changedPath, false, false);
0632
0633 // Check if root has changed, either to a null root, or
0634 // to an entirely new root.
0635 if (changedNode == root
0636 || (changedNode == null && ((changedPath == null
0637 && treeModel != null && treeModel.getRoot() == null) || (changedPath != null && changedPath
0638 .getPathCount() == 1)))) {
0639 rebuild(true);
0640 } else if (changedNode != null) {
0641 int nodeIndex, oldRow;
0642 TreeStateNode newNode, parent;
0643 boolean wasExpanded, wasVisible;
0644 int newIndex;
0645
0646 wasExpanded = changedNode.isExpanded();
0647 wasVisible = (changedNode.getRow() != -1);
0648 /* Remove the current node and recreate a new one. */
0649 parent = (TreeStateNode) changedNode.getParent();
0650 nodeIndex = parent.getIndex(changedNode);
0651 if (wasVisible && wasExpanded) {
0652 changedNode.collapse(false);
0653 }
0654 if (wasVisible)
0655 visibleNodes.removeElement(changedNode);
0656 changedNode.removeFromParent();
0657 createNodeAt(parent, nodeIndex);
0658 newNode = (TreeStateNode) parent.getChildAt(nodeIndex);
0659 if (wasVisible && wasExpanded)
0660 newNode.expand(false);
0661 newIndex = newNode.getRow();
0662 if (!isFixedRowHeight() && wasVisible) {
0663 if (newIndex == 0)
0664 updateYLocationsFrom(newIndex);
0665 else
0666 updateYLocationsFrom(newIndex - 1);
0667 this .visibleNodesChanged();
0668 } else if (wasVisible)
0669 this .visibleNodesChanged();
0670 }
0671 }
0672 }
0673
0674 //
0675 // Local methods
0676 //
0677
0678 private void visibleNodesChanged() {
0679 }
0680
0681 /**
0682 * Adds a mapping for node.
0683 */
0684 private void addMapping(TreeStateNode node) {
0685 treePathMapping.put(node.getTreePath(), node);
0686 }
0687
0688 /**
0689 * Removes the mapping for a previously added node.
0690 */
0691 private void removeMapping(TreeStateNode node) {
0692 treePathMapping.remove(node.getTreePath());
0693 }
0694
0695 /**
0696 * Returns the node previously added for <code>path</code>. This may
0697 * return null, if you to create a node use getNodeForPath.
0698 */
0699 private TreeStateNode getMapping(TreePath path) {
0700 return (TreeStateNode) treePathMapping.get(path);
0701 }
0702
0703 /**
0704 * Retursn the bounds for row, <code>row</code> by reference in
0705 * <code>placeIn</code>. If <code>placeIn</code> is null a new
0706 * Rectangle will be created and returned.
0707 */
0708 private Rectangle getBounds(int row, Rectangle placeIn) {
0709 if (updateNodeSizes)
0710 updateNodeSizes(false);
0711
0712 if (row >= 0 && row < getRowCount()) {
0713 return getNode(row).getNodeBounds(placeIn);
0714 }
0715 return null;
0716 }
0717
0718 /**
0719 * Completely rebuild the tree, all expanded state, and node caches are
0720 * removed. All nodes are collapsed, except the root.
0721 */
0722 private void rebuild(boolean clearSelection) {
0723 Object rootObject;
0724
0725 treePathMapping.clear();
0726 if (treeModel != null
0727 && (rootObject = treeModel.getRoot()) != null) {
0728 root = createNodeForValue(rootObject);
0729 root.path = new TreePath(rootObject);
0730 addMapping(root);
0731 root.updatePreferredSize(0);
0732 visibleNodes.removeAllElements();
0733 if (isRootVisible())
0734 visibleNodes.addElement(root);
0735 if (!root.isExpanded())
0736 root.expand();
0737 else {
0738 Enumeration cursor = root.children();
0739 while (cursor.hasMoreElements()) {
0740 visibleNodes.addElement(cursor.nextElement());
0741 }
0742 if (!isFixedRowHeight())
0743 updateYLocationsFrom(0);
0744 }
0745 } else {
0746 visibleNodes.removeAllElements();
0747 root = null;
0748 }
0749 if (clearSelection && treeSelectionModel != null) {
0750 treeSelectionModel.clearSelection();
0751 }
0752 this .visibleNodesChanged();
0753 }
0754
0755 /**
0756 * Creates a new node to represent the node at <I>childIndex</I> in
0757 * <I>parent</I>s children. This should be called if the node doesn't
0758 * already exist and <I>parent</I> has been expanded at least once.
0759 * The newly created node will be made visible if <I>parent</I> is
0760 * currently expanded. This does not update the position of any
0761 * cells, nor update the selection if it needs to be. If succesful
0762 * in creating the new TreeStateNode, it is returned, otherwise
0763 * null is returned.
0764 */
0765 private TreeStateNode createNodeAt(TreeStateNode parent,
0766 int childIndex) {
0767 boolean isParentRoot;
0768 Object newValue;
0769 TreeStateNode newChildNode;
0770
0771 newValue = treeModel.getChild(parent.getValue(), childIndex);
0772 newChildNode = createNodeForValue(newValue);
0773 parent.insert(newChildNode, childIndex);
0774 newChildNode.updatePreferredSize(-1);
0775 isParentRoot = (parent == root);
0776 if (newChildNode != null && parent.isExpanded()
0777 && (parent.getRow() != -1 || isParentRoot)) {
0778 int newRow;
0779
0780 /* Find the new row to insert this newly visible node at. */
0781 if (childIndex == 0) {
0782 if (isParentRoot && !isRootVisible())
0783 newRow = 0;
0784 else
0785 newRow = parent.getRow() + 1;
0786 } else if (childIndex == parent.getChildCount())
0787 newRow = parent.getLastVisibleNode().getRow() + 1;
0788 else {
0789 TreeStateNode previousNode;
0790
0791 previousNode = (TreeStateNode) parent
0792 .getChildAt(childIndex - 1);
0793 newRow = previousNode.getLastVisibleNode().getRow() + 1;
0794 }
0795 visibleNodes.insertElementAt(newChildNode, newRow);
0796 }
0797 return newChildNode;
0798 }
0799
0800 /**
0801 * Returns the TreeStateNode identified by path. This mirrors
0802 * the behavior of getNodeForPath, but tries to take advantage of
0803 * path if it is an instance of AbstractTreePath.
0804 */
0805 private TreeStateNode getNodeForPath(TreePath path,
0806 boolean onlyIfVisible, boolean shouldCreate) {
0807 if (path != null) {
0808 TreeStateNode node;
0809
0810 node = getMapping(path);
0811 if (node != null) {
0812 if (onlyIfVisible && !node.isVisible())
0813 return null;
0814 return node;
0815 }
0816
0817 // Check all the parent paths, until a match is found.
0818 Stack paths;
0819
0820 if (tempStacks.size() == 0) {
0821 paths = new Stack();
0822 } else {
0823 paths = (Stack) tempStacks.pop();
0824 }
0825
0826 try {
0827 paths.push(path);
0828 path = path.getParentPath();
0829 node = null;
0830 while (path != null) {
0831 node = getMapping(path);
0832 if (node != null) {
0833 // Found a match, create entries for all paths in
0834 // paths.
0835 while (node != null && paths.size() > 0) {
0836 path = (TreePath) paths.pop();
0837 node.getLoadedChildren(shouldCreate);
0838
0839 int childIndex = treeModel.getIndexOfChild(
0840 node.getUserObject(), path
0841 .getLastPathComponent());
0842
0843 if (childIndex == -1
0844 || childIndex >= node
0845 .getChildCount()
0846 || (onlyIfVisible && !node
0847 .isVisible())) {
0848 node = null;
0849 } else
0850 node = (TreeStateNode) node
0851 .getChildAt(childIndex);
0852 }
0853 return node;
0854 }
0855 paths.push(path);
0856 path = path.getParentPath();
0857 }
0858 } finally {
0859 paths.removeAllElements();
0860 tempStacks.push(paths);
0861 }
0862 // If we get here it means they share a different root!
0863 // We could throw an exception...
0864 }
0865 return null;
0866 }
0867
0868 /**
0869 * Updates the y locations of all of the visible nodes after
0870 * location.
0871 */
0872 private void updateYLocationsFrom(int location) {
0873 if (location >= 0 && location < getRowCount()) {
0874 int counter, maxCounter, newYOrigin;
0875 TreeStateNode aNode;
0876
0877 aNode = getNode(location);
0878 newYOrigin = aNode.getYOrigin()
0879 + aNode.getPreferredHeight();
0880 for (counter = location + 1, maxCounter = visibleNodes
0881 .size(); counter < maxCounter; counter++) {
0882 aNode = (TreeStateNode) visibleNodes.elementAt(counter);
0883 aNode.setYOrigin(newYOrigin);
0884 newYOrigin += aNode.getPreferredHeight();
0885 }
0886 }
0887 }
0888
0889 /**
0890 * Resets the y origin of all the visible nodes as well as messaging
0891 * all the visible nodes to updatePreferredSize(). You should not
0892 * normally have to call this. Expanding and contracting the nodes
0893 * automaticly adjusts the locations.
0894 * updateAll determines if updatePreferredSize() is call on all nodes
0895 * or just those that don't have a valid size.
0896 */
0897 private void updateNodeSizes(boolean updateAll) {
0898 int aY, counter, maxCounter;
0899 TreeStateNode node;
0900
0901 updateNodeSizes = false;
0902 for (aY = counter = 0, maxCounter = visibleNodes.size(); counter < maxCounter; counter++) {
0903 node = (TreeStateNode) visibleNodes.elementAt(counter);
0904 node.setYOrigin(aY);
0905 if (updateAll || !node.hasValidSize())
0906 node.updatePreferredSize(counter);
0907 aY += node.getPreferredHeight();
0908 }
0909 }
0910
0911 /**
0912 * Returns the index of the row containing location. If there
0913 * are no rows, -1 is returned. If location is beyond the last
0914 * row index, the last row index is returned.
0915 */
0916 private int getRowContainingYLocation(int location) {
0917 if (isFixedRowHeight()) {
0918 if (getRowCount() == 0)
0919 return -1;
0920 return Math.max(0, Math.min(getRowCount() - 1, location
0921 / getRowHeight()));
0922 }
0923
0924 int max, maxY, mid, min, minY;
0925 TreeStateNode node;
0926
0927 if ((max = getRowCount()) <= 0)
0928 return -1;
0929 mid = min = 0;
0930 while (min < max) {
0931 mid = (max - min) / 2 + min;
0932 node = (TreeStateNode) visibleNodes.elementAt(mid);
0933 minY = node.getYOrigin();
0934 maxY = minY + node.getPreferredHeight();
0935 if (location < minY) {
0936 max = mid - 1;
0937 } else if (location >= maxY) {
0938 min = mid + 1;
0939 } else
0940 break;
0941 }
0942 if (min == max) {
0943 mid = min;
0944 if (mid >= getRowCount())
0945 mid = getRowCount() - 1;
0946 }
0947 return mid;
0948 }
0949
0950 /**
0951 * Ensures that all the path components in path are expanded, accept
0952 * for the last component which will only be expanded if expandLast
0953 * is true.
0954 * Returns true if succesful in finding the path.
0955 */
0956 private void ensurePathIsExpanded(TreePath aPath, boolean expandLast) {
0957 if (aPath != null) {
0958 // Make sure the last entry isn't a leaf.
0959 if (treeModel.isLeaf(aPath.getLastPathComponent())) {
0960 aPath = aPath.getParentPath();
0961 expandLast = true;
0962 }
0963 if (aPath != null) {
0964 TreeStateNode lastNode = getNodeForPath(aPath, false,
0965 true);
0966
0967 if (lastNode != null) {
0968 lastNode.makeVisible();
0969 if (expandLast)
0970 lastNode.expand();
0971 }
0972 }
0973 }
0974 }
0975
0976 /**
0977 * Returns the AbstractTreeUI.VisibleNode displayed at the given row
0978 */
0979 private TreeStateNode getNode(int row) {
0980 return (TreeStateNode) visibleNodes.elementAt(row);
0981 }
0982
0983 /**
0984 * Returns the maximum node width.
0985 */
0986 private int getMaxNodeWidth() {
0987 int maxWidth = 0;
0988 int nodeWidth;
0989 int counter;
0990 TreeStateNode node;
0991
0992 for (counter = getRowCount() - 1; counter >= 0; counter--) {
0993 node = this .getNode(counter);
0994 nodeWidth = node.getPreferredWidth() + node.getXOrigin();
0995 if (nodeWidth > maxWidth)
0996 maxWidth = nodeWidth;
0997 }
0998 return maxWidth;
0999 }
1000
1001 /**
1002 * Responsible for creating a TreeStateNode that will be used
1003 * to track display information about value.
1004 */
1005 private TreeStateNode createNodeForValue(Object value) {
1006 return new TreeStateNode(value);
1007 }
1008
1009 /**
1010 * TreeStateNode is used to keep track of each of
1011 * the nodes that have been expanded. This will also cache the preferred
1012 * size of the value it represents.
1013 */
1014 private class TreeStateNode extends DefaultMutableTreeNode {
1015 /** Preferred size needed to draw the user object. */
1016 protected int preferredWidth;
1017 protected int preferredHeight;
1018
1019 /** X location that the user object will be drawn at. */
1020 protected int xOrigin;
1021
1022 /** Y location that the user object will be drawn at. */
1023 protected int yOrigin;
1024
1025 /** Is this node currently expanded? */
1026 protected boolean expanded;
1027
1028 /** Has this node been expanded at least once? */
1029 protected boolean hasBeenExpanded;
1030
1031 /** Path of this node. */
1032 protected TreePath path;
1033
1034 public TreeStateNode(Object value) {
1035 super (value);
1036 }
1037
1038 //
1039 // Overriden DefaultMutableTreeNode methods
1040 //
1041
1042 /**
1043 * Messaged when this node is added somewhere, resets the path
1044 * and adds a mapping from path to this node.
1045 */
1046 public void setParent(MutableTreeNode parent) {
1047 super .setParent(parent);
1048 if (parent != null) {
1049 path = ((TreeStateNode) parent).getTreePath()
1050 .pathByAddingChild(getUserObject());
1051 addMapping(this );
1052 }
1053 }
1054
1055 /**
1056 * Messaged when this node is removed from its parent, this messages
1057 * <code>removedFromMapping</code> to remove all the children.
1058 */
1059 public void remove(int childIndex) {
1060 TreeStateNode node = (TreeStateNode) getChildAt(childIndex);
1061
1062 node.removeFromMapping();
1063 super .remove(childIndex);
1064 }
1065
1066 /**
1067 * Messaged to set the user object. This resets the path.
1068 */
1069 public void setUserObject(Object o) {
1070 super .setUserObject(o);
1071 if (path != null) {
1072 TreeStateNode parent = (TreeStateNode) getParent();
1073
1074 if (parent != null)
1075 resetChildrenPaths(parent.getTreePath());
1076 else
1077 resetChildrenPaths(null);
1078 }
1079 }
1080
1081 /**
1082 * Returns the children of the receiver.
1083 * If the receiver is not currently expanded, this will return an
1084 * empty enumeration.
1085 */
1086 public Enumeration children() {
1087 if (!this .isExpanded()) {
1088 return DefaultMutableTreeNode.EMPTY_ENUMERATION;
1089 } else {
1090 return super .children();
1091 }
1092 }
1093
1094 /**
1095 * Returns true if the receiver is a leaf.
1096 */
1097 public boolean isLeaf() {
1098 return getModel().isLeaf(this .getValue());
1099 }
1100
1101 //
1102 // VariableHeightLayoutCache
1103 //
1104
1105 /**
1106 * Returns the location and size of this node.
1107 */
1108 public Rectangle getNodeBounds(Rectangle placeIn) {
1109 if (placeIn == null)
1110 placeIn = new Rectangle(getXOrigin(), getYOrigin(),
1111 getPreferredWidth(), getPreferredHeight());
1112 else {
1113 placeIn.x = getXOrigin();
1114 placeIn.y = getYOrigin();
1115 placeIn.width = getPreferredWidth();
1116 placeIn.height = getPreferredHeight();
1117 }
1118 return placeIn;
1119 }
1120
1121 /**
1122 * @return x location to draw node at.
1123 */
1124 public int getXOrigin() {
1125 if (!hasValidSize())
1126 updatePreferredSize(getRow());
1127 return xOrigin;
1128 }
1129
1130 /**
1131 * Returns the y origin the user object will be drawn at.
1132 */
1133 public int getYOrigin() {
1134 if (isFixedRowHeight()) {
1135 int aRow = getRow();
1136
1137 if (aRow == -1)
1138 return -1;
1139 return getRowHeight() * aRow;
1140 }
1141 return yOrigin;
1142 }
1143
1144 /**
1145 * Returns the preferred height of the receiver.
1146 */
1147 public int getPreferredHeight() {
1148 if (isFixedRowHeight())
1149 return getRowHeight();
1150 else if (!hasValidSize())
1151 updatePreferredSize(getRow());
1152 return preferredHeight;
1153 }
1154
1155 /**
1156 * Returns the preferred width of the receiver.
1157 */
1158 public int getPreferredWidth() {
1159 if (!hasValidSize())
1160 updatePreferredSize(getRow());
1161 return preferredWidth;
1162 }
1163
1164 /**
1165 * Returns true if this node has a valid size.
1166 */
1167 public boolean hasValidSize() {
1168 return (preferredHeight != 0);
1169 }
1170
1171 /**
1172 * Returns the row of the receiver.
1173 */
1174 public int getRow() {
1175 return visibleNodes.indexOf(this );
1176 }
1177
1178 /**
1179 * Returns true if this node has been expanded at least once.
1180 */
1181 public boolean hasBeenExpanded() {
1182 return hasBeenExpanded;
1183 }
1184
1185 /**
1186 * Returns true if the receiver has been expanded.
1187 */
1188 public boolean isExpanded() {
1189 return expanded;
1190 }
1191
1192 /**
1193 * Returns the last visible node that is a child of this
1194 * instance.
1195 */
1196 public TreeStateNode getLastVisibleNode() {
1197 TreeStateNode node = this ;
1198
1199 while (node.isExpanded() && node.getChildCount() > 0)
1200 node = (TreeStateNode) node.getLastChild();
1201 return node;
1202 }
1203
1204 /**
1205 * Returns true if the receiver is currently visible.
1206 */
1207 public boolean isVisible() {
1208 if (this == root)
1209 return true;
1210
1211 TreeStateNode parent = (TreeStateNode) getParent();
1212
1213 return (parent != null && parent.isExpanded() && parent
1214 .isVisible());
1215 }
1216
1217 /**
1218 * Returns the number of children this will have. If the children
1219 * have not yet been loaded, this messages the model.
1220 */
1221 public int getModelChildCount() {
1222 if (hasBeenExpanded)
1223 return super .getChildCount();
1224 return getModel().getChildCount(getValue());
1225 }
1226
1227 /**
1228 * Returns the number of visible children, that is the number of
1229 * children that are expanded, or leafs.
1230 */
1231 public int getVisibleChildCount() {
1232 int childCount = 0;
1233
1234 if (isExpanded()) {
1235 int maxCounter = getChildCount();
1236
1237 childCount += maxCounter;
1238 for (int counter = 0; counter < maxCounter; counter++)
1239 childCount += ((TreeStateNode) getChildAt(counter))
1240 .getVisibleChildCount();
1241 }
1242 return childCount;
1243 }
1244
1245 /**
1246 * Toggles the receiver between expanded and collapsed.
1247 */
1248 public void toggleExpanded() {
1249 if (isExpanded()) {
1250 collapse();
1251 } else {
1252 expand();
1253 }
1254 }
1255
1256 /**
1257 * Makes the receiver visible, but invoking
1258 * <code>expandParentAndReceiver</code> on the superclass.
1259 */
1260 public void makeVisible() {
1261 TreeStateNode parent = (TreeStateNode) getParent();
1262
1263 if (parent != null)
1264 parent.expandParentAndReceiver();
1265 }
1266
1267 /**
1268 * Expands the receiver.
1269 */
1270 public void expand() {
1271 expand(true);
1272 }
1273
1274 /**
1275 * Collapses the receiver.
1276 */
1277 public void collapse() {
1278 collapse(true);
1279 }
1280
1281 /**
1282 * Returns the value the receiver is representing. This is a cover
1283 * for getUserObject.
1284 */
1285 public Object getValue() {
1286 return getUserObject();
1287 }
1288
1289 /**
1290 * Returns a TreePath instance for this node.
1291 */
1292 public TreePath getTreePath() {
1293 return path;
1294 }
1295
1296 //
1297 // Local methods
1298 //
1299
1300 /**
1301 * Recreates the receivers path, and all its childrens paths.
1302 */
1303 protected void resetChildrenPaths(TreePath parentPath) {
1304 removeMapping(this );
1305 if (parentPath == null)
1306 path = new TreePath(getUserObject());
1307 else
1308 path = parentPath.pathByAddingChild(getUserObject());
1309 addMapping(this );
1310 for (int counter = getChildCount() - 1; counter >= 0; counter--)
1311 ((TreeStateNode) getChildAt(counter))
1312 .resetChildrenPaths(path);
1313 }
1314
1315 /**
1316 * Sets y origin the user object will be drawn at to
1317 * <I>newYOrigin</I>.
1318 */
1319 protected void setYOrigin(int newYOrigin) {
1320 yOrigin = newYOrigin;
1321 }
1322
1323 /**
1324 * Shifts the y origin by <code>offset</code>.
1325 */
1326 protected void shiftYOriginBy(int offset) {
1327 yOrigin += offset;
1328 }
1329
1330 /**
1331 * Updates the receivers preferredSize by invoking
1332 * <code>updatePreferredSize</code> with an argument of -1.
1333 */
1334 protected void updatePreferredSize() {
1335 updatePreferredSize(getRow());
1336 }
1337
1338 /**
1339 * Updates the preferred size by asking the current renderer
1340 * for the Dimension needed to draw the user object this
1341 * instance represents.
1342 */
1343 protected void updatePreferredSize(int index) {
1344 Rectangle bounds = getNodeDimensions(this .getUserObject(),
1345 index, getLevel(), isExpanded(), boundsBuffer);
1346
1347 if (bounds == null) {
1348 xOrigin = 0;
1349 preferredWidth = preferredHeight = 0;
1350 updateNodeSizes = true;
1351 } else if (bounds.height == 0) {
1352 xOrigin = 0;
1353 preferredWidth = preferredHeight = 0;
1354 updateNodeSizes = true;
1355 } else {
1356 xOrigin = bounds.x;
1357 preferredWidth = bounds.width;
1358 if (isFixedRowHeight())
1359 preferredHeight = getRowHeight();
1360 else
1361 preferredHeight = bounds.height;
1362 }
1363 }
1364
1365 /**
1366 * Marks the receivers size as invalid. Next time the size, location
1367 * is asked for it will be obtained.
1368 */
1369 protected void markSizeInvalid() {
1370 preferredHeight = 0;
1371 }
1372
1373 /**
1374 * Marks the receivers size, and all its descendants sizes, as invalid.
1375 */
1376 protected void deepMarkSizeInvalid() {
1377 markSizeInvalid();
1378 for (int counter = getChildCount() - 1; counter >= 0; counter--)
1379 ((TreeStateNode) getChildAt(counter))
1380 .deepMarkSizeInvalid();
1381 }
1382
1383 /**
1384 * Returns the children of the receiver. If the children haven't
1385 * been loaded from the model and
1386 * <code>createIfNeeded</code> is true, the children are first
1387 * loaded.
1388 */
1389 protected Enumeration getLoadedChildren(boolean createIfNeeded) {
1390 if (!createIfNeeded || hasBeenExpanded)
1391 return super .children();
1392
1393 TreeStateNode newNode;
1394 Object realNode = getValue();
1395 TreeModel treeModel = getModel();
1396 int count = treeModel.getChildCount(realNode);
1397
1398 hasBeenExpanded = true;
1399
1400 int childRow = getRow();
1401
1402 if (childRow == -1) {
1403 for (int i = 0; i < count; i++) {
1404 newNode = createNodeForValue(treeModel.getChild(
1405 realNode, i));
1406 this .add(newNode);
1407 newNode.updatePreferredSize(-1);
1408 }
1409 } else {
1410 childRow++;
1411 for (int i = 0; i < count; i++) {
1412 newNode = createNodeForValue(treeModel.getChild(
1413 realNode, i));
1414 this .add(newNode);
1415 newNode.updatePreferredSize(childRow++);
1416 }
1417 }
1418 return super .children();
1419 }
1420
1421 /**
1422 * Messaged from expand and collapse. This is meant for subclassers
1423 * that may wish to do something interesting with this.
1424 */
1425 protected void didAdjustTree() {
1426 }
1427
1428 /**
1429 * Invokes <code>expandParentAndReceiver</code> on the parent,
1430 * and expands the receiver.
1431 */
1432 protected void expandParentAndReceiver() {
1433 TreeStateNode parent = (TreeStateNode) getParent();
1434
1435 if (parent != null)
1436 parent.expandParentAndReceiver();
1437 expand();
1438 }
1439
1440 /**
1441 * Expands this node in the tree. This will load the children
1442 * from the treeModel if this node has not previously been
1443 * expanded. If <I>adjustTree</I> is true the tree and selection
1444 * are updated accordingly.
1445 */
1446 protected void expand(boolean adjustTree) {
1447 if (!isExpanded() && !isLeaf()) {
1448 boolean isFixed = isFixedRowHeight();
1449 int startHeight = getPreferredHeight();
1450 int originalRow = getRow();
1451
1452 expanded = true;
1453 updatePreferredSize(originalRow);
1454
1455 if (!hasBeenExpanded) {
1456 TreeStateNode newNode;
1457 Object realNode = getValue();
1458 TreeModel treeModel = getModel();
1459 int count = treeModel.getChildCount(realNode);
1460
1461 hasBeenExpanded = true;
1462 if (originalRow == -1) {
1463 for (int i = 0; i < count; i++) {
1464 newNode = createNodeForValue(treeModel
1465 .getChild(realNode, i));
1466 this .add(newNode);
1467 newNode.updatePreferredSize(-1);
1468 }
1469 } else {
1470 int offset = originalRow + 1;
1471 for (int i = 0; i < count; i++) {
1472 newNode = createNodeForValue(treeModel
1473 .getChild(realNode, i));
1474 this .add(newNode);
1475 newNode.updatePreferredSize(offset);
1476 }
1477 }
1478 }
1479
1480 int i = originalRow;
1481 Enumeration cursor = preorderEnumeration();
1482 cursor.nextElement(); // don't add me, I'm already in
1483
1484 int newYOrigin;
1485
1486 if (isFixed)
1487 newYOrigin = 0;
1488 else if (this == root && !isRootVisible())
1489 newYOrigin = 0;
1490 else
1491 newYOrigin = getYOrigin()
1492 + this .getPreferredHeight();
1493 TreeStateNode aNode;
1494 if (!isFixed) {
1495 while (cursor.hasMoreElements()) {
1496 aNode = (TreeStateNode) cursor.nextElement();
1497 if (!updateNodeSizes && !aNode.hasValidSize())
1498 aNode.updatePreferredSize(i + 1);
1499 aNode.setYOrigin(newYOrigin);
1500 newYOrigin += aNode.getPreferredHeight();
1501 visibleNodes.insertElementAt(aNode, ++i);
1502 }
1503 } else {
1504 while (cursor.hasMoreElements()) {
1505 aNode = (TreeStateNode) cursor.nextElement();
1506 visibleNodes.insertElementAt(aNode, ++i);
1507 }
1508 }
1509
1510 if (adjustTree
1511 && (originalRow != i || getPreferredHeight() != startHeight)) {
1512 // Adjust the Y origin of any nodes following this row.
1513 if (!isFixed && ++i < getRowCount()) {
1514 int counter;
1515 int heightDiff = newYOrigin
1516 - (getYOrigin() + getPreferredHeight())
1517 + (getPreferredHeight() - startHeight);
1518
1519 for (counter = visibleNodes.size() - 1; counter >= i; counter--)
1520 ((TreeStateNode) visibleNodes
1521 .elementAt(counter))
1522 .shiftYOriginBy(heightDiff);
1523 }
1524 didAdjustTree();
1525 visibleNodesChanged();
1526 }
1527
1528 // Update the rows in the selection
1529 if (treeSelectionModel != null) {
1530 treeSelectionModel.resetRowSelection();
1531 }
1532 }
1533 }
1534
1535 /**
1536 * Collapses this node in the tree. If <I>adjustTree</I> is
1537 * true the tree and selection are updated accordingly.
1538 */
1539 protected void collapse(boolean adjustTree) {
1540 if (isExpanded()) {
1541 Enumeration cursor = preorderEnumeration();
1542 cursor.nextElement(); // don't remove me, I'm still visible
1543 int rowsDeleted = 0;
1544 boolean isFixed = isFixedRowHeight();
1545 int lastYEnd;
1546 if (isFixed)
1547 lastYEnd = 0;
1548 else
1549 lastYEnd = getPreferredHeight() + getYOrigin();
1550 int startHeight = getPreferredHeight();
1551 int startYEnd = lastYEnd;
1552 int myRow = getRow();
1553
1554 if (!isFixed) {
1555 while (cursor.hasMoreElements()) {
1556 TreeStateNode node = (TreeStateNode) cursor
1557 .nextElement();
1558 if (node.isVisible()) {
1559 rowsDeleted++;
1560 //visibleNodes.removeElement(node);
1561 lastYEnd = node.getYOrigin()
1562 + node.getPreferredHeight();
1563 }
1564 }
1565 } else {
1566 while (cursor.hasMoreElements()) {
1567 TreeStateNode node = (TreeStateNode) cursor
1568 .nextElement();
1569 if (node.isVisible()) {
1570 rowsDeleted++;
1571 //visibleNodes.removeElement(node);
1572 }
1573 }
1574 }
1575
1576 // Clean up the visible nodes.
1577 for (int counter = rowsDeleted + myRow; counter > myRow; counter--) {
1578 visibleNodes.removeElementAt(counter);
1579 }
1580
1581 expanded = false;
1582
1583 if (myRow == -1)
1584 markSizeInvalid();
1585 else if (adjustTree)
1586 updatePreferredSize(myRow);
1587
1588 if (myRow != -1
1589 && adjustTree
1590 && (rowsDeleted > 0 || startHeight != getPreferredHeight())) {
1591 // Adjust the Y origin of any rows following this one.
1592 startYEnd += (getPreferredHeight() - startHeight);
1593 if (!isFixed && (myRow + 1) < getRowCount()
1594 && startYEnd != lastYEnd) {
1595 int counter, maxCounter, shiftAmount;
1596
1597 shiftAmount = startYEnd - lastYEnd;
1598 for (counter = myRow + 1, maxCounter = visibleNodes
1599 .size(); counter < maxCounter; counter++)
1600 ((TreeStateNode) visibleNodes
1601 .elementAt(counter))
1602 .shiftYOriginBy(shiftAmount);
1603 }
1604 didAdjustTree();
1605 visibleNodesChanged();
1606 }
1607 if (treeSelectionModel != null && rowsDeleted > 0
1608 && myRow != -1) {
1609 treeSelectionModel.resetRowSelection();
1610 }
1611 }
1612 }
1613
1614 /**
1615 * Removes the receiver, and all its children, from the mapping
1616 * table.
1617 */
1618 protected void removeFromMapping() {
1619 if (path != null) {
1620 removeMapping(this );
1621 for (int counter = getChildCount() - 1; counter >= 0; counter--)
1622 ((TreeStateNode) getChildAt(counter))
1623 .removeFromMapping();
1624 }
1625 }
1626 } // End of VariableHeightLayoutCache.TreeStateNode
1627
1628 /**
1629 * An enumerator to iterate through visible nodes.
1630 */
1631 private class VisibleTreeStateNodeEnumeration implements
1632 Enumeration<TreePath> {
1633 /** Parent thats children are being enumerated. */
1634 protected TreeStateNode parent;
1635 /** Index of next child. An index of -1 signifies parent should be
1636 * visibled next. */
1637 protected int nextIndex;
1638 /** Number of children in parent. */
1639 protected int childCount;
1640
1641 protected VisibleTreeStateNodeEnumeration(TreeStateNode node) {
1642 this (node, -1);
1643 }
1644
1645 protected VisibleTreeStateNodeEnumeration(TreeStateNode parent,
1646 int startIndex) {
1647 this .parent = parent;
1648 this .nextIndex = startIndex;
1649 this .childCount = this .parent.getChildCount();
1650 }
1651
1652 /**
1653 * @return true if more visible nodes.
1654 */
1655 public boolean hasMoreElements() {
1656 return (parent != null);
1657 }
1658
1659 /**
1660 * @return next visible TreePath.
1661 */
1662 public TreePath nextElement() {
1663 if (!hasMoreElements())
1664 throw new NoSuchElementException(
1665 "No more visible paths");
1666
1667 TreePath retObject;
1668
1669 if (nextIndex == -1) {
1670 retObject = parent.getTreePath();
1671 } else {
1672 TreeStateNode node = (TreeStateNode) parent
1673 .getChildAt(nextIndex);
1674
1675 retObject = node.getTreePath();
1676 }
1677 updateNextObject();
1678 return retObject;
1679 }
1680
1681 /**
1682 * Determines the next object by invoking <code>updateNextIndex</code>
1683 * and if not succesful <code>findNextValidParent</code>.
1684 */
1685 protected void updateNextObject() {
1686 if (!updateNextIndex()) {
1687 findNextValidParent();
1688 }
1689 }
1690
1691 /**
1692 * Finds the next valid parent, this should be called when nextIndex
1693 * is beyond the number of children of the current parent.
1694 */
1695 protected boolean findNextValidParent() {
1696 if (parent == root) {
1697 // mark as invalid!
1698 parent = null;
1699 return false;
1700 }
1701 while (parent != null) {
1702 TreeStateNode newParent = (TreeStateNode) parent
1703 .getParent();
1704
1705 if (newParent != null) {
1706 nextIndex = newParent.getIndex(parent);
1707 parent = newParent;
1708 childCount = parent.getChildCount();
1709 if (updateNextIndex())
1710 return true;
1711 } else
1712 parent = null;
1713 }
1714 return false;
1715 }
1716
1717 /**
1718 * Updates <code>nextIndex</code> returning false if it is beyond
1719 * the number of children of parent.
1720 */
1721 protected boolean updateNextIndex() {
1722 // nextIndex == -1 identifies receiver, make sure is expanded
1723 // before descend.
1724 if (nextIndex == -1 && !parent.isExpanded())
1725 return false;
1726
1727 // Check that it can have kids
1728 if (childCount == 0)
1729 return false;
1730 // Make sure next index not beyond child count.
1731 else if (++nextIndex >= childCount)
1732 return false;
1733
1734 TreeStateNode child = (TreeStateNode) parent
1735 .getChildAt(nextIndex);
1736
1737 if (child != null && child.isExpanded()) {
1738 parent = child;
1739 nextIndex = -1;
1740 childCount = child.getChildCount();
1741 }
1742 return true;
1743 }
1744 } // VariableHeightLayoutCache.VisibleTreeStateNodeEnumeration
1745 }
|