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