/*
 * 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.

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 java.beans package. Please see XMLEncoder.

Author:Scott Violet
/** * 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&trade; * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @author Scott Violet */
public class FixedHeightLayoutCache extends AbstractLayoutCache {
Root node.
/** Root node. */
private FHTreeStateNode root;
Number of rows currently visible.
/** 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.
/** * 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.
/** * Maps from TreePath to a FHTreeStateNode. */
private Hashtable<TreePath, FHTreeStateNode> treePathMapping;
Used for getting path/row information.
/** * Used for getting path/row information. */
private SearchInfo info; private Stack<Stack<TreePath>> tempStacks; public FixedHeightLayoutCache() { super(); tempStacks = new Stack<Stack<TreePath>>(); boundsBuffer = new Rectangle(); treePathMapping = new Hashtable<TreePath, FHTreeStateNode>(); info = new SearchInfo(); setRowHeight(1); }
Sets the TreeModel that will provide the data.
Params:
  • newModel – the TreeModel that is to provide the data
/** * 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.
Params:
  • rootVisible – true if the root node of the tree is to be displayed
See Also:
/** * 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.
Params:
  • rowHeight – the height of each cell, in pixels
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
Params:
  • path – a TreePath specifying a node
  • placeIn – a Rectangle object giving the available space
Returns:a Rectangle object specifying the space to be used
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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 path expanded state to isExpanded.
/** * 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.
/** * 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 //

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.

e.path() returns the path the parent of the changed node(s).

e.childIndices() returns the index(es) of the changed node(s).

/** * <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 (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(); } } } }

Invoked after nodes have been inserted into the tree.

e.path() returns the parent of the new nodes

e.childIndices() returns the indices of the new nodes in ascending order.

/** * <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 (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(); } } }

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.

e.path() returns the former parent of the deleted nodes.

e.childIndices() returns the indices the nodes had before they were deleted in ascending order.

/** * <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 = 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(); } } }

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.

e.path() holds the path to the node.

e.childIndices() returns null.

/** * <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> * <p>e.childIndices() returns null.</p> */
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 childIndex is -1, the bounds of parent are returned, otherwise the bounds of the node at childIndex are returned.
/** * 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.
/** * 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.
/** * Adds a mapping for node. */
private void addMapping(FHTreeStateNode node) { treePathMapping.put(node.getTreePath(), node); }
Removes the mapping for a previously added node.
/** * Removes the mapping for a previously added node. */
private void removeMapping(FHTreeStateNode node) { treePathMapping.remove(node.getTreePath()); }
Returns the node previously added for path. This may return null, if you to create a node use getNodeForPath.
/** * 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 treePathMapping.get(path); }
Sent to completely rebuild the visible tree. All nodes are collapsed.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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?
/** Is this node expanded? */
protected boolean isExpanded;
Index of this node from the model.
/** Index of this node from the model. */
protected int childIndex;
Child count of the receiver.
/** Child count of the receiver. */
protected int childCount;
Row of the receiver. This is only valid if the row is expanded.
/** Row of the receiver. This is only valid if the row is expanded. */
protected int row;
Path of this node.
/** 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.
/** * 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 removedFromMapping to remove all the children.
/** * 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.
/** * 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.
/** * Returns the index of the receiver in the model. */
public int getChildIndex() { return childIndex; }
Returns the TreePath of the receiver.
/** * Returns the <code>TreePath</code> 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 index has not yet been created (expanded).
/** * 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). */
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.
/** * 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.
/** * Returns the row of the receiver. */
public int getRow() { return row; }
Returns the row of the child with a model index of index.
/** * 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() - (childCount - index); }
Returns the number of children in the receiver by descending all expanded nodes and messaging them with getTotalChildCount.
/** * 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.
/** * Returns true if this node is expanded. */
public boolean isExpanded() { return isExpanded; }
The highest visible nodes have a depth of 0.
/** * 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.
/** * 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.
/** * 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 userObject. This does NOT check to ensure there isn't already a child node to manage userObject.
/** * 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 amount.
/** * 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 index index is the index of the child to start adjusting from, which is not necessarily the model index.
/** * 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.
/** * 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 nextRow and recursively updates all the children of the receivers rows. The index the next row is to be placed as is returned.
/** * 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 + childCount - lastModelIndex; }
Resets the receivers children's rows. Starting with the child at childIndex (and modelIndex) to newRow. This uses setRowAndChildren to recursively descend children, and uses resetRowSelection to ascend parents.
/** * Resets the receivers children's 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 += 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 expandParentAndReceiver on the superclass.
/** * 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 expandParentAndReceiver on the parent, and expands the receiver.
/** * 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.
/** * 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 adjustRows is true, the rows of nodes after the receiver are adjusted.
/** * 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(); } }
Returns true if the receiver is a leaf.
/** * 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.
/** * 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 modelIndex. isChildVisible should be true if the receiver is visible and expanded.
/** * 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); 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 amount, starting at index.
/** * 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.
/** * Messaged when a child has been inserted at index. For all the * children that have a childIndex &ge; 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 row. nextRow gives the bounds of the receiver. Information about the found row is returned in info. This should be invoked on root with nextRow set to getRowCount().
/** * 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 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).
/** * 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 stopIndex. This does not include the number of children that the child at stopIndex might have.
/** * 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.
/** * Messaged when this node either expands or collapses. */
protected void didAdjustTree() { } } // FixedHeightLayoutCache.FHTreeStateNode
Used as a placeholder when getting the path in FHTreeStateNodes.
/** * 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.
/** * 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.
/** Parent thats children are being enumerated. */
protected FHTreeStateNode parent;
Index of next child. An index of -1 signifies parent should be visibled next.
/** Index of next child. An index of -1 signifies parent should be * visibled next. */
protected int nextIndex;
Number of children in parent.
/** 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()); }
Returns:true if more visible nodes.
/** * @return true if more visible nodes. */
public boolean hasMoreElements() { return (parent != null); }
Returns:next visible TreePath.
/** * @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 updateNextIndex and if not succesful findNextValidParent.
/** * 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.
/** * 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 nextIndex returning false if it is beyond the number of children of parent.
/** * 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 }