/*
* 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 Rob Davis
* @author Ray Ryan
* @author Scott Violet
*/
/**
* The array of nodes that are currently visible, in the order they
* are displayed.
*/
/**
* This is set to true if one of the entries has an invalid size.
*/
private boolean updateNodeSizes;
/**
* The root node of the internal cache of nodes that have been shown.
* If the treeModel is vending a network rather than a true tree,
* there may be one cached node for each path to a modeled node.
*/
/**
* Used in getting sizes for nodes to avoid creating a new Rectangle
* every time a size is needed.
*/
/**
* Maps from <code>TreePath</code> to a <code>TreeStateNode</code>.
*/
/**
* A stack of stacks.
*/
public VariableHeightLayoutCache() {
super();
boundsBuffer = new Rectangle();
}
/**
* Sets the <code>TreeModel</code> that will provide the data.
*
* @param newModel the <code>TreeModel</code> that is to provide the data
* @beaninfo
* bound: true
* description: The TreeModel that will provide the data.
*/
rebuild(false);
}
/**
* Determines whether or not the root node from
* the <code>TreeModel</code> is visible.
*
* @param rootVisible true if the root node of the tree is to be displayed
* @see #rootVisible
* @beaninfo
* bound: true
* description: Whether or not the root node
* from the TreeModel is visible.
*/
if(rootVisible) {
}
if(treeSelectionModel != null)
(root.getTreePath());
}
if(treeSelectionModel != null)
if(getRowCount() > 0)
}
super.setRootVisible(rootVisible);
}
/**
* Sets the height of each cell. If the specified value
* is less than or equal to zero the current cell renderer is
* queried for each row's height.
*
* @param rowHeight the height of each cell, in pixels
* @beaninfo
* bound: true
* description: The height of each cell.
*/
if(rowHeight != getRowHeight()) {
super.setRowHeight(rowHeight);
this.visibleNodesChanged();
}
}
/**
* Sets the renderer that is responsible for drawing nodes in the tree.
* @param nd the renderer
*/
super.setNodeDimensions(nd);
}
/**
* Marks the path <code>path</code> expanded state to
* <code>isExpanded</code>.
* @param path the <code>TreePath</code> of interest
* @param isExpanded true if the path should be expanded, otherwise false
*/
if(isExpanded)
ensurePathIsExpanded(path, true);
else {
node.makeVisible();
}
}
}
}
/**
* Returns true if the path is expanded, and visible.
* @return true if the path is expanded and visible, otherwise false
*/
false;
}
/**
* Returns the <code>Rectangle</code> enclosing the label portion
* into which the item identified by <code>path</code> will be drawn.
*
* @param path the path to be drawn
* @param placeIn the bounds of the enclosing rectangle
* @return the bounds of the enclosing rectangle or <code>null</code>
* if the node could not be ascertained
*/
if(updateNodeSizes)
updateNodeSizes(false);
}
return null;
}
/**
* Returns the path for <code>row</code>. If <code>row</code>
* is not visible, <code>null</code> is returned.
*
* @param row the location of interest
* @return the path for <code>row</code>, or <code>null</code>
* if <code>row</code> is not visible
*/
}
return null;
}
/**
* Returns the row where the last item identified in path is visible.
* Will return -1 if any of the elements in path are not
* currently visible.
*
* @param path the <code>TreePath</code> of interest
* @return the row where the last item in path is visible
*/
return -1;
return -1;
}
/**
* Returns the number of visible rows.
* @return the number of visible rows
*/
public int getRowCount() {
return visibleNodes.size();
}
/**
* Instructs the <code>LayoutCache</code> that the bounds for
* <code>path</code> are invalid, and need to be updated.
*
* @param path the <code>TreePath</code> which is now invalid
*/
}
}
/**
* Returns the preferred height.
* @return the preferred height
*/
public int getPreferredHeight() {
// Get the height
int rowCount = getRowCount();
if(rowCount > 0) {
}
return 0;
}
/**
* Returns the preferred width and height for the region in
* <code>visibleRegion</code>.
*
* @param bounds the region being queried
*/
if(updateNodeSizes)
updateNodeSizes(false);
return getMaxNodeWidth();
}
/**
* Returns the path to the node that is closest to x,y. If
* there is nothing currently visible this will return <code>null</code>,
* otherwise it will 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.
*
* @param x the x-coordinate
* @param y the y-coordinate
* @return the path to the node that is closest to x, y
*/
if(getRowCount() == 0)
return null;
if(updateNodeSizes)
updateNodeSizes(false);
int row = getRowContainingYLocation(y);
}
/**
* Returns an <code>Enumerator</code> 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.
*
* @param path the location in the <code>TreePath</code> to start
* @return an <code>Enumerator</code> that increments over the visible
* paths
*/
return new VisibleTreeStateNodeEnumeration(node);
}
return null;
}
/**
* Returns the number of visible children for <code>path</code>.
* @return the number of visible children for <code>path</code>
*/
}
/**
* Informs the <code>TreeState</code> that it needs to recalculate
* all the sizes it is referencing.
*/
public void invalidateSizes() {
updateNodeSizes(true);
}
}
/**
* Returns true if the value identified by <code>path</code> is
* currently expanded.
* @return true if the value identified by <code>path</code> is
* currently expanded
*/
}
return false;
}
//
// TreeModelListener methods
//
/**
* 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><code>e.path</code> returns the path the parent of the
* changed node(s).
*
* <p><code>e.childIndices</code> returns the index(es) of the
* changed node(s).
*
* @param e the <code>TreeModelEvent</code> of interest
*/
if(e != null) {
int changedIndexs[];
changedIndexs = e.getChildIndices();
if(changedNode != null) {
/* Update the size of the changed node, as well as all the
child indexs that are passed in. */
int counter;
counter++) {
/* Reset the user object. */
changedIndexs[counter]));
}
}
else if (changedNode == root) {
// Null indicies for root indicates it changed.
}
if(!isFixedRowHeight()) {
if(aRow != -1)
this.updateYLocationsFrom(aRow);
}
this.visibleNodesChanged();
}
}
}
/**
* Invoked after nodes have been inserted into the tree.
*
* <p><code>e.path</code> returns the parent of the new nodes.
* <p><code>e.childIndices</code> returns the indices of the new nodes in
* ascending order.
*
* @param e the <code>TreeModelEvent</code> of interest
*/
if(e != null) {
int changedIndexs[];
changedIndexs = e.getChildIndices();
/* Only need to update the children if the node has been
expanded once. */
// PENDING(scott): make sure childIndexs is sorted!
if(changedParentNode.hasBeenExpanded()) {
boolean makeVisible;
int counter;
int oldChildCount = changedParentNode.
!rootVisible) ||
{
}
if(oldChildCount == 0) {
// Update the size of the parent.
}
if(treeSelectionModel != null)
/* Update the y origins from the index of the parent
to the end of the visible rows. */
if(!isFixedRowHeight() && (makeVisible ||
(oldChildCount == 0 &&
changedParentNode.isVisible()))) {
if(changedParentNode == root)
this.updateYLocationsFrom(0);
else
getRow());
this.visibleNodesChanged();
}
else if(makeVisible)
this.visibleNodesChanged();
}
}
}
}
}
/**
* 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><code>e.path</code> returns the former parent of the deleted nodes.
*
* <p><code>e.childIndices</code> returns the indices the nodes had
* before they were deleted in ascending order.
*
* @param e the <code>TreeModelEvent</code> of interest
*/
if(e != null) {
int changedIndexs[];
changedIndexs = e.getChildIndices();
// PENDING(scott): make sure that changedIndexs are sorted in
// ascending order.
if(changedParentNode.hasBeenExpanded()) {
boolean makeInvisible;
int counter;
int removedRow;
!rootVisible) ||
counter--) {
if(removedNode.isExpanded()) {
removedNode.collapse(false);
}
/* Let the selection model now. */
if(makeInvisible) {
if(removedRow != -1) {
}
}
}
// Update the size of the parent.
if (changedParentNode.isExpanded() &&
changedParentNode.isLeaf()) {
// Node has become a leaf, collapse it.
changedParentNode.collapse(false);
}
}
if(treeSelectionModel != null)
/* Update the y origins from the index of the parent
to the end of the visible rows. */
if(!isFixedRowHeight() && (makeInvisible ||
changedParentNode.isVisible()))) {
if(changedParentNode == root) {
/* It is possible for first row to have been
removed if the root isn't visible, in which
case ylocations will be off! */
if(getRowCount() > 0)
}
else
this.visibleNodesChanged();
}
else if(makeInvisible)
this.visibleNodesChanged();
}
== 0) {
}
}
}
}
/**
* Invoked after the tree has drastically changed structure from a
* given node down. If the path returned by <code>e.getPath</code>
* 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><code>e.path</code> holds the path to the node.
* <p><code>e.childIndices</code> returns <code>null</code>.
*
* @param e the <code>TreeModelEvent</code> of interest
*/
if(e != null)
{
// 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;
int newIndex;
/* Remove the current node and recreate a new one. */
if(wasVisible && wasExpanded) {
changedNode.collapse(false);
}
if(wasVisible)
if(wasVisible && wasExpanded)
if(!isFixedRowHeight() && wasVisible) {
if(newIndex == 0)
else
this.visibleNodesChanged();
}
else if(wasVisible)
this.visibleNodesChanged();
}
}
}
//
// Local methods
//
private void visibleNodesChanged() {
}
/**
* 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.
*/
}
/**
* Retursn the bounds for row, <code>row</code> by reference in
* <code>placeIn</code>. If <code>placeIn</code> is null a new
* Rectangle will be created and returned.
*/
if(updateNodeSizes)
updateNodeSizes(false);
}
return null;
}
/**
* Completely rebuild the tree, all expanded state, and node caches are
* removed. All nodes are collapsed, except the root.
*/
if (isRootVisible())
if(!root.isExpanded())
else {
while(cursor.hasMoreElements()) {
}
if(!isFixedRowHeight())
}
}
else {
}
}
this.visibleNodesChanged();
}
/**
* Creates a new node to represent the node at <I>childIndex</I> in
* <I>parent</I>s children. This should be called if the node doesn't
* already exist and <I>parent</I> has been expanded at least once.
* The newly created node will be made visible if <I>parent</I> is
* currently expanded. This does not update the position of any
* cells, nor update the selection if it needs to be. If succesful
* in creating the new TreeStateNode, it is returned, otherwise
* null is returned.
*/
int childIndex) {
boolean isParentRoot;
int newRow;
/* Find the new row to insert this newly visible node at. */
if(childIndex == 0) {
if(isParentRoot && !isRootVisible())
newRow = 0;
else
}
else {
}
}
return newChildNode;
}
/**
* Returns the TreeStateNode identified by path. This mirrors
* the behavior of getNodeForPath, but tries to take advantage of
* path if it is an instance of AbstractTreePath.
*/
boolean onlyIfVisible,
boolean shouldCreate) {
return null;
return node;
}
// Check all the parent paths, until a match is found.
}
else {
}
try {
// Found a match, create entries for all paths in
// paths.
int childIndex = treeModel.
if(childIndex == -1 ||
}
else
(childIndex);
}
return node;
}
}
}
finally {
}
// If we get here it means they share a different root!
// We could throw an exception...
}
return null;
}
/**
* Updates the y locations of all of the visible nodes after
* location.
*/
}
}
}
/**
* Resets the y origin of all the visible nodes as well as messaging
* all the visible nodes to updatePreferredSize(). You should not
* normally have to call this. Expanding and contracting the nodes
* automaticly adjusts the locations.
* updateAll determines if updatePreferredSize() is call on all nodes
* or just those that don't have a valid size.
*/
updateNodeSizes = false;
}
}
/**
* 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(isFixedRowHeight()) {
if(getRowCount() == 0)
return -1;
location / getRowHeight()));
}
return -1;
}
}
else
break;
}
if(mid >= getRowCount())
}
return mid;
}
/**
* 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.
*/
// Make sure the last entry isn't a leaf.
expandLast = true;
}
true);
if(expandLast)
}
}
}
}
/**
* Returns the AbstractTreeUI.VisibleNode displayed at the given row
*/
}
/**
* Returns the maximum node width.
*/
private int getMaxNodeWidth() {
int maxWidth = 0;
int nodeWidth;
int counter;
}
return maxWidth;
}
/**
* Responsible for creating a TreeStateNode that will be used
* to track display information about value.
*/
return new TreeStateNode(value);
}
/**
* TreeStateNode is used to keep track of each of
* the nodes that have been expanded. This will also cache the preferred
* size of the value it represents.
*/
/** Preferred size needed to draw the user object. */
protected int preferredWidth;
protected int preferredHeight;
/** X location that the user object will be drawn at. */
protected int xOrigin;
/** Y location that the user object will be drawn at. */
protected int yOrigin;
/** Is this node currently expanded? */
protected boolean expanded;
/** Has this node been expanded at least once? */
protected boolean hasBeenExpanded;
/** Path of this node. */
super(value);
}
//
// 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 children of the receiver.
* If the receiver is not currently expanded, this will return an
* empty enumeration.
*/
if (!this.isExpanded()) {
} else {
return super.children();
}
}
/**
* Returns true if the receiver is a leaf.
*/
public boolean isLeaf() {
}
//
// VariableHeightLayoutCache
//
/**
* Returns the location and size of this node.
*/
else {
placeIn.x = getXOrigin();
placeIn.y = getYOrigin();
}
return placeIn;
}
/**
* @return x location to draw node at.
*/
public int getXOrigin() {
if(!hasValidSize())
return xOrigin;
}
/**
* Returns the y origin the user object will be drawn at.
*/
public int getYOrigin() {
if(isFixedRowHeight()) {
if(aRow == -1)
return -1;
return getRowHeight() * aRow;
}
return yOrigin;
}
/**
* Returns the preferred height of the receiver.
*/
public int getPreferredHeight() {
if(isFixedRowHeight())
return getRowHeight();
else if(!hasValidSize())
return preferredHeight;
}
/**
* Returns the preferred width of the receiver.
*/
public int getPreferredWidth() {
if(!hasValidSize())
return preferredWidth;
}
/**
* Returns true if this node has a valid size.
*/
public boolean hasValidSize() {
return (preferredHeight != 0);
}
/**
* Returns the row of the receiver.
*/
public int getRow() {
return visibleNodes.indexOf(this);
}
/**
* Returns true if this node has been expanded at least once.
*/
public boolean hasBeenExpanded() {
return hasBeenExpanded;
}
/**
* Returns true if the receiver has been expanded.
*/
public boolean isExpanded() {
return expanded;
}
/**
* Returns the last visible node that is a child of this
* instance.
*/
TreeStateNode node = this;
return node;
}
/**
* Returns true if the receiver is currently visible.
*/
public boolean isVisible() {
if(this == root)
return true;
}
/**
* Returns the number of children this will have. If the children
* have not yet been loaded, this messages the model.
*/
public int getModelChildCount() {
if(hasBeenExpanded)
return super.getChildCount();
}
/**
* Returns the number of visible children, that is the number of
* children that are expanded, or leafs.
*/
public int getVisibleChildCount() {
int childCount = 0;
if(isExpanded()) {
int maxCounter = getChildCount();
childCount += maxCounter;
}
return childCount;
}
/**
* Toggles the receiver between expanded and collapsed.
*/
public void toggleExpanded() {
if (isExpanded()) {
collapse();
} else {
expand();
}
}
/**
* Makes the receiver visible, but invoking
* <code>expandParentAndReceiver</code> on the superclass.
*/
public void makeVisible() {
}
/**
* Expands the receiver.
*/
public void expand() {
expand(true);
}
/**
* Collapses the receiver.
*/
public void collapse() {
collapse(true);
}
/**
* Returns the value the receiver is representing. This is a cover
* for getUserObject.
*/
return getUserObject();
}
/**
* Returns a TreePath instance for this node.
*/
return path;
}
//
// Local methods
//
/**
* Recreates the receivers path, and all its childrens paths.
*/
removeMapping(this);
if(parentPath == null)
else
addMapping(this);
}
/**
* Sets y origin the user object will be drawn at to
* <I>newYOrigin</I>.
*/
}
/**
* Shifts the y origin by <code>offset</code>.
*/
}
/**
* Updates the receivers preferredSize by invoking
* <code>updatePreferredSize</code> with an argument of -1.
*/
protected void updatePreferredSize() {
}
/**
* Updates the preferred size by asking the current renderer
* for the Dimension needed to draw the user object this
* instance represents.
*/
isExpanded(),
xOrigin = 0;
updateNodeSizes = true;
}
xOrigin = 0;
updateNodeSizes = true;
}
else {
if(isFixedRowHeight())
else
}
}
/**
* Marks the receivers size as invalid. Next time the size, location
* is asked for it will be obtained.
*/
protected void markSizeInvalid() {
preferredHeight = 0;
}
/**
* Marks the receivers size, and all its descendants sizes, as invalid.
*/
protected void deepMarkSizeInvalid() {
}
/**
* Returns the children of the receiver. If the children haven't
* been loaded from the model and
* <code>createIfNeeded</code> is true, the children are first
* loaded.
*/
if(!createIfNeeded || hasBeenExpanded)
return super.children();
hasBeenExpanded = true;
if(childRow == -1) {
for (int i = 0; i < count; i++) {
}
}
else {
childRow++;
for (int i = 0; i < count; i++) {
}
}
return super.children();
}
/**
* Messaged from expand and collapse. This is meant for subclassers
* that may wish to do something interesting with this.
*/
protected void didAdjustTree() {
}
/**
* Invokes <code>expandParentAndReceiver</code> on the parent,
* and expands the receiver.
*/
protected void expandParentAndReceiver() {
expand();
}
/**
* Expands this node in the tree. This will load the children
* from the treeModel if this node has not previously been
* expanded. If <I>adjustTree</I> is true the tree and selection
* are updated accordingly.
*/
if (!isExpanded() && !isLeaf()) {
boolean isFixed = isFixedRowHeight();
int startHeight = getPreferredHeight();
int originalRow = getRow();
expanded = true;
if (!hasBeenExpanded) {
hasBeenExpanded = true;
if(originalRow == -1) {
for (int i = 0; i < count; i++) {
(realNode, i));
}
}
else {
for (int i = 0; i < count; i++) {
(realNode, i));
}
}
}
int i = originalRow;
int newYOrigin;
if(isFixed)
newYOrigin = 0;
else if(this == root && !isRootVisible())
newYOrigin = 0;
else
if(!isFixed) {
while (cursor.hasMoreElements()) {
}
}
else {
while (cursor.hasMoreElements()) {
}
}
if(adjustTree && (originalRow != i ||
getPreferredHeight() != startHeight)) {
// Adjust the Y origin of any nodes following this row.
if(!isFixed && ++i < getRowCount()) {
int counter;
int heightDiff = newYOrigin -
(getYOrigin() + getPreferredHeight()) +
(getPreferredHeight() - startHeight);
counter--)
}
}
// Update the rows in the selection
if(treeSelectionModel != null) {
}
}
}
/**
* Collapses this node in the tree. If <I>adjustTree</I> is
* true the tree and selection are updated accordingly.
*/
if (isExpanded()) {
int rowsDeleted = 0;
boolean isFixed = isFixedRowHeight();
int lastYEnd;
if(isFixed)
lastYEnd = 0;
else
int startHeight = getPreferredHeight();
if(!isFixed) {
while(cursor.hasMoreElements()) {
nextElement();
rowsDeleted++;
//visibleNodes.removeElement(node);
}
}
}
else {
while(cursor.hasMoreElements()) {
nextElement();
rowsDeleted++;
//visibleNodes.removeElement(node);
}
}
}
// Clean up the visible nodes.
counter--) {
}
expanded = false;
if(myRow == -1)
else if (adjustTree)
// Adjust the Y origin of any rows following this one.
visibleNodes.size();
}
}
myRow != -1) {
}
}
}
/**
* Removes the receiver, and all its children, from the mapping
* table.
*/
protected void removeFromMapping() {
removeMapping(this);
}
}
} // End of VariableHeightLayoutCache.TreeStateNode
/**
* An enumerator to iterate through visible nodes.
*/
private class VisibleTreeStateNodeEnumeration implements
/** 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;
}
/**
* @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 {
}
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();
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;
}
} // VariableHeightLayoutCache.VisibleTreeStateNodeEnumeration
}