|
Java example source code file (FixedHeightLayoutCache.java)
This example Java source code file (FixedHeightLayoutCache.java) is included in the alvinalexander.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Learn more about this Java project at its project page.
The FixedHeightLayoutCache.java Java example source code
/*
* Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.
*/
package javax.swing.tree;
import javax.swing.event.TreeModelEvent;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Stack;
import sun.swing.SwingUtilities2;
/**
* NOTE: This will become more open in a future release.
* <p>
* <strong>Warning:
* 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™
* has been added to the <code>java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*
* @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<TreePath, FHTreeStateNode> treePathMapping;
/**
* Used for getting path/row information.
*/
private SearchInfo info;
private Stack<Stack tempStacks;
public FixedHeightLayoutCache() {
super();
tempStacks = new Stack<Stack();
boundsBuffer = new Rectangle();
treePathMapping = new Hashtable<TreePath, FHTreeStateNode>();
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(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
*/
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 && node.isExpanded()) {
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<TreePath> 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 expanded state to
* <code>isExpanded.
*/
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>e.childIndices() returns the index(es) of the changed node(s).
*/
public void treeNodesChanged(TreeModelEvent e) {
if(e != null) {
int changedIndexs[];
FHTreeStateNode changedParent = getNodeForPath
(SwingUtilities2.getTreePath(e, getModel()), 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>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
(SwingUtilities2.getTreePath(e, getModel()), 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>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.
*/
public void treeNodesRemoved(TreeModelEvent e) {
if(e != null) {
int changedIndexs[];
int maxCounter;
TreePath parentPath = SwingUtilities2.getTreePath(e, getModel());
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();
if (treeModel.getChildCount(changedParentNode.
getUserObject()) == 0 &&
changedParentNode.isLeaf()) {
// Node has become a leaf, collapse it.
changedParentNode.collapse(false);
}
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>e.path() holds the path to the node.
* <p>e.childIndices() returns null.
*/
public void treeStructureChanged(TreeModelEvent e) {
if(e != null) {
TreePath changedPath = SwingUtilities2.getTreePath(e, getModel());
FHTreeStateNode changedNode = getNodeForPath
(changedPath, false, false);
// Check if root has changed, either to a null root, or
// to an entirely new root.
if (changedNode == root ||
(changedNode == null &&
((changedPath == null && treeModel != null &&
treeModel.getRoot() == null) ||
(changedPath != null && changedPath.getPathCount() <= 1)))) {
rebuild(true);
}
else if(changedNode != null) {
boolean wasExpanded, wasVisible;
FHTreeStateNode parent = (FHTreeStateNode)
changedNode.getParent();
wasExpanded = changedNode.isExpanded();
wasVisible = changedNode.isVisible();
int index = parent.getIndex(changedNode);
changedNode.collapse(false);
parent.remove(index);
if(wasVisible && wasExpanded) {
int row = changedNode.getRow();
parent.resetChildrenRowsFrom(row, index,
changedNode.getChildIndex());
changedNode = getNodeForPath(changedPath, false, true);
changedNode.expand();
}
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
* is -1, the bounds of <code>parent are returned, otherwise
* the bounds of the node at <code>childIndex 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. This may
* return null, if you to create a node use getNodeForPath.
*/
private FHTreeStateNode getMapping(TreePath path) {
return treePathMapping.get(path);
}
/**
* Sent to completely rebuild the visible tree. All nodes are collapsed.
*/
private void rebuild(boolean clearSelection) {
Object rootUO;
treePathMapping.clear();
if(treeModel != null && (rootUO = treeModel.getRoot()) != null) {
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;
}
if(clearSelection && 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 {@literal >} 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<TreePath> paths;
if(tempStacks.size() == 0) {
paths = new Stack<TreePath>();
}
else {
paths = 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 = 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;
/** 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. */
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 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 receiver in the model.
*/
public int getChildIndex() {
return childIndex;
}
/**
* Returns the <code>TreePath of the receiver.
*/
public TreePath getTreePath() {
return path;
}
/**
* Returns the child for the passed in model index, this will
* return <code>null if the child for index
* 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.
*/
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() -
(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()) {
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 = childCount;
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 children's 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.
* This does NOT check to ensure there isn't already a child node
* to manage <code>userObject.
*/
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.
*/
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 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 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 + childCount - lastModelIndex;
}
/**
* Resets the receivers children's rows. Starting with the child
* at <code>childIndex (and modelIndex ) to
* <code>newRow. This uses setRowAndChildren
* to recursively descend children, and uses
* <code>resetRowSelection 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 += childCount - 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 on the superclass.
*/
protected void makeVisible() {
FHTreeStateNode parent = (FHTreeStateNode)getParent();
if(parent != null)
parent.expandParentAndReceiver();
}
/**
* Invokes <code>expandParentAndReceiver 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 && !isLeaf()) {
boolean visible = isVisible();
isExpanded = true;
childCount = treeModel.getChildCount(getUserObject());
if(visible) {
didExpand();
}
// Update the selection model.
if(visible && treeSelectionModel != null) {
treeSelectionModel.resetRowSelection();
}
}
}
/**
* Collapses the receiver. If <code>adjustRows 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();
}
}
/**
* Returns true if the receiver is a leaf.
*/
public boolean isLeaf() {
TreeModel model = getModel();
return (model != null) ? model.isLeaf(this.getUserObject()) :
true;
}
/**
* 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>isChildVisible 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);
childCount--;
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--;
childCount--;
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);
}
childCount--;
}
}
/**
* Adjusts the child indexs of the receivers children by
* <code>amount, starting at index .
*/
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++;
childCount++;
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);
}
childCount++;
}
/**
* Returns true if there is a row for <code>row.
* <code>nextRow gives the bounds of the receiver.
* Information about the found row is returned in <code>info.
* This should be invoked on root with <code>nextRow set
* to <code>getRowCount().
*/
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 child's 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;
}
// 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. This does not include the number
* of children that the child at <code>stopIndex 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 similar to
// VariableHeightTreeState.VisibleTreeStateNodeEnumeration
private class VisibleFHTreeStateNodeEnumeration
implements Enumeration<TreePath>
{
/** 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 TreePath nextElement() {
if(!hasMoreElements())
throw new NoSuchElementException("No more visible paths");
TreePath 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
* and if not succesful <code>findNextValidParent.
*/
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 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
}
Other Java examples (source code examples)
Here is a short list of links related to this Java FixedHeightLayoutCache.java source code file:
|