1. /*
  2. * @(#)FixedHeightLayoutCache.java 1.11 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.tree;
  8. import javax.swing.event.TreeModelEvent;
  9. import java.awt.Dimension;
  10. import java.awt.Rectangle;
  11. import java.util.Enumeration;
  12. import java.util.Hashtable;
  13. import java.util.NoSuchElementException;
  14. import java.util.Stack;
  15. /**
  16. * NOTE: This will become more open in a future release.
  17. * <p>
  18. * <strong>Warning:</strong>
  19. * Serialized objects of this class will not be compatible with
  20. * future Swing releases. The current serialization support is appropriate
  21. * for short term storage or RMI between applications running the same
  22. * version of Swing. A future release of Swing will provide support for
  23. * long term persistence.
  24. *
  25. * @version 1.11 11/29/01
  26. * @author Scott Violet
  27. */
  28. public class FixedHeightLayoutCache extends AbstractLayoutCache {
  29. /** Root node. */
  30. private FHTreeStateNode root;
  31. /** Number of rows currently visible. */
  32. private int rowCount;
  33. /**
  34. * Used in getting sizes for nodes to avoid creating a new Rectangle
  35. * every time a size is needed.
  36. */
  37. private Rectangle boundsBuffer;
  38. /**
  39. * Maps from TreePath to a FHTreeStateNode.
  40. */
  41. private Hashtable treePathMapping;
  42. /**
  43. * Used for getting path/row information.
  44. */
  45. private SearchInfo info;
  46. private Stack tempStacks;
  47. public FixedHeightLayoutCache() {
  48. super();
  49. tempStacks = new Stack();
  50. boundsBuffer = new Rectangle();
  51. treePathMapping = new Hashtable();
  52. info = new SearchInfo();
  53. }
  54. /**
  55. * Sets the TreeModel that will provide the data.
  56. *
  57. * @param newModel the TreeModel that is to provide the data
  58. */
  59. public void setModel(TreeModel newModel) {
  60. super.setModel(newModel);
  61. rebuild();
  62. }
  63. /**
  64. * Determines whether or not the root node from
  65. * the TreeModel is visible.
  66. *
  67. * @param rootVisible true if the root node of the tree is to be displayed
  68. * @see #rootVisible
  69. */
  70. public void setRootVisible(boolean rootVisible) {
  71. if(isRootVisible() != rootVisible) {
  72. super.setRootVisible(rootVisible);
  73. if(root != null) {
  74. if(rootVisible) {
  75. rowCount++;
  76. root.adjustRowBy(1);
  77. }
  78. else {
  79. rowCount--;
  80. root.adjustRowBy(-1);
  81. }
  82. visibleNodesChanged();
  83. }
  84. }
  85. }
  86. /**
  87. * Sets the height of each cell. If rowHeight is less than or equal to
  88. * 0 this will throw an IllegalArgumentException.
  89. *
  90. * @param rowHeight the height of each cell, in pixels
  91. */
  92. public void setRowHeight(int rowHeight) {
  93. if(rowHeight <= 0)
  94. throw new IllegalArgumentException("FixedHeightLayoutCache only supports row heights greater than 0");
  95. if(getRowHeight() != rowHeight) {
  96. super.setRowHeight(rowHeight);
  97. visibleNodesChanged();
  98. }
  99. }
  100. /**
  101. * Returns the number of visible rows.
  102. */
  103. public int getRowCount() {
  104. return rowCount;
  105. }
  106. /**
  107. * Does nothing, FixedHeightLayoutCache doesn't cache width, and that
  108. * is all that could change.
  109. */
  110. public void invalidatePathBounds(TreePath path) {
  111. }
  112. /**
  113. * Informs the TreeState that it needs to recalculate all the sizes
  114. * it is referencing.
  115. */
  116. public void invalidateSizes() {
  117. // Nothing to do here, rowHeight still same, which is all
  118. // this is interested in, visible region may have changed though.
  119. visibleNodesChanged();
  120. }
  121. /**
  122. * Returns true if the value identified by row is currently expanded.
  123. */
  124. public boolean isExpanded(TreePath path) {
  125. if(path != null) {
  126. FHTreeStateNode lastNode = getNodeForPath(path, true, false);
  127. return (lastNode != null && lastNode.isExpanded());
  128. }
  129. return false;
  130. }
  131. /**
  132. * Returns a rectangle giving the bounds needed to draw path.
  133. *
  134. * @param path a TreePath specifying a node
  135. * @param placeIn a Rectangle object giving the available space
  136. * @return a Rectangle object specifying the space to be used
  137. */
  138. public Rectangle getBounds(TreePath path, Rectangle placeIn) {
  139. if(path == null)
  140. return null;
  141. FHTreeStateNode node = getNodeForPath(path, true, false);
  142. if(node != null)
  143. return getBounds(node, -1, placeIn);
  144. // node hasn't been created yet.
  145. TreePath parentPath = path.getParentPath();
  146. node = getNodeForPath(parentPath, true, false);
  147. if(node != null) {
  148. int childIndex = treeModel.getIndexOfChild
  149. (parentPath.getLastPathComponent(),
  150. path.getLastPathComponent());
  151. if(childIndex != -1)
  152. return getBounds(node, childIndex, placeIn);
  153. }
  154. return null;
  155. }
  156. /**
  157. * Returns the path for passed in row. If row is not visible
  158. * null is returned.
  159. */
  160. public TreePath getPathForRow(int row) {
  161. if(row >= 0 && row < getRowCount()) {
  162. if(root.getPathForRow(row, getRowCount(), info)) {
  163. return info.getPath();
  164. }
  165. }
  166. return null;
  167. }
  168. /**
  169. * Returns the row that the last item identified in path is visible
  170. * at. Will return -1 if any of the elements in path are not
  171. * currently visible.
  172. */
  173. public int getRowForPath(TreePath path) {
  174. if(path == null || root == null)
  175. return -1;
  176. FHTreeStateNode node = getNodeForPath(path, true, false);
  177. if(node != null)
  178. return node.getRow();
  179. TreePath parentPath = path.getParentPath();
  180. node = getNodeForPath(parentPath, true, false);
  181. if(node != null) {
  182. return node.getRowToModelIndex(treeModel.getIndexOfChild
  183. (parentPath.getLastPathComponent(),
  184. path.getLastPathComponent()));
  185. }
  186. return -1;
  187. }
  188. /**
  189. * Returns the path to the node that is closest to x,y. If
  190. * there is nothing currently visible this will return null, otherwise
  191. * it'll always return a valid path. If you need to test if the
  192. * returned object is exactly at x, y you should get the bounds for
  193. * the returned path and test x, y against that.
  194. */
  195. public TreePath getPathClosestTo(int x, int y) {
  196. if(getRowCount() == 0)
  197. return null;
  198. int row = getRowContainingYLocation(y);
  199. return getPathForRow(row);
  200. }
  201. /**
  202. * Returns the number of visible children for row.
  203. */
  204. public int getVisibleChildCount(TreePath path) {
  205. FHTreeStateNode node = getNodeForPath(path, true, false);
  206. if(node == null)
  207. return 0;
  208. return node.getTotalChildCount();
  209. }
  210. /**
  211. * Returns an Enumerator that increments over the visible paths
  212. * starting at the passed in location. The ordering of the enumeration
  213. * is based on how the paths are displayed.
  214. */
  215. public Enumeration getVisiblePathsFrom(TreePath path) {
  216. if(path == null)
  217. return null;
  218. FHTreeStateNode node = getNodeForPath(path, true, false);
  219. if(node != null) {
  220. return new VisibleFHTreeStateNodeEnumeration(node);
  221. }
  222. TreePath parentPath = path.getParentPath();
  223. node = getNodeForPath(parentPath, true, false);
  224. if(node != null && node.isExpanded()) {
  225. return new VisibleFHTreeStateNodeEnumeration(node,
  226. treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
  227. path.getLastPathComponent()));
  228. }
  229. return null;
  230. }
  231. /**
  232. * Marks the path <code>path</code> expanded state to
  233. * <code>isExpanded</code>.
  234. */
  235. public void setExpandedState(TreePath path, boolean isExpanded) {
  236. if(isExpanded)
  237. ensurePathIsExpanded(path, true);
  238. else if(path != null) {
  239. TreePath parentPath = path.getParentPath();
  240. // YECK! Make the parent expanded.
  241. if(parentPath != null) {
  242. FHTreeStateNode parentNode = getNodeForPath(parentPath,
  243. false, true);
  244. if(parentNode != null)
  245. parentNode.makeVisible();
  246. }
  247. // And collapse the child.
  248. FHTreeStateNode childNode = getNodeForPath(path, true,
  249. false);
  250. if(childNode != null)
  251. childNode.collapse(true);
  252. }
  253. }
  254. /**
  255. * Returns true if the path is expanded, and visible.
  256. */
  257. public boolean getExpandedState(TreePath path) {
  258. FHTreeStateNode node = getNodeForPath(path, true, false);
  259. return (node != null) ? (node.isVisible() && node.isExpanded()) :
  260. false;
  261. }
  262. //
  263. // TreeModelListener methods
  264. //
  265. /**
  266. * <p>Invoked after a node (or a set of siblings) has changed in some
  267. * way. The node(s) have not changed locations in the tree or
  268. * altered their children arrays, but other attributes have
  269. * changed and may affect presentation. Example: the name of a
  270. * file has changed, but it is in the same location in the file
  271. * system.</p>
  272. *
  273. * <p>e.path() returns the path the parent of the changed node(s).</p>
  274. *
  275. * <p>e.childIndices() returns the index(es) of the changed node(s).</p>
  276. */
  277. public void treeNodesChanged(TreeModelEvent e) {
  278. if(e != null) {
  279. int changedIndexs[];
  280. FHTreeStateNode changedParent = getNodeForPath
  281. (e.getTreePath(), false, false);
  282. int maxCounter;
  283. changedIndexs = e.getChildIndices();
  284. /* Only need to update the children if the node has been
  285. expanded once. */
  286. // PENDING(scott): make sure childIndexs is sorted!
  287. if (changedParent != null) {
  288. if (changedIndexs != null &&
  289. (maxCounter = changedIndexs.length) > 0) {
  290. Object parentValue = changedParent.getUserObject();
  291. for(int counter = 0; counter < maxCounter; counter++) {
  292. FHTreeStateNode child = changedParent.
  293. getChildAtModelIndex(changedIndexs[counter]);
  294. if(child != null) {
  295. child.setUserObject(treeModel.getChild(parentValue,
  296. changedIndexs[counter]));
  297. }
  298. }
  299. if(changedParent.isVisible() && changedParent.isExpanded())
  300. visibleNodesChanged();
  301. }
  302. // Null for root indicates it changed.
  303. else if (changedParent == root && changedParent.isVisible() &&
  304. changedParent.isExpanded()) {
  305. visibleNodesChanged();
  306. }
  307. }
  308. }
  309. }
  310. /**
  311. * <p>Invoked after nodes have been inserted into the tree.</p>
  312. *
  313. * <p>e.path() returns the parent of the new nodes
  314. * <p>e.childIndices() returns the indices of the new nodes in
  315. * ascending order.
  316. */
  317. public void treeNodesInserted(TreeModelEvent e) {
  318. if(e != null) {
  319. int changedIndexs[];
  320. FHTreeStateNode changedParent = getNodeForPath
  321. (e.getTreePath(), false, false);
  322. int maxCounter;
  323. changedIndexs = e.getChildIndices();
  324. /* Only need to update the children if the node has been
  325. expanded once. */
  326. // PENDING(scott): make sure childIndexs is sorted!
  327. if(changedParent != null && changedIndexs != null &&
  328. (maxCounter = changedIndexs.length) > 0) {
  329. boolean isVisible =
  330. (changedParent.isVisible() &&
  331. changedParent.isExpanded());
  332. for(int counter = 0; counter < maxCounter; counter++) {
  333. changedParent.childInsertedAtModelIndex
  334. (changedIndexs[counter], isVisible);
  335. }
  336. if(isVisible && treeSelectionModel != null)
  337. treeSelectionModel.resetRowSelection();
  338. if(changedParent.isVisible())
  339. this.visibleNodesChanged();
  340. }
  341. }
  342. }
  343. /**
  344. * <p>Invoked after nodes have been removed from the tree. Note that
  345. * if a subtree is removed from the tree, this method may only be
  346. * invoked once for the root of the removed subtree, not once for
  347. * each individual set of siblings removed.</p>
  348. *
  349. * <p>e.path() returns the former parent of the deleted nodes.</p>
  350. *
  351. * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p>
  352. */
  353. public void treeNodesRemoved(TreeModelEvent e) {
  354. if(e != null) {
  355. int changedIndexs[];
  356. int maxCounter;
  357. TreePath parentPath = e.getTreePath();
  358. FHTreeStateNode changedParentNode = getNodeForPath
  359. (parentPath, false, false);
  360. changedIndexs = e.getChildIndices();
  361. // PENDING(scott): make sure that changedIndexs are sorted in
  362. // ascending order.
  363. if(changedParentNode != null && changedIndexs != null &&
  364. (maxCounter = changedIndexs.length) > 0) {
  365. Object[] children = e.getChildren();
  366. boolean isVisible =
  367. (changedParentNode.isVisible() &&
  368. changedParentNode.isExpanded());
  369. for(int counter = maxCounter - 1; counter >= 0; counter--) {
  370. changedParentNode.removeChildAtModelIndex
  371. (changedIndexs[counter], isVisible);
  372. /* Clean up the selection. */
  373. if(treeSelectionModel != null && children != null &&
  374. children[counter] != null) {
  375. treeSelectionModel.removeSelectionPath(parentPath.
  376. pathByAddingChild(children[counter]));
  377. }
  378. }
  379. if(isVisible) {
  380. if(treeSelectionModel != null)
  381. treeSelectionModel.resetRowSelection();
  382. visibleNodesChanged();
  383. }
  384. else if(changedParentNode.isVisible())
  385. visibleNodesChanged();
  386. }
  387. }
  388. }
  389. /**
  390. * <p>Invoked after the tree has drastically changed structure from a
  391. * given node down. If the path returned by e.getPath() is of length
  392. * one and the first element does not identify the current root node
  393. * the first element should become the new root of the tree.<p>
  394. *
  395. * <p>e.path() holds the path to the node.</p>
  396. * <p>e.childIndices() returns null.</p>
  397. */
  398. public void treeStructureChanged(TreeModelEvent e) {
  399. if(e != null) {
  400. TreePath changedPath = e.getTreePath();
  401. if(changedPath != null && changedPath.getPathCount() > 0) {
  402. if(changedPath.getPathCount() == 1)
  403. rebuild();
  404. else {
  405. FHTreeStateNode changedNode = getNodeForPath
  406. (changedPath, false, false);
  407. if(changedNode != null) {
  408. boolean wasExpanded, wasVisible;
  409. int newIndex;
  410. FHTreeStateNode parent = (FHTreeStateNode)
  411. changedNode.getParent();
  412. int row = changedNode.getRow();
  413. wasExpanded = changedNode.isExpanded();
  414. wasVisible = changedNode.isVisible();
  415. if(wasVisible && wasExpanded) {
  416. changedNode.collapse(true);
  417. changedNode.removeFromParent();
  418. changedNode = getNodeForPath(changedPath, false,
  419. true);
  420. changedNode.expand();
  421. }
  422. else
  423. changedNode.removeFromParent();
  424. if(treeSelectionModel != null && wasVisible &&
  425. wasExpanded)
  426. treeSelectionModel.resetRowSelection();
  427. if(wasVisible)
  428. this.visibleNodesChanged();
  429. }
  430. }
  431. }
  432. }
  433. }
  434. //
  435. // Local methods
  436. //
  437. private void visibleNodesChanged() {
  438. }
  439. /**
  440. * Returns the bounds for the given node. If <code>childIndex</code>
  441. * is -1, the bounds of <code>parent</code> are returned, otherwise
  442. * the bounds of the node at <code>childIndex</code> are returned.
  443. */
  444. private Rectangle getBounds(FHTreeStateNode parent, int childIndex,
  445. Rectangle placeIn) {
  446. boolean expanded;
  447. int level;
  448. int row;
  449. Object value;
  450. if(childIndex == -1) {
  451. // Getting bounds for parent
  452. row = parent.getRow();
  453. value = parent.getUserObject();
  454. expanded = parent.isExpanded();
  455. level = parent.getLevel();
  456. }
  457. else {
  458. row = parent.getRowToModelIndex(childIndex);
  459. value = treeModel.getChild(parent.getUserObject(), childIndex);
  460. expanded = false;
  461. level = parent.getLevel() + 1;
  462. }
  463. Rectangle bounds = getNodeDimensions(value, row, level,
  464. expanded, boundsBuffer);
  465. // No node dimensions, bail.
  466. if(bounds == null)
  467. return null;
  468. if(placeIn == null)
  469. placeIn = new Rectangle();
  470. placeIn.x = bounds.x;
  471. placeIn.height = getRowHeight();
  472. placeIn.y = row * placeIn.height;
  473. placeIn.width = boundsBuffer.width;
  474. return placeIn;
  475. }
  476. /**
  477. * Adjust the large row count of the AbstractTreeUI the receiver was
  478. * created with.
  479. */
  480. private void adjustRowCountBy(int changeAmount) {
  481. rowCount += changeAmount;
  482. }
  483. /**
  484. * Adds a mapping for node.
  485. */
  486. private void addMapping(FHTreeStateNode node) {
  487. treePathMapping.put(node.getTreePath(), node);
  488. }
  489. /**
  490. * Removes the mapping for a previously added node.
  491. */
  492. private void removeMapping(FHTreeStateNode node) {
  493. treePathMapping.remove(node.getTreePath());
  494. }
  495. /**
  496. * Returns the node previously added for <code>path</code>. This may
  497. * return null, if you to create a node use getNodeForPath.
  498. */
  499. private FHTreeStateNode getMapping(TreePath path) {
  500. return (FHTreeStateNode)treePathMapping.get(path);
  501. }
  502. /**
  503. * Sent to completely rebuild the visible tree. All nodes are collapsed.
  504. */
  505. private void rebuild() {
  506. treePathMapping.clear();
  507. if(treeModel != null) {
  508. Object rootUO = treeModel.getRoot();
  509. root = createNodeForValue(rootUO, 0);
  510. root.path = new TreePath(rootUO);
  511. addMapping(root);
  512. if(isRootVisible()) {
  513. rowCount = 1;
  514. root.row = 0;
  515. }
  516. else {
  517. rowCount = 0;
  518. root.row = -1;
  519. }
  520. root.expand();
  521. }
  522. else {
  523. root = null;
  524. rowCount = 0;
  525. }
  526. /* Clear out the selection model, might not always be the right
  527. thing to do, but the tree is being rebuilt, soooo.... */
  528. if(treeSelectionModel != null) {
  529. treeSelectionModel.clearSelection();
  530. }
  531. this.visibleNodesChanged();
  532. }
  533. /**
  534. * Returns the index of the row containing location. If there
  535. * are no rows, -1 is returned. If location is beyond the last
  536. * row index, the last row index is returned.
  537. */
  538. private int getRowContainingYLocation(int location) {
  539. if(getRowCount() == 0)
  540. return -1;
  541. return Math.max(0, Math.min(getRowCount() - 1,
  542. location / getRowHeight()));
  543. }
  544. /**
  545. * Ensures that all the path components in path are expanded, accept
  546. * for the last component which will only be expanded if expandLast
  547. * is true.
  548. * Returns true if succesful in finding the path.
  549. */
  550. private boolean ensurePathIsExpanded(TreePath aPath,
  551. boolean expandLast) {
  552. if(aPath != null) {
  553. // Make sure the last entry isn't a leaf.
  554. if(treeModel.isLeaf(aPath.getLastPathComponent())) {
  555. aPath = aPath.getParentPath();
  556. expandLast = true;
  557. }
  558. if(aPath != null) {
  559. FHTreeStateNode lastNode = getNodeForPath(aPath, false,
  560. true);
  561. if(lastNode != null) {
  562. lastNode.makeVisible();
  563. if(expandLast)
  564. lastNode.expand();
  565. return true;
  566. }
  567. }
  568. }
  569. return false;
  570. }
  571. /**
  572. * Creates and returns an instance of FHTreeStateNode.
  573. */
  574. private FHTreeStateNode createNodeForValue(Object value,int childIndex) {
  575. return new FHTreeStateNode(value, childIndex, -1);
  576. }
  577. /**
  578. * Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate,
  579. * path.length) as long as path is non-null and the length is > 0.
  580. * Otherwise returns null.
  581. */
  582. private FHTreeStateNode getNodeForPath(TreePath path,
  583. boolean onlyIfVisible,
  584. boolean shouldCreate) {
  585. if(path != null) {
  586. FHTreeStateNode node;
  587. node = getMapping(path);
  588. if(node != null) {
  589. if(onlyIfVisible && !node.isVisible())
  590. return null;
  591. return node;
  592. }
  593. if(onlyIfVisible)
  594. return null;
  595. // Check all the parent paths, until a match is found.
  596. Stack paths;
  597. if(tempStacks.size() == 0) {
  598. paths = new Stack();
  599. }
  600. else {
  601. paths = (Stack)tempStacks.pop();
  602. }
  603. try {
  604. paths.push(path);
  605. path = path.getParentPath();
  606. node = null;
  607. while(path != null) {
  608. node = getMapping(path);
  609. if(node != null) {
  610. // Found a match, create entries for all paths in
  611. // paths.
  612. while(node != null && paths.size() > 0) {
  613. path = (TreePath)paths.pop();
  614. node = node.createChildFor(path.
  615. getLastPathComponent());
  616. }
  617. return node;
  618. }
  619. paths.push(path);
  620. path = path.getParentPath();
  621. }
  622. }
  623. finally {
  624. paths.removeAllElements();
  625. tempStacks.push(paths);
  626. }
  627. // If we get here it means they share a different root!
  628. return null;
  629. }
  630. return null;
  631. }
  632. /**
  633. * FHTreeStateNode is used to track what has been expanded.
  634. * FHTreeStateNode differs from VariableHeightTreeState.TreeStateNode
  635. * in that it is highly model intensive. That is almost all queries to a
  636. * FHTreeStateNode result in the TreeModel being queried. And it
  637. * obviously does not support variable sized row heights.
  638. */
  639. private class FHTreeStateNode extends DefaultMutableTreeNode {
  640. /** Is this node expanded? */
  641. protected boolean isExpanded;
  642. /** Index of this node from the model. */
  643. protected int childIndex;
  644. /** Row of the receiver. This is only valid if the row is expanded.
  645. */
  646. protected int row;
  647. /** Path of this node. */
  648. protected TreePath path;
  649. public FHTreeStateNode(Object userObject, int childIndex, int row) {
  650. super(userObject);
  651. this.childIndex = childIndex;
  652. this.row = row;
  653. }
  654. //
  655. // Overriden DefaultMutableTreeNode methods
  656. //
  657. /**
  658. * Messaged when this node is added somewhere, resets the path
  659. * and adds a mapping from path to this node.
  660. */
  661. public void setParent(MutableTreeNode parent) {
  662. super.setParent(parent);
  663. if(parent != null) {
  664. path = ((FHTreeStateNode)parent).getTreePath().
  665. pathByAddingChild(getUserObject());
  666. addMapping(this);
  667. }
  668. }
  669. /**
  670. * Messaged when this node is removed from its parent, this messages
  671. * <code>removedFromMapping</code> to remove all the children.
  672. */
  673. public void remove(int childIndex) {
  674. FHTreeStateNode node = (FHTreeStateNode)getChildAt(childIndex);
  675. node.removeFromMapping();
  676. super.remove(childIndex);
  677. }
  678. /**
  679. * Messaged to set the user object. This resets the path.
  680. */
  681. public void setUserObject(Object o) {
  682. super.setUserObject(o);
  683. if(path != null) {
  684. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  685. if(parent != null)
  686. resetChildrenPaths(parent.getTreePath());
  687. else
  688. resetChildrenPaths(null);
  689. }
  690. }
  691. //
  692. //
  693. /**
  694. * Returns the index of the reciever in the model.
  695. */
  696. public int getChildIndex() {
  697. return childIndex;
  698. }
  699. /**
  700. * Returns the TreePath of the receiver.
  701. */
  702. public TreePath getTreePath() {
  703. return path;
  704. }
  705. /**
  706. * Returns the child for the passed in model index, this will
  707. * return null if the child for <code>index</code> has not yet
  708. * been created (expanded).
  709. */
  710. public FHTreeStateNode getChildAtModelIndex(int index) {
  711. // PENDING: Make this a binary search!
  712. for(int counter = getChildCount() - 1; counter >= 0; counter--)
  713. if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
  714. return (FHTreeStateNode)getChildAt(counter);
  715. return null;
  716. }
  717. /**
  718. * Returns true if this node is visible. This is determined by
  719. * asking all the parents if they are expanded.
  720. */
  721. public boolean isVisible() {
  722. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  723. if(parent == null)
  724. return true;
  725. return (parent.isExpanded() && parent.isVisible());
  726. }
  727. /**
  728. * Returns the row of the receiver.
  729. */
  730. public int getRow() {
  731. return row;
  732. }
  733. /**
  734. * Returns the row of the child with a model index of
  735. * <code>index</code>.
  736. */
  737. public int getRowToModelIndex(int index) {
  738. FHTreeStateNode child;
  739. int lastRow = getRow() + 1;
  740. int retValue = lastRow;
  741. // This too could be a binary search!
  742. for(int counter = 0, maxCounter = getChildCount();
  743. counter < maxCounter; counter++) {
  744. child = (FHTreeStateNode)getChildAt(counter);
  745. if(child.childIndex >= index) {
  746. if(child.childIndex == index)
  747. return child.row;
  748. if(counter == 0)
  749. return getRow() + 1 + index;
  750. return child.row - (child.childIndex - index);
  751. }
  752. }
  753. // YECK!
  754. return getRow() + 1 + getTotalChildCount() -
  755. (treeModel.getChildCount(getUserObject()) -
  756. index);
  757. }
  758. /**
  759. * Returns the number of children in the receiver by descending all
  760. * expanded nodes and messaging them with getTotalChildCount.
  761. */
  762. public int getTotalChildCount() {
  763. if(isExpanded()) {
  764. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  765. int pIndex;
  766. if(parent != null && (pIndex = parent.getIndex(this)) + 1 <
  767. parent.getChildCount()) {
  768. // This node has a created sibling, to calc total
  769. // child count directly from that!
  770. FHTreeStateNode nextSibling = (FHTreeStateNode)parent.
  771. getChildAt(pIndex + 1);
  772. return nextSibling.row - row -
  773. (nextSibling.childIndex - childIndex);
  774. }
  775. else {
  776. int retCount = getModel().getChildCount(getUserObject());
  777. for(int counter = getChildCount() - 1; counter >= 0;
  778. counter--) {
  779. retCount += ((FHTreeStateNode)getChildAt(counter))
  780. .getTotalChildCount();
  781. }
  782. return retCount;
  783. }
  784. }
  785. return 0;
  786. }
  787. /**
  788. * Returns true if this node is expanded.
  789. */
  790. public boolean isExpanded() {
  791. return isExpanded;
  792. }
  793. /**
  794. * The highest visible nodes have a depth of 0.
  795. */
  796. public int getVisibleLevel() {
  797. if (isRootVisible()) {
  798. return getLevel();
  799. } else {
  800. return getLevel() - 1;
  801. }
  802. }
  803. /**
  804. * Recreates the receivers path, and all its childrens paths.
  805. */
  806. protected void resetChildrenPaths(TreePath parentPath) {
  807. removeMapping(this);
  808. if(parentPath == null)
  809. path = new TreePath(getUserObject());
  810. else
  811. path = parentPath.pathByAddingChild(getUserObject());
  812. addMapping(this);
  813. for(int counter = getChildCount() - 1; counter >= 0; counter--)
  814. ((FHTreeStateNode)getChildAt(counter)).
  815. resetChildrenPaths(path);
  816. }
  817. /**
  818. * Removes the receiver, and all its children, from the mapping
  819. * table.
  820. */
  821. protected void removeFromMapping() {
  822. if(path != null) {
  823. removeMapping(this);
  824. for(int counter = getChildCount() - 1; counter >= 0; counter--)
  825. ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
  826. }
  827. }
  828. /**
  829. * Creates a new node to represent <code>userObject</code>.
  830. * This does NOT check to ensure there isn't already a child node
  831. * to manage <code>userObject</code>.
  832. */
  833. protected FHTreeStateNode createChildFor(Object userObject) {
  834. int newChildIndex = treeModel.getIndexOfChild
  835. (getUserObject(), userObject);
  836. if(newChildIndex < 0)
  837. return null;
  838. FHTreeStateNode aNode;
  839. FHTreeStateNode child = createNodeForValue(userObject,
  840. newChildIndex);
  841. int childRow;
  842. if(isVisible()) {
  843. childRow = getRowToModelIndex(newChildIndex);
  844. }
  845. else {
  846. childRow = -1;
  847. }
  848. child.row = childRow;
  849. for(int counter = 0, maxCounter = getChildCount();
  850. counter < maxCounter; counter++) {
  851. aNode = (FHTreeStateNode)getChildAt(counter);
  852. if(aNode.childIndex > newChildIndex) {
  853. insert(child, counter);
  854. return child;
  855. }
  856. }
  857. add(child);
  858. return child;
  859. }
  860. /**
  861. * Adjusts the receiver, and all its children rows by
  862. * <code>amount</code>.
  863. */
  864. protected void adjustRowBy(int amount) {
  865. row += amount;
  866. if(isExpanded) {
  867. for(int counter = getChildCount() - 1; counter >= 0;
  868. counter--)
  869. ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
  870. }
  871. }
  872. /**
  873. * Adjusts this node, its child, and its parent starting at
  874. * an index of <code>index</code> index is the index of the child
  875. * to start adjusting from, which is not necessarily the model
  876. * index.
  877. */
  878. protected void adjustRowBy(int amount, int startIndex) {
  879. // Could check isVisible, but probably isn't worth it.
  880. if(isExpanded) {
  881. // children following startIndex.
  882. for(int counter = getChildCount() - 1; counter >= startIndex;
  883. counter--)
  884. ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
  885. }
  886. // Parent
  887. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  888. if(parent != null) {
  889. parent.adjustRowBy(amount, parent.getIndex(this) + 1);
  890. }
  891. }
  892. /**
  893. * Messaged when the node has expanded. This updates all of
  894. * the receivers children rows, as well as the total row count.
  895. */
  896. protected void didExpand() {
  897. int nextRow = setRowAndChildren(row);
  898. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  899. int childRowCount = nextRow - row - 1;
  900. if(parent != null) {
  901. parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
  902. }
  903. adjustRowCountBy(childRowCount);
  904. }
  905. /**
  906. * Sets the receivers row to <code>nextRow</code> and recursively
  907. * updates all the children of the receivers rows. The index the
  908. * next row is to be placed as is returned.
  909. */
  910. protected int setRowAndChildren(int nextRow) {
  911. row = nextRow;
  912. if(!isExpanded())
  913. return row + 1;
  914. int lastRow = row + 1;
  915. int lastModelIndex = 0;
  916. FHTreeStateNode child;
  917. int maxCounter = getChildCount();
  918. for(int counter = 0; counter < maxCounter; counter++) {
  919. child = (FHTreeStateNode)getChildAt(counter);
  920. lastRow += (child.childIndex - lastModelIndex);
  921. lastModelIndex = child.childIndex + 1;
  922. if(child.isExpanded) {
  923. lastRow = child.setRowAndChildren(lastRow);
  924. }
  925. else {
  926. child.row = lastRow++;
  927. }
  928. }
  929. return lastRow + treeModel.getChildCount(getUserObject()) -
  930. lastModelIndex;
  931. }
  932. /**
  933. * Resets the receivers childrens rows. Starting with the child
  934. * at <code>childIndex</code> (and <code>modelIndex</code>) to
  935. * <code>newRow</code>. This uses <code>setRowAndChildren</code>
  936. * to recursively descend children, and uses
  937. * <code>resetRowSelection</code> to ascend parents.
  938. */
  939. // This can be rather expensive, but is needed for the collapse
  940. // case this is resulting from a remove (although I could fix
  941. // that by having instances of FHTreeStateNode hold a ref to
  942. // the number of children). I prefer this though, making determing
  943. // the row of a particular node fast is very nice!
  944. protected void resetChildrenRowsFrom(int newRow, int childIndex,
  945. int modelIndex) {
  946. int lastRow = newRow;
  947. int lastModelIndex = modelIndex;
  948. FHTreeStateNode node;
  949. int maxCounter = getChildCount();
  950. for(int counter = childIndex; counter < maxCounter; counter++) {
  951. node = (FHTreeStateNode)getChildAt(counter);
  952. lastRow += (node.childIndex - lastModelIndex);
  953. lastModelIndex = node.childIndex + 1;
  954. if(node.isExpanded) {
  955. lastRow = node.setRowAndChildren(lastRow);
  956. }
  957. else {
  958. node.row = lastRow++;
  959. }
  960. }
  961. lastRow += treeModel.getChildCount(getUserObject()) -
  962. lastModelIndex;
  963. node = (FHTreeStateNode)getParent();
  964. if(node != null) {
  965. node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
  966. this.childIndex + 1);
  967. }
  968. else { // This is the root, reset total ROWCOUNT!
  969. rowCount = lastRow;
  970. }
  971. }
  972. /**
  973. * Makes the receiver visible, but invoking
  974. * <code>expandParentAndReceiver</code> on the superclass.
  975. */
  976. protected void makeVisible() {
  977. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  978. if(parent != null)
  979. parent.expandParentAndReceiver();
  980. }
  981. /**
  982. * Invokes <code>expandParentAndReceiver</code> on the parent,
  983. * and expands the receiver.
  984. */
  985. protected void expandParentAndReceiver() {
  986. FHTreeStateNode parent = (FHTreeStateNode)getParent();
  987. if(parent != null)
  988. parent.expandParentAndReceiver();
  989. expand();
  990. }
  991. /**
  992. * Expands the receiver.
  993. */
  994. protected void expand() {
  995. if(!isExpanded) {
  996. boolean visible = isVisible();
  997. isExpanded = true;
  998. if(visible) {
  999. didExpand();
  1000. }
  1001. // Update the selection model.
  1002. if(visible && treeSelectionModel != null) {
  1003. treeSelectionModel.resetRowSelection();
  1004. }
  1005. }
  1006. }
  1007. /**
  1008. * Collapses the receiver. If <code>adjustRows</code> is true,
  1009. * the rows of nodes after the receiver are adjusted.
  1010. */
  1011. protected void collapse(boolean adjustRows) {
  1012. if(isExpanded) {
  1013. TreePath[] selPaths;
  1014. if(treeSelectionModel != null)
  1015. selPaths = treeSelectionModel.getSelectionPaths();
  1016. else
  1017. selPaths = null;
  1018. if(isVisible() && adjustRows) {
  1019. int childCount = getTotalChildCount();
  1020. isExpanded = false;
  1021. adjustRowCountBy(-childCount);
  1022. // We can do this because adjustRowBy won't descend
  1023. // the children.
  1024. adjustRowBy(-childCount, 0);
  1025. }
  1026. else
  1027. isExpanded = false;
  1028. /* update the selection */
  1029. if(selPaths != null) {
  1030. boolean shouldRemove = false;
  1031. TreePath ourPath = getTreePath();
  1032. for(int counter = selPaths.length - 1; counter >= 0;
  1033. counter--) {
  1034. if(selPaths[counter] != null &&
  1035. ourPath.isDescendant(selPaths[counter]) &&
  1036. !ourPath.equals(selPaths[counter]))
  1037. shouldRemove = true;
  1038. else
  1039. selPaths[counter] = null;
  1040. }
  1041. if(shouldRemove)
  1042. treeSelectionModel.removeSelectionPaths(selPaths);
  1043. }
  1044. if(adjustRows && isVisible() && treeSelectionModel != null)
  1045. treeSelectionModel.resetRowSelection();
  1046. }
  1047. }
  1048. /**
  1049. * Adds newChild to this nodes children at the appropriate location.
  1050. * The location is determined from the childIndex of newChild.
  1051. */
  1052. protected void addNode(FHTreeStateNode newChild) {
  1053. boolean added = false;
  1054. int childIndex = newChild.getChildIndex();
  1055. for(int counter = 0, maxCounter = getChildCount();
  1056. counter < maxCounter; counter++) {
  1057. if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
  1058. childIndex) {
  1059. added = true;
  1060. insert(newChild, counter);
  1061. counter = maxCounter;
  1062. }
  1063. }
  1064. if(!added)
  1065. add(newChild);
  1066. }
  1067. /**
  1068. * Removes the child at <code>modelIndex</code>.
  1069. * <code>isChildVisible</code> should be true if the receiver
  1070. * is visible and expanded.
  1071. */
  1072. protected void removeChildAtModelIndex(int modelIndex,
  1073. boolean isChildVisible) {
  1074. FHTreeStateNode childNode = getChildAtModelIndex(modelIndex);
  1075. if(childNode != null) {
  1076. int row = childNode.getRow();
  1077. int index = getIndex(childNode);
  1078. childNode.collapse(false);
  1079. remove(index);
  1080. adjustChildIndexs(index, -1);
  1081. if(isChildVisible) {
  1082. // Adjust the rows.
  1083. resetChildrenRowsFrom(row, index, modelIndex);
  1084. }
  1085. }
  1086. else {
  1087. int maxCounter = getChildCount();
  1088. FHTreeStateNode aChild;
  1089. for(int counter = 0; counter < maxCounter; counter++) {
  1090. aChild = (FHTreeStateNode)getChildAt(counter);
  1091. if(aChild.childIndex >= modelIndex) {
  1092. if(isChildVisible) {
  1093. adjustRowBy(-1, counter);
  1094. adjustRowCountBy(-1);
  1095. }
  1096. // Since matched and children are always sorted by
  1097. // index, no need to continue testing with the
  1098. // above.
  1099. for(; counter < maxCounter; counter++)
  1100. ((FHTreeStateNode)getChildAt(counter)).
  1101. childIndex--;
  1102. return;
  1103. }
  1104. }
  1105. // No children to adjust, but it was a child, so we still need
  1106. // to adjust nodes after this one.
  1107. if(isChildVisible) {
  1108. adjustRowBy(-1, maxCounter);
  1109. adjustRowCountBy(-1);
  1110. }
  1111. }
  1112. }
  1113. /**
  1114. * Adjusts the child indexs of the receivers children by
  1115. * <code>amount</code>, starting at <code>index</code>.
  1116. */
  1117. protected void adjustChildIndexs(int index, int amount) {
  1118. for(int counter = index, maxCounter = getChildCount();
  1119. counter < maxCounter; counter++) {
  1120. ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
  1121. }
  1122. }
  1123. /**
  1124. * Messaged when a child has been inserted at index. For all the
  1125. * children that have a childIndex >= index their index is incremented
  1126. * by one.
  1127. */
  1128. protected void childInsertedAtModelIndex(int index,
  1129. boolean isExpandedAndVisible) {
  1130. FHTreeStateNode aChild;
  1131. int maxCounter = getChildCount();
  1132. for(int counter = 0; counter < maxCounter; counter++) {
  1133. aChild = (FHTreeStateNode)getChildAt(counter);
  1134. if(aChild.childIndex >= index) {
  1135. if(isExpandedAndVisible) {
  1136. adjustRowBy(1, counter);
  1137. adjustRowCountBy(1);
  1138. }
  1139. /* Since matched and children are always sorted by
  1140. index, no need to continue testing with the above. */
  1141. for(; counter < maxCounter; counter++)
  1142. ((FHTreeStateNode)getChildAt(counter)).childIndex++;
  1143. return;
  1144. }
  1145. }
  1146. // No children to adjust, but it was a child, so we still need
  1147. // to adjust nodes after this one.
  1148. if(isExpandedAndVisible) {
  1149. adjustRowBy(1, maxCounter);
  1150. adjustRowCountBy(1);
  1151. }
  1152. }
  1153. /**
  1154. * Returns true if there is a row for <code>row</code>.
  1155. * <code>nextRow</code> gives the bounds of the receiver.
  1156. * Information about the found row is returned in <code>info</code>.
  1157. * This should be invoked on root with <code>nextRow</code> set
  1158. * to <code>getRowCount</code>().
  1159. */
  1160. protected boolean getPathForRow(int row, int nextRow,
  1161. SearchInfo info) {
  1162. if(this.row == row) {
  1163. info.node = this;
  1164. info.isNodeParentNode = false;
  1165. info.childIndex = childIndex;
  1166. return true;
  1167. }
  1168. FHTreeStateNode child;
  1169. FHTreeStateNode lastChild = null;
  1170. for(int counter = 0, maxCounter = getChildCount();
  1171. counter < maxCounter; counter++) {
  1172. child = (FHTreeStateNode)getChildAt(counter);
  1173. if(child.row > row) {
  1174. if(counter == 0) {
  1175. // No node exists for it, and is first.
  1176. info.node = this;
  1177. info.isNodeParentNode = true;
  1178. info.childIndex = row - this.row - 1;
  1179. return true;
  1180. }
  1181. else {
  1182. // May have been in last childs bounds.
  1183. int lastChildEndRow = 1 + child.row -
  1184. (child.childIndex - lastChild.childIndex);
  1185. if(row < lastChildEndRow) {
  1186. return lastChild.getPathForRow(row,
  1187. lastChildEndRow, info);
  1188. }
  1189. // Between last child and child, but not in last child
  1190. info.node = this;
  1191. info.isNodeParentNode = true;
  1192. info.childIndex = row - lastChildEndRow +
  1193. lastChild.childIndex + 1;
  1194. return true;
  1195. }
  1196. }
  1197. lastChild = child;
  1198. }
  1199. int childCount = treeModel.getChildCount(getUserObject());
  1200. // Not in children, but we should have it, offset from
  1201. // nextRow.
  1202. if(lastChild != null) {
  1203. int lastChildEndRow = nextRow -
  1204. (childCount - lastChild.childIndex) + 1;
  1205. if(row < lastChildEndRow) {
  1206. return lastChild.getPathForRow(row, lastChildEndRow, info);
  1207. }
  1208. // Between last child and child, but not in last child
  1209. info.node = this;
  1210. info.isNodeParentNode = true;
  1211. info.childIndex = row - lastChildEndRow +
  1212. lastChild.childIndex + 1;
  1213. return true;
  1214. }
  1215. else {
  1216. // No children.
  1217. int retChildIndex = row - this.row - 1;
  1218. if(retChildIndex >= childCount) {
  1219. return false;
  1220. }
  1221. info.node = this;
  1222. info.isNodeParentNode = true;
  1223. info.childIndex = retChildIndex;
  1224. return true;
  1225. }
  1226. }
  1227. /**
  1228. * Asks all the children of the receiver for their totalChildCount
  1229. * and returns this value (plus stopIndex).
  1230. */
  1231. protected int getCountTo(int stopIndex) {
  1232. FHTreeStateNode aChild;
  1233. int retCount = stopIndex + 1;
  1234. for(int counter = 0, maxCounter = getChildCount();
  1235. counter < maxCounter; counter++) {
  1236. aChild = (FHTreeStateNode)getChildAt(counter);
  1237. if(aChild.childIndex >= stopIndex)
  1238. counter = maxCounter;
  1239. else
  1240. retCount += aChild.getTotalChildCount();
  1241. }
  1242. if(parent != null)
  1243. return retCount + ((FHTreeStateNode)getParent())
  1244. .getCountTo(childIndex);
  1245. if(!isRootVisible())
  1246. return (retCount - 1);
  1247. return retCount;
  1248. }
  1249. /**
  1250. * Returns the number of children that are expanded to
  1251. * <code>stopIndex</code>. This does not include the number
  1252. * of children that the child at <code>stopIndex</code> might
  1253. * have.
  1254. */
  1255. protected int getNumExpandedChildrenTo(int stopIndex) {
  1256. FHTreeStateNode aChild;
  1257. int retCount = stopIndex;
  1258. for(int counter = 0, maxCounter = getChildCount();
  1259. counter < maxCounter; counter++) {
  1260. aChild = (FHTreeStateNode)getChildAt(counter);
  1261. if(aChild.childIndex >= stopIndex)
  1262. return retCount;
  1263. else {
  1264. retCount += aChild.getTotalChildCount();
  1265. }
  1266. }
  1267. return retCount;
  1268. }
  1269. /**
  1270. * Messaged when this node either expands or collapses.
  1271. */
  1272. protected void didAdjustTree() {
  1273. }
  1274. } // FixedHeightLayoutCache.FHTreeStateNode
  1275. /**
  1276. * Used as a placeholder when getting the path in FHTreeStateNodes.
  1277. */
  1278. private class SearchInfo {
  1279. protected FHTreeStateNode node;
  1280. protected boolean isNodeParentNode;
  1281. protected int childIndex;
  1282. protected TreePath getPath() {
  1283. if(node == null)
  1284. return null;
  1285. if(isNodeParentNode)
  1286. return node.getTreePath().pathByAddingChild(treeModel.getChild
  1287. (node.getUserObject(),
  1288. childIndex));
  1289. return node.path;
  1290. }
  1291. } // FixedHeightLayoutCache.SearchInfo
  1292. /**
  1293. * An enumerator to iterate through visible nodes.
  1294. */
  1295. // This is very similiar to
  1296. // VariableHeightTreeState.VisibleTreeStateNodeEnumeration
  1297. private class VisibleFHTreeStateNodeEnumeration implements Enumeration {
  1298. /** Parent thats children are being enumerated. */
  1299. protected FHTreeStateNode parent;
  1300. /** Index of next child. An index of -1 signifies parent should be
  1301. * visibled next. */
  1302. protected int nextIndex;
  1303. /** Number of children in parent. */
  1304. protected int childCount;
  1305. protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode node) {
  1306. this(node, -1);
  1307. }
  1308. protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode parent,
  1309. int startIndex) {
  1310. this.parent = parent;
  1311. this.nextIndex = startIndex;
  1312. this.childCount = treeModel.getChildCount(this.parent.
  1313. getUserObject());
  1314. }
  1315. /**
  1316. * @return true if more visible nodes.
  1317. */
  1318. public boolean hasMoreElements() {
  1319. return (parent != null);
  1320. }
  1321. /**
  1322. * @return next visible TreePath.
  1323. */
  1324. public Object nextElement() {
  1325. if(!hasMoreElements())
  1326. throw new NoSuchElementException("No more visible paths");
  1327. Object retObject;
  1328. if(nextIndex == -1)
  1329. retObject = parent.getTreePath();
  1330. else {
  1331. FHTreeStateNode node = parent.getChildAtModelIndex(nextIndex);
  1332. if(node == null)
  1333. retObject = parent.getTreePath().pathByAddingChild
  1334. (treeModel.getChild(parent.getUserObject(),
  1335. nextIndex));
  1336. else
  1337. retObject = node.getTreePath();
  1338. }
  1339. updateNextObject();
  1340. return retObject;
  1341. }
  1342. /**
  1343. * Determines the next object by invoking <code>updateNextIndex</code>
  1344. * and if not succesful <code>findNextValidParent</code>.
  1345. */
  1346. protected void updateNextObject() {
  1347. if(!updateNextIndex()) {
  1348. findNextValidParent();
  1349. }
  1350. }
  1351. /**
  1352. * Finds the next valid parent, this should be called when nextIndex
  1353. * is beyond the number of children of the current parent.
  1354. */
  1355. protected boolean findNextValidParent() {
  1356. if(parent == root) {
  1357. // mark as invalid!
  1358. parent = null;
  1359. return false;
  1360. }
  1361. while(parent != null) {
  1362. FHTreeStateNode newParent = (FHTreeStateNode)parent.
  1363. getParent();
  1364. if(newParent != null) {
  1365. nextIndex = parent.childIndex;
  1366. parent = newParent;
  1367. childCount = treeModel.getChildCount
  1368. (parent.getUserObject());
  1369. if(updateNextIndex())
  1370. return true;
  1371. }
  1372. else
  1373. parent = null;
  1374. }
  1375. return false;
  1376. }
  1377. /**
  1378. * Updates <code>nextIndex</code> returning false if it is beyond
  1379. * the number of children of parent.
  1380. */
  1381. protected boolean updateNextIndex() {
  1382. // nextIndex == -1 identifies receiver, make sure is expanded
  1383. // before descend.
  1384. if(nextIndex == -1 && !parent.isExpanded()) {
  1385. return false;
  1386. }
  1387. // Check that it can have kids
  1388. if(childCount == 0) {
  1389. return false;
  1390. }
  1391. // Make sure next index not beyond child count.
  1392. else if(++nextIndex >= childCount) {
  1393. return false;
  1394. }
  1395. FHTreeStateNode child = parent.getChildAtModelIndex(nextIndex);
  1396. if(child != null && child.isExpanded()) {
  1397. parent = child;
  1398. nextIndex = -1;
  1399. childCount = treeModel.getChildCount(child.getUserObject());
  1400. }
  1401. return true;
  1402. }
  1403. } // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration
  1404. }