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