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