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