diff options
Diffstat (limited to 'libjava/classpath/javax/swing/tree')
18 files changed, 7361 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java new file mode 100644 index 000000000..29ce165a9 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java @@ -0,0 +1,456 @@ +/* AbstractLayoutCache.java -- + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.awt.Rectangle; +import java.util.Enumeration; + +import javax.swing.event.TreeModelEvent; + +/** + * class AbstractLayoutCache + * + * @author Andrew Selkirk + */ +public abstract class AbstractLayoutCache + implements RowMapper +{ + /** + * class NodeDimensions + */ + public abstract static class NodeDimensions + { + /** + * Creates <code>NodeDimensions</code> object. + */ + public NodeDimensions() + { + // Do nothing here. + } + + /** + * Get the node dimensions. The NodeDimensions property must be set (unless + * the method is overridden, like if {@link FixedHeightLayoutCache}. If the + * method is not overridden and the property is not set, the InternalError is + * thrown. + * + * @param value the last node in the path + * @param row the node row + * @param depth the indentation depth + * @param expanded true if this node is expanded, false otherwise + * @param bounds the area where the tree is displayed + */ + public abstract Rectangle getNodeDimensions(Object value, int row, + int depth, boolean expanded, + Rectangle bounds); + } + + /** + * nodeDimensions + */ + protected NodeDimensions nodeDimensions; + + /** + * treeModel + */ + protected TreeModel treeModel; + + /** + * treeSelectionModel + */ + protected TreeSelectionModel treeSelectionModel; + + /** + * rootVisible + */ + protected boolean rootVisible; + + /** + * rowHeight + */ + protected int rowHeight; + + /** + * Constructor AbstractLayoutCache + */ + public AbstractLayoutCache() + { + // Do nothing here. + } + + /** + * setNodeDimensions + * + * @param dimensions TODO + */ + public void setNodeDimensions(NodeDimensions dimensions) + { + nodeDimensions = dimensions; + } + + /** + * getNodeDimensions + * + * @return NodeDimensions + */ + public NodeDimensions getNodeDimensions() + { + return nodeDimensions; + } + + /** + * Get the node dimensions. The NodeDimensions property must be set + * (unless the method is overridden, like if + * {@link FixedHeightLayoutCache}. If the method is not overridden and + * the property is not set, the InternalError is thrown. + * + * @param value the last node in the path + * @param row the node row + * @param depth the indentation depth + * @param expanded true if this node is expanded, false otherwise + * @param bounds the area where the tree is displayed + */ + protected Rectangle getNodeDimensions(Object value, int row, int depth, + boolean expanded, Rectangle bounds) + { + Rectangle d = null; + if (nodeDimensions != null) + d = nodeDimensions.getNodeDimensions(value, row, depth, expanded, + bounds); + return d; + } + + /** + * Sets the model that provides the tree data. + * + * @param model the model + */ + public void setModel(TreeModel model) + { + treeModel = model; + } + + /** + * Returns the model that provides the tree data. + * + * @return the model + */ + public TreeModel getModel() + { + return treeModel; + } + + /** + * setRootVisible + * + * @param visible <code>true</code> if root should be visible, + * <code>false</code> otherwise + */ + public void setRootVisible(boolean visible) + { + rootVisible = visible; + } + + /** + * isRootVisible + * + * @return <code>true</code> if root is visible, + * <code>false</code> otherwise + */ + public boolean isRootVisible() + { + return rootVisible; + } + + /** + * setRowHeight + * + * @param height the row height + */ + public void setRowHeight(int height) + { + rowHeight = height; + invalidateSizes(); + } + + /** + * getRowHeight + * + * @return the row height + */ + public int getRowHeight() + { + return rowHeight; + } + + /** + * setSelectionModel + * + * @param model the model + */ + public void setSelectionModel(TreeSelectionModel model) + { + if (treeSelectionModel != null) + treeSelectionModel.setRowMapper(null); + treeSelectionModel = model; + if (treeSelectionModel != null) + treeSelectionModel.setRowMapper(this); + + } + + /** + * getSelectionModel + * + * @return the model + */ + public TreeSelectionModel getSelectionModel() + { + return treeSelectionModel; + } + + /** + * Get the sum of heights for all rows. This class provides a general not + * optimized implementation that is overridded in derived classes + * ({@link VariableHeightLayoutCache}, {@link FixedHeightLayoutCache}) for + * the better performance. + */ + public int getPreferredHeight() + { + int height = 0; + int n = getRowCount(); + Rectangle r = new Rectangle(); + for (int i = 0; i < n; i++) + { + TreePath path = getPathForRow(i); + height += getBounds(path, r).height; + } + return height; + } + + /** + * Get the maximal width. This class provides a general not + * optimized implementation that is overridded in derived classes + * ({@link VariableHeightLayoutCache}, {@link FixedHeightLayoutCache}) for + * the better performance. + * + * @param rect the rectangle that is used during the method work + */ + public int getPreferredWidth(Rectangle rect) + { + int maximalWidth = 0; + Rectangle r = new Rectangle(); + int n = getRowCount(); + for (int i = 0; i < n; i++) + { + TreePath path = getPathForRow(i); + r.setBounds(0, 0, 0, 0); + r = getBounds(path, r); + if (r.x + r.width > maximalWidth) + maximalWidth = r.x + r.width; + // Invalidate the cached value as this may be the very early call + // before the heigth is properly set (the vertical coordinate may + // not be correct). + invalidatePathBounds(path); + } + return maximalWidth; + } + /** + * isExpanded + * + * @param value0 TODO + * + * @return boolean + */ + public abstract boolean isExpanded(TreePath value0); + + /** + * getBounds + * + * @param value0 TODO + * @param value1 TODO + * + * @return Rectangle + */ + public abstract Rectangle getBounds(TreePath value0, Rectangle value1); + + /** + * getPathForRow + * + * @param row the row + * + * @return the tree path + */ + public abstract TreePath getPathForRow(int row); + + /** + * getRowForPath + * + * @param path the tree path + * + * @return the row + */ + public abstract int getRowForPath(TreePath path); + + /** + * getPathClosestTo + * + * @param value0 TODO + * @param value1 TODO + * + * @return the tree path + */ + public abstract TreePath getPathClosestTo(int value0, int value1); + + /** + * getVisiblePathsFrom + * + * @param path the tree path + * + * @return Enumeration + */ + public abstract Enumeration<TreePath> getVisiblePathsFrom(TreePath path); + + /** + * getVisibleChildCount + * + * @param path the tree path + * + * @return int + */ + public abstract int getVisibleChildCount(TreePath path); + + /** + * setExpandedState + * + * @param value0 TODO + * + * @param value1 TODO + */ + public abstract void setExpandedState(TreePath value0, boolean value1); + + /** + * getExpandedState + * + * @param path the tree path + * + * @return boolean + */ + public abstract boolean getExpandedState(TreePath path); + + /** + * getRowCount + * + * @return the number of rows + */ + public abstract int getRowCount(); + + /** + * invalidateSizes + */ + public abstract void invalidateSizes(); + + /** + * invalidatePathBounds + * + * @param path the tree path + */ + public abstract void invalidatePathBounds(TreePath path); + + /** + * treeNodesChanged + * + * @param event the event to send + */ + public abstract void treeNodesChanged(TreeModelEvent event); + + /** + * treeNodesInserted + * + * @param event the event to send + */ + public abstract void treeNodesInserted(TreeModelEvent event); + + /** + * treeNodesRemoved + * + * @param event the event to send + */ + public abstract void treeNodesRemoved(TreeModelEvent event); + + /** + * treeStructureChanged + * + * @param event the event to send + */ + public abstract void treeStructureChanged(TreeModelEvent event); + + /** + * Get the tree row numbers for the given pathes. This method performs + * the "bulk" conversion that may be faster than mapping pathes one by + * one. To have the benefit from the bulk conversion, the method must be + * overridden in the derived classes. The default method delegates work + * to the {@link #getRowForPath(TreePath)}. + * + * @param paths the tree paths the array of the tree pathes. + * @return the array of the matching tree rows. + */ + public int[] getRowsForPaths(TreePath[] paths) + { + int[] rows = null; + if (paths != null) + { + rows = new int[paths.length]; + for (int i = 0; i < rows.length; i++) + rows[i] = getRowForPath(paths[i]); + } + return rows; + } + + /** + * Returns true if this layout supposes that all rows have the fixed + * height. + * + * @return boolean true if all rows in the tree must have the fixed + * height (false by default). + */ + protected boolean isFixedRowHeight() + { + return rowHeight > 0; + } +} diff --git a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java new file mode 100644 index 000000000..260c385aa --- /dev/null +++ b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java @@ -0,0 +1,1217 @@ +/* DefaultMutableTreeNode.java -- + Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import gnu.java.util.EmptyEnumeration; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Stack; +import java.util.Vector; + + +/** + * A default implementation of the {@link MutableTreeNode} interface. + * + * @author Andrew Selkirk + * @author Robert Schuster (robertschuster@fsfe.org) + */ +public class DefaultMutableTreeNode + implements Cloneable, MutableTreeNode, Serializable +{ + private static final long serialVersionUID = -4298474751201349152L; + + /** + * An empty enumeration, returned by {@link #children()} if a node has no + * children. + */ + public static final Enumeration<TreeNode> EMPTY_ENUMERATION = + new EmptyEnumeration<TreeNode>(); + + /** + * The parent of this node (possibly <code>null</code>). + */ + protected MutableTreeNode parent; + + /** + * The child nodes for this node (may be empty). + */ + protected Vector<MutableTreeNode> children = new Vector<MutableTreeNode>(); + + /** + * userObject + */ + protected transient Object userObject; + + /** + * allowsChildren + */ + protected boolean allowsChildren; + + /** + * Creates a <code>DefaultMutableTreeNode</code> object. + * This is equivalent to <code>DefaultMutableTreeNode(null, true)</code>. + */ + public DefaultMutableTreeNode() + { + this(null, true); + } + + /** + * Creates a <code>DefaultMutableTreeNode</code> object with the given + * user object attached to it. This is equivalent to + * <code>DefaultMutableTreeNode(userObject, true)</code>. + * + * @param userObject the user object (<code>null</code> permitted). + */ + public DefaultMutableTreeNode(Object userObject) + { + this(userObject, true); + } + + /** + * Creates a <code>DefaultMutableTreeNode</code> object with the given + * user object attached to it. + * + * @param userObject the user object (<code>null</code> permitted). + * @param allowsChildren <code>true</code> if the code allows to add child + * nodes, <code>false</code> otherwise + */ + public DefaultMutableTreeNode(Object userObject, boolean allowsChildren) + { + this.userObject = userObject; + this.allowsChildren = allowsChildren; + } + + /** + * Returns a clone of the node. The clone contains a shallow copy of the + * user object, and does not copy the parent node or the child nodes. + * + * @return A clone of the node. + */ + public Object clone() + { + return new DefaultMutableTreeNode(this.userObject, this.allowsChildren); + } + + /** + * Returns a string representation of the node. This implementation returns + * <code>getUserObject().toString()</code>, or <code>null</code> if there + * is no user object. + * + * @return A string representation of the node (possibly <code>null</code>). + */ + public String toString() + { + if (userObject == null) + return null; + + return userObject.toString(); + } + + /** + * Adds a new child node to this node and sets this node as the parent of + * the child node. The child node must not be an ancestor of this node. + * If the tree uses the {@link DefaultTreeModel}, you must subsequently + * call {@link DefaultTreeModel#reload(TreeNode)}. + * + * @param child the child node (<code>null</code> not permitted). + * + * @throws IllegalStateException if {@link #getAllowsChildren()} returns + * <code>false</code>. + * @throws IllegalArgumentException if {@link #isNodeAncestor} returns + * <code>true</code>. + * @throws IllegalArgumentException if <code>child</code> is + * <code>null</code>. + */ + public void add(MutableTreeNode child) + { + if (! allowsChildren) + throw new IllegalStateException(); + + if (child == null) + throw new IllegalArgumentException(); + + if (isNodeAncestor(child)) + throw new IllegalArgumentException("Cannot add ancestor node."); + + children.add(child); + child.setParent(this); + } + + /** + * Returns the parent node of this node. + * + * @return The parent node (possibly <code>null</code>). + */ + public TreeNode getParent() + { + return parent; + } + + /** + * Removes the child with the given index from this node. + * + * @param index the index (in the range <code>0</code> to + * <code>getChildCount() - 1</code>). + * + * @throws ArrayIndexOutOfBoundsException if <code>index</code> is outside + * the valid range. + */ + public void remove(int index) + { + MutableTreeNode child = children.remove(index); + child.setParent(null); + } + + /** + * Removes the given child from this node and sets its parent to + * <code>null</code>. + * + * @param node the child node (<code>null</code> not permitted). + * + * @throws IllegalArgumentException if <code>node</code> is not a child of + * this node. + * @throws IllegalArgumentException if <code>node</code> is null. + */ + public void remove(MutableTreeNode node) + { + if (node == null) + throw new IllegalArgumentException("Null 'node' argument."); + if (node.getParent() != this) + throw new IllegalArgumentException( + "The given 'node' is not a child of this node."); + children.remove(node); + node.setParent(null); + } + + /** + * writeObject + * + * @param stream the output stream + * + * @exception IOException If an error occurs + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + // TODO: Implement me. + } + + /** + * readObject + * + * @param stream the input stream + * + * @exception IOException If an error occurs + * @exception ClassNotFoundException TODO + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + // TODO: Implement me. + } + + /** + * Inserts given child node at the given index. + * + * @param node the child node (<code>null</code> not permitted). + * @param index the index. + * + * @throws IllegalArgumentException if <code>node</code> is + * </code>null</code>. + */ + public void insert(MutableTreeNode node, int index) + { + if (! allowsChildren) + throw new IllegalStateException(); + + if (node == null) + throw new IllegalArgumentException("Null 'node' argument."); + + if (isNodeAncestor(node)) + throw new IllegalArgumentException("Cannot insert ancestor node."); + + children.insertElementAt(node, index); + } + + /** + * Returns a path to this node from the root. + * + * @return an array of tree nodes + */ + public TreeNode[] getPath() + { + return getPathToRoot(this, 0); + } + + /** + * Returns an enumeration containing all children of this node. + * <code>EMPTY_ENUMERATION</code> is returned if this node has no children. + * + * @return an enumeration of tree nodes + */ + @SuppressWarnings("unchecked") // Required for API compatibility + public Enumeration children() + { + if (children.size() == 0) + return EMPTY_ENUMERATION; + + return children.elements(); + } + + /** + * Set the parent node for this node. + * + * @param node the parent node + */ + public void setParent(MutableTreeNode node) + { + parent = node; + } + + /** + * Returns the child node at a given index. + * + * @param index the index + * + * @return the child node + */ + public TreeNode getChildAt(int index) + { + return children.elementAt(index); + } + + /** + * Returns the number of children of this node. + * + * @return the number of children + */ + public int getChildCount() + { + return children.size(); + } + + /** + * Returns the index of the specified child node, or -1 if the node is not + * in fact a child of this node. + * + * @param node the node (<code>null</code> not permitted). + * + * @return The index of the specified child node, or -1. + * + * @throws IllegalArgumentException if <code>node</code> is <code>null</code>. + */ + public int getIndex(TreeNode node) + { + if (node == null) + throw new IllegalArgumentException("Null 'node' argument."); + return children.indexOf(node); + } + + /** + * Sets the flag that controls whether or not this node allows the addition / + * insertion of child nodes. If the flag is set to <code>false</code>, any + * existing children are removed. + * + * @param allowsChildren the flag. + */ + public void setAllowsChildren(boolean allowsChildren) + { + if (!allowsChildren) + removeAllChildren(); + this.allowsChildren = allowsChildren; + } + + /** + * getAllowsChildren + * + * @return boolean + */ + public boolean getAllowsChildren() + { + return allowsChildren; + } + + /** + * Sets the user object for this node + * + * @param userObject the user object + */ + public void setUserObject(Object userObject) + { + this.userObject = userObject; + } + + /** + * Returns the user object attached to this node. <code>null</code> is + * returned when no user object is set. + * + * @return the user object + */ + public Object getUserObject() + { + return userObject; + } + + /** + * Removes this node from its parent. + */ + public void removeFromParent() + { + parent.remove(this); + parent = null; + } + + /** + * Removes all child nodes from this node. + */ + public void removeAllChildren() + { + for (int i = getChildCount() - 1; i >= 0; i--) + remove(i); + } + + /** + * Returns <code>true</code> if <code>node</code> is an ancestor of this + * tree node, and <code>false</code> otherwise. An ancestor node is any of: + * <ul> + * <li>this tree node;</li> + * <li>the parent node (if there is one);</li> + * <li>any ancestor of the parent node;</li> + * </ul> + * If <code>node</code> is <code>null</code>, this method returns + * <code>false</code>. + * + * @param node the node (<code>null</code> permitted). + * + * @return A boolean. + */ + public boolean isNodeAncestor(TreeNode node) + { + if (node == null) + return false; + + TreeNode current = this; + + while (current != null && current != node) + current = current.getParent(); + + return current == node; + } + + /** + * Returns <code>true</code> if <code>node</code> is a descendant of this + * tree node, and <code>false</code> otherwise. A descendant node is any of: + * <ul> + * <li>this tree node;</li> + * <li>the child nodes belonging to this tree node, if there are any;</li> + * <li>any descendants of the child nodes;</li> + * </ul> + * If <code>node</code> is <code>null</code>, this method returns + * <code>false</code>. + * + * @param node the node (<code>null</code> permitted). + * + * @return A boolean. + */ + public boolean isNodeDescendant(DefaultMutableTreeNode node) + { + if (node == null) + return false; + + TreeNode current = node; + + while (current != null + && current != this) + current = current.getParent(); + + return current == this; + } + + /** + * getSharedAncestor + * + * @param node TODO + * + * @return TreeNode + */ + public TreeNode getSharedAncestor(DefaultMutableTreeNode node) + { + TreeNode current = this; + ArrayList<TreeNode> list = new ArrayList<TreeNode>(); + + while (current != null) + { + list.add(current); + current = current.getParent(); + } + + current = node; + + while (current != null) + { + if (list.contains(current)) + return current; + + current = current.getParent(); + } + + return null; + } + + /** + * isNodeRelated + * + * @param node TODO + * + * @return boolean + */ + public boolean isNodeRelated(DefaultMutableTreeNode node) + { + if (node == null) + return false; + + return node.getRoot() == getRoot(); + } + + /** + * getDepth + * + * @return int + */ + public int getDepth() + { + if ((! allowsChildren) + || children.size() == 0) + return 0; + + Stack<Integer> stack = new Stack<Integer>(); + stack.push(new Integer(0)); + TreeNode node = getChildAt(0); + int depth = 0; + int current = 1; + + while (! stack.empty()) + { + if (node.getChildCount() != 0) + { + node = node.getChildAt(0); + stack.push(new Integer(0)); + current++; + } + else + { + if (current > depth) + depth = current; + + int size; + int index; + + do + { + node = node.getParent(); + size = node.getChildCount(); + index = stack.pop().intValue() + 1; + current--; + } + while (index >= size + && node != this); + + if (index < size) + { + node = node.getChildAt(index); + stack.push(new Integer(index)); + current++; + } + } + } + + return depth; + } + + /** + * getLevel + * + * @return int + */ + public int getLevel() + { + int count = -1; + TreeNode current = this; + + do + { + current = current.getParent(); + count++; + } + while (current != null); + + return count; + } + + /** + * getPathToRoot + * + * @param node TODO + * @param depth TODO + * + * @return TreeNode[] + */ + protected TreeNode[] getPathToRoot(TreeNode node, int depth) + { + if (node == null) + { + if (depth == 0) + return null; + + return new TreeNode[depth]; + } + + TreeNode[] path = getPathToRoot(node.getParent(), depth + 1); + path[path.length - depth - 1] = node; + return path; + } + + /** + * getUserObjectPath + * + * @return Object[] + */ + public Object[] getUserObjectPath() + { + TreeNode[] path = getPathToRoot(this, 0); + Object[] object = new Object[path.length]; + + for (int index = 0; index < path.length; ++index) + object[index] = ((DefaultMutableTreeNode) path[index]).getUserObject(); + + return object; + } + + /** + * Returns the root node by iterating the parents of this node. + * + * @return the root node + */ + public TreeNode getRoot() + { + TreeNode current = this; + TreeNode check = current.getParent(); + + while (check != null) + { + current = check; + check = current.getParent(); + } + + return current; + } + + /** + * Tells whether this node is the root node or not. + * + * @return <code>true</code> if this is the root node, + * <code>false</code>otherwise + */ + public boolean isRoot() + { + return parent == null; + } + + /** + * getNextNode + * + * @return DefaultMutableTreeNode + */ + public DefaultMutableTreeNode getNextNode() + { + // Return first child. + if (getChildCount() != 0) + return (DefaultMutableTreeNode) getChildAt(0); + + // Return next sibling (if needed the sibling of some parent). + DefaultMutableTreeNode node = this; + DefaultMutableTreeNode sibling; + + do + { + sibling = node.getNextSibling(); + node = (DefaultMutableTreeNode) node.getParent(); + } + while (sibling == null && + node != null); + + // Return sibling. + return sibling; + } + + /** + * getPreviousNode + * + * @return DefaultMutableTreeNode + */ + public DefaultMutableTreeNode getPreviousNode() + { + // Return null if no parent. + if (parent == null) + return null; + + DefaultMutableTreeNode sibling = getPreviousSibling(); + + // Return parent if no sibling. + if (sibling == null) + return (DefaultMutableTreeNode) parent; + + // Return last leaf of sibling. + if (sibling.getChildCount() != 0) + return sibling.getLastLeaf(); + + // Return sibling. + return sibling; + } + + /** + * preorderEnumeration + * + * @return Enumeration + */ + @SuppressWarnings("unchecked") // Required for API compatibility + public Enumeration preorderEnumeration() + { + return new PreorderEnumeration(this); + } + + /** + * postorderEnumeration + * + * @return Enumeration + */ + @SuppressWarnings("unchecked") // Required for API compatibility + public Enumeration postorderEnumeration() + { + return new PostorderEnumeration(this); + } + + /** + * breadthFirstEnumeration + * + * @return Enumeration + */ + @SuppressWarnings("unchecked") // Required for API compatibility + public Enumeration breadthFirstEnumeration() + { + return new BreadthFirstEnumeration(this); + } + + /** + * depthFirstEnumeration + * + * @return Enumeration + */ + @SuppressWarnings("unchecked") // Required for API compatibility + public Enumeration depthFirstEnumeration() + { + return postorderEnumeration(); + } + + /** + * pathFromAncestorEnumeration + * + * @param node TODO + * + * @return Enumeration + */ + @SuppressWarnings("unchecked") // Required for API compatibility + public Enumeration pathFromAncestorEnumeration(TreeNode node) + { + if (node == null) + throw new IllegalArgumentException(); + + TreeNode parent = this; + Vector<TreeNode> nodes = new Vector<TreeNode>(); + nodes.add(this); + + while (parent != node && parent != null) + { + parent = parent.getParent(); + nodes.add(0, parent); + } + + if (parent != node) + throw new IllegalArgumentException(); + + return nodes.elements(); + } + + /** + * Returns <code>true</code> if <code>node</code> is a child of this tree + * node, and <code>false</code> otherwise. If <code>node</code> is + * <code>null</code>, this method returns <code>false</code>. + * + * @param node the node (<code>null</code> permitted). + * + * @return A boolean. + */ + public boolean isNodeChild(TreeNode node) + { + if (node == null) + return false; + + return node.getParent() == this; + } + + /** + * Returns the first child node belonging to this tree node. + * + * @return The first child node. + * + * @throws NoSuchElementException if this tree node has no children. + */ + public TreeNode getFirstChild() + { + return children.firstElement(); + } + + /** + * Returns the last child node belonging to this tree node. + * + * @return The last child node. + * + * @throws NoSuchElementException if this tree node has no children. + */ + public TreeNode getLastChild() + { + return children.lastElement(); + } + + /** + * Returns the next child after the specified <code>node</code>, or + * <code>null</code> if there is no child after the specified + * <code>node</code>. + * + * @param node a child of this node (<code>null</code> not permitted). + * + * @return The next child, or <code>null</code>. + * + * @throws IllegalArgumentException if <code>node</code> is not a child of + * this node, or is <code>null</code>. + */ + public TreeNode getChildAfter(TreeNode node) + { + if (node == null || node.getParent() != this) + throw new IllegalArgumentException(); + + int index = getIndex(node) + 1; + + if (index == getChildCount()) + return null; + + return getChildAt(index); + } + + /** + * Returns the previous child before the specified <code>node</code>, or + * <code>null</code> if there is no child before the specified + * <code>node</code>. + * + * @param node a child of this node (<code>null</code> not permitted). + * + * @return The previous child, or <code>null</code>. + * + * @throws IllegalArgumentException if <code>node</code> is not a child of + * this node, or is <code>null</code>. + */ + public TreeNode getChildBefore(TreeNode node) + { + if (node == null || node.getParent() != this) + throw new IllegalArgumentException(); + + int index = getIndex(node) - 1; + + if (index < 0) + return null; + + return getChildAt(index); + } + + /** + * Returns <code>true</code> if this tree node and <code>node</code> share + * the same parent. If <code>node</code> is this tree node, the method + * returns <code>true</code> and if <code>node</code> is <code>null</code> + * this method returns <code>false</code>. + * + * @param node the node (<code>null</code> permitted). + * + * @return A boolean. + */ + public boolean isNodeSibling(TreeNode node) + { + if (node == null) + return false; + if (node == this) + return true; + return node.getParent() == getParent() && getParent() != null; + } + + /** + * Returns the number of siblings for this tree node. If the tree node has + * a parent, this method returns the child count for the parent, otherwise + * it returns <code>1</code>. + * + * @return The sibling count. + */ + public int getSiblingCount() + { + if (parent == null) + return 1; + + return parent.getChildCount(); + } + + /** + * Returns the next sibling for this tree node. If this node has no parent, + * or this node is the last child of its parent, this method returns + * <code>null</code>. + * + * @return The next sibling, or <code>null</code>. + */ + public DefaultMutableTreeNode getNextSibling() + { + if (parent == null) + return null; + + int index = parent.getIndex(this) + 1; + + if (index == parent.getChildCount()) + return null; + + return (DefaultMutableTreeNode) parent.getChildAt(index); + } + + /** + * Returns the previous sibling for this tree node. If this node has no + * parent, or this node is the first child of its parent, this method returns + * <code>null</code>. + * + * @return The previous sibling, or <code>null</code>. + */ + public DefaultMutableTreeNode getPreviousSibling() + { + if (parent == null) + return null; + + int index = parent.getIndex(this) - 1; + + if (index < 0) + return null; + + return (DefaultMutableTreeNode) parent.getChildAt(index); + } + + /** + * Returns <code>true</code> if this tree node is a lead node (that is, it + * has no children), and <code>false</otherwise>. + * + * @return A boolean. + */ + public boolean isLeaf() + { + return children.size() == 0; + } + + /** + * Returns the first leaf node that is a descendant of this node. Recall + * that a node is its own descendant, so if this node has no children then + * it is returned as the first leaf. + * + * @return The first leaf node. + */ + public DefaultMutableTreeNode getFirstLeaf() + { + TreeNode current = this; + + while (current.getChildCount() > 0) + current = current.getChildAt(0); + + return (DefaultMutableTreeNode) current; + } + + /** + * Returns the last leaf node that is a descendant of this node. Recall + * that a node is its own descendant, so if this node has no children then + * it is returned as the last leaf. + * + * @return The first leaf node. + */ + public DefaultMutableTreeNode getLastLeaf() + { + TreeNode current = this; + int size = current.getChildCount(); + + while (size > 0) + { + current = current.getChildAt(size - 1); + size = current.getChildCount(); + } + + return (DefaultMutableTreeNode) current; + } + + /** + * Returns the next leaf node after this tree node. + * + * @return The next leaf node, or <code>null</code>. + */ + public DefaultMutableTreeNode getNextLeaf() + { + // if there is a next sibling, return its first leaf + DefaultMutableTreeNode sibling = getNextSibling(); + if (sibling != null) + return sibling.getFirstLeaf(); + // otherwise move up one level and try again... + if (parent != null) + return ((DefaultMutableTreeNode) parent).getNextLeaf(); + return null; + } + + /** + * Returns the previous leaf node before this tree node. + * + * @return The previous leaf node, or <code>null</code>. + */ + public DefaultMutableTreeNode getPreviousLeaf() + { + // if there is a previous sibling, return its last leaf + DefaultMutableTreeNode sibling = getPreviousSibling(); + if (sibling != null) + return sibling.getLastLeaf(); + // otherwise move up one level and try again... + if (parent != null) + return ((DefaultMutableTreeNode) parent).getPreviousLeaf(); + return null; + } + + /** + * getLeafCount + * + * @return int + */ + public int getLeafCount() + { + int count = 0; + Enumeration<?> e = depthFirstEnumeration(); + + while (e.hasMoreElements()) + { + TreeNode current = (TreeNode) e.nextElement(); + + if (current.isLeaf()) + count++; + } + + return count; + } + + /** Provides an enumeration of a tree in breadth-first traversal + * order. + */ + static class BreadthFirstEnumeration implements Enumeration<TreeNode> + { + + LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); + + BreadthFirstEnumeration(TreeNode node) + { + queue.add(node); + } + + public boolean hasMoreElements() + { + return !queue.isEmpty(); + } + + @SuppressWarnings("unchecked") + public TreeNode nextElement() + { + if (queue.isEmpty()) + throw new NoSuchElementException("No more elements left."); + + TreeNode node = queue.removeFirst(); + + Enumeration<TreeNode> children = + (Enumeration<TreeNode>) node.children(); + while (children.hasMoreElements()) + queue.add(children.nextElement()); + + return node; + } + } + + /** Provides an enumeration of a tree traversing it + * preordered. + */ + static class PreorderEnumeration implements Enumeration<TreeNode> + { + TreeNode next; + + Stack<Enumeration<TreeNode>> childrenEnums = + new Stack<Enumeration<TreeNode>>(); + + @SuppressWarnings("unchecked") + PreorderEnumeration(TreeNode node) + { + next = node; + childrenEnums.push((Enumeration<TreeNode>) node.children()); + } + + public boolean hasMoreElements() + { + return next != null; + } + + public TreeNode nextElement() + { + if (next == null) + throw new NoSuchElementException("No more elements left."); + + TreeNode current = next; + + Enumeration<TreeNode> children = childrenEnums.peek(); + + // Retrieves the next element. + next = traverse(children); + + return current; + } + + @SuppressWarnings("unchecked") + private TreeNode traverse(Enumeration<TreeNode> children) + { + // If more children are available step down. + if (children.hasMoreElements()) + { + TreeNode child = children.nextElement(); + childrenEnums.push((Enumeration<TreeNode>) child.children()); + + return child; + } + + // If no children are left, we return to a higher level. + childrenEnums.pop(); + + // If there are no more levels left, there is no next + // element to return. + if (childrenEnums.isEmpty()) + return null; + else + { + return traverse(childrenEnums.peek()); + } + } + } + + /** Provides an enumeration of a tree traversing it + * postordered (= depth-first). + */ + static class PostorderEnumeration implements Enumeration<TreeNode> + { + + Stack<TreeNode> nodes = new Stack<TreeNode>(); + Stack<Enumeration<TreeNode>> childrenEnums = + new Stack<Enumeration<TreeNode>>(); + + @SuppressWarnings("unchecked") + PostorderEnumeration(TreeNode node) + { + nodes.push(node); + childrenEnums.push((Enumeration<TreeNode>) node.children()); + } + + public boolean hasMoreElements() + { + return !nodes.isEmpty(); + } + + public TreeNode nextElement() + { + if (nodes.isEmpty()) + throw new NoSuchElementException("No more elements left!"); + + Enumeration<TreeNode> children = childrenEnums.peek(); + + return traverse(children); + } + + @SuppressWarnings("unchecked") + private TreeNode traverse(Enumeration<TreeNode> children) + { + if (children.hasMoreElements()) + { + TreeNode node = children.nextElement(); + nodes.push(node); + + Enumeration<TreeNode> newChildren = + (Enumeration<TreeNode>) node.children(); + childrenEnums.push(newChildren); + + return traverse(newChildren); + } + else + { + childrenEnums.pop(); + + // Returns the node whose children + // have all been visited. (= postorder) + TreeNode next = nodes.peek(); + nodes.pop(); + + return next; + } + } + + } + +} diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java new file mode 100644 index 000000000..9ee0a14ba --- /dev/null +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java @@ -0,0 +1,792 @@ +/* DefaultTreeCellEditor.java -- + Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EventObject; + +import javax.swing.DefaultCellEditor; +import javax.swing.Icon; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.CellEditorListener; +import javax.swing.event.EventListenerList; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; + +/** + * Participates in the tree cell editing. + * + * @author Andrew Selkirk + * @author Audrius Meskauskas + */ +public class DefaultTreeCellEditor + implements ActionListener, TreeCellEditor, TreeSelectionListener +{ + /** + * This container that appears on the tree during editing session. + * It contains the editing component displays various other editor - + * specific parts like editing icon. + */ + public class EditorContainer extends Container + { + /** + * Use v 1.5 serial version UID for interoperability. + */ + static final long serialVersionUID = 6470339600449699810L; + + /** + * Creates an <code>EditorContainer</code> object. + */ + public EditorContainer() + { + setLayout(null); + } + + /** + * This method only exists for API compatibility and is useless as it does + * nothing. It got probably introduced by accident. + */ + public void EditorContainer() + { + // Do nothing here. + } + + /** + * Overrides Container.paint to paint the node's icon and use the selection + * color for the background. + * + * @param g - + * the specified Graphics window + */ + public void paint(Graphics g) + { + // Paint editing icon. + if (editingIcon != null) + { + // From the previous version, the left margin is taken as half + // of the icon width. + int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2); + editingIcon.paintIcon(this, g, 0, y); + } + // Paint border. + Color c = getBorderSelectionColor(); + if (c != null) + { + g.setColor(c); + g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); + } + super.paint(g); + } + + /** + * Lays out this Container, moving the editor component to the left + * (leaving place for the icon). + */ + public void doLayout() + { + if (editingComponent != null) + { + editingComponent.getPreferredSize(); + editingComponent.setBounds(offset, 0, getWidth() - offset, + getHeight()); + } + } + + public Dimension getPreferredSize() + { + Dimension dim; + if (editingComponent != null) + { + dim = editingComponent.getPreferredSize(); + dim.width += offset + 5; + if (renderer != null) + { + Dimension r = renderer.getPreferredSize(); + dim.height = Math.max(dim.height, r.height); + } + if (editingIcon != null) + dim.height = Math.max(dim.height, editingIcon.getIconHeight()); + dim.width = Math.max(100, dim.width); + } + else + dim = new Dimension(0, 0); + return dim; + } + } + + /** + * The default text field, used in the editing sessions. + */ + public class DefaultTextField extends JTextField + { + /** + * Use v 1.5 serial version UID for interoperability. + */ + static final long serialVersionUID = -6629304544265300143L; + + /** + * The border of the text field. + */ + protected Border border; + + /** + * Creates a <code>DefaultTextField</code> object. + * + * @param aBorder the border to use + */ + public DefaultTextField(Border aBorder) + { + border = aBorder; + } + + /** + * Gets the font of this component. + * @return this component's font; if a font has not been set for + * this component, the font of its parent is returned (if the parent + * is not null, otherwise null is returned). + */ + public Font getFont() + { + Font font = super.getFont(); + if (font == null) + { + Component parent = getParent(); + if (parent != null) + return parent.getFont(); + return null; + } + return font; + } + + /** + * Returns the border of the text field. + * + * @return the border + */ + public Border getBorder() + { + return border; + } + + /** + * Overrides JTextField.getPreferredSize to return the preferred size + * based on current font, if set, or else use renderer's font. + * + * @return the Dimension of this textfield. + */ + public Dimension getPreferredSize() + { + Dimension size = super.getPreferredSize(); + if (renderer != null && DefaultTreeCellEditor.this.getFont() == null) + { + size.height = renderer.getPreferredSize().height; + } + return renderer.getPreferredSize(); + } + } + + private EventListenerList listenerList = new EventListenerList(); + + /** + * Editor handling the editing. + */ + protected TreeCellEditor realEditor; + + /** + * Renderer, used to get border and offsets from. + */ + protected DefaultTreeCellRenderer renderer; + + /** + * Editing container, will contain the editorComponent. + */ + protected Container editingContainer; + + /** + * Component used in editing, obtained from the editingContainer. + */ + protected transient Component editingComponent; + + /** + * As of Java 2 platform v1.4 this field should no longer be used. + * If you wish to provide similar behavior you should directly + * override isCellEditable. + */ + protected boolean canEdit; + + /** + * Used in editing. Indicates x position to place editingComponent. + */ + protected transient int offset; + + /** + * JTree instance listening too. + */ + protected transient JTree tree; + + /** + * Last path that was selected. + */ + protected transient TreePath lastPath; + + /** + * Used before starting the editing session. + */ + protected transient javax.swing.Timer timer; + + /** + * Row that was last passed into getTreeCellEditorComponent. + */ + protected transient int lastRow; + + /** + * True if the border selection color should be drawn. + */ + protected Color borderSelectionColor; + + /** + * Icon to use when editing. + */ + protected transient Icon editingIcon; + + /** + * Font to paint with, null indicates font of renderer is to be used. + */ + protected Font font; + + /** + * Constructs a DefaultTreeCellEditor object for a JTree using the + * specified renderer and a default editor. (Use this constructor + * for normal editing.) + * + * @param tree - a JTree object + * @param renderer - a DefaultTreeCellRenderer object + */ + public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) + { + this(tree, renderer, null); + } + + /** + * Constructs a DefaultTreeCellEditor object for a JTree using the specified + * renderer and the specified editor. (Use this constructor + * for specialized editing.) + * + * @param tree - a JTree object + * @param renderer - a DefaultTreeCellRenderer object + * @param editor - a TreeCellEditor object + */ + public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, + TreeCellEditor editor) + { + this.renderer = renderer; + realEditor = editor; + if (realEditor == null) + realEditor = createTreeCellEditor(); + editingContainer = createContainer(); + setTree(tree); + Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); + setBorderSelectionColor(c); + } + + /** + * writeObject + * + * @param value0 + * TODO + * @exception IOException + * TODO + */ + private void writeObject(ObjectOutputStream value0) throws IOException + { + // TODO + } + + /** + * readObject + * @param value0 TODO + * @exception IOException TODO + * @exception ClassNotFoundException TODO + */ + private void readObject(ObjectInputStream value0) + throws IOException, ClassNotFoundException + { + // TODO + } + + /** + * Sets the color to use for the border. + * @param newColor - the new border color + */ + public void setBorderSelectionColor(Color newColor) + { + this.borderSelectionColor = newColor; + } + + /** + * Returns the color the border is drawn. + * @return Color + */ + public Color getBorderSelectionColor() + { + return borderSelectionColor; + } + + /** + * Sets the font to edit with. null indicates the renderers + * font should be used. This will NOT override any font you have + * set in the editor the receiver was instantied with. If null for + * an editor was passed in, a default editor will be created that + * will pick up this font. + * + * @param font - the editing Font + */ + public void setFont(Font font) + { + if (font != null) + this.font = font; + else + this.font = renderer.getFont(); + } + + /** + * Gets the font used for editing. + * + * @return the editing font + */ + public Font getFont() + { + return font; + } + + /** + * Configures the editor. Passed onto the realEditor. + * Sets an initial value for the editor. This will cause + * the editor to stopEditing and lose any partially edited value + * if the editor is editing when this method is called. + * Returns the component that should be added to the client's Component + * hierarchy. Once installed in the client's hierarchy this component will + * then be able to draw and receive user input. + * + * @param tree - the JTree that is asking the editor to edit; this parameter can be null + * @param value - the value of the cell to be edited + * @param isSelected - true is the cell is to be rendered with selection highlighting + * @param expanded - true if the node is expanded + * @param leaf - true if the node is a leaf node + * @param row - the row index of the node being edited + * + * @return the component for editing + */ + public Component getTreeCellEditorComponent(JTree tree, Object value, + boolean isSelected, + boolean expanded, + boolean leaf, int row) + { + setTree(tree); + lastRow = row; + determineOffset(tree, value, isSelected, expanded, leaf, row); + if (editingComponent != null) + editingContainer.remove(editingComponent); + + editingComponent = realEditor.getTreeCellEditorComponent(tree, value, + isSelected, + expanded, leaf, + row); + Font f = getFont(); + if (f == null) + { + if (renderer != null) + f = renderer.getFont(); + if (f == null) + f = tree.getFont(); + } + editingContainer.setFont(f); + prepareForEditing(); + return editingContainer; + } + + /** + * Returns the value currently being edited (requests it from the + * {@link #realEditor}. + * + * @return the value currently being edited + */ + public Object getCellEditorValue() + { + return realEditor.getCellEditorValue(); + } + + /** + * If the realEditor returns true to this message, prepareForEditing + * is messaged and true is returned. + * + * @param event - the event the editor should use to consider whether to + * begin editing or not + * @return true if editing can be started + */ + public boolean isCellEditable(EventObject event) + { + boolean ret = false; + boolean ed = false; + if (event != null) + { + if (event.getSource() instanceof JTree) + { + setTree((JTree) event.getSource()); + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + TreePath path = tree.getPathForLocation(me.getX(), me.getY()); + ed = lastPath != null && path != null && lastPath.equals(path); + if (path != null) + { + lastRow = tree.getRowForPath(path); + Object val = path.getLastPathComponent(); + boolean isSelected = tree.isRowSelected(lastRow); + boolean isExpanded = tree.isExpanded(path); + TreeModel m = tree.getModel(); + boolean isLeaf = m.isLeaf(val); + determineOffset(tree, val, isSelected, isExpanded, isLeaf, + lastRow); + } + } + } + } + if (! realEditor.isCellEditable(event)) + ret = false; + else + { + if (canEditImmediately(event)) + ret = true; + else if (ed && shouldStartEditingTimer(event)) + startEditingTimer(); + else if (timer != null && timer.isRunning()) + timer.stop(); + } + if (ret) + prepareForEditing(); + return ret; + + } + + /** + * Messages the realEditor for the return value. + * + * @param event - + * the event the editor should use to start editing + * @return true if the editor would like the editing cell to be selected; + * otherwise returns false + */ + public boolean shouldSelectCell(EventObject event) + { + return true; + } + + /** + * If the realEditor will allow editing to stop, the realEditor + * is removed and true is returned, otherwise false is returned. + * @return true if editing was stopped; false otherwise + */ + public boolean stopCellEditing() + { + boolean ret = false; + if (realEditor.stopCellEditing()) + { + finish(); + ret = true; + } + return ret; + } + + /** + * Messages cancelCellEditing to the realEditor and removes it + * from this instance. + */ + public void cancelCellEditing() + { + realEditor.cancelCellEditing(); + finish(); + } + + private void finish() + { + if (editingComponent != null) + editingContainer.remove(editingComponent); + editingComponent = null; + } + + /** + * Adds a <code>CellEditorListener</code> object to this editor. + * + * @param listener + * the listener to add + */ + public void addCellEditorListener(CellEditorListener listener) + { + realEditor.addCellEditorListener(listener); + } + + /** + * Removes a <code>CellEditorListener</code> object. + * + * @param listener the listener to remove + */ + public void removeCellEditorListener(CellEditorListener listener) + { + realEditor.removeCellEditorListener(listener); + } + + /** + * Returns all added <code>CellEditorListener</code> objects to this editor. + * + * @return an array of listeners + * + * @since 1.4 + */ + public CellEditorListener[] getCellEditorListeners() + { + return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class); + } + + /** + * Resets lastPath. + * + * @param e - the event that characterizes the change. + */ + public void valueChanged(TreeSelectionEvent e) + { + if (tree != null) + { + if (tree.getSelectionCount() == 1) + lastPath = tree.getSelectionPath(); + else + lastPath = null; + } + // TODO: We really should do the following here, but can't due + // to buggy DefaultTreeSelectionModel. This selection model + // should only fire if the selection actually changes. +// if (timer != null) +// timer.stop(); + } + + /** + * Messaged when the timer fires. + * + * @param e the event that characterizes the action. + */ + public void actionPerformed(ActionEvent e) + { + if (tree != null && lastPath != null) + tree.startEditingAtPath(lastPath); + } + + /** + * Sets the tree currently editing for. This is needed to add a selection + * listener. + * + * @param newTree - + * the new tree to be edited + */ + protected void setTree(JTree newTree) + { + if (tree != newTree) + { + if (tree != null) + tree.removeTreeSelectionListener(this); + tree = newTree; + if (tree != null) + tree.addTreeSelectionListener(this); + + if (timer != null) + timer.stop(); + } + } + + /** + * Returns true if event is a MouseEvent and the click count is 1. + * + * @param event - the event being studied + * @return true if editing should start + */ + protected boolean shouldStartEditingTimer(EventObject event) + { + boolean ret = false; + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 + && inHitRegion(me.getX(), me.getY()); + } + return ret; + } + + /** + * Starts the editing timer (if one installed). + */ + protected void startEditingTimer() + { + if (timer == null) + { + timer = new Timer(1200, this); + timer.setRepeats(false); + } + timer.start(); + } + + /** + * Returns true if event is null, or it is a MouseEvent with + * a click count > 2 and inHitRegion returns true. + * + * @param event - the event being studied + * @return true if event is null, or it is a MouseEvent with + * a click count > 2 and inHitRegion returns true + */ + protected boolean canEditImmediately(EventObject event) + { + if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event). + getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(), + ((MouseEvent) event).getY()))) + return true; + return false; + } + + /** + * Returns true if the passed in location is a valid mouse location + * to start editing from. This is implemented to return false if x is + * less than or equal to the width of the icon and icon + * gap displayed by the renderer. In other words this returns true if + * the user clicks over the text part displayed by the renderer, and + * false otherwise. + * + * @param x - the x-coordinate of the point + * @param y - the y-coordinate of the point + * + * @return true if the passed in location is a valid mouse location + */ + protected boolean inHitRegion(int x, int y) + { + Rectangle bounds = tree.getPathBounds(lastPath); + return bounds.contains(x, y); + } + + /** + * determineOffset + * @param tree - + * @param value - + * @param isSelected - + * @param expanded - + * @param leaf - + * @param row - + */ + protected void determineOffset(JTree tree, Object value, boolean isSelected, + boolean expanded, boolean leaf, int row) + { + if (renderer != null) + { + if (leaf) + editingIcon = renderer.getLeafIcon(); + else if (expanded) + editingIcon = renderer.getOpenIcon(); + else + editingIcon = renderer.getClosedIcon(); + if (editingIcon != null) + offset = renderer.getIconTextGap() + editingIcon.getIconWidth(); + else + offset = renderer.getIconTextGap(); + } + else + { + editingIcon = null; + offset = 0; + } + } + + /** + * Invoked just before editing is to start. Will add the + * editingComponent to the editingContainer. + */ + protected void prepareForEditing() + { + if (editingComponent != null) + editingContainer.add(editingComponent); + } + + /** + * Creates the container to manage placement of editingComponent. + * + * @return the container to manage the placement of the editingComponent. + */ + protected Container createContainer() + { + return new DefaultTreeCellEditor.EditorContainer(); + } + + /** + * This is invoked if a TreeCellEditor is not supplied in the constructor. + * It returns a TextField editor. + * + * @return a new TextField editor + */ + protected TreeCellEditor createTreeCellEditor() + { + Border border = UIManager.getBorder("Tree.editorBorder"); + JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); + DefaultCellEditor editor = new DefaultCellEditor(tf); + editor.setClickCountToStart(1); + realEditor = editor; + return editor; + } +} diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java new file mode 100644 index 000000000..a2c8fc204 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java @@ -0,0 +1,720 @@ +/* DefaultTreeCellRenderer.java + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + + This file is part of GNU Classpath. + + GNU Classpath is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + GNU Classpath 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 for more details. + + You should have received a copy of the GNU General Public License + along with GNU Classpath; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + + +package javax.swing.tree; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Rectangle; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JTree; +import javax.swing.LookAndFeel; +import javax.swing.UIManager; +import javax.swing.plaf.UIResource; + +/** + * A default implementation of the {@link TreeCellRenderer} interface. + * + * @author Andrew Selkirk + */ +public class DefaultTreeCellRenderer + extends JLabel + implements TreeCellRenderer +{ + + /** + * A flag indicating the current selection status. + */ + protected boolean selected; + + /** + * A flag indicating the current focus status. + */ + protected boolean hasFocus; + + /** + * Indicates if the focus border is also drawn around the icon. + */ + private boolean drawsFocusBorderAroundIcon; + + /** + * The icon used to represent non-leaf nodes that are closed. + * + * @see #setClosedIcon(Icon) + */ + protected transient Icon closedIcon; + + /** + * The icon used to represent leaf nodes. + * + * @see #setLeafIcon(Icon) + */ + protected transient Icon leafIcon; + + /** + * The icon used to represent non-leaf nodes that are open. + * + * @see #setOpenIcon(Icon) + */ + protected transient Icon openIcon; + + /** + * The color used for text in selected cells. + * + * @see #setTextSelectionColor(Color) + */ + protected Color textSelectionColor; + + /** + * The color used for text in non-selected cells. + * + * @see #setTextNonSelectionColor(Color) + */ + protected Color textNonSelectionColor; + + /** + * The background color for selected cells. + * + * @see #setBackgroundSelectionColor(Color) + */ + protected Color backgroundSelectionColor; + + /** + * The background color for non-selected cells. + * + * @see #setBackgroundNonSelectionColor(Color) + */ + protected Color backgroundNonSelectionColor; + + /** + * The border color for selected tree cells. + * + * @see #setBorderSelectionColor(Color) + */ + protected Color borderSelectionColor; + + /** + * Creates a new tree cell renderer with defaults appropriate for the + * current {@link LookAndFeel}. + */ + public DefaultTreeCellRenderer() + { + setLeafIcon(getDefaultLeafIcon()); + setOpenIcon(getDefaultOpenIcon()); + setClosedIcon(getDefaultClosedIcon()); + + setTextNonSelectionColor(UIManager.getColor("Tree.textForeground")); + setTextSelectionColor(UIManager.getColor("Tree.selectionForeground")); + setBackgroundNonSelectionColor(UIManager.getColor("Tree.textBackground")); + setBackgroundSelectionColor(UIManager.getColor("Tree.selectionBackground")); + setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor")); + Object val = UIManager.get("Tree.drawsFocusBorderAroundIcon"); + drawsFocusBorderAroundIcon = val != null && ((Boolean) val).booleanValue(); + } + + /** + * Returns the default icon for non-leaf tree cells that are open (expanded). + * The icon is fetched from the defaults table for the current + * {@link LookAndFeel} using the key <code>Tree.openIcon</code>. + * + * @return The default icon. + */ + public Icon getDefaultOpenIcon() + { + return UIManager.getIcon("Tree.openIcon"); + } + + /** + * Returns the default icon for non-leaf tree cells that are closed (not + * expanded). The icon is fetched from the defaults table for the current + * {@link LookAndFeel} using the key <code>Tree.closedIcon</code>. + * + * @return The default icon. + */ + public Icon getDefaultClosedIcon() + { + return UIManager.getIcon("Tree.closedIcon"); + } + + /** + * Returns the default icon for leaf tree cells. The icon is fetched from + * the defaults table for the current {@link LookAndFeel} using the key + * <code>Tree.leafIcon</code>. + * + * @return The default icon. + */ + public Icon getDefaultLeafIcon() + { + return UIManager.getIcon("Tree.leafIcon"); + } + + /** + * Sets the icon to be displayed for non-leaf nodes that are open (expanded). + * Set this to <code>null</code> if no icon is required. + * + * @param icon the icon (<code>null</code> permitted). + * + * @see #getOpenIcon() + */ + public void setOpenIcon(Icon icon) + { + openIcon = icon; + } + + /** + * Returns the icon displayed for non-leaf nodes that are open (expanded). + * The default value is initialised from the {@link LookAndFeel}. + * + * @return The open icon (possibly <code>null</code>). + * + * @see #setOpenIcon(Icon) + */ + public Icon getOpenIcon() + { + return openIcon; + } + + /** + * Sets the icon to be displayed for non-leaf nodes that are closed. Set + * this to <code>null</code> if no icon is required. + * + * @param icon the icon (<code>null</code> permitted). + * + * @see #getClosedIcon() + */ + public void setClosedIcon(Icon icon) + { + closedIcon = icon; + } + + /** + * Returns the icon displayed for non-leaf nodes that are closed. The + * default value is initialised from the {@link LookAndFeel}. + * + * @return The closed icon (possibly <code>null</code>). + * + * @see #setClosedIcon(Icon) + */ + public Icon getClosedIcon() + { + return closedIcon; + } + + /** + * Sets the icon to be displayed for leaf nodes. Set this to + * <code>null</code> if no icon is required. + * + * @param icon the icon (<code>null</code> permitted). + * + * @see #getLeafIcon() + */ + public void setLeafIcon(Icon icon) + { + leafIcon = icon; + } + + /** + * Returns the icon displayed for leaf nodes. The default value is + * initialised from the {@link LookAndFeel}. + * + * @return The leaf icon (possibly <code>null</code>). + * + * @see #setLeafIcon(Icon) + */ + public Icon getLeafIcon() + { + return leafIcon; + } + + /** + * Sets the text color for tree cells that are selected. + * + * @param c the color (<code>null</code> permitted). + * + * @see #getTextSelectionColor() + */ + public void setTextSelectionColor(Color c) + { + textSelectionColor = c; + } + + /** + * Returns the text color for tree cells that are selected. + * The default value is obtained from the {@link LookAndFeel} defaults + * table using the key <code>Tree.selectionForeground</code>. + * + * @return The text color for tree cells that are selected. + * + * @see #setTextSelectionColor(Color) + */ + public Color getTextSelectionColor() + { + return textSelectionColor; + } + + /** + * Sets the text color for tree cells that are not selected. + * + * @param c the color (<code>null</code> permitted). + * + * @see #getTextNonSelectionColor() + */ + public void setTextNonSelectionColor(Color c) + { + textNonSelectionColor = c; + } + + /** + * Returns the text color for tree cells that are not selected. + * The default value is obtained from the {@link LookAndFeel} defaults + * table using the key <code>Tree.selectionForeground</code>. + * + * @return The background color for tree cells that are not selected. + * + * @see #setTextgroundNonSelectionColor(Color) + */ + public Color getTextNonSelectionColor() + { + return textNonSelectionColor; + } + + /** + * Sets the background color for tree cells that are selected. + * + * @param c the color (<code>null</code> permitted). + * + * @see #getBackgroundSelectionColor() + */ + public void setBackgroundSelectionColor(Color c) + { + backgroundSelectionColor = c; + } + + /** + * Returns the background color for tree cells that are selected. + * The default value is obtained from the {@link LookAndFeel} defaults + * table using the key <code>Tree.selectionBackground</code>. + * + * @return The background color for tree cells that are selected. + * + * @see #setBackgroundSelectionColor(Color) + */ + public Color getBackgroundSelectionColor() + { + return backgroundSelectionColor; + } + + /** + * Sets the background color for tree cells that are not selected. + * + * @param c the color (<code>null</code> permitted). + * + * @see #getBackgroundNonSelectionColor() + */ + public void setBackgroundNonSelectionColor(Color c) + { + backgroundNonSelectionColor = c; + } + + /** + * Returns the background color for tree cells that are not selected. + * The default value is obtained from the {@link LookAndFeel} defaults + * table using the key <code>Tree.textBackground</code>. + * + * @return The background color for tree cells that are not selected. + * + * @see #setBackgroundNonSelectionColor(Color) + */ + public Color getBackgroundNonSelectionColor() + { + return backgroundNonSelectionColor; + } + + /** + * Sets the border color for tree cells that are selected. + * + * @param c the color (<code>null</code> permitted). + * + * @see #getBorderSelectionColor() + */ + public void setBorderSelectionColor(Color c) + { + borderSelectionColor = c; + } + + /** + * Returns the border color for tree cells that are selected. + * The default value is obtained from the {@link LookAndFeel} defaults + * table using the key <code>Tree.selectionBorderColor</code>. + * + * @return The border color for tree cells that are selected. + * + * @see #setBorderSelectionColor(Color) + */ + public Color getBorderSelectionColor() + { + return borderSelectionColor; + } + + /** + * Sets the font. + * + * @param f the font. + * + * @see #getFont() + */ + public void setFont(Font f) + { + if (f != null && f instanceof UIResource) + f = null; + super.setFont(f); + } + + /** + * Sets the background color. + * + * @param c the color. + */ + public void setBackground(Color c) + { + if (c != null && c instanceof UIResource) + c = null; + super.setBackground(c); + } + + /** + * Returns a component (in fact <code>this</code>) that can be used to + * render a tree cell with the specified state. + * + * @param tree the tree that the cell belongs to. + * @param val the cell value. + * @param selected indicates whether or not the cell is selected. + * @param expanded indicates whether or not the cell is expanded. + * @param leaf indicates whether or not the cell is a leaf in the tree. + * @param row the row index. + * @param hasFocus indicates whether or not the cell has the focus. + * + * @return <code>this</code>. + */ + public Component getTreeCellRendererComponent(JTree tree, Object val, + boolean selected, + boolean expanded, boolean leaf, + int row, boolean hasFocus) + { + if (leaf) + setIcon(getLeafIcon()); + else if (expanded) + setIcon(getOpenIcon()); + else + setIcon(getClosedIcon()); + + setText(val.toString()); + this.selected = selected; + this.hasFocus = hasFocus; + setHorizontalAlignment(LEFT); + setOpaque(false); + setVerticalAlignment(CENTER); + setEnabled(true); + super.setFont(UIManager.getFont("Tree.font")); + + if (selected) + { + super.setBackground(getBackgroundSelectionColor()); + setForeground(getTextSelectionColor()); + + if (hasFocus) + setBorderSelectionColor(UIManager.getLookAndFeelDefaults(). + getColor("Tree.selectionBorderColor")); + else + setBorderSelectionColor(null); + } + else + { + super.setBackground(getBackgroundNonSelectionColor()); + setForeground(getTextNonSelectionColor()); + setBorderSelectionColor(null); + } + + return this; + } + + /** + * Returns the current font. + * + * @return The current font. + * + * @see #setFont(Font) + */ + public Font getFont() + { + return super.getFont(); + } + + /** + * Paints the value. The background is filled based on selected. + * + * @param g the graphics device. + */ + public void paint(Graphics g) + { + // Determine background color. + Color bgColor; + if (selected) + bgColor = getBackgroundSelectionColor(); + else + { + bgColor = getBackgroundNonSelectionColor(); + if (bgColor == null) + bgColor = getBackground(); + } + // Paint background. + int xOffset = -1; + if (bgColor != null) + { + xOffset = getXOffset(); + g.setColor(bgColor); + g.fillRect(xOffset, 0, getWidth() - xOffset, getHeight()); + } + + if (hasFocus) + { + if (drawsFocusBorderAroundIcon) + xOffset = 0; + else if (xOffset == -1) + xOffset = getXOffset(); + paintFocus(g, xOffset, 0, getWidth() - xOffset, getHeight()); + } + super.paint(g); + } + + /** + * Paints the focus indicator. + */ + private void paintFocus(Graphics g, int x, int y, int w, int h) + { + Color col = getBorderSelectionColor(); + if (col != null) + { + g.setColor(col); + g.drawRect(x, y, w - 1, h - 1); + } + } + + /** + * Determines the X offset of the label that is caused by + * the icon. + * + * @return the X offset of the label + */ + private int getXOffset() + { + Icon i = getIcon(); + int offs = 0; + if (i != null && getText() != null) + offs = i.getIconWidth() + Math.max(0, getIconTextGap() - 1); + return offs; + } + + /** + * Returns the preferred size of the cell. + * + * @return The preferred size of the cell. + */ + public Dimension getPreferredSize() + { + Dimension size = super.getPreferredSize(); + size.width += 3; + return size; + } + + /** + * For performance reasons, this method is overridden to do nothing. + */ + public void validate() + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + */ + public void revalidate() + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param tm ignored + * @param x coordinate of the region to mark as dirty + * @param y coordinate of the region to mark as dirty + * @param width dimension of the region to mark as dirty + * @param height dimension of the region to mark as dirty + */ + public void repaint(long tm, int x, int y, int width, int height) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param area the area to repaint. + */ + public void repaint(Rectangle area) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + protected void firePropertyChange(String name, Object oldValue, + Object newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, byte oldValue, byte newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, char oldValue, char newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, short oldValue, short newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, int oldValue, int newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, long oldValue, long newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, float oldValue, float newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, double oldValue, double newValue) + { + // Overridden for performance reasons. + } + + /** + * For performance reasons, this method is overridden to do nothing. + * + * @param name the property name. + * @param oldValue the old value. + * @param newValue the new value. + */ + public void firePropertyChange(String name, boolean oldValue, + boolean newValue) + { + // Overridden for performance reasons. + } + +} diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java new file mode 100644 index 000000000..0fa444586 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java @@ -0,0 +1,622 @@ +/* DefaultTreeModel.java -- + Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.tree; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.EventListener; + +import javax.swing.event.EventListenerList; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; + +/** + * DefaultTreeModel + * + * @author Andrew Selkirk + */ +public class DefaultTreeModel + implements Serializable, TreeModel +{ + static final long serialVersionUID = -2621068368932566998L; + + /** + * root + */ + protected TreeNode root; + + /** + * listenerList + */ + protected EventListenerList listenerList = new EventListenerList(); + + /** + * asksAllowsChildren + */ + protected boolean asksAllowsChildren; + + /** + * Constructor DefaultTreeModel where any node can have children. + * + * @param root the tree root. + */ + public DefaultTreeModel(TreeNode root) + { + this (root, false); + } + + /** + * Create the DefaultTreeModel that may check if the nodes can have + * children or not. + * + * @param aRoot the tree root. + * @param asksAllowsChildren if true, each node is asked if it can have + * children. If false, the model does not care about this, supposing, that + * any node can have children. + */ + public DefaultTreeModel(TreeNode aRoot, boolean asksAllowsChildren) + { + if (aRoot == null) + aRoot = new DefaultMutableTreeNode(); + this.root = aRoot; + this.asksAllowsChildren = asksAllowsChildren; + } + + /** + * writeObject + * + * @param obj the object. + * @exception IOException TODO + */ + private void writeObject(ObjectOutputStream obj) throws IOException + { + // TODO + } + + /** + * readObject + * + * @param value0 TODO + * @exception IOException TODO + * @exception ClassNotFoundException TODO + */ + private void readObject(ObjectInputStream value0) throws IOException, + ClassNotFoundException + { + // TODO + } + + /** + * asksAllowsChildren + * + * @return boolean + */ + public boolean asksAllowsChildren() + { + return asksAllowsChildren; + } + + /** + * setAsksAllowsChildren + * + * @param value TODO + */ + public void setAsksAllowsChildren(boolean value) + { + asksAllowsChildren = value; + } + + /** + * setRoot + * + * @param root the root node. + */ + public void setRoot(TreeNode root) + { + this.root = root; + } + + /** + * getRoot + * + * @return Object + */ + public Object getRoot() + { + return root; + } + + /** + * getIndexOfChild + * + * @param parent TODO + * @param child TODO + * @return int + */ + public int getIndexOfChild(Object parent, Object child) + { + for (int i = 0; i < getChildCount(parent); i++) + { + if (getChild(parent, i).equals(child)) + return i; + } + return -1; + } + + /** + * getChild + * + * @param node TODO + * @param idx TODO + * @return Object + */ + public Object getChild(Object node, int idx) + { + if (node instanceof TreeNode) + return ((TreeNode) node).getChildAt(idx); + else + return null; + } + + /** + * getChildCount + * + * @param node TODO + * @return int + */ + public int getChildCount(Object node) + { + if (node instanceof TreeNode) + return ((TreeNode) node).getChildCount(); + else + return 0; + } + + /** + * Returns if the specified node is a leaf or not. When + * {@link #asksAllowsChildren} is true, then this checks if the TreeNode + * allows children, otherwise it returns the TreeNode's <code>leaf</code> + * property. + * + * @param node the node to check + * + * @return boolean <code>true</code> if the node is a leaf node, + * <code>false</code> otherwise + * + * @throws ClassCastException if the specified node is not a + * <code>TreeNode</code> instance + * + * @see TreeNode#getAllowsChildren() + * @see TreeNode#isLeaf() + */ + public boolean isLeaf(Object node) + { + // The RI throws a ClassCastException when node isn't a TreeNode, so do we. + TreeNode treeNode = (TreeNode) node; + boolean leaf; + if (asksAllowsChildren) + leaf = ! treeNode.getAllowsChildren(); + else + leaf = treeNode.isLeaf(); + return leaf; + } + + /** + * <p> + * Invoke this method if you've modified the TreeNodes upon which this model + * depends. The model will notify all of its listeners that the model has + * changed. It will fire the events, necessary to update the layout caches and + * repaint the tree. The tree will <i>not</i> be properly refreshed if you + * call the JTree.repaint instead. + * </p> + * <p> + * This method will refresh the information about whole tree from the root. If + * only part of the tree should be refreshed, it is more effective to call + * {@link #reload(TreeNode)}. + * </p> + */ + public void reload() + { + // Need to duplicate the code because the root can formally be + // no an instance of the TreeNode. + int n = getChildCount(root); + int[] childIdx = new int[n]; + Object[] children = new Object[n]; + + for (int i = 0; i < n; i++) + { + childIdx[i] = i; + children[i] = getChild(root, i); + } + + fireTreeStructureChanged(this, new Object[] { root }, childIdx, children); + } + + /** + * Invoke this method if you've modified the TreeNodes upon which this model + * depends. The model will notify all of its listeners that the model has + * changed. It will fire the events, necessary to update the layout caches and + * repaint the tree. The tree will <i>not</i> be properly refreshed if you + * call the JTree.repaint instead. + * + * @param node - the tree node, from which the tree nodes have changed + * (inclusive). If you do not know this node, call {@link #reload()} + * instead. + */ + public void reload(TreeNode node) + { + int n = getChildCount(node); + int[] childIdx = new int[n]; + Object[] children = new Object[n]; + + for (int i = 0; i < n; i++) + { + childIdx[i] = i; + children[i] = getChild(node, i); + } + + fireTreeStructureChanged(this, getPathToRoot(node), childIdx, children); + } + + /** + * Messaged when the user has altered the value for the item + * identified by path to newValue. If newValue signifies a truly new + * value the model should post a treeNodesChanged event. + * This sets the user object of the TreeNode identified by + * path and posts a node changed. If you use custom user objects + * in the TreeModel you're going to need to subclass this and set + * the user object of the changed node to something meaningful. + * + * @param path - path to the node that the user has altered + * @param newValue - the new value from the TreeCellEditor + */ + public void valueForPathChanged(TreePath path, Object newValue) + { + Object node = path.getLastPathComponent(); + if (node instanceof MutableTreeNode) + { + ((MutableTreeNode) node).setUserObject(newValue); + int[] ci = null; + Object[] c = null; + Object[] parentPath = path.getPath(); + if (path.getPathCount() > 1) + { + Object parent = ((TreeNode) node).getParent(); + ci = new int[1]; + ci[0] = getIndexOfChild(parent, node); + node = newValue; + path = path.getParentPath().pathByAddingChild(node); + c = new Object[1]; + c[0] = node; + parentPath = path.getParentPath().getPath(); + } + + fireTreeNodesChanged(this, parentPath, ci, c); + } + } + + /** + * Invoked this to insert newChild at location index in parents children. + * This will then message nodesWereInserted to create the appropriate event. + * This is the preferred way to add children as it will create the + * appropriate event. + * + * @param newChild is the node to add to the parent's children + * @param parent is the parent of the newChild + * @param index is the index of the newChild + */ + public void insertNodeInto(MutableTreeNode newChild, MutableTreeNode parent, + int index) + { + newChild.setParent(parent); + parent.insert(newChild, index); + int[] childIndices = new int[1]; + childIndices[0] = index; + nodesWereInserted(parent, childIndices); + } + + /** + * Message this to remove node from its parent. This will message + * nodesWereRemoved to create the appropriate event. This is the preferred + * way to remove a node as it handles the event creation for you. + * + * @param node to be removed + */ + public void removeNodeFromParent(MutableTreeNode node) + { + TreeNode parent = node.getParent(); + Object[] children = new Object[1]; + children[0] = node; + int[] childIndices = new int[1]; + childIndices[0] = getIndexOfChild(parent, node); + node.removeFromParent(); + nodesWereRemoved(parent, childIndices, children); + } + + /** + * Invoke this method after you've changed how node is to be represented + * in the tree. + * + * @param node that was changed + */ + public void nodeChanged(TreeNode node) + { + TreeNode parent = node.getParent(); + int[] childIndices = new int[1]; + childIndices[0] = getIndexOfChild(parent, node); + Object[] children = new Object[1]; + children[0] = node; + fireTreeNodesChanged(this, getPathToRoot(node), childIndices, children); + } + + /** + * Invoke this method after you've inserted some TreeNodes + * into node. childIndices should be the index of the new elements and must + * be sorted in ascending order. + * + * @param parent that had a child added to + * @param childIndices of the children added + */ + public void nodesWereInserted(TreeNode parent, int[] childIndices) + { + Object[] children = new Object[childIndices.length]; + for (int i = 0; i < children.length; i++) + children[i] = getChild(parent, childIndices[i]); + fireTreeNodesInserted(this, getPathToRoot(parent), childIndices, children); + } + + /** + * Invoke this method after you've removed some TreeNodes from node. + * childIndices should be the index of the removed elements and + * must be sorted in ascending order. And removedChildren should be the + * array of the children objects that were removed. + * + * @param parent that had a child added to + * @param childIndices of the children added + * @param removedChildren are all the children removed from parent. + */ + public void nodesWereRemoved(TreeNode parent, int[] childIndices, + Object[] removedChildren) + { + fireTreeNodesRemoved(this, getPathToRoot(parent), childIndices, + removedChildren); + } + + /** + * Invoke this method after you've changed how the children identified by + * childIndices are to be represented in the tree. + * + * @param node that is the parent of the children that changed in a tree. + * @param childIndices are the child nodes that changed. + */ + public void nodesChanged(TreeNode node, int[] childIndices) + { + Object[] children = new Object[childIndices.length]; + for (int i = 0; i < children.length; i++) + children[i] = getChild(node, childIndices[i]); + fireTreeNodesChanged(this, getPathToRoot(node), childIndices, children); + } + + /** + * Invoke this method if you've totally changed the children of node and + * its childrens children. This will post a treeStructureChanged event. + * + * @param node that had its children and grandchildren changed. + */ + public void nodeStructureChanged(TreeNode node) + { + int n = getChildCount(root); + int[] childIdx = new int[n]; + Object[] children = new Object[n]; + + for (int i = 0; i < n; i++) + { + childIdx[i] = i; + children[i] = getChild(root, i); + } + + fireTreeStructureChanged(this, new Object[] { root }, childIdx, children); + } + + /** + * Builds the parents of node up to and including the root node, where + * the original node is the last element in the returned array. The + * length of the returned array gives the node's depth in the tree. + * + * @param node - the TreeNode to get the path for + * @return TreeNode[] - the path from node to the root + */ + public TreeNode[] getPathToRoot(TreeNode node) + { + return getPathToRoot(node, 0); + } + + /** + * Builds the parents of node up to and including the root node, where + * the original node is the last element in the returned array. The + * length of the returned array gives the node's depth in the tree. + * + * @param node - the TreeNode to get the path for + * @param depth - an int giving the number of steps already taken + * towards the root (on recursive calls), used to size the returned array + * @return an array of TreeNodes giving the path from the root to the + * specified node + */ + protected TreeNode[] getPathToRoot(TreeNode node, int depth) + { + if (node == null) + { + if (depth == 0) + return null; + + return new TreeNode[depth]; + } + + TreeNode[] path = getPathToRoot(node.getParent(), depth + 1); + path[path.length - depth - 1] = node; + return path; + } + + /** + * Registers a listere to the model. + * + * @param listener the listener to add + */ + public void addTreeModelListener(TreeModelListener listener) + { + listenerList.add(TreeModelListener.class, listener); + } + + /** + * Removes a listener from the model. + * + * @param listener the listener to remove + */ + public void removeTreeModelListener(TreeModelListener listener) + { + listenerList.remove(TreeModelListener.class, listener); + } + + /** + * Returns all registered <code>TreeModelListener</code> listeners. + * + * @return an array of listeners. + * + * @since 1.4 + */ + public TreeModelListener[] getTreeModelListeners() + { + return (TreeModelListener[]) listenerList + .getListeners(TreeModelListener.class); + } + + /** + * Notifies all listeners that have registered interest for notification + * on this event type. The event instance is lazily created using the parameters + * passed into the fire method. + * + * @param source the node being changed + * @param path the path to the root node + * @param childIndices the indices of the changed elements + * @param children the changed elements + */ + protected void fireTreeNodesChanged(Object source, Object[] path, + int[] childIndices, Object[] children) + { + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, + children); + + TreeModelListener[] listeners = getTreeModelListeners(); + + for (int i = listeners.length - 1; i >= 0; --i) + listeners[i].treeNodesChanged(event); + } + + /** + * fireTreeNodesInserted + * + * @param source the node where new nodes got inserted + * @param path the path to the root node + * @param childIndices the indices of the new elements + * @param children the new elements + */ + protected void fireTreeNodesInserted(Object source, Object[] path, + int[] childIndices, Object[] children) + { + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, + children); + TreeModelListener[] listeners = getTreeModelListeners(); + + for (int i = listeners.length - 1; i >= 0; --i) + listeners[i].treeNodesInserted(event); + } + + /** + * fireTreeNodesRemoved + * + * @param source the node where nodes got removed- + * @param path the path to the root node + * @param childIndices the indices of the removed elements + * @param children the removed elements + */ + protected void fireTreeNodesRemoved(Object source, Object[] path, + int[] childIndices, Object[] children) + { + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, + children); + TreeModelListener[] listeners = getTreeModelListeners(); + + for (int i = listeners.length - 1; i >= 0; --i) + listeners[i].treeNodesRemoved(event); + } + + /** + * fireTreeStructureChanged + * + * @param source the node where the model has changed + * @param path the path to the root node + * @param childIndices the indices of the affected elements + * @param children the affected elements + */ + protected void fireTreeStructureChanged(Object source, Object[] path, + int[] childIndices, Object[] children) + { + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, + children); + TreeModelListener[] listeners = getTreeModelListeners(); + + for (int i = listeners.length - 1; i >= 0; --i) + listeners[i].treeStructureChanged(event); + } + + /** + * Returns the registered listeners of a given type. + * + * @param listenerType the listener type to return + * + * @return an array of listeners + * + * @since 1.3 + */ + public <T extends EventListener> T[] getListeners(Class<T> listenerType) + { + return listenerList.getListeners(listenerType); + } +} diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java new file mode 100644 index 000000000..29add0e7b --- /dev/null +++ b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java @@ -0,0 +1,1202 @@ +/* DefaultTreeSelectionModel.java + Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import gnu.java.lang.CPStringBuilder; + +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.BitSet; +import java.util.EventListener; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +import javax.swing.DefaultListSelectionModel; +import javax.swing.event.EventListenerList; +import javax.swing.event.SwingPropertyChangeSupport; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; + +/** + * The implementation of the default tree selection model. The installed + * listeners are notified about the path and not the row changes. If you + * specifically need to track the row changes, register the listener for the + * expansion events. + * + * @author Andrew Selkirk + * @author Audrius Meskauskas + */ +public class DefaultTreeSelectionModel + implements Cloneable, Serializable, TreeSelectionModel +{ + + /** + * According to the API docs, the method + * {@link DefaultTreeSelectionModel#notifyPathChange} should + * expect instances of a class PathPlaceHolder in the Vector parameter. + * This seems to be a non-public class, so I can only make guesses about the + * use of it. + */ + private static class PathPlaceHolder + { + /** + * The path that we wrap. + */ + TreePath path; + + /** + * Indicates if the path is new or already in the selection. + */ + boolean isNew; + + /** + * Creates a new instance. + * + * @param p the path to wrap + * @param n if the path is new or already in the selection + */ + PathPlaceHolder(TreePath p, boolean n) + { + path = p; + isNew = n; + } + } + + /** + * Use serialVersionUID for interoperability. + */ + static final long serialVersionUID = 3288129636638950196L; + + /** + * The name of the selection mode property. + */ + public static final String SELECTION_MODE_PROPERTY = "selectionMode"; + + /** + * Our Swing property change support. + */ + protected SwingPropertyChangeSupport changeSupport; + + /** + * The current selection. + */ + protected TreePath[] selection; + + /** + * Our TreeSelectionListeners. + */ + protected EventListenerList listenerList; + + /** + * The current RowMapper. + */ + protected transient RowMapper rowMapper; + + /** + * The current listSelectionModel. + */ + protected DefaultListSelectionModel listSelectionModel; + + /** + * The current selection mode. + */ + protected int selectionMode; + + /** + * The path that has been added last. + */ + protected TreePath leadPath; + + /** + * The index of the last added path. + */ + protected int leadIndex; + + /** + * The row of the last added path according to the RowMapper. + */ + protected int leadRow = -1; + + /** + * A supporting datastructure that is used in addSelectionPaths() and + * removeSelectionPaths(). It contains currently selected paths. + * + * @see #addSelectionPaths(TreePath[]) + * @see #removeSelectionPaths(TreePath[]) + * @see #setSelectionPaths(TreePath[]) + */ + private transient HashSet<TreePath> selectedPaths; + + /** + * A supporting datastructure that is used in addSelectionPaths() and + * removeSelectionPaths(). It contains the paths that are added or removed. + * + * @see #addSelectionPaths(TreePath[]) + * @see #removeSelectionPaths(TreePath[]) + * @see #setSelectionPaths(TreePath[]) + */ + private transient HashSet<TreePath> tmpPaths; + + /** + * Constructs a new DefaultTreeSelectionModel. + */ + public DefaultTreeSelectionModel() + { + setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); + listSelectionModel = new DefaultListSelectionModel(); + listenerList = new EventListenerList(); + leadIndex = -1; + tmpPaths = new HashSet<TreePath>(); + selectedPaths = new HashSet<TreePath>(); + } + + /** + * Creates a clone of this DefaultTreeSelectionModel with the same selection. + * The cloned instance will have the same registered listeners, the listeners + * themselves will not be cloned. The selection will be cloned. + * + * @exception CloneNotSupportedException should not be thrown here + * @return a copy of this DefaultTreeSelectionModel + */ + public Object clone() throws CloneNotSupportedException + { + DefaultTreeSelectionModel cloned = + (DefaultTreeSelectionModel) super.clone(); + cloned.changeSupport = null; + cloned.selection = (TreePath[]) selection.clone(); + cloned.listenerList = new EventListenerList(); + cloned.listSelectionModel = + (DefaultListSelectionModel) listSelectionModel.clone(); + cloned.selectedPaths = new HashSet<TreePath>(); + cloned.tmpPaths = new HashSet<TreePath>(); + + return cloned; + } + + /** + * Returns a string that shows this object's properties. + * The returned string lists the selected tree rows, if any. + * + * @return a string that shows this object's properties + */ + public String toString() + { + if (isSelectionEmpty()) + return "[selection empty]"; + else + { + CPStringBuilder b = new CPStringBuilder("selected rows: ["); + for (int i = 0; i < selection.length; i++) + { + b.append(getRow(selection[i])); + b.append(' '); + } + b.append(", lead " + getLeadSelectionRow()); + return b.toString(); + } + } + + /** + * writeObject + * + * @param value0 TODO + * @exception IOException TODO + */ + private void writeObject(ObjectOutputStream value0) throws IOException + { + // TODO + } + + /** + * readObject + * + * @param value0 TODO + * @exception IOException TODO + * @exception ClassNotFoundException TODO + */ + private void readObject(ObjectInputStream value0) throws IOException, + ClassNotFoundException + { + // TODO + } + + /** + * Sets the RowMapper that should be used to map between paths and their rows. + * + * @param mapper the RowMapper to set + * @see RowMapper + */ + public void setRowMapper(RowMapper mapper) + { + rowMapper = mapper; + resetRowSelection(); + } + + /** + * Returns the RowMapper that is currently used to map between paths and their + * rows. + * + * @return the current RowMapper + * @see RowMapper + */ + public RowMapper getRowMapper() + { + return rowMapper; + } + + /** + * Sets the current selection mode. Possible values are + * {@link #SINGLE_TREE_SELECTION}, {@link #CONTIGUOUS_TREE_SELECTION} and + * {@link #DISCONTIGUOUS_TREE_SELECTION}. + * + * @param mode the selection mode to be set + * @see #getSelectionMode + * @see #SINGLE_TREE_SELECTION + * @see #CONTIGUOUS_TREE_SELECTION + * @see #DISCONTIGUOUS_TREE_SELECTION + */ + public void setSelectionMode(int mode) + { + int oldMode = selectionMode; + selectionMode = mode; + // Make sure we have a valid selection mode. + if (selectionMode != SINGLE_TREE_SELECTION + && selectionMode != CONTIGUOUS_TREE_SELECTION + && selectionMode != DISCONTIGUOUS_TREE_SELECTION) + selectionMode = DISCONTIGUOUS_TREE_SELECTION; + + // Fire property change event. + if (oldMode != selectionMode && changeSupport != null) + changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode, + selectionMode); + } + + /** + * Returns the current selection mode. + * + * @return the current selection mode + * @see #setSelectionMode + * @see #SINGLE_TREE_SELECTION + * @see #CONTIGUOUS_TREE_SELECTION + * @see #DISCONTIGUOUS_TREE_SELECTION + */ + public int getSelectionMode() + { + return selectionMode; + } + + /** + * Sets this path as the only selection. If this changes the selection the + * registered TreeSelectionListeners are notified. + * + * @param path the path to set as selection + */ + public void setSelectionPath(TreePath path) + { + TreePath[] paths = null; + if (path != null) + paths = new TreePath[]{ path }; + setSelectionPaths(paths); + } + + /** + * Get the number of the tree row for the given path. + * + * @param path the tree path + * @return the tree row for this path or -1 if the path is not visible. + */ + int getRow(TreePath path) + { + RowMapper mapper = getRowMapper(); + + if (mapper instanceof AbstractLayoutCache) + { + // The absolute majority of cases, unless the TreeUI is very + // seriously rewritten + AbstractLayoutCache ama = (AbstractLayoutCache) mapper; + return ama.getRowForPath(path); + } + else if (mapper != null) + { + // Generic non optimized implementation. + int[] rows = mapper.getRowsForPaths(new TreePath[] { path }); + if (rows.length == 0) + return - 1; + else + return rows[0]; + } + return -1; + } + + /** + * Sets the paths as selection. This method checks for duplicates and removes + * them. If this changes the selection the registered TreeSelectionListeners + * are notified. + * + * @param paths the paths to set as selection + */ + public void setSelectionPaths(TreePath[] paths) + { + int oldLength = 0; + if (selection != null) + oldLength = selection.length; + int newLength = 0; + if (paths != null) + newLength = paths.length; + if (newLength > 0 || oldLength > 0) + { + // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with + // a non-contiguous path, we only allow the first path element. + if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1) + || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0 + && ! arePathsContiguous(paths))) + { + paths = new TreePath[] { paths[0] }; + newLength = 1; + } + // Find new paths. + Vector<PathPlaceHolder> changedPaths = null; + tmpPaths.clear(); + int validPaths = 0; + TreePath oldLeadPath = leadPath; + for (int i = 0; i < newLength; i++) + { + if (paths[i] != null && ! tmpPaths.contains(paths[i])) + { + validPaths++; + tmpPaths.add(paths[i]); + if (! selectedPaths.contains(paths[i])) + { + if (changedPaths == null) + changedPaths = new Vector<PathPlaceHolder>(); + changedPaths.add(new PathPlaceHolder(paths[i], true)); + } + leadPath = paths[i]; + } + } + // Put together the new selection. + TreePath[] newSelection = null; + if (validPaths != 0) + { + if (validPaths != newLength) + { + // Some of the paths are already selected, put together + // the new selection carefully. + newSelection = new TreePath[validPaths]; + Iterator<TreePath> newPaths = tmpPaths.iterator(); + validPaths = 0; + for (int i = 0; newPaths.hasNext(); i++) + newSelection[i] = newPaths.next(); + } + else + { + newSelection = new TreePath[paths.length]; + System.arraycopy(paths, 0, newSelection, 0, paths.length); + } + } + + // Find paths that have been selected, but are no more. + for (int i = 0; i < oldLength; i++) + { + if (selection[i] != null && ! tmpPaths.contains(selection[i])) + { + if (changedPaths == null) + changedPaths = new Vector<PathPlaceHolder>(); + changedPaths.add(new PathPlaceHolder(selection[i], false)); + } + } + + // Perform changes and notification. + selection = newSelection; + HashSet<TreePath> tmp = selectedPaths; + selectedPaths = tmpPaths; + tmpPaths = tmp; + tmpPaths.clear(); + + // Not necessary, but required according to the specs and to tests. + if (selection != null) + insureUniqueness(); + updateLeadIndex(); + resetRowSelection(); + if (changedPaths != null && changedPaths.size() > 0) + notifyPathChange(changedPaths, oldLeadPath); + } + } + + /** + * Adds a path to the list of selected paths. This method checks if the path + * is already selected and doesn't add the same path twice. If this changes + * the selection the registered TreeSelectionListeners are notified. + * + * The lead path is changed to the added path. This also happen if the + * passed path was already selected before. + * + * @param path the path to add to the selection + */ + public void addSelectionPath(TreePath path) + { + if (path != null) + { + TreePath[] add = new TreePath[]{ path }; + addSelectionPaths(add); + } + } + + /** + * Adds the paths to the list of selected paths. This method checks if the + * paths are already selected and doesn't add the same path twice. If this + * changes the selection the registered TreeSelectionListeners are notified. + * + * @param paths the paths to add to the selection + */ + public void addSelectionPaths(TreePath[] paths) + { + int length = paths != null ? paths.length : 0; + if (length > 0) + { + if (selectionMode == SINGLE_TREE_SELECTION) + setSelectionPaths(paths); + else if (selectionMode == CONTIGUOUS_TREE_SELECTION + && ! canPathsBeAdded(paths)) + { + if (arePathsContiguous(paths)) + setSelectionPaths(paths); + else + setSelectionPaths(new TreePath[] { paths[0] }); + } + else + { + Vector<PathPlaceHolder> changedPaths = null; + tmpPaths.clear(); + int validPaths = 0; + TreePath oldLeadPath = leadPath; + int oldPaths = 0; + if (selection != null) + oldPaths = selection.length; + int i; + for (i = 0; i < length; i++) + { + if (paths[i] != null) + { + if (! selectedPaths.contains(paths[i])) + { + validPaths++; + if (changedPaths == null) + changedPaths = new Vector<PathPlaceHolder>(); + changedPaths.add(new PathPlaceHolder(paths[i], true)); + selectedPaths.add(paths[i]); + tmpPaths.add(paths[i]); + } + leadPath = paths[i]; + } + } + if (validPaths > 0) + { + TreePath[] newSelection = new TreePath[oldPaths + validPaths]; + if (oldPaths > 0) + System.arraycopy(selection, 0, newSelection, 0, oldPaths); + if (validPaths != paths.length) + { + // Some of the paths are already selected, put together + // the new selection carefully. + Iterator<TreePath> newPaths = tmpPaths.iterator(); + i = oldPaths; + while (newPaths.hasNext()) + { + newSelection[i] = newPaths.next(); + i++; + } + } + else + System.arraycopy(paths, 0, newSelection, oldPaths, + validPaths); + selection = newSelection; + insureUniqueness(); + updateLeadIndex(); + resetRowSelection(); + if (changedPaths != null && changedPaths.size() > 0) + notifyPathChange(changedPaths, oldLeadPath); + } + else + leadPath = oldLeadPath; + tmpPaths.clear(); + } + } + } + + /** + * Removes the path from the selection. If this changes the selection the + * registered TreeSelectionListeners are notified. + * + * @param path the path to remove + */ + public void removeSelectionPath(TreePath path) + { + if (path != null) + removeSelectionPaths(new TreePath[]{ path }); + } + + /** + * Removes the paths from the selection. If this changes the selection the + * registered TreeSelectionListeners are notified. + * + * @param paths the paths to remove + */ + public void removeSelectionPaths(TreePath[] paths) + { + if (paths != null && selection != null && paths.length > 0) + { + if (! canPathsBeRemoved(paths)) + clearSelection(); + else + { + Vector<PathPlaceHolder> pathsToRemove = null; + for (int i = paths.length - 1; i >= 0; i--) + { + if (paths[i] != null && selectedPaths.contains(paths[i])) + { + if (pathsToRemove == null) + pathsToRemove = new Vector<PathPlaceHolder>(); + selectedPaths.remove(paths[i]); + pathsToRemove.add(new PathPlaceHolder(paths[i], + false)); + } + } + if (pathsToRemove != null) + { + int numRemove = pathsToRemove.size(); + TreePath oldLead = leadPath; + if (numRemove == selection.length) + selection = null; + else + { + selection = new TreePath[selection.length - numRemove]; + Iterator<TreePath> keep = selectedPaths.iterator(); + for (int valid = 0; keep.hasNext(); valid++) + selection[valid] = keep.next(); + } + // Update lead path. + if (leadPath != null && ! selectedPaths.contains(leadPath)) + { + if (selection != null) + leadPath = selection[selection.length - 1]; + else + leadPath = null; + } + else if (selection != null) + leadPath = selection[selection.length - 1]; + else + leadPath = null; + updateLeadIndex(); + resetRowSelection(); + notifyPathChange(pathsToRemove, oldLead); + } + } + } + } + + /** + * Returns the first path in the selection. This is especially useful when the + * selectionMode is {@link #SINGLE_TREE_SELECTION}. + * + * @return the first path in the selection + */ + public TreePath getSelectionPath() + { + if ((selection == null) || (selection.length == 0)) + return null; + else + return selection[0]; + } + + /** + * Returns the complete selection. + * + * @return the complete selection + */ + public TreePath[] getSelectionPaths() + { + return selection; + } + + /** + * Returns the number of paths in the selection. + * + * @return the number of paths in the selection + */ + public int getSelectionCount() + { + if (selection == null) + return 0; + else + return selection.length; + } + + /** + * Checks if a given path is in the selection. + * + * @param path the path to check + * @return <code>true</code> if the path is in the selection, + * <code>false</code> otherwise + */ + public boolean isPathSelected(TreePath path) + { + if (selection == null) + return false; + + for (int i = 0; i < selection.length; i++) + { + if (selection[i].equals(path)) + return true; + } + return false; + } + + /** + * Checks if the selection is empty. + * + * @return <code>true</code> if the selection is empty, <code>false</code> + * otherwise + */ + public boolean isSelectionEmpty() + { + return (selection == null) || (selection.length == 0); + } + + /** + * Removes all paths from the selection. Fire the unselection event. + */ + public void clearSelection() + { + if (selection != null) + { + int selectionLength = selection.length; + boolean[] news = new boolean[selectionLength]; + Arrays.fill(news, false); + TreeSelectionEvent event = new TreeSelectionEvent(this, selection, + news, leadPath, + null); + leadPath = null; + leadIndex = 0; + leadRow = 0; + selectedPaths.clear(); + selection = null; + resetRowSelection(); + fireValueChanged(event); + } + } + + /** + * Adds a <code>TreeSelectionListener</code> object to this model. + * + * @param listener the listener to add + */ + public void addTreeSelectionListener(TreeSelectionListener listener) + { + listenerList.add(TreeSelectionListener.class, listener); + } + + /** + * Removes a <code>TreeSelectionListener</code> object from this model. + * + * @param listener the listener to remove + */ + public void removeTreeSelectionListener(TreeSelectionListener listener) + { + listenerList.remove(TreeSelectionListener.class, listener); + } + + /** + * Returns all <code>TreeSelectionListener</code> added to this model. + * + * @return an array of listeners + * @since 1.4 + */ + public TreeSelectionListener[] getTreeSelectionListeners() + { + return (TreeSelectionListener[]) getListeners(TreeSelectionListener.class); + } + + /** + * fireValueChanged + * + * @param event the event to fire. + */ + protected void fireValueChanged(TreeSelectionEvent event) + { + TreeSelectionListener[] listeners = getTreeSelectionListeners(); + + for (int i = 0; i < listeners.length; ++i) + listeners[i].valueChanged(event); + } + + /** + * Returns all added listeners of a special type. + * + * @param listenerType the listener type + * @return an array of listeners + * @since 1.3 + */ + public <T extends EventListener> T[] getListeners(Class<T> listenerType) + { + return listenerList.getListeners(listenerType); + } + + /** + * Returns the currently selected rows. + * + * @return the currently selected rows + */ + public int[] getSelectionRows() + { + int[] rows = null; + if (rowMapper != null && selection != null) + { + rows = rowMapper.getRowsForPaths(selection); + if (rows != null) + { + // Find invisible rows. + int invisible = 0; + for (int i = rows.length - 1; i >= 0; i--) + { + if (rows[i] == -1) + invisible++; + + } + // Clean up invisible rows. + if (invisible > 0) + { + if (invisible == rows.length) + rows = null; + else + { + int[] newRows = new int[rows.length - invisible]; + int visCount = 0; + for (int i = rows.length - 1; i >= 0; i--) + { + if (rows[i] != -1) + { + newRows[visCount] = rows[i]; + visCount++; + } + } + rows = newRows; + } + } + } + } + return rows; + } + + /** + * Returns the smallest row index from the selection. + * + * @return the smallest row index from the selection + */ + public int getMinSelectionRow() + { + return listSelectionModel.getMinSelectionIndex(); + } + + /** + * Returns the largest row index from the selection. + * + * @return the largest row index from the selection + */ + public int getMaxSelectionRow() + { + return listSelectionModel.getMaxSelectionIndex(); + } + + /** + * Checks if a particular row is selected. + * + * @param row the index of the row to check + * @return <code>true</code> if the row is in this selection, + * <code>false</code> otherwise + * @throws NullPointerException if the row mapper is not set (can only happen + * if the user has plugged in the custom incorrect TreeUI + * implementation. + */ + public boolean isRowSelected(int row) + { + return listSelectionModel.isSelectedIndex(row); + } + + /** + * Updates the mappings from TreePaths to row indices. + */ + public void resetRowSelection() + { + listSelectionModel.clearSelection(); + if (selection != null && rowMapper != null) + { + int[] rows = rowMapper.getRowsForPaths(selection); + // Update list selection model. + for (int i = 0; i < rows.length; i++) + { + int row = rows[i]; + if (row != -1) + listSelectionModel.addSelectionInterval(row, row); + } + // Update lead selection. + if (leadIndex != -1 && rows != null) + leadRow = rows[leadIndex]; + else if (leadPath != null) + { + TreePath[] tmp = new TreePath[]{ leadPath }; + rows = rowMapper.getRowsForPaths(tmp); + leadRow = rows != null ? rows[0] : -1; + } + else + leadRow = -1; + insureRowContinuity(); + } + else + leadRow = -1; + } + + /** + * getLeadSelectionRow + * + * @return int + */ + public int getLeadSelectionRow() + { + return leadRow; + } + + /** + * getLeadSelectionPath + * + * @return TreePath + */ + public TreePath getLeadSelectionPath() + { + return leadPath; + } + + /** + * Adds a <code>PropertyChangeListener</code> object to this model. + * + * @param listener the listener to add. + */ + public void addPropertyChangeListener(PropertyChangeListener listener) + { + if (changeSupport == null) + changeSupport = new SwingPropertyChangeSupport(this); + changeSupport.addPropertyChangeListener(listener); + } + + /** + * Removes a <code>PropertyChangeListener</code> object from this model. + * + * @param listener the listener to remove. + */ + public void removePropertyChangeListener(PropertyChangeListener listener) + { + if (changeSupport != null) + changeSupport.removePropertyChangeListener(listener); + } + + /** + * Returns all added <code>PropertyChangeListener</code> objects. + * + * @return an array of listeners. + * @since 1.4 + */ + public PropertyChangeListener[] getPropertyChangeListeners() + { + PropertyChangeListener[] listeners = null; + if (changeSupport != null) + listeners = changeSupport.getPropertyChangeListeners(); + else + listeners = new PropertyChangeListener[0]; + return listeners; + } + + /** + * Makes sure the currently selected paths are valid according to the current + * selectionMode. If the selectionMode is set to + * {@link #CONTIGUOUS_TREE_SELECTION} and the selection isn't contiguous then + * the selection is reset to the first set of contguous paths. If the + * selectionMode is set to {@link #SINGLE_TREE_SELECTION} and the selection + * has more than one path, the selection is reset to the contain only the + * first path. + */ + protected void insureRowContinuity() + { + if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null + && rowMapper != null) + { + int min = listSelectionModel.getMinSelectionIndex(); + if (min != -1) + { + int max = listSelectionModel.getMaxSelectionIndex(); + for (int i = min; i <= max; i++) + { + if (! listSelectionModel.isSelectedIndex(i)) + { + if (i == min) + clearSelection(); + else + { + TreePath[] newSelection = new TreePath[i - min]; + int[] rows = rowMapper.getRowsForPaths(selection); + for (int j = 0; j < rows.length; j++) + { + if (rows[j] < i) + newSelection[rows[j] - min] = selection[j]; + } + setSelectionPaths(newSelection); + break; + } + } + } + } + } + else if (selectionMode == SINGLE_TREE_SELECTION && selection != null + && selection.length > 1) + setSelectionPath(selection[0]); + } + + /** + * Returns <code>true</code> if the paths are contiguous (take subsequent + * rows in the diplayed tree view. The method returns <code>true</code> if + * we have no RowMapper assigned. + * + * @param paths the paths to check for continuity + * @return <code>true</code> if the paths are contiguous or we have no + * RowMapper assigned + */ + protected boolean arePathsContiguous(TreePath[] paths) + { + if (rowMapper == null || paths.length < 2) + return true; + + int length = paths.length; + TreePath[] tmp = new TreePath[1]; + tmp[0] = paths[0]; + int min = rowMapper.getRowsForPaths(tmp)[0]; + BitSet selected = new BitSet(); + int valid = 0; + for (int i = 0; i < length; i++) + { + if (paths[i] != null) + { + tmp[0] = paths[i]; + int[] rows = rowMapper.getRowsForPaths(tmp); + if (rows == null) + return false; // No row mapping yet, can't be selected. + int row = rows[0]; + if (row == -1 || row < (min - length) || row > (min + length)) + return false; // Not contiguous. + min = Math.min(min, row); + if (! selected.get(row)) + { + selected.set(row); + valid++; + } + + } + } + int max = valid + min; + for (int i = min; i < max; i++) + if (! selected.get(i)) + return false; // Not contiguous. + return true; + } + + /** + * Checks if the paths can be added. This returns <code>true</code> if: + * <ul> + * <li><code>paths</code> is <code>null</code> or empty</li> + * <li>we have no RowMapper assigned</li> + * <li>nothing is currently selected</li> + * <li>selectionMode is {@link #DISCONTIGUOUS_TREE_SELECTION}</li> + * <li>adding the paths to the selection still results in a contiguous set of + * paths</li> + * + * @param paths the paths to check + * @return <code>true</code> if the paths can be added with respect to the + * selectionMode + */ + protected boolean canPathsBeAdded(TreePath[] paths) + { + if (paths == null || paths.length == 0 || rowMapper == null + || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION) + return true; + + BitSet selected = new BitSet(); + int min = listSelectionModel.getMinSelectionIndex(); + int max = listSelectionModel.getMaxSelectionIndex(); + TreePath[] tmp = new TreePath[1]; + if (min != -1) + { + // Set the bitmask of selected elements. + for (int i = min; i <= max; i++) + selected.set(i); + } + else + { + tmp[0] = paths[0]; + min = rowMapper.getRowsForPaths(tmp)[0]; + max = min; + } + // Mark new paths as selected. + for (int i = paths.length - 1; i >= 0; i--) + { + if (paths[i] != null) + { + tmp[0] = paths[i]; + int[] rows = rowMapper.getRowsForPaths(tmp); + if (rows == null) + return false; // Now row mapping yet, can't be selected. + int row = rows[0]; + if (row == -1) + return false; // Now row mapping yet, can't be selected. + min = Math.min(min, row); + max = Math.max(max, row); + selected.set(row); + } + } + // Now look if the new selection would be contiguous. + for (int i = min; i <= max; i++) + if (! selected.get(i)) + return false; + return true; + } + + /** + * Checks if the paths can be removed without breaking the continuity of the + * selection according to selectionMode. + * + * @param paths the paths to check + * @return <code>true</code> if the paths can be removed with respect to the + * selectionMode + */ + protected boolean canPathsBeRemoved(TreePath[] paths) + { + if (rowMapper == null || isSelectionEmpty() + || selectionMode == DISCONTIGUOUS_TREE_SELECTION) + return true; + + HashSet<TreePath> set = new HashSet<TreePath>(); + for (int i = 0; i < selection.length; i++) + set.add(selection[i]); + + for (int i = 0; i < paths.length; i++) + set.remove(paths[i]); + + TreePath[] remaining = new TreePath[set.size()]; + Iterator<TreePath> iter = set.iterator(); + + for (int i = 0; i < remaining.length; i++) + remaining[i] = iter.next(); + + return arePathsContiguous(remaining); + } + + /** + * Notify the installed listeners that the given patches have changed. This + * method will call listeners if invoked, but it is not called from the + * implementation of this class. + * + * @param vPaths the vector of the changed patches + * @param oldLeadSelection the old selection index + */ + protected void notifyPathChange(Vector<PathPlaceHolder> vPaths, + TreePath oldLeadSelection) + { + + int numChangedPaths = vPaths.size(); + boolean[] news = new boolean[numChangedPaths]; + TreePath[] paths = new TreePath[numChangedPaths]; + for (int i = 0; i < numChangedPaths; i++) + { + PathPlaceHolder p = vPaths.get(i); + news[i] = p.isNew; + paths[i] = p.path; + } + + TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news, + oldLeadSelection, + leadPath); + fireValueChanged(event); + } + + /** + * Updates the lead selection row number after changing the lead selection + * path. + */ + protected void updateLeadIndex() + { + leadIndex = -1; + if (leadPath != null) + { + leadRow = -1; + if (selection == null) + leadPath = null; + else + { + for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--) + { + if (selection[i] == leadPath) + leadIndex = i; + } + } + } + } + + /** + * This method exists due historical reasons and returns without action + * (unless overridden). For compatibility with the applications that override + * it, it is still called from the {@link #setSelectionPaths(TreePath[])} and + * {@link #addSelectionPaths(TreePath[])}. + */ + protected void insureUniqueness() + { + // Following the API 1.4, the method should return without action. + } +} diff --git a/libjava/classpath/javax/swing/tree/ExpandVetoException.java b/libjava/classpath/javax/swing/tree/ExpandVetoException.java new file mode 100644 index 000000000..fd6330cf7 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/ExpandVetoException.java @@ -0,0 +1,76 @@ +/* ExpandVetoException.java -- + Copyright (C) 2002, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.tree; + +import javax.swing.event.TreeExpansionEvent; + +/** + * ExpandVetoException + * @author Andrew Selkirk + */ +public class ExpandVetoException extends Exception +{ + + /** + * event + */ + protected TreeExpansionEvent event; + + + /** + * Constructor ExpandVetoException + * @param event Tree Expansion Event + */ + public ExpandVetoException(TreeExpansionEvent event) + { + super(); + this.event = event; + } + + /** + * Constructor ExpandVetoException + * @param event Tree Expansion Event + * @param message Message + */ + public ExpandVetoException(TreeExpansionEvent event, String message) + { + super(message); + this.event = event; + } + +} diff --git a/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java new file mode 100644 index 000000000..6ff2e3595 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java @@ -0,0 +1,628 @@ +/* FixedHeightLayoutCache.java -- Fixed cell height tree layout cache +Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.tree; + +import gnu.javax.swing.tree.GnuPath; + +import java.awt.Rectangle; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.Set; +import java.util.Vector; + +import javax.swing.event.TreeModelEvent; + + +/** + * The fixed height tree layout. This class assumes that all cells in the tree + * have the same fixed height. This may be not the case, for instance, if leaves + * and branches have different height, of if the tree rows may have arbitrary + * variable height. This class will also work if the NodeDimensions are not + * set. + * + * @author Audrius Meskauskas + * @author Andrew Selkirk + */ +public class FixedHeightLayoutCache + extends VariableHeightLayoutCache +{ + /** + * The cached node record. + */ + class NodeRecord + { + NodeRecord(int aRow, int aDepth, Object aNode, Object aParent) + { + row = aRow; + depth = aDepth; + parent = aParent; + node = aNode; + + isExpanded = expanded.contains(aNode); + } + + /** + * The row, where the tree node is displayed. + */ + final int row; + + /** + * The nesting depth + */ + final int depth; + + /** + * The parent of the given node, null for the root node. + */ + final Object parent; + + /** + * This node. + */ + final Object node; + + /** + * True for the expanded nodes. The value is calculated in constructor. + * Using this field saves one hashtable access operation. + */ + final boolean isExpanded; + + /** + * The cached bounds of the tree row. + */ + Rectangle bounds; + + /** + * The path from the tree top to the given node (computed under first + * demand) + */ + private TreePath path; + + /** + * Get the path for this node. The derived class is returned, + * making check for the last child of some parent easier. + */ + TreePath getPath() + { + if (path == null) + { + boolean lastChild = false; + if (parent != null) + { + int nc = treeModel.getChildCount(parent); + if (nc > 0) + { + int n = treeModel.getIndexOfChild(parent, node); + if (n == nc - 1) + lastChild = true; + } + } + + LinkedList<Object> lpath = new LinkedList<Object>(); + NodeRecord rp = this; + while (rp != null) + { + lpath.addFirst(rp.node); + if (rp.parent != null) + { + Object parent = rp.parent; + rp = (NodeRecord) nodes.get(parent); + // Add the root node, even if it is not visible. + if (rp == null) + lpath.addFirst(parent); + } + else + rp = null; + } + path = new GnuPath(lpath.toArray(), lastChild); + } + return path; + } + + /** + * Get the rectangle bounds (compute, if required). + */ + Rectangle getBounds() + { + // This method may be called in the context when the tree rectangle is + // not known. To work around this, it is assumed near infinitely large. + if (bounds == null) + bounds = getNodeDimensions(node, row, depth, isExpanded, + new Rectangle()); + return bounds; + } + } + + /** + * The set of all expanded tree nodes. + */ + Set<Object> expanded = new HashSet<Object>(); + + /** + * Maps nodes to the row numbers. + */ + Hashtable<Object,NodeRecord> nodes = new Hashtable<Object,NodeRecord>(); + + /** + * Maps row numbers to nodes. + */ + Hashtable<Integer,Object> row2node = new Hashtable<Integer,Object>(); + + /** + * If true, the row map must be recomputed before using. + */ + boolean dirty; + + /** + * The cumulative height of all rows. + */ + int totalHeight; + + /** + * The maximal width. + */ + int maximalWidth; + + /** + * Creates the unitialised instance. Before using the class, the row height + * must be set with the {@link #setRowHeight(int)} and the model must be set + * with {@link #setModel(TreeModel)}. The node dimensions may not be set. + */ + public FixedHeightLayoutCache() + { + // Nothing to do here. + } + + /** + * Get the total number of rows in the tree. Every displayed node occupies the + * single row. The root node row is included if the root node is set as + * visible (false by default). + * + * @return int the number of the displayed rows. + */ + public int getRowCount() + { + if (dirty) update(); + return row2node.size(); + } + + /** + * Refresh the row map. + */ + private final void update() + { + nodes.clear(); + row2node.clear(); + + totalHeight = maximalWidth = 0; + + Object root = treeModel.getRoot(); + + if (rootVisible) + { + countRows(root, null, 0); + } + else + { + int sc = treeModel.getChildCount(root); + for (int i = 0; i < sc; i++) + { + Object child = treeModel.getChild(root, i); + countRows(child, root, 0); + } + } + dirty = false; + } + + /** + * Recursively counts all rows in the tree. + */ + private final void countRows(Object node, Object parent, int depth) + { + Integer n = new Integer(row2node.size()); + row2node.put(n, node); + + NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent); + nodes.put(node, nr); + + // For expanded nodes and for the root node. + if (expanded.contains(node)) + { + int sc = treeModel.getChildCount(node); + int deeper = depth + 1; + for (int i = 0; i < sc; i++) + { + Object child = treeModel.getChild(node, i); + countRows(child, node, deeper); + } + } + } + + /** + * Discard the bound information for the given path. + * + * @param path the path, for that the bound information must be recomputed. + */ + public void invalidatePathBounds(TreePath path) + { + NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent()); + if (r != null) + r.bounds = null; + } + + /** + * Mark all cached information as invalid. + */ + public void invalidateSizes() + { + dirty = true; + } + + /** + * Set the expanded state of the given path. The expansion states must be + * always updated when expanding and colapsing the tree nodes. Otherwise + * other methods will not work correctly after the nodes are collapsed or + * expanded. + * + * @param path the tree path, for that the state is being set. + * @param isExpanded the expanded state of the given path. + */ + public void setExpandedState(TreePath path, boolean isExpanded) + { + if (isExpanded) + expanded.add(path.getLastPathComponent()); + else + expanded.remove(path.getLastPathComponent()); + + dirty = true; + } + + /** + * Get the expanded state for the given tree path. + * + * @return true if the given path is expanded, false otherwise. + */ + public boolean isExpanded(TreePath path) + { + return expanded.contains(path.getLastPathComponent()); + } + + /** + * Get bounds for the given tree path. + * + * @param path the tree path + * @param rect the rectangle that will be reused to return the result. + * @return Rectangle the bounds of the last line, defined by the given path. + */ + public Rectangle getBounds(TreePath path, Rectangle rect) + { + if (path == null) + return null; + if (dirty) + update(); + Object last = path.getLastPathComponent(); + NodeRecord r = nodes.get(last); + if (r == null) + // This node is not visible. + { + rect.x = rect.y = rect.width = rect.height = 0; + } + else + { + if (r.bounds == null) + { + Rectangle dim = getNodeDimensions(last, r.row, r.depth, + r.isExpanded, rect); + r.bounds = dim; + } + + rect.setRect(r.bounds); + } + return rect; + } + + /** + * Get the path, the last element of that is displayed in the given row. + * + * @param row the row + * @return TreePath the path + */ + public TreePath getPathForRow(int row) + { + if (dirty) + update(); + Object last = row2node.get(new Integer(row)); + if (last == null) + return null; + else + { + NodeRecord r = nodes.get(last); + return r.getPath(); + } + } + + /** + * Get the row, displaying the last node of the given path. + * + * @param path the path + * @return int the row number or -1 if the end of the path is not visible. + */ + public int getRowForPath(TreePath path) + { + if (path == null) + return -1; + + if (dirty) update(); + + NodeRecord r = nodes.get(path.getLastPathComponent()); + if (r == null) + return - 1; + else + return r.row; + } + + /** + * Get the path, closest to the given point. + * + * @param x the point x coordinate + * @param y the point y coordinate + * @return the tree path, closest to the the given point + */ + public TreePath getPathClosestTo(int x, int y) + { + if (dirty) + update(); + + // As the rows have arbitrary height, we need to iterate. + NodeRecord best = null; + NodeRecord r; + Enumeration<NodeRecord> en = nodes.elements(); + + int dist = Integer.MAX_VALUE; + + while (en.hasMoreElements() && dist > 0) + { + r = en.nextElement(); + if (best == null) + { + best = r; + dist = distance(r.getBounds(), x, y); + } + else + { + int rr = distance(r.getBounds(), x, y); + if (rr < dist) + { + best = r; + dist = rr; + } + } + } + + if (best == null) + return null; + else + return best.getPath(); + } + + /** + * Get the closest distance from this point till the given rectangle. Only + * vertical distance is taken into consideration. + */ + int distance(Rectangle r, int x, int y) + { + if (y < r.y) + return r.y - y; + else if (y > r.y + r.height) + return y - (r.y + r.height); + else + return 0; + } + + /** + * Get the number of the visible childs for the given tree path. If the node + * is not expanded, 0 is returned. Otherwise, the number of children is + * obtained from the model as the number of children for the last path + * component. + * + * @param path the tree path + * @return int the number of the visible childs (for row). + */ + public int getVisibleChildCount(TreePath path) + { + if (isExpanded(path)) + return 0; + else + return treeModel.getChildCount(path.getLastPathComponent()); + } + + /** + * Get the enumeration over all visible paths that start from the given + * parent path. + * + * @param parentPath the parent path + * @return the enumeration over pathes + */ + public Enumeration<TreePath> getVisiblePathsFrom(TreePath parentPath) + { + if (dirty) + update(); + Vector<TreePath> p = new Vector<TreePath>(parentPath.getPathCount()); + Object node; + NodeRecord nr; + + for (int i = 0; i < parentPath.getPathCount(); i++) + { + node = parentPath.getPathComponent(i); + nr = nodes.get(node); + if (nr.row >= 0) + p.add((TreePath) node); + } + return p.elements(); + } + + /** + * Return the expansion state of the given tree path. The expansion state + * must be previously set with the + * {@link #setExpandedState(TreePath, boolean)} + * + * @param path the path being checked + * @return true if the last node of the path is expanded, false otherwise. + */ + public boolean getExpandedState(TreePath path) + { + return expanded.contains(path.getLastPathComponent()); + } + + /** + * The listener method, called when the tree nodes are changed. + * + * @param event the change event + */ + public void treeNodesChanged(TreeModelEvent event) + { + dirty = true; + } + + /** + * The listener method, called when the tree nodes are inserted. + * + * @param event the change event + */ + public void treeNodesInserted(TreeModelEvent event) + { + dirty = true; + } + + /** + * The listener method, called when the tree nodes are removed. + * + * @param event the change event + */ + public void treeNodesRemoved(TreeModelEvent event) + { + dirty = true; + } + + /** + * Called when the tree structure has been changed. + * + * @param event the change event + */ + public void treeStructureChanged(TreeModelEvent event) + { + dirty = true; + } + + /** + * Set the tree model that will provide the data. + */ + public void setModel(TreeModel newModel) + { + treeModel = newModel; + // The root node is expanded by default. + expanded.add(treeModel.getRoot()); + dirty = true; + } + + /** + * Inform the instance if the tree root node is visible. If this method + * is not called, it is assumed that the tree root node is not visible. + * + * @param visible true if the tree root node is visible, false + * otherwise. + */ + public void setRootVisible(boolean visible) + { + rootVisible = visible; + dirty = true; + } + + /** + * Get the sum of heights for all rows. + */ + public int getPreferredHeight() + { + if (dirty) + update(); + totalHeight = 0; + Enumeration<NodeRecord> en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = en.nextElement(); + Rectangle r = nr.getBounds(); + totalHeight += r.height; + } + return totalHeight; + } + + /** + * Get the maximal width. + */ + public int getPreferredWidth(Rectangle value) + { + if (dirty) + update(); + + maximalWidth = 0; + Enumeration<NodeRecord> en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = en.nextElement(); + Rectangle r = nr.getBounds(); + if (r.x + r.width > maximalWidth) + maximalWidth = r.x + r.width; + } + return maximalWidth; + } + + /** + * Returns true if this layout supposes that all rows have the fixed + * height. + * + * @return boolean true if all rows in the tree must have the fixed + * height (true by default). + */ + protected boolean isFixedRowHeight() + { + return true; + } + +} diff --git a/libjava/classpath/javax/swing/tree/MutableTreeNode.java b/libjava/classpath/javax/swing/tree/MutableTreeNode.java new file mode 100644 index 000000000..4a0f4b69f --- /dev/null +++ b/libjava/classpath/javax/swing/tree/MutableTreeNode.java @@ -0,0 +1,104 @@ +/* MutableTreeNode.java -- + Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.tree; + +/** + * MutableTreeNode public interface + + * @author Andrew Selkirk + */ +public interface MutableTreeNode extends TreeNode +{ + /** + * Inserts a node as child at a given index. + * + * @param child the note to insert + * @param index the index + * + * @see #remove(int) + * @see #remove(MutableTreeNode) + * @see #setParent(MutableTreeNode) + */ + void insert(MutableTreeNode child, int index); + + /** + * Removes the child node a given index. + * + * @param index the index + * + * @see #insert(MutableTreeNode,int) + * @see #remove(MutableTreeNode) + * @see #removeFromParent() + */ + void remove(int index); + + /** + * Removes a given child node. + * + * @param node the node to remove + * + * @see #insert(MutableTreeNode,int) + * @see #remove(int) + * @see #removeFromParent() + */ + void remove(MutableTreeNode node); + + /** + * Sets a user object, the data represented by the node. + * + * @param object the data + */ + void setUserObject(Object object); + + /** + * Removes this node from its parent. + * + * @see #remove(int) + * @see #remove(MutableTreeNode) + */ + void removeFromParent(); + + /** + * Sets the parent of the node. + * + * @param parent the parent + * + * @see #insert(MutableTreeNode,int) + */ + void setParent(MutableTreeNode parent); +} diff --git a/libjava/classpath/javax/swing/tree/RowMapper.java b/libjava/classpath/javax/swing/tree/RowMapper.java new file mode 100644 index 000000000..d62b703c6 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/RowMapper.java @@ -0,0 +1,54 @@ +/* RowMapper.java -- + Copyright (C) 2002, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.tree; + +/** + * RowMapper public interface + * @author Andrew Selkirk + */ +public interface RowMapper +{ + + /** + * getRowsForPaths + * @param path TreePath + * @return TODO + */ + int[] getRowsForPaths(TreePath[] path); + +} diff --git a/libjava/classpath/javax/swing/tree/TreeCellEditor.java b/libjava/classpath/javax/swing/tree/TreeCellEditor.java new file mode 100644 index 000000000..614f98df8 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/TreeCellEditor.java @@ -0,0 +1,71 @@ +/* TreeCellEditor.java -- + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.awt.Component; + +import javax.swing.CellEditor; +import javax.swing.JTree; + +/** + * A <code>TreeCellEditor</code> is used by the {@link JTree} component to + * edit individual tree elements (nodes). + * + * @author Andrew Selkirk + */ +public interface TreeCellEditor extends CellEditor +{ + /** + * Returns a component that has been configured to edit one element (or + * node) in a {@link JTree} component. The arguments to this method are used + * to pass in the value and state of the element to be edited. + * + * @param tree the tree. + * @param value the value to render. + * @param isSelected is the tree element selected? + * @param expanded is the tree element expanded? + * @param leaf is the tree element a leaf node? + * @param row the row index. + * + * @return A component that is configured for editing the tree element. + */ + Component getTreeCellEditorComponent(JTree tree, Object value, + boolean isSelected, boolean expanded, + boolean leaf, int row); +} diff --git a/libjava/classpath/javax/swing/tree/TreeCellRenderer.java b/libjava/classpath/javax/swing/tree/TreeCellRenderer.java new file mode 100644 index 000000000..fdfc0a116 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/TreeCellRenderer.java @@ -0,0 +1,73 @@ +/* TreeCellRenderer.java -- + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.awt.Component; + +import javax.swing.JTree; + +/** + * A <code>TreeCellRenderer</code> is used by the {@link JTree} component to + * paint individual tree elements (nodes). + * + * @author Andrew Selkirk + */ +public interface TreeCellRenderer +{ + /** + * Returns a component that has been configured to display one element (or + * node) in a {@link JTree} component. The arguments to this method are used + * to pass in the value and state of the element to be rendered. + * + * @param tree the tree. + * @param value the value to render. + * @param selected is the tree element selected? + * @param expanded is the tree element expanded? + * @param leaf is the tree element a leaf node? + * @param row the row index. + * @param hasFocus does the tree element have the focus? + * + * @return A component that is configured for rendering the tree element. + */ + Component getTreeCellRendererComponent(JTree tree, Object value, + boolean selected, boolean expanded, + boolean leaf, int row, + boolean hasFocus); + +} diff --git a/libjava/classpath/javax/swing/tree/TreeModel.java b/libjava/classpath/javax/swing/tree/TreeModel.java new file mode 100644 index 000000000..ec1884efd --- /dev/null +++ b/libjava/classpath/javax/swing/tree/TreeModel.java @@ -0,0 +1,105 @@ +/* TreeModel.java -- + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import javax.swing.event.TreeModelListener; + +/** + * TreeModel public interface + * @author Andrew Selkirk + */ +public interface TreeModel +{ + /** + * getRoot + * @returns Object + */ + Object getRoot(); + + /** + * getChild + * @param parent TODO + * @param index TODO + * @returns Object + */ + Object getChild(Object parent, int index); + + /** + * getChildCount + * @param parent TODO + * @returns int + */ + int getChildCount(Object parent); + + /** + * isLeaf + * @param node TODO + * @returns boolean + */ + boolean isLeaf(Object node); + + /** + * valueForPathChanged + * @param path TODO + * @param newvalue TODO + */ + void valueForPathChanged(TreePath path, Object newvalue); + + /** + * getIndexOfChild + * @param parent TODO + * @param child TODO + * @returns int + */ + int getIndexOfChild(Object parent, Object child); + + /** + * addTreeModelListener + * @param listener TODO + */ + void addTreeModelListener(TreeModelListener listener); + + /** + * removeTreeModelListener + * @param listener TODO + */ + void removeTreeModelListener(TreeModelListener listener); + + +} diff --git a/libjava/classpath/javax/swing/tree/TreeNode.java b/libjava/classpath/javax/swing/tree/TreeNode.java new file mode 100644 index 000000000..b68b498a7 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/TreeNode.java @@ -0,0 +1,113 @@ +/* TreeNode.java -- + Copyright (C) 2002, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.util.Enumeration; + +/** + * A tree node. + * + * @author Andrew Selkirk + */ +public interface TreeNode +{ + + /** + * Returns the parent node for this tree node, or <code>null</code> if this + * node has no parent. + * + * @return The parent node (possibly <code>null</code>). + */ + TreeNode getParent(); + + /** + * Returns the index of the specified child node, or -1 if the node is not + * in fact a child of this node. + * + * @param node the node (<code>null</code> not permitted). + * + * @return The index of the specified child node, or -1. + * + * @throws IllegalArgumentException if <code>node</code> is <code>null</code>. + */ + int getIndex(TreeNode node); + + /** + * Returns the child node at the given index. + * + * @param index the index (in the range <code>0</code> to + * <code>getChildCount() - 1</code>). + * + * @return The child node at the given index. + */ + TreeNode getChildAt(int index); + + /** + * Returns the number of children for this node. + * + * @return The number of children for this node. + */ + int getChildCount(); + + /** + * Returns <code>true</code> if this node allows children, and + * <code>false</code> otherwise. + * + * @return A boolean. + */ + boolean getAllowsChildren(); + + /** + * Returns <code>true</code> if this node is a leaf node, and + * <code>false</code> otherwise. + * + * @return A boolean. + */ + boolean isLeaf(); + + /** + * Returns an enumeration of the children of this node, or an empty + * enumeration if this node has no children. + * + * @return An enumeration of the children of this node. + */ + @SuppressWarnings("unchecked") // Required for API compatibility + Enumeration children(); + +} diff --git a/libjava/classpath/javax/swing/tree/TreePath.java b/libjava/classpath/javax/swing/tree/TreePath.java new file mode 100644 index 000000000..764970696 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/TreePath.java @@ -0,0 +1,312 @@ +/* TreePath.java -- + Copyright (C) 2002, 2005, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * A <code>TreePath</code> represents a sequence of tree elements that form + * a path starting from the root of a tree. A tree element can be represented + * by any {@link Object}. + * + * @author Andrew Selkirk + */ +public class TreePath implements Serializable +{ + static final long serialVersionUID = 4380036194768077479L; + + /** + * The actual patch. The {@link DefaultTreeSelectionModel#clone()} + * assumes that the TreePath is immutable, so it is marked final here. + */ + private final Object[] path; + + /** + * The parent path (to be reused). + */ + private transient TreePath parentPath; + + + /** + * Creates a path from the list of objects representing tree elements. The + * incoming array is copied so that subsequent changes do not affect this + * tree path. + * + * @param path the elements in the path (<code>null</code> not permitted). + * + * @throws IllegalArgumentException if <code>path</code> is <code>null</code>. + */ + public TreePath(Object[] path) + { + if (path == null) + throw new IllegalArgumentException("Null 'path' not permitted."); + this.path = new Object[path.length]; + System.arraycopy(path, 0, this.path, 0, path.length); + } + + /** + * Creates a new path from a single element. + * + * @param element the element (<code>null</code> not permitted). + * + * @throws IllegalArgumentException if <code>element</code> is + * <code>null</code>. + */ + public TreePath(Object element) + { + path = new Object[1]; + path[0] = element; + } + + /** + * Creates a new tree path by adding the specified <code>element</code> to + * the <code>path</code>. + * + * @param path a tree path. + * @param element a path element. + */ + protected TreePath(TreePath path, Object element) + { + if (element == null) + throw new NullPointerException("Null 'element' argument."); + Object[] treepath = path.getPath(); + + // Create Tree Path + this.path = new Object[treepath.length + 1]; + System.arraycopy(treepath, 0, this.path, 0, treepath.length); + this.path[treepath.length] = element; + } + + /** + * Creates a new tree path using the first <code>length</code> elements + * from the given array. + * + * @param path the path elements. + * @param length the path length. + */ + protected TreePath(Object[] path, int length) + { + // Create Path + this.path = new Object[length]; + System.arraycopy(path, 0, this.path, 0, length); + } + + /** + * Default constructor. + */ + protected TreePath() + { + path = new Object[0]; + } + + + /** + * Returns a hashcode for the path. + * + * @return A hashcode. + */ + public int hashCode() + { + return getLastPathComponent().hashCode(); + } + + /** + * Tests this path for equality with an arbitrary object. An object is + * considered equal to this path if and only if: + * <ul> + * <li>the object is not <code>null</code>;</li> + * <li>the object is an instanceof {@link TreePath};</li> + * <li>the object contains the same elements in the same order as this + * {@link TreePath};</li> + * </ul> + * + * @param object the object (<code>null</code> permitted). + * + * @return <code>true</code> if <code>obj</code> is equal to this tree path, + * and <code>false</code> otherwise. + */ + public boolean equals(Object object) + { + Object[] treepath; + int index; + + if (object instanceof TreePath) + { + treepath = ((TreePath) object).getPath(); + if (treepath.length != path.length) + return false; + for (index = 0; index < path.length; index++) + { + if (!path[index].equals(treepath[index])) + return false; + } + + // Tree Path's are equals + return true; + } + + // Unequal + return false; + } + + /** + * Returns a string representation of this path. + * + * @return A string representation of this path. + */ + public String toString() + { + if (path.length == 1) + return String.valueOf(path[0]); + else + return Arrays.asList(path).toString(); + } + + /** + * Returns an array containing the path elements. + * + * @return An array containing the path elements. + */ + public Object[] getPath() + { + return (Object[]) path.clone(); + } + + /** + * Returns the last object in the path. + * + * @return The last object in the path. + */ + public Object getLastPathComponent() + { + return path[path.length - 1]; + } + + /** + * Returns the number of elements in the path. + * + * @return The number of elements in the path. + */ + public int getPathCount() + { + return path.length; + } + + /** + * Returns the element at the specified position in the path. + * + * @param position the element position (<code>0 < N - 1</code>, where + * <code>N</code> is the number of elements in the path). + * + * @return The element at the specified position. + * + * @throws IllegalArgumentException if <code>position</code> is outside the + * valid range. + */ + public Object getPathComponent(int position) + { + if (position < 0 || position >= getPathCount()) + throw new IllegalArgumentException("Invalid position: " + position); + return path[position]; + } + + /** + * Returns <code>true</code> if <code>path</code> is a descendant of this + * path, and <code>false</code> otherwise. If <code>path</code> is + * <code>null</code>, this method returns <code>false</code>. + * + * @param path the path to check (<code>null</code> permitted). + * + * @return <code>true</code> if <code>path</code> is a descendant of this + * path, and <code>false</code> otherwise + */ + public boolean isDescendant(TreePath path) + { + if (path == null) + return false; + int count = getPathCount(); + int otherPathLength = path.getPathCount(); + if (otherPathLength < count) + return false; + while (otherPathLength > count) + { + otherPathLength--; + path = path.getParentPath(); + } + + return equals(path); + } + + /** + * Creates a new path that is equivalent to this path plus the specified + * element. + * + * @param element the element. + * + * @return A tree path. + */ + public TreePath pathByAddingChild(Object element) + { + return new TreePath(this, element); + } + + /** + * Returns the parent path, which is a path containing all the same elements + * as this path, except for the last one. If this path contains only one + * element, the method returns <code>null</code>. + * + * @return The parent path, or <code>null</code> if this path has only one + * element. + */ + public TreePath getParentPath() + { + // If this path has only one element, then we return null. That + // is what the JDK does. + if (path.length <= 1) + return null; + + // Reuse the parent path, if possible. The parent path is requested + // during the tree repainting, so reusing generates a lot less garbage. + if (parentPath == null) + parentPath = new TreePath(this.getPath(), path.length - 1); + + return parentPath; + } +} diff --git a/libjava/classpath/javax/swing/tree/TreeSelectionModel.java b/libjava/classpath/javax/swing/tree/TreeSelectionModel.java new file mode 100644 index 000000000..f2ba02a17 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/TreeSelectionModel.java @@ -0,0 +1,112 @@ +/* TreeSelectionModel.java -- + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.tree; + +import java.beans.PropertyChangeListener; + +import javax.swing.event.TreeSelectionListener; + +/** + * TreeSelectionModel public interface + * @author Andrew Selkirk + */ +public interface TreeSelectionModel +{ + + int SINGLE_TREE_SELECTION = 1; + + int CONTIGUOUS_TREE_SELECTION = 2; + + int DISCONTIGUOUS_TREE_SELECTION = 4; + + void setSelectionMode(int mode); + + int getSelectionMode(); + + void setSelectionPath(TreePath path); + + void setSelectionPaths(TreePath[] paths); + + void addSelectionPath(TreePath path); + + void addSelectionPaths(TreePath[] paths); + + void removeSelectionPath(TreePath path); + + void removeSelectionPaths(TreePath[] paths); + + TreePath getSelectionPath(); + + TreePath[] getSelectionPaths(); + + int getSelectionCount(); + + boolean isPathSelected(TreePath path); + + boolean isSelectionEmpty(); + + void clearSelection(); + + void setRowMapper(RowMapper newMapper); + + RowMapper getRowMapper(); + + int[] getSelectionRows(); + + int getMinSelectionRow(); + + int getMaxSelectionRow(); + + boolean isRowSelected(int row); + + void resetRowSelection(); + + int getLeadSelectionRow(); + + TreePath getLeadSelectionPath(); + + void addPropertyChangeListener(PropertyChangeListener listener); + + void removePropertyChangeListener(PropertyChangeListener listener); + + void addTreeSelectionListener(TreeSelectionListener x); + + void removeTreeSelectionListener(TreeSelectionListener x); + +} diff --git a/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java new file mode 100644 index 000000000..0887061b3 --- /dev/null +++ b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java @@ -0,0 +1,657 @@ +/* VariableHeightLayoutCache.java -- + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.tree; + +import gnu.javax.swing.tree.GnuPath; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.Set; +import java.util.Vector; + +import javax.swing.event.TreeModelEvent; + +/** + * The fixed height tree layout. This class requires the NodeDimensions to be + * set and ignores the row height property. + * + * @specnote the methods, of this class, returning TreePath, actually returns + * the derived class GnuPath that provides additional information for optimized + * painting. See the GnuPath code for details. + * + * @author Audrius Meskauskas + */ +public class VariableHeightLayoutCache + extends AbstractLayoutCache +{ + + private static final Rectangle RECT_CACHE = new Rectangle(); + + /** + * The cached node record. + */ + class NodeRecord + { + NodeRecord(int aRow, int aDepth, Object aNode, Object aParent) + { + row = aRow; + depth = aDepth; + parent = aParent; + node = aNode; + isExpanded = expanded.contains(aNode); + bounds = new Rectangle(0, -1, 0, 0); + } + + /** + * The row, where the tree node is displayed. + */ + final int row; + + /** + * The nesting depth + */ + final int depth; + + /** + * The parent of the given node, null for the root node. + */ + final Object parent; + + /** + * This node. + */ + final Object node; + + /** + * True for the expanded nodes. The value is calculated in constructor. + * Using this field saves one hashtable access operation. + */ + final boolean isExpanded; + + /** + * The cached bounds of the tree row. + */ + Rectangle bounds; + + /** + * The path from the tree top to the given node (computed under first + * demand) + */ + private TreePath path; + + /** + * Get the path for this node. The derived class is returned, making check + * for the last child of some parent easier. + */ + TreePath getPath() + { + if (path == null) + { + boolean lastChild = false; + if (parent != null) + { + int nc = treeModel.getChildCount(parent); + if (nc > 0) + { + int n = treeModel.getIndexOfChild(parent, node); + if (n == nc - 1) + lastChild = true; + } + } + + LinkedList<Object> lpath = new LinkedList<Object>(); + NodeRecord rp = this; + while (rp != null) + { + lpath.addFirst(rp.node); + if (rp.parent != null) + { + Object parent = rp.parent; + rp = nodes.get(parent); + // Add the root node, even if it is not visible. + if (rp == null) + lpath.addFirst(parent); + } + else + rp = null; + } + path = new GnuPath(lpath.toArray(), lastChild); + } + return path; + } + + /** + * Get the rectangle bounds (compute, if required). + */ + Rectangle getBounds() + { + return bounds; + } + } + + /** + * The set of all expanded tree nodes. + */ + Set<Object> expanded = new HashSet<Object>(); + + /** + * Maps nodes to the row numbers. + */ + Hashtable<Object,NodeRecord> nodes = new Hashtable<Object,NodeRecord>(); + + /** + * Maps row numbers to nodes. + */ + ArrayList<Object> row2node = new ArrayList<Object>(); + + /** + * If true, the row map must be recomputed before using. + */ + boolean dirty; + + /** + * The cumulative height of all rows. + */ + int totalHeight; + + /** + * The maximal width. + */ + int maximalWidth; + + /** + * Creates the unitialised instance. Before using the class, the row height + * must be set with the {@link #setRowHeight(int)} and the model must be set + * with {@link #setModel(TreeModel)}. The node dimensions may not be set. + */ + public VariableHeightLayoutCache() + { + // Nothing to do here. + } + + /** + * Get the total number of rows in the tree. Every displayed node occupies the + * single row. The root node row is included if the root node is set as + * visible (false by default). + * + * @return int the number of the displayed rows. + */ + public int getRowCount() + { + if (dirty) update(); + return row2node.size(); + } + + /** + * Refresh the row map. + */ + private final void update() + { + nodes.clear(); + row2node.clear(); + + totalHeight = maximalWidth = 0; + + if (treeModel == null) + return; + + Object root = treeModel.getRoot(); + countRows(root, null, 0, 0); + dirty = false; + } + + /** + * Recursively counts all rows in the tree. + */ + private final int countRows(Object node, Object parent, int depth, int y) + { + boolean visible = node != treeModel.getRoot() || rootVisible; + int row = row2node.size(); + if (visible) + { + row2node.add(node); + } + NodeRecord nr = new NodeRecord(row, depth, node, parent); + NodeDimensions d = getNodeDimensions(); + Rectangle r = RECT_CACHE; + if (d != null) + r = d.getNodeDimensions(node, row, depth, nr.isExpanded, r); + else + r.setBounds(0, 0, 0, 0); + + if (! visible) + r.y = -1; + else + r.y = Math.max(0, y); + + if (isFixedRowHeight()) + r.height = getRowHeight(); + + nr.bounds.setBounds(r); + nodes.put(node, nr); + + if (visible) + y += r.height; + + int sc = treeModel.getChildCount(node); + int deeper = depth + 1; + if (expanded.contains(node)) + { + for (int i = 0; i < sc; i++) + { + Object child = treeModel.getChild(node, i); + y = countRows(child, node, deeper, y); + } + } + return y; + } + + /** + * Discard the bound information for the given path. + * + * @param path the path, for that the bound information must be recomputed. + */ + public void invalidatePathBounds(TreePath path) + { + NodeRecord r = nodes.get(path.getLastPathComponent()); + if (r != null) + r.bounds = null; + } + + /** + * Mark all cached information as invalid. + */ + public void invalidateSizes() + { + dirty = true; + } + + /** + * Set the expanded state of the given path. The expansion states must be + * always updated when expanding and colapsing the tree nodes. Otherwise + * other methods will not work correctly after the nodes are collapsed or + * expanded. + * + * @param path the tree path, for that the state is being set. + * @param isExpanded the expanded state of the given path. + */ + public void setExpandedState(TreePath path, boolean isExpanded) + { + if (isExpanded) + { + int length = path.getPathCount(); + for (int i = 0; i < length; i++) + expanded.add(path.getPathComponent(i)); + } + else + expanded.remove(path.getLastPathComponent()); + + dirty = true; + } + + /** + * Get the expanded state for the given tree path. + * + * @return true if the given path is expanded, false otherwise. + */ + public boolean isExpanded(TreePath path) + { + return expanded.contains(path.getLastPathComponent()); + } + + /** + * Get bounds for the given tree path. + * + * @param path the tree path + * @param rect the rectangle that will be reused to return the result. + * @return Rectangle the bounds of the last line, defined by the given path. + */ + public Rectangle getBounds(TreePath path, Rectangle rect) + { + if (path == null) + return null; + if (dirty) + update(); + + Object last = path.getLastPathComponent(); + Rectangle result = null; + NodeRecord r = nodes.get(last); + if (r != null) + { + // The RI allows null arguments for rect, in which case a new Rectangle + // is created. + result = rect; + if (result == null) + result = new Rectangle(r.bounds); + else + result.setBounds(r.bounds); + } + return result; + } + + /** + * Get the path, the last element of that is displayed in the given row. + * + * @param row the row + * @return TreePath the path + */ + public TreePath getPathForRow(int row) + { + if (dirty) + update(); + + TreePath path = null; + // Search row in the nodes map. TODO: This is inefficient, optimize this. + Enumeration<NodeRecord> nodesEnum = nodes.elements(); + while (nodesEnum.hasMoreElements() && path == null) + { + NodeRecord record = nodesEnum.nextElement(); + if (record.row == row) + path = record.getPath(); + } + return path; + } + + /** + * Get the row, displaying the last node of the given path. + * + * @param path the path + * @return int the row number or -1 if the end of the path is not visible. + */ + public int getRowForPath(TreePath path) + { + if (path == null) + return -1; + + if (dirty) + update(); + + NodeRecord r = nodes.get(path.getLastPathComponent()); + if (r == null) + return - 1; + else + return r.row; + } + + /** + * Get the path, closest to the given point. + * + * @param x the point x coordinate + * @param y the point y coordinate + * @return the tree path, closest to the the given point + */ + public TreePath getPathClosestTo(int x, int y) + { + if (dirty) + update(); + + // As the rows have arbitrary height, we need to iterate. + NodeRecord best = null; + NodeRecord r; + Enumeration<NodeRecord> en = nodes.elements(); + + int dist = Integer.MAX_VALUE; + + while (en.hasMoreElements() && dist > 0) + { + r = en.nextElement(); + if (best == null) + { + best = r; + dist = distance(r.getBounds(), x, y); + } + else + { + int rr = distance(r.getBounds(), x, y); + if (rr < dist) + { + best = r; + dist = rr; + } + } + } + + if (best == null) + return null; + else + return best.getPath(); + } + + /** + * Get the closest distance from this point till the given rectangle. Only + * vertical distance is taken into consideration. + */ + int distance(Rectangle r, int x, int y) + { + if (y < r.y) + return r.y - y; + else if (y > r.y + r.height - 1) + return y - (r.y + r.height - 1); + else + return 0; + } + + /** + * Get the number of the visible childs for the given tree path. If the node + * is not expanded, 0 is returned. Otherwise, the number of children is + * obtained from the model as the number of children for the last path + * component. + * + * @param path the tree path + * @return int the number of the visible childs (for row). + */ + public int getVisibleChildCount(TreePath path) + { + if (! isExpanded(path) || treeModel == null) + return 0; + else + return treeModel.getChildCount(path.getLastPathComponent()); + } + + /** + * Get the enumeration over all visible paths that start from the given + * parent path. + * + * @param parentPath the parent path + * @return the enumeration over pathes + */ + public Enumeration<TreePath> getVisiblePathsFrom(TreePath parentPath) + { + if (dirty) + update(); + Vector<TreePath> p = new Vector<TreePath>(parentPath.getPathCount()); + Object node; + NodeRecord nr; + + for (int i = 0; i < parentPath.getPathCount(); i++) + { + node = parentPath.getPathComponent(i); + nr = nodes.get(node); + if (nr != null && nr.row >= 0) + p.add((TreePath) node); + } + return p.elements(); + } + + /** + * Return the expansion state of the given tree path. The expansion state + * must be previously set with the + * {@link #setExpandedState(TreePath, boolean)} + * + * @param path the path being checked + * @return true if the last node of the path is expanded, false otherwise. + */ + public boolean getExpandedState(TreePath path) + { + return expanded.contains(path.getLastPathComponent()); + } + + /** + * The listener method, called when the tree nodes are changed. + * + * @param event the change event + */ + public void treeNodesChanged(TreeModelEvent event) + { + dirty = true; + } + + /** + * The listener method, called when the tree nodes are inserted. + * + * @param event the change event + */ + public void treeNodesInserted(TreeModelEvent event) + { + dirty = true; + } + + /** + * The listener method, called when the tree nodes are removed. + * + * @param event the change event + */ + public void treeNodesRemoved(TreeModelEvent event) + { + dirty = true; + } + + /** + * Called when the tree structure has been changed. + * + * @param event the change event + */ + public void treeStructureChanged(TreeModelEvent event) + { + dirty = true; + } + + /** + * Set the tree model that will provide the data. + */ + public void setModel(TreeModel newModel) + { + treeModel = newModel; + dirty = true; + if (treeModel != null) + { + // The root node is expanded by default. + expanded.add(treeModel.getRoot()); + } + } + + /** + * Inform the instance if the tree root node is visible. If this method + * is not called, it is assumed that the tree root node is not visible. + * + * @param visible true if the tree root node is visible, false + * otherwise. + */ + public void setRootVisible(boolean visible) + { + rootVisible = visible; + dirty = true; + } + + /** + * Get the sum of heights for all rows. + */ + public int getPreferredHeight() + { + if (dirty) + update(); + int height = 0; + int rowCount = getRowCount(); + if (rowCount > 0) + { + NodeRecord last = nodes.get(row2node.get(rowCount - 1)); + height = last.bounds.y + last.bounds.height; + } + return height; + } + + /** + * Get the maximal width. + */ + public int getPreferredWidth(Rectangle value) + { + if (dirty) + update(); + + maximalWidth = 0; + Enumeration<NodeRecord> en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = en.nextElement(); + if (nr != null) + { + Rectangle r = nr.getBounds(); + int width = r.x + r.width; + if (width > maximalWidth) + maximalWidth = width; + } + } + return maximalWidth; + } + + /** + * Sets the node dimensions and invalidates the cached layout. + * + * @param dim the dimensions to set + */ + public void setNodeDimensions(NodeDimensions dim) + { + super.setNodeDimensions(dim); + dirty = true; + } + + /** + * Sets the row height and marks the layout as invalid. + * + * @param height the row height to set + */ + public void setRowHeight(int height) + { + super.setRowHeight(height); + dirty = true; + } +} diff --git a/libjava/classpath/javax/swing/tree/package.html b/libjava/classpath/javax/swing/tree/package.html new file mode 100644 index 000000000..22538e4ba --- /dev/null +++ b/libjava/classpath/javax/swing/tree/package.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in javax.swing.tree package. + Copyright (C) 2002, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. --> + +<html> +<head><title>GNU Classpath - javax.swing.tree</title></head> + +<body> +<p>Interfaces and classes that support the {@link javax.swing.JTree} +component.</p> + +</body> +</html> |