- /*
- * @(#)FixedHeightLayoutCache.java 1.13 00/02/02
- *
- * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
-
- package javax.swing.tree;
-
- import javax.swing.event.TreeModelEvent;
- import java.awt.Dimension;
- import java.awt.Rectangle;
- import java.util.Enumeration;
- import java.util.Hashtable;
- import java.util.NoSuchElementException;
- import java.util.Stack;
-
- /**
- * NOTE: This will become more open in a future release.
- * <p>
- * <strong>Warning:</strong>
- * Serialized objects of this class will not be compatible with
- * future Swing releases. The current serialization support is appropriate
- * for short term storage or RMI between applications running the same
- * version of Swing. A future release of Swing will provide support for
- * long term persistence.
- *
- * @version 1.13 02/02/00
- * @author Scott Violet
- */
-
- public class FixedHeightLayoutCache extends AbstractLayoutCache {
- /** Root node. */
- private FHTreeStateNode root;
-
- /** Number of rows currently visible. */
- private int rowCount;
-
- /**
- * Used in getting sizes for nodes to avoid creating a new Rectangle
- * every time a size is needed.
- */
- private Rectangle boundsBuffer;
-
- /**
- * Maps from TreePath to a FHTreeStateNode.
- */
- private Hashtable treePathMapping;
-
- /**
- * Used for getting path/row information.
- */
- private SearchInfo info;
-
- private Stack tempStacks;
-
-
- public FixedHeightLayoutCache() {
- super();
- tempStacks = new Stack();
- boundsBuffer = new Rectangle();
- treePathMapping = new Hashtable();
- info = new SearchInfo();
- setRowHeight(1);
- }
-
- /**
- * Sets the TreeModel that will provide the data.
- *
- * @param newModel the TreeModel that is to provide the data
- */
- public void setModel(TreeModel newModel) {
- super.setModel(newModel);
- rebuild();
- }
-
- /**
- * Determines whether or not the root node from
- * the TreeModel is visible.
- *
- * @param rootVisible true if the root node of the tree is to be displayed
- * @see #rootVisible
- */
- public void setRootVisible(boolean rootVisible) {
- if(isRootVisible() != rootVisible) {
- super.setRootVisible(rootVisible);
- if(root != null) {
- if(rootVisible) {
- rowCount++;
- root.adjustRowBy(1);
- }
- else {
- rowCount--;
- root.adjustRowBy(-1);
- }
- visibleNodesChanged();
- }
- }
- }
-
- /**
- * Sets the height of each cell. If rowHeight is less than or equal to
- * 0 this will throw an IllegalArgumentException.
- *
- * @param rowHeight the height of each cell, in pixels
- */
- public void setRowHeight(int rowHeight) {
- if(rowHeight <= 0)
- throw new IllegalArgumentException("FixedHeightLayoutCache only supports row heights greater than 0");
- if(getRowHeight() != rowHeight) {
- super.setRowHeight(rowHeight);
- visibleNodesChanged();
- }
- }
-
- /**
- * Returns the number of visible rows.
- */
- public int getRowCount() {
- return rowCount;
- }
-
- /**
- * Does nothing, FixedHeightLayoutCache doesn't cache width, and that
- * is all that could change.
- */
- public void invalidatePathBounds(TreePath path) {
- }
-
-
- /**
- * Informs the TreeState that it needs to recalculate all the sizes
- * it is referencing.
- */
- public void invalidateSizes() {
- // Nothing to do here, rowHeight still same, which is all
- // this is interested in, visible region may have changed though.
- visibleNodesChanged();
- }
-
- /**
- * Returns true if the value identified by row is currently expanded.
- */
- public boolean isExpanded(TreePath path) {
- if(path != null) {
- FHTreeStateNode lastNode = getNodeForPath(path, true, false);
-
- return (lastNode != null && lastNode.isExpanded());
- }
- return false;
- }
-
- /**
- * Returns a rectangle giving the bounds needed to draw path.
- *
- * @param path a TreePath specifying a node
- * @param placeIn a Rectangle object giving the available space
- * @return a Rectangle object specifying the space to be used
- */
- public Rectangle getBounds(TreePath path, Rectangle placeIn) {
- if(path == null)
- return null;
-
- FHTreeStateNode node = getNodeForPath(path, true, false);
-
- if(node != null)
- return getBounds(node, -1, placeIn);
-
- // node hasn't been created yet.
- TreePath parentPath = path.getParentPath();
-
- node = getNodeForPath(parentPath, true, false);
- if(node != null) {
- int childIndex = treeModel.getIndexOfChild
- (parentPath.getLastPathComponent(),
- path.getLastPathComponent());
-
- if(childIndex != -1)
- return getBounds(node, childIndex, placeIn);
- }
- return null;
- }
-
- /**
- * Returns the path for passed in row. If row is not visible
- * null is returned.
- */
- public TreePath getPathForRow(int row) {
- if(row >= 0 && row < getRowCount()) {
- if(root.getPathForRow(row, getRowCount(), info)) {
- return info.getPath();
- }
- }
- return null;
- }
-
- /**
- * Returns the row that the last item identified in path is visible
- * at. Will return -1 if any of the elements in path are not
- * currently visible.
- */
- public int getRowForPath(TreePath path) {
- if(path == null || root == null)
- return -1;
-
- FHTreeStateNode node = getNodeForPath(path, true, false);
-
- if(node != null)
- return node.getRow();
-
- TreePath parentPath = path.getParentPath();
-
- node = getNodeForPath(parentPath, true, false);
- if(node != null && node.isExpanded()) {
- return node.getRowToModelIndex(treeModel.getIndexOfChild
- (parentPath.getLastPathComponent(),
- path.getLastPathComponent()));
- }
- return -1;
- }
-
- /**
- * Returns the path to the node that is closest to x,y. If
- * there is nothing currently visible this will return null, otherwise
- * it'll always return a valid path. If you need to test if the
- * returned object is exactly at x, y you should get the bounds for
- * the returned path and test x, y against that.
- */
- public TreePath getPathClosestTo(int x, int y) {
- if(getRowCount() == 0)
- return null;
-
- int row = getRowContainingYLocation(y);
-
- return getPathForRow(row);
- }
-
- /**
- * Returns the number of visible children for row.
- */
- public int getVisibleChildCount(TreePath path) {
- FHTreeStateNode node = getNodeForPath(path, true, false);
-
- if(node == null)
- return 0;
- return node.getTotalChildCount();
- }
-
- /**
- * Returns an Enumerator that increments over the visible paths
- * starting at the passed in location. The ordering of the enumeration
- * is based on how the paths are displayed.
- */
- public Enumeration getVisiblePathsFrom(TreePath path) {
- if(path == null)
- return null;
-
- FHTreeStateNode node = getNodeForPath(path, true, false);
-
- if(node != null) {
- return new VisibleFHTreeStateNodeEnumeration(node);
- }
- TreePath parentPath = path.getParentPath();
-
- node = getNodeForPath(parentPath, true, false);
- if(node != null && node.isExpanded()) {
- return new VisibleFHTreeStateNodeEnumeration(node,
- treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
- path.getLastPathComponent()));
- }
- return null;
- }
-
- /**
- * Marks the path <code>path</code> expanded state to
- * <code>isExpanded</code>.
- */
- public void setExpandedState(TreePath path, boolean isExpanded) {
- if(isExpanded)
- ensurePathIsExpanded(path, true);
- else if(path != null) {
- TreePath parentPath = path.getParentPath();
-
- // YECK! Make the parent expanded.
- if(parentPath != null) {
- FHTreeStateNode parentNode = getNodeForPath(parentPath,
- false, true);
- if(parentNode != null)
- parentNode.makeVisible();
- }
- // And collapse the child.
- FHTreeStateNode childNode = getNodeForPath(path, true,
- false);
-
- if(childNode != null)
- childNode.collapse(true);
- }
- }
-
- /**
- * Returns true if the path is expanded, and visible.
- */
- public boolean getExpandedState(TreePath path) {
- FHTreeStateNode node = getNodeForPath(path, true, false);
-
- return (node != null) ? (node.isVisible() && node.isExpanded()) :
- false;
- }
-
- //
- // TreeModelListener methods
- //
-
- /**
- * <p>Invoked after a node (or a set of siblings) has changed in some
- * way. The node(s) have not changed locations in the tree or
- * altered their children arrays, but other attributes have
- * changed and may affect presentation. Example: the name of a
- * file has changed, but it is in the same location in the file
- * system.</p>
- *
- * <p>e.path() returns the path the parent of the changed node(s).</p>
- *
- * <p>e.childIndices() returns the index(es) of the changed node(s).</p>
- */
- public void treeNodesChanged(TreeModelEvent e) {
- if(e != null) {
- int changedIndexs[];
- FHTreeStateNode changedParent = getNodeForPath
- (e.getTreePath(), false, false);
- int maxCounter;
-
- changedIndexs = e.getChildIndices();
- /* Only need to update the children if the node has been
- expanded once. */
- // PENDING(scott): make sure childIndexs is sorted!
- if (changedParent != null) {
- if (changedIndexs != null &&
- (maxCounter = changedIndexs.length) > 0) {
- Object parentValue = changedParent.getUserObject();
-
- for(int counter = 0; counter < maxCounter; counter++) {
- FHTreeStateNode child = changedParent.
- getChildAtModelIndex(changedIndexs[counter]);
-
- if(child != null) {
- child.setUserObject(treeModel.getChild(parentValue,
- changedIndexs[counter]));
- }
- }
- if(changedParent.isVisible() && changedParent.isExpanded())
- visibleNodesChanged();
- }
- // Null for root indicates it changed.
- else if (changedParent == root && changedParent.isVisible() &&
- changedParent.isExpanded()) {
- visibleNodesChanged();
- }
- }
- }
- }
-
- /**
- * <p>Invoked after nodes have been inserted into the tree.</p>
- *
- * <p>e.path() returns the parent of the new nodes
- * <p>e.childIndices() returns the indices of the new nodes in
- * ascending order.
- */
- public void treeNodesInserted(TreeModelEvent e) {
- if(e != null) {
- int changedIndexs[];
- FHTreeStateNode changedParent = getNodeForPath
- (e.getTreePath(), false, false);
- int maxCounter;
-
- changedIndexs = e.getChildIndices();
- /* Only need to update the children if the node has been
- expanded once. */
- // PENDING(scott): make sure childIndexs is sorted!
- if(changedParent != null && changedIndexs != null &&
- (maxCounter = changedIndexs.length) > 0) {
- boolean isVisible =
- (changedParent.isVisible() &&
- changedParent.isExpanded());
-
- for(int counter = 0; counter < maxCounter; counter++) {
- changedParent.childInsertedAtModelIndex
- (changedIndexs[counter], isVisible);
- }
- if(isVisible && treeSelectionModel != null)
- treeSelectionModel.resetRowSelection();
- if(changedParent.isVisible())
- this.visibleNodesChanged();
- }
- }
- }
-
- /**
- * <p>Invoked after nodes have been removed from the tree. Note that
- * if a subtree is removed from the tree, this method may only be
- * invoked once for the root of the removed subtree, not once for
- * each individual set of siblings removed.</p>
- *
- * <p>e.path() returns the former parent of the deleted nodes.</p>
- *
- * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p>
- */
- public void treeNodesRemoved(TreeModelEvent e) {
- if(e != null) {
- int changedIndexs[];
- int maxCounter;
- TreePath parentPath = e.getTreePath();
- FHTreeStateNode changedParentNode = getNodeForPath
- (parentPath, false, false);
-
- changedIndexs = e.getChildIndices();
- // PENDING(scott): make sure that changedIndexs are sorted in
- // ascending order.
- if(changedParentNode != null && changedIndexs != null &&
- (maxCounter = changedIndexs.length) > 0) {
- Object[] children = e.getChildren();
- boolean isVisible =
- (changedParentNode.isVisible() &&
- changedParentNode.isExpanded());
-
- for(int counter = maxCounter - 1; counter >= 0; counter--) {
- changedParentNode.removeChildAtModelIndex
- (changedIndexs[counter], isVisible);
- }
- if(isVisible) {
- if(treeSelectionModel != null)
- treeSelectionModel.resetRowSelection();
- visibleNodesChanged();
- }
- else if(changedParentNode.isVisible())
- visibleNodesChanged();
- }
- }
- }
-
- /**
- * <p>Invoked after the tree has drastically changed structure from a
- * given node down. If the path returned by e.getPath() is of length
- * one and the first element does not identify the current root node
- * the first element should become the new root of the tree.<p>
- *
- * <p>e.path() holds the path to the node.</p>
- * <p>e.childIndices() returns null.</p>
- */
- public void treeStructureChanged(TreeModelEvent e) {
- if(e != null) {
- TreePath changedPath = e.getTreePath();
-
- if(changedPath != null && changedPath.getPathCount() > 0) {
- if(changedPath.getPathCount() == 1)
- rebuild();
- else {
- FHTreeStateNode changedNode = getNodeForPath
- (changedPath, false, false);
-
- if(changedNode != null) {
- boolean wasExpanded, wasVisible;
- int newIndex;
- FHTreeStateNode parent = (FHTreeStateNode)
- changedNode.getParent();
- int row = changedNode.getRow();
-
-
- wasExpanded = changedNode.isExpanded();
- wasVisible = changedNode.isVisible();
- if(wasVisible && wasExpanded) {
- changedNode.collapse(true);
- changedNode.removeFromParent();
- changedNode = getNodeForPath(changedPath, false,
- true);
- changedNode.expand();
- }
- else
- changedNode.removeFromParent();
- if(treeSelectionModel != null && wasVisible &&
- wasExpanded)
- treeSelectionModel.resetRowSelection();
- if(wasVisible)
- this.visibleNodesChanged();
- }
- }
- }
- }
- }
-
-
- //
- // Local methods
- //
-
- private void visibleNodesChanged() {
- }
-
- /**
- * Returns the bounds for the given node. If <code>childIndex</code>
- * is -1, the bounds of <code>parent</code> are returned, otherwise
- * the bounds of the node at <code>childIndex</code> are returned.
- */
- private Rectangle getBounds(FHTreeStateNode parent, int childIndex,
- Rectangle placeIn) {
- boolean expanded;
- int level;
- int row;
- Object value;
-
- if(childIndex == -1) {
- // Getting bounds for parent
- row = parent.getRow();
- value = parent.getUserObject();
- expanded = parent.isExpanded();
- level = parent.getLevel();
- }
- else {
- row = parent.getRowToModelIndex(childIndex);
- value = treeModel.getChild(parent.getUserObject(), childIndex);
- expanded = false;
- level = parent.getLevel() + 1;
- }
-
- Rectangle bounds = getNodeDimensions(value, row, level,
- expanded, boundsBuffer);
- // No node dimensions, bail.
- if(bounds == null)
- return null;
-
- if(placeIn == null)
- placeIn = new Rectangle();
-
- placeIn.x = bounds.x;
- placeIn.height = getRowHeight();
- placeIn.y = row * placeIn.height;
- placeIn.width = bounds.width;
- return placeIn;
- }
-
- /**
- * Adjust the large row count of the AbstractTreeUI the receiver was
- * created with.
- */
- private void adjustRowCountBy(int changeAmount) {
- rowCount += changeAmount;
- }
-
- /**
- * Adds a mapping for node.
- */
- private void addMapping(FHTreeStateNode node) {
- treePathMapping.put(node.getTreePath(), node);
- }
-
- /**
- * Removes the mapping for a previously added node.
- */
- private void removeMapping(FHTreeStateNode node) {
- treePathMapping.remove(node.getTreePath());
- }
-
- /**
- * Returns the node previously added for <code>path</code>. This may
- * return null, if you to create a node use getNodeForPath.
- */
- private FHTreeStateNode getMapping(TreePath path) {
- return (FHTreeStateNode)treePathMapping.get(path);
- }
-
- /**
- * Sent to completely rebuild the visible tree. All nodes are collapsed.
- */
- private void rebuild() {
- treePathMapping.clear();
- if(treeModel != null) {
- Object rootUO = treeModel.getRoot();
-
- root = createNodeForValue(rootUO, 0);
- root.path = new TreePath(rootUO);
- addMapping(root);
- if(isRootVisible()) {
- rowCount = 1;
- root.row = 0;
- }
- else {
- rowCount = 0;
- root.row = -1;
- }
- root.expand();
- }
- else {
- root = null;
- rowCount = 0;
- }
- /* Clear out the selection model, might not always be the right
- thing to do, but the tree is being rebuilt, soooo.... */
- if(treeSelectionModel != null) {
- treeSelectionModel.clearSelection();
- }
- this.visibleNodesChanged();
- }
-
- /**
- * Returns the index of the row containing location. If there
- * are no rows, -1 is returned. If location is beyond the last
- * row index, the last row index is returned.
- */
- private int getRowContainingYLocation(int location) {
- if(getRowCount() == 0)
- return -1;
- return Math.max(0, Math.min(getRowCount() - 1,
- location / getRowHeight()));
- }
-
- /**
- * Ensures that all the path components in path are expanded, accept
- * for the last component which will only be expanded if expandLast
- * is true.
- * Returns true if succesful in finding the path.
- */
- private boolean ensurePathIsExpanded(TreePath aPath,
- boolean expandLast) {
- if(aPath != null) {
- // Make sure the last entry isn't a leaf.
- if(treeModel.isLeaf(aPath.getLastPathComponent())) {
- aPath = aPath.getParentPath();
- expandLast = true;
- }
- if(aPath != null) {
- FHTreeStateNode lastNode = getNodeForPath(aPath, false,
- true);
-
- if(lastNode != null) {
- lastNode.makeVisible();
- if(expandLast)
- lastNode.expand();
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Creates and returns an instance of FHTreeStateNode.
- */
- private FHTreeStateNode createNodeForValue(Object value,int childIndex) {
- return new FHTreeStateNode(value, childIndex, -1);
- }
-
- /**
- * Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate,
- * path.length) as long as path is non-null and the length is > 0.
- * Otherwise returns null.
- */
- private FHTreeStateNode getNodeForPath(TreePath path,
- boolean onlyIfVisible,
- boolean shouldCreate) {
- if(path != null) {
- FHTreeStateNode node;
-
- node = getMapping(path);
- if(node != null) {
- if(onlyIfVisible && !node.isVisible())
- return null;
- return node;
- }
- if(onlyIfVisible)
- return null;
-
- // Check all the parent paths, until a match is found.
- Stack paths;
-
- if(tempStacks.size() == 0) {
- paths = new Stack();
- }
- else {
- paths = (Stack)tempStacks.pop();
- }
-
- try {
- paths.push(path);
- path = path.getParentPath();
- node = null;
- while(path != null) {
- node = getMapping(path);
- if(node != null) {
- // Found a match, create entries for all paths in
- // paths.
- while(node != null && paths.size() > 0) {
- path = (TreePath)paths.pop();
- node = node.createChildFor(path.
- getLastPathComponent());
- }
- return node;
- }
- paths.push(path);
- path = path.getParentPath();
- }
- }
- finally {
- paths.removeAllElements();
- tempStacks.push(paths);
- }
- // If we get here it means they share a different root!
- return null;
- }
- return null;
- }
-
- /**
- * FHTreeStateNode is used to track what has been expanded.
- * FHTreeStateNode differs from VariableHeightTreeState.TreeStateNode
- * in that it is highly model intensive. That is almost all queries to a
- * FHTreeStateNode result in the TreeModel being queried. And it
- * obviously does not support variable sized row heights.
- */
- private class FHTreeStateNode extends DefaultMutableTreeNode {
- /** Is this node expanded? */
- protected boolean isExpanded;
-
- /** Index of this node from the model. */
- protected int childIndex;
-
- /** Row of the receiver. This is only valid if the row is expanded.
- */
- protected int row;
-
- /** Path of this node. */
- protected TreePath path;
-
-
- public FHTreeStateNode(Object userObject, int childIndex, int row) {
- super(userObject);
- this.childIndex = childIndex;
- this.row = row;
- }
-
- //
- // Overriden DefaultMutableTreeNode methods
- //
-
- /**
- * Messaged when this node is added somewhere, resets the path
- * and adds a mapping from path to this node.
- */
- public void setParent(MutableTreeNode parent) {
- super.setParent(parent);
- if(parent != null) {
- path = ((FHTreeStateNode)parent).getTreePath().
- pathByAddingChild(getUserObject());
- addMapping(this);
- }
- }
-
- /**
- * Messaged when this node is removed from its parent, this messages
- * <code>removedFromMapping</code> to remove all the children.
- */
- public void remove(int childIndex) {
- FHTreeStateNode node = (FHTreeStateNode)getChildAt(childIndex);
-
- node.removeFromMapping();
- super.remove(childIndex);
- }
-
- /**
- * Messaged to set the user object. This resets the path.
- */
- public void setUserObject(Object o) {
- super.setUserObject(o);
- if(path != null) {
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
-
- if(parent != null)
- resetChildrenPaths(parent.getTreePath());
- else
- resetChildrenPaths(null);
- }
- }
-
- //
- //
-
- /**
- * Returns the index of the reciever in the model.
- */
- public int getChildIndex() {
- return childIndex;
- }
-
- /**
- * Returns the TreePath of the receiver.
- */
- public TreePath getTreePath() {
- return path;
- }
-
- /**
- * Returns the child for the passed in model index, this will
- * return null if the child for <code>index</code> has not yet
- * been created (expanded).
- */
- public FHTreeStateNode getChildAtModelIndex(int index) {
- // PENDING: Make this a binary search!
- for(int counter = getChildCount() - 1; counter >= 0; counter--)
- if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
- return (FHTreeStateNode)getChildAt(counter);
- return null;
- }
-
- /**
- * Returns true if this node is visible. This is determined by
- * asking all the parents if they are expanded.
- */
- public boolean isVisible() {
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
-
- if(parent == null)
- return true;
- return (parent.isExpanded() && parent.isVisible());
- }
-
- /**
- * Returns the row of the receiver.
- */
- public int getRow() {
- return row;
- }
-
- /**
- * Returns the row of the child with a model index of
- * <code>index</code>.
- */
- public int getRowToModelIndex(int index) {
- FHTreeStateNode child;
- int lastRow = getRow() + 1;
- int retValue = lastRow;
-
- // This too could be a binary search!
- for(int counter = 0, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- child = (FHTreeStateNode)getChildAt(counter);
- if(child.childIndex >= index) {
- if(child.childIndex == index)
- return child.row;
- if(counter == 0)
- return getRow() + 1 + index;
- return child.row - (child.childIndex - index);
- }
- }
- // YECK!
- return getRow() + 1 + getTotalChildCount() -
- (treeModel.getChildCount(getUserObject()) -
- index);
- }
-
- /**
- * Returns the number of children in the receiver by descending all
- * expanded nodes and messaging them with getTotalChildCount.
- */
- public int getTotalChildCount() {
- if(isExpanded()) {
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
- int pIndex;
-
- if(parent != null && (pIndex = parent.getIndex(this)) + 1 <
- parent.getChildCount()) {
- // This node has a created sibling, to calc total
- // child count directly from that!
- FHTreeStateNode nextSibling = (FHTreeStateNode)parent.
- getChildAt(pIndex + 1);
-
- return nextSibling.row - row -
- (nextSibling.childIndex - childIndex);
- }
- else {
- int retCount = getModel().getChildCount(getUserObject());
-
- for(int counter = getChildCount() - 1; counter >= 0;
- counter--) {
- retCount += ((FHTreeStateNode)getChildAt(counter))
- .getTotalChildCount();
- }
- return retCount;
- }
- }
- return 0;
- }
-
- /**
- * Returns true if this node is expanded.
- */
- public boolean isExpanded() {
- return isExpanded;
- }
-
- /**
- * The highest visible nodes have a depth of 0.
- */
- public int getVisibleLevel() {
- if (isRootVisible()) {
- return getLevel();
- } else {
- return getLevel() - 1;
- }
- }
-
- /**
- * Recreates the receivers path, and all its childrens paths.
- */
- protected void resetChildrenPaths(TreePath parentPath) {
- removeMapping(this);
- if(parentPath == null)
- path = new TreePath(getUserObject());
- else
- path = parentPath.pathByAddingChild(getUserObject());
- addMapping(this);
- for(int counter = getChildCount() - 1; counter >= 0; counter--)
- ((FHTreeStateNode)getChildAt(counter)).
- resetChildrenPaths(path);
- }
-
- /**
- * Removes the receiver, and all its children, from the mapping
- * table.
- */
- protected void removeFromMapping() {
- if(path != null) {
- removeMapping(this);
- for(int counter = getChildCount() - 1; counter >= 0; counter--)
- ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
- }
- }
-
- /**
- * Creates a new node to represent <code>userObject</code>.
- * This does NOT check to ensure there isn't already a child node
- * to manage <code>userObject</code>.
- */
- protected FHTreeStateNode createChildFor(Object userObject) {
- int newChildIndex = treeModel.getIndexOfChild
- (getUserObject(), userObject);
-
- if(newChildIndex < 0)
- return null;
-
- FHTreeStateNode aNode;
- FHTreeStateNode child = createNodeForValue(userObject,
- newChildIndex);
- int childRow;
-
- if(isVisible()) {
- childRow = getRowToModelIndex(newChildIndex);
- }
- else {
- childRow = -1;
- }
- child.row = childRow;
- for(int counter = 0, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- aNode = (FHTreeStateNode)getChildAt(counter);
- if(aNode.childIndex > newChildIndex) {
- insert(child, counter);
- return child;
- }
- }
- add(child);
- return child;
- }
-
- /**
- * Adjusts the receiver, and all its children rows by
- * <code>amount</code>.
- */
- protected void adjustRowBy(int amount) {
- row += amount;
- if(isExpanded) {
- for(int counter = getChildCount() - 1; counter >= 0;
- counter--)
- ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
- }
- }
-
- /**
- * Adjusts this node, its child, and its parent starting at
- * an index of <code>index</code> index is the index of the child
- * to start adjusting from, which is not necessarily the model
- * index.
- */
- protected void adjustRowBy(int amount, int startIndex) {
- // Could check isVisible, but probably isn't worth it.
- if(isExpanded) {
- // children following startIndex.
- for(int counter = getChildCount() - 1; counter >= startIndex;
- counter--)
- ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
- }
- // Parent
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
-
- if(parent != null) {
- parent.adjustRowBy(amount, parent.getIndex(this) + 1);
- }
- }
-
- /**
- * Messaged when the node has expanded. This updates all of
- * the receivers children rows, as well as the total row count.
- */
- protected void didExpand() {
- int nextRow = setRowAndChildren(row);
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
- int childRowCount = nextRow - row - 1;
-
- if(parent != null) {
- parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
- }
- adjustRowCountBy(childRowCount);
- }
-
- /**
- * Sets the receivers row to <code>nextRow</code> and recursively
- * updates all the children of the receivers rows. The index the
- * next row is to be placed as is returned.
- */
- protected int setRowAndChildren(int nextRow) {
- row = nextRow;
-
- if(!isExpanded())
- return row + 1;
-
- int lastRow = row + 1;
- int lastModelIndex = 0;
- FHTreeStateNode child;
- int maxCounter = getChildCount();
-
- for(int counter = 0; counter < maxCounter; counter++) {
- child = (FHTreeStateNode)getChildAt(counter);
- lastRow += (child.childIndex - lastModelIndex);
- lastModelIndex = child.childIndex + 1;
- if(child.isExpanded) {
- lastRow = child.setRowAndChildren(lastRow);
- }
- else {
- child.row = lastRow++;
- }
- }
- return lastRow + treeModel.getChildCount(getUserObject()) -
- lastModelIndex;
- }
-
- /**
- * Resets the receivers childrens rows. Starting with the child
- * at <code>childIndex</code> (and <code>modelIndex</code>) to
- * <code>newRow</code>. This uses <code>setRowAndChildren</code>
- * to recursively descend children, and uses
- * <code>resetRowSelection</code> to ascend parents.
- */
- // This can be rather expensive, but is needed for the collapse
- // case this is resulting from a remove (although I could fix
- // that by having instances of FHTreeStateNode hold a ref to
- // the number of children). I prefer this though, making determing
- // the row of a particular node fast is very nice!
- protected void resetChildrenRowsFrom(int newRow, int childIndex,
- int modelIndex) {
- int lastRow = newRow;
- int lastModelIndex = modelIndex;
- FHTreeStateNode node;
- int maxCounter = getChildCount();
-
- for(int counter = childIndex; counter < maxCounter; counter++) {
- node = (FHTreeStateNode)getChildAt(counter);
- lastRow += (node.childIndex - lastModelIndex);
- lastModelIndex = node.childIndex + 1;
- if(node.isExpanded) {
- lastRow = node.setRowAndChildren(lastRow);
- }
- else {
- node.row = lastRow++;
- }
- }
- lastRow += treeModel.getChildCount(getUserObject()) -
- lastModelIndex;
- node = (FHTreeStateNode)getParent();
- if(node != null) {
- node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
- this.childIndex + 1);
- }
- else { // This is the root, reset total ROWCOUNT!
- rowCount = lastRow;
- }
- }
-
- /**
- * Makes the receiver visible, but invoking
- * <code>expandParentAndReceiver</code> on the superclass.
- */
- protected void makeVisible() {
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
-
- if(parent != null)
- parent.expandParentAndReceiver();
- }
-
- /**
- * Invokes <code>expandParentAndReceiver</code> on the parent,
- * and expands the receiver.
- */
- protected void expandParentAndReceiver() {
- FHTreeStateNode parent = (FHTreeStateNode)getParent();
-
- if(parent != null)
- parent.expandParentAndReceiver();
- expand();
- }
-
- /**
- * Expands the receiver.
- */
- protected void expand() {
- if(!isExpanded) {
- boolean visible = isVisible();
-
- isExpanded = true;
- if(visible) {
- didExpand();
- }
-
- // Update the selection model.
- if(visible && treeSelectionModel != null) {
- treeSelectionModel.resetRowSelection();
- }
- }
- }
-
- /**
- * Collapses the receiver. If <code>adjustRows</code> is true,
- * the rows of nodes after the receiver are adjusted.
- */
- protected void collapse(boolean adjustRows) {
- if(isExpanded) {
- if(isVisible() && adjustRows) {
- int childCount = getTotalChildCount();
-
- isExpanded = false;
- adjustRowCountBy(-childCount);
- // We can do this because adjustRowBy won't descend
- // the children.
- adjustRowBy(-childCount, 0);
- }
- else
- isExpanded = false;
-
- if(adjustRows && isVisible() && treeSelectionModel != null)
- treeSelectionModel.resetRowSelection();
- }
- }
-
- /**
- * Adds newChild to this nodes children at the appropriate location.
- * The location is determined from the childIndex of newChild.
- */
- protected void addNode(FHTreeStateNode newChild) {
- boolean added = false;
- int childIndex = newChild.getChildIndex();
-
- for(int counter = 0, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
- childIndex) {
- added = true;
- insert(newChild, counter);
- counter = maxCounter;
- }
- }
- if(!added)
- add(newChild);
- }
-
- /**
- * Removes the child at <code>modelIndex</code>.
- * <code>isChildVisible</code> should be true if the receiver
- * is visible and expanded.
- */
- protected void removeChildAtModelIndex(int modelIndex,
- boolean isChildVisible) {
- FHTreeStateNode childNode = getChildAtModelIndex(modelIndex);
-
- if(childNode != null) {
- int row = childNode.getRow();
- int index = getIndex(childNode);
-
- childNode.collapse(false);
- remove(index);
- adjustChildIndexs(index, -1);
- if(isChildVisible) {
- // Adjust the rows.
- resetChildrenRowsFrom(row, index, modelIndex);
- }
- }
- else {
- int maxCounter = getChildCount();
- FHTreeStateNode aChild;
-
- for(int counter = 0; counter < maxCounter; counter++) {
- aChild = (FHTreeStateNode)getChildAt(counter);
- if(aChild.childIndex >= modelIndex) {
- if(isChildVisible) {
- adjustRowBy(-1, counter);
- adjustRowCountBy(-1);
- }
- // Since matched and children are always sorted by
- // index, no need to continue testing with the
- // above.
- for(; counter < maxCounter; counter++)
- ((FHTreeStateNode)getChildAt(counter)).
- childIndex--;
- return;
- }
- }
- // No children to adjust, but it was a child, so we still need
- // to adjust nodes after this one.
- if(isChildVisible) {
- adjustRowBy(-1, maxCounter);
- adjustRowCountBy(-1);
- }
- }
- }
-
- /**
- * Adjusts the child indexs of the receivers children by
- * <code>amount</code>, starting at <code>index</code>.
- */
- protected void adjustChildIndexs(int index, int amount) {
- for(int counter = index, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
- }
- }
-
- /**
- * Messaged when a child has been inserted at index. For all the
- * children that have a childIndex >= index their index is incremented
- * by one.
- */
- protected void childInsertedAtModelIndex(int index,
- boolean isExpandedAndVisible) {
- FHTreeStateNode aChild;
- int maxCounter = getChildCount();
-
- for(int counter = 0; counter < maxCounter; counter++) {
- aChild = (FHTreeStateNode)getChildAt(counter);
- if(aChild.childIndex >= index) {
- if(isExpandedAndVisible) {
- adjustRowBy(1, counter);
- adjustRowCountBy(1);
- }
- /* Since matched and children are always sorted by
- index, no need to continue testing with the above. */
- for(; counter < maxCounter; counter++)
- ((FHTreeStateNode)getChildAt(counter)).childIndex++;
- return;
- }
- }
- // No children to adjust, but it was a child, so we still need
- // to adjust nodes after this one.
- if(isExpandedAndVisible) {
- adjustRowBy(1, maxCounter);
- adjustRowCountBy(1);
- }
- }
-
- /**
- * Returns true if there is a row for <code>row</code>.
- * <code>nextRow</code> gives the bounds of the receiver.
- * Information about the found row is returned in <code>info</code>.
- * This should be invoked on root with <code>nextRow</code> set
- * to <code>getRowCount</code>().
- */
- protected boolean getPathForRow(int row, int nextRow,
- SearchInfo info) {
- if(this.row == row) {
- info.node = this;
- info.isNodeParentNode = false;
- info.childIndex = childIndex;
- return true;
- }
-
- FHTreeStateNode child;
- FHTreeStateNode lastChild = null;
-
- for(int counter = 0, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- child = (FHTreeStateNode)getChildAt(counter);
- if(child.row > row) {
- if(counter == 0) {
- // No node exists for it, and is first.
- info.node = this;
- info.isNodeParentNode = true;
- info.childIndex = row - this.row - 1;
- return true;
- }
- else {
- // May have been in last childs bounds.
- int lastChildEndRow = 1 + child.row -
- (child.childIndex - lastChild.childIndex);
-
- if(row < lastChildEndRow) {
- return lastChild.getPathForRow(row,
- lastChildEndRow, info);
- }
- // Between last child and child, but not in last child
- info.node = this;
- info.isNodeParentNode = true;
- info.childIndex = row - lastChildEndRow +
- lastChild.childIndex + 1;
- return true;
- }
- }
- lastChild = child;
- }
-
- int childCount = treeModel.getChildCount(getUserObject());
-
- // Not in children, but we should have it, offset from
- // nextRow.
- if(lastChild != null) {
- int lastChildEndRow = nextRow -
- (childCount - lastChild.childIndex) + 1;
-
- if(row < lastChildEndRow) {
- return lastChild.getPathForRow(row, lastChildEndRow, info);
- }
- // Between last child and child, but not in last child
- info.node = this;
- info.isNodeParentNode = true;
- info.childIndex = row - lastChildEndRow +
- lastChild.childIndex + 1;
- return true;
- }
- else {
- // No children.
- int retChildIndex = row - this.row - 1;
-
- if(retChildIndex >= childCount) {
- return false;
- }
- info.node = this;
- info.isNodeParentNode = true;
- info.childIndex = retChildIndex;
- return true;
- }
- }
-
- /**
- * Asks all the children of the receiver for their totalChildCount
- * and returns this value (plus stopIndex).
- */
- protected int getCountTo(int stopIndex) {
- FHTreeStateNode aChild;
- int retCount = stopIndex + 1;
-
- for(int counter = 0, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- aChild = (FHTreeStateNode)getChildAt(counter);
- if(aChild.childIndex >= stopIndex)
- counter = maxCounter;
- else
- retCount += aChild.getTotalChildCount();
- }
- if(parent != null)
- return retCount + ((FHTreeStateNode)getParent())
- .getCountTo(childIndex);
- if(!isRootVisible())
- return (retCount - 1);
- return retCount;
- }
-
- /**
- * Returns the number of children that are expanded to
- * <code>stopIndex</code>. This does not include the number
- * of children that the child at <code>stopIndex</code> might
- * have.
- */
- protected int getNumExpandedChildrenTo(int stopIndex) {
- FHTreeStateNode aChild;
- int retCount = stopIndex;
-
- for(int counter = 0, maxCounter = getChildCount();
- counter < maxCounter; counter++) {
- aChild = (FHTreeStateNode)getChildAt(counter);
- if(aChild.childIndex >= stopIndex)
- return retCount;
- else {
- retCount += aChild.getTotalChildCount();
- }
- }
- return retCount;
- }
-
- /**
- * Messaged when this node either expands or collapses.
- */
- protected void didAdjustTree() {
- }
-
- } // FixedHeightLayoutCache.FHTreeStateNode
-
-
- /**
- * Used as a placeholder when getting the path in FHTreeStateNodes.
- */
- private class SearchInfo {
- protected FHTreeStateNode node;
- protected boolean isNodeParentNode;
- protected int childIndex;
-
- protected TreePath getPath() {
- if(node == null)
- return null;
-
- if(isNodeParentNode)
- return node.getTreePath().pathByAddingChild(treeModel.getChild
- (node.getUserObject(),
- childIndex));
- return node.path;
- }
- } // FixedHeightLayoutCache.SearchInfo
-
-
- /**
- * An enumerator to iterate through visible nodes.
- */
- // This is very similiar to
- // VariableHeightTreeState.VisibleTreeStateNodeEnumeration
- private class VisibleFHTreeStateNodeEnumeration implements Enumeration {
- /** Parent thats children are being enumerated. */
- protected FHTreeStateNode parent;
- /** Index of next child. An index of -1 signifies parent should be
- * visibled next. */
- protected int nextIndex;
- /** Number of children in parent. */
- protected int childCount;
-
- protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode node) {
- this(node, -1);
- }
-
- protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode parent,
- int startIndex) {
- this.parent = parent;
- this.nextIndex = startIndex;
- this.childCount = treeModel.getChildCount(this.parent.
- getUserObject());
- }
-
- /**
- * @return true if more visible nodes.
- */
- public boolean hasMoreElements() {
- return (parent != null);
- }
-
- /**
- * @return next visible TreePath.
- */
- public Object nextElement() {
- if(!hasMoreElements())
- throw new NoSuchElementException("No more visible paths");
-
- Object retObject;
-
- if(nextIndex == -1)
- retObject = parent.getTreePath();
- else {
- FHTreeStateNode node = parent.getChildAtModelIndex(nextIndex);
-
- if(node == null)
- retObject = parent.getTreePath().pathByAddingChild
- (treeModel.getChild(parent.getUserObject(),
- nextIndex));
- else
- retObject = node.getTreePath();
- }
- updateNextObject();
- return retObject;
- }
-
- /**
- * Determines the next object by invoking <code>updateNextIndex</code>
- * and if not succesful <code>findNextValidParent</code>.
- */
- protected void updateNextObject() {
- if(!updateNextIndex()) {
- findNextValidParent();
- }
- }
-
- /**
- * Finds the next valid parent, this should be called when nextIndex
- * is beyond the number of children of the current parent.
- */
- protected boolean findNextValidParent() {
- if(parent == root) {
- // mark as invalid!
- parent = null;
- return false;
- }
- while(parent != null) {
- FHTreeStateNode newParent = (FHTreeStateNode)parent.
- getParent();
-
- if(newParent != null) {
- nextIndex = parent.childIndex;
- parent = newParent;
- childCount = treeModel.getChildCount
- (parent.getUserObject());
- if(updateNextIndex())
- return true;
- }
- else
- parent = null;
- }
- return false;
- }
-
- /**
- * Updates <code>nextIndex</code> returning false if it is beyond
- * the number of children of parent.
- */
- protected boolean updateNextIndex() {
- // nextIndex == -1 identifies receiver, make sure is expanded
- // before descend.
- if(nextIndex == -1 && !parent.isExpanded()) {
- return false;
- }
-
- // Check that it can have kids
- if(childCount == 0) {
- return false;
- }
- // Make sure next index not beyond child count.
- else if(++nextIndex >= childCount) {
- return false;
- }
-
- FHTreeStateNode child = parent.getChildAtModelIndex(nextIndex);
-
- if(child != null && child.isExpanded()) {
- parent = child;
- nextIndex = -1;
- childCount = treeModel.getChildCount(child.getUserObject());
- }
- return true;
- }
- } // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration
- }