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