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