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