< prev index next >

src/java.desktop/share/classes/javax/swing/tree/VariableHeightLayoutCache.java

Print this page




  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&trade;
  47  * has been added to the <code>java.beans</code> package.
  48  * Please see {@link java.beans.XMLEncoder}.
  49  *
  50  * @author Rob Davis
  51  * @author Ray Ryan
  52  * @author Scott Violet
  53  */
  54 @SuppressWarnings("serial") // Same-version serialization only
  55 public class VariableHeightLayoutCache extends AbstractLayoutCache {
  56     /**
  57      * The array of nodes that are currently visible, in the order they
  58      * are displayed.
  59      */
  60     private Vector<Object> visibleNodes;
  61 
  62     /**
  63      * This is set to true if one of the entries has an invalid size.
  64      */
  65     private boolean           updateNodeSizes;
  66 
  67     /**
  68      * The root node of the internal cache of nodes that have been shown.
  69      * If the treeModel is vending a network rather than a true tree,
  70      * there may be one cached node for each path to a modeled node.
  71      */
  72     private TreeStateNode     root;
  73 
  74     /**
  75      * Used in getting sizes for nodes to avoid creating a new Rectangle
  76      * every time a size is needed.
  77      */
  78     private Rectangle         boundsBuffer;
  79 
  80     /**
  81      * Maps from <code>TreePath</code> to a <code>TreeStateNode</code>.
  82      */
  83     private Hashtable<TreePath, TreeStateNode> treePathMapping;
  84 
  85     /**
  86      * A stack of stacks.
  87      */
  88     private Stack<Stack<TreePath>> tempStacks;
  89 
  90 
  91     /**
  92      * Constructs a {@code VariableHeightLayoutCache}.
  93      */
  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&trade;
  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;
< prev index next >