/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* 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. As of 1.4, support for long term storage
* of all JavaBeans<sup><font size="-2">TM</font></sup>
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @author Scott Violet
*/
/** Root node. */
/** 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.
*/
/**
* Maps from TreePath to a FHTreeStateNode.
*/
/**
*/
public FixedHeightLayoutCache() {
super();
boundsBuffer = new Rectangle();
info = new SearchInfo();
setRowHeight(1);
}
/**
* Sets the TreeModel that will provide the data.
*
* @param newModel the TreeModel that is to provide the data
*/
rebuild(false);
}
/**
* 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
*/
if(isRootVisible() != rootVisible) {
super.setRootVisible(rootVisible);
if(rootVisible) {
rowCount++;
}
else {
rowCount--;
}
}
}
}
/**
* 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
*/
if(rowHeight <= 0)
throw new IllegalArgumentException("FixedHeightLayoutCache only supports row heights greater than 0");
if(getRowHeight() != rowHeight) {
super.setRowHeight(rowHeight);
}
}
/**
* 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.
*/
}
/**
* 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.
}
/**
* Returns true if the value identified by row is currently expanded.
*/
}
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
*/
return null;
// node hasn't been created yet.
if(childIndex != -1)
}
return null;
}
/**
* Returns the path for passed in row. If row is not visible
* null is returned.
*/
}
}
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.
*/
return -1;
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.
*/
if(getRowCount() == 0)
return null;
int row = getRowContainingYLocation(y);
return getPathForRow(row);
}
/**
* Returns the number of visible children for row.
*/
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.
*/
return null;
return new VisibleFHTreeStateNodeEnumeration(node);
}
return new VisibleFHTreeStateNodeEnumeration(node,
path.getLastPathComponent()));
}
return null;
}
/**
* Marks the path <code>path</code> expanded state to
* <code>isExpanded</code>.
*/
if(isExpanded)
ensurePathIsExpanded(path, true);
// YECK! Make the parent expanded.
if(parentPath != null) {
false, true);
if(parentNode != null)
}
// And collapse the child.
false);
}
}
/**
* Returns true if the path is expanded, and visible.
*/
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>
*/
if(e != null) {
int changedIndexs[];
(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 &&
changedIndexs[counter]));
}
}
}
// Null for root indicates it changed.
changedParent.isExpanded()) {
}
}
}
}
/**
* <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.
*/
if(e != null) {
int changedIndexs[];
(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!
boolean isVisible =
(changedParent.isVisible() &&
}
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>
*/
if(e != null) {
int changedIndexs[];
int maxCounter;
(parentPath, false, false);
changedIndexs = e.getChildIndices();
// PENDING(scott): make sure that changedIndexs are sorted in
// ascending order.
boolean isVisible =
(changedParentNode.isVisible() &&
}
if(isVisible) {
if(treeSelectionModel != null)
getUserObject()) == 0 &&
changedParentNode.isLeaf()) {
// Node has become a leaf, collapse it.
changedParentNode.collapse(false);
}
}
else if(changedParentNode.isVisible())
}
}
}
/**
* <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>
*/
if(e != null) {
(changedPath, false, false);
// Check if root has changed, either to a null root, or
// to an entirely new root.
if (changedNode == root ||
(changedNode == null &&
rebuild(true);
}
else if(changedNode != null) {
boolean wasExpanded, wasVisible;
changedNode.collapse(false);
if(wasVisible && wasExpanded) {
}
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.
*/
boolean expanded;
int level;
int row;
if(childIndex == -1) {
// Getting bounds for parent
}
else {
expanded = false;
}
// No node dimensions, bail.
return null;
return placeIn;
}
/**
* Adjust the large row count of the AbstractTreeUI the receiver was
* created with.
*/
rowCount += changeAmount;
}
/**
* Adds a mapping for node.
*/
}
/**
* Removes the mapping for a previously added node.
*/
}
/**
* Returns the node previously added for <code>path</code>. This may
* return null, if you to create a node use getNodeForPath.
*/
}
/**
* Sent to completely rebuild the visible tree. All nodes are collapsed.
*/
if(isRootVisible()) {
rowCount = 1;
}
else {
rowCount = 0;
}
}
else {
rowCount = 0;
}
}
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.
*/
if(getRowCount() == 0)
return -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.
*/
boolean expandLast) {
// Make sure the last entry isn't a leaf.
expandLast = true;
}
true);
if(expandLast)
return true;
}
}
}
return false;
}
/**
* Creates and returns an instance of FHTreeStateNode.
*/
}
/**
* Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate,
* path.length) as long as path is non-null and the length is > 0.
* Otherwise returns null.
*/
boolean onlyIfVisible,
boolean shouldCreate) {
return null;
return node;
}
if(onlyIfVisible)
return null;
// Check all the parent paths, until a match is found.
}
else {
}
try {
// Found a match, create entries for all paths in
// paths.
}
return node;
}
}
}
finally {
}
// 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.
*/
/** Is this node expanded? */
protected boolean isExpanded;
/** Index of this node from the model. */
protected int childIndex;
/** Child count of the receiver. */
protected int childCount;
/** Row of the receiver. This is only valid if the row is expanded.
*/
protected int row;
/** Path of this node. */
super(userObject);
this.childIndex = childIndex;
}
//
// Overriden DefaultMutableTreeNode methods
//
/**
* Messaged when this node is added somewhere, resets the path
* and adds a mapping from path to this node.
*/
addMapping(this);
}
}
/**
* Messaged when this node is removed from its parent, this messages
* <code>removedFromMapping</code> to remove all the children.
*/
super.remove(childIndex);
}
/**
* Messaged to set the user object. This resets the path.
*/
super.setUserObject(o);
else
}
}
//
//
/**
* Returns the index of the receiver in the model.
*/
public int getChildIndex() {
return childIndex;
}
/**
* Returns the <code>TreePath</code> of the receiver.
*/
return path;
}
/**
* Returns the child for the passed in model index, this will
* return <code>null</code> if the child for <code>index</code>
* has not yet been created (expanded).
*/
// PENDING: Make this a binary search!
return null;
}
/**
* Returns true if this node is visible. This is determined by
* asking all the parents if they are expanded.
*/
public boolean isVisible() {
return true;
}
/**
* 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>.
*/
// This too could be a binary search!
if(counter == 0)
}
}
// YECK!
(childCount - index);
}
/**
* Returns the number of children in the receiver by descending all
* expanded nodes and messaging them with getTotalChildCount.
*/
public int getTotalChildCount() {
if(isExpanded()) {
int pIndex;
parent.getChildCount()) {
// This node has a created sibling, to calc total
// child count directly from that!
}
else {
int retCount = childCount;
counter--) {
}
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.
*/
removeMapping(this);
if(parentPath == null)
else
addMapping(this);
}
/**
* Removes the receiver, and all its children, from the mapping
* table.
*/
protected void removeFromMapping() {
removeMapping(this);
}
}
/**
* 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>.
*/
(getUserObject(), userObject);
if(newChildIndex < 0)
return null;
int childRow;
if(isVisible()) {
}
else {
childRow = -1;
}
return child;
}
}
return child;
}
/**
* Adjusts the receiver, and all its children rows by
* <code>amount</code>.
*/
if(isExpanded) {
counter--)
}
}
/**
* 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.
*/
// Could check isVisible, but probably isn't worth it.
if(isExpanded) {
// children following startIndex.
counter--)
}
// Parent
}
}
/**
* Messaged when the node has expanded. This updates all of
* the receivers children rows, as well as the total row count.
*/
protected void didExpand() {
}
}
/**
* 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.
*/
if(!isExpanded())
return row + 1;
int lastModelIndex = 0;
int maxCounter = getChildCount();
if(child.isExpanded) {
}
else {
}
}
}
/**
* 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!
int modelIndex) {
int lastModelIndex = modelIndex;
int maxCounter = getChildCount();
if(node.isExpanded) {
}
else {
}
}
this.childIndex + 1);
}
else { // This is the root, reset total ROWCOUNT!
}
}
/**
* Makes the receiver visible, but invoking
* <code>expandParentAndReceiver</code> on the superclass.
*/
protected void makeVisible() {
}
/**
* Invokes <code>expandParentAndReceiver</code> on the parent,
* and expands the receiver.
*/
protected void expandParentAndReceiver() {
expand();
}
/**
* Expands the receiver.
*/
protected void expand() {
if(!isExpanded && !isLeaf()) {
isExpanded = true;
if(visible) {
didExpand();
}
// Update the selection model.
}
}
}
/**
* Collapses the receiver. If <code>adjustRows</code> is true,
* the rows of nodes after the receiver are adjusted.
*/
if(isExpanded) {
if(isVisible() && adjustRows) {
int childCount = getTotalChildCount();
isExpanded = false;
// We can do this because adjustRowBy won't descend
// the children.
}
else
isExpanded = false;
}
}
/**
* Returns true if the receiver is a leaf.
*/
public boolean isLeaf() {
true;
}
/**
* Adds newChild to this nodes children at the appropriate location.
* The location is determined from the childIndex of newChild.
*/
boolean added = false;
childIndex) {
added = true;
}
}
if(!added)
}
/**
* Removes the child at <code>modelIndex</code>.
* <code>isChildVisible</code> should be true if the receiver
* is visible and expanded.
*/
boolean isChildVisible) {
childCount--;
if(isChildVisible) {
// Adjust the rows.
}
}
else {
int maxCounter = getChildCount();
if(isChildVisible) {
adjustRowCountBy(-1);
}
// Since matched and children are always sorted by
// index, no need to continue testing with the
// above.
childIndex--;
childCount--;
return;
}
}
// No children to adjust, but it was a child, so we still need
// to adjust nodes after this one.
if(isChildVisible) {
adjustRowCountBy(-1);
}
childCount--;
}
}
/**
* Adjusts the child indexs of the receivers children by
* <code>amount</code>, starting at <code>index</code>.
*/
}
}
/**
* Messaged when a child has been inserted at index. For all the
* children that have a childIndex >= index their index is incremented
* by one.
*/
boolean isExpandedAndVisible) {
int maxCounter = getChildCount();
if(isExpandedAndVisible) {
adjustRowCountBy(1);
}
/* Since matched and children are always sorted by
index, no need to continue testing with the above. */
childCount++;
return;
}
}
// No children to adjust, but it was a child, so we still need
// to adjust nodes after this one.
if(isExpandedAndVisible) {
adjustRowCountBy(1);
}
childCount++;
}
/**
* 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>().
*/
SearchInfo info) {
info.isNodeParentNode = false;
return true;
}
if(counter == 0) {
// No node exists for it, and is first.
info.isNodeParentNode = true;
return true;
}
else {
// May have been in last childs bounds.
if(row < lastChildEndRow) {
}
// Between last child and child, but not in last child
info.isNodeParentNode = true;
return true;
}
}
}
// Not in children, but we should have it, offset from
// nextRow.
int lastChildEndRow = nextRow -
if(row < lastChildEndRow) {
}
// Between last child and child, but not in last child
info.isNodeParentNode = true;
return true;
}
else {
// No children.
if(retChildIndex >= childCount) {
return false;
}
info.isNodeParentNode = true;
return true;
}
}
/**
* Asks all the children of the receiver for their totalChildCount
* and returns this value (plus stopIndex).
*/
else
}
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.
*/
return retCount;
else {
}
}
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 boolean isNodeParentNode;
protected int childIndex;
return null;
if(isNodeParentNode)
(node.getUserObject(),
childIndex));
}
} // FixedHeightLayoutCache.SearchInfo
/**
* An enumerator to iterate through visible nodes.
*/
// This is very similiar to
// VariableHeightTreeState.VisibleTreeStateNodeEnumeration
private class VisibleFHTreeStateNodeEnumeration
implements Enumeration<TreePath>
{
/** Parent thats children are being enumerated. */
/** 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;
this(node, -1);
}
int startIndex) {
this.nextIndex = startIndex;
getUserObject());
}
/**
* @return true if more visible nodes.
*/
public boolean hasMoreElements() {
}
/**
* @return next visible TreePath.
*/
if(!hasMoreElements())
throw new NoSuchElementException("No more visible paths");
if(nextIndex == -1)
else {
nextIndex));
else
}
return retObject;
}
/**
* Determines the next object by invoking <code>updateNextIndex</code>
* and if not succesful <code>findNextValidParent</code>.
*/
protected void updateNextObject() {
if(!updateNextIndex()) {
}
}
/**
* Finds the next valid parent, this should be called when nextIndex
* is beyond the number of children of the current parent.
*/
protected boolean findNextValidParent() {
// mark as invalid!
return false;
}
getParent();
(parent.getUserObject());
if(updateNextIndex())
return true;
}
else
}
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.
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;
}
nextIndex = -1;
}
return true;
}
} // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration
}