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