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