/** * 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; }
/** 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(); }
/** * 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; }
/** 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; }
protected TreePath getPath() { if (node == null) return null; if (isNodeParentNode) return node.getTreePath() .pathByAddingChild(treeModel.getChild(node.getUserObject(), childIndex)); return node.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); } }
/** * 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); }
/** * 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>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(); } } } }
/** * 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; }
/** * 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; }
/** * 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>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(); } } }
/** * 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); } }
/** * 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(); } } }
/** * 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; }
/** 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 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 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 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; }
/** * 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; }
/** * 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--; } }
/** * 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; }
/** * 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; }
/** * 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; }
/** * 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); }
/** * 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; }
/** * 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; }
/** * 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(); } } }
// 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; } }
/** @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; }
/** Invokes <code>expandParentAndReceiver</code> on the parent, and expands the receiver. */ protected void expandParentAndReceiver() { FHTreeStateNode parent = (FHTreeStateNode) getParent(); if (parent != null) parent.expandParentAndReceiver(); expand(); }