diff options
author | Mark Wielaard <mark@gcc.gnu.org> | 2006-05-18 17:29:21 +0000 |
---|---|---|
committer | Mark Wielaard <mark@gcc.gnu.org> | 2006-05-18 17:29:21 +0000 |
commit | 4f9533c7722fa07511a94d005227961f4a4dec23 (patch) | |
tree | 9f9c470de62ee62fba1331a396450d728d2b1fad /libjava/classpath/javax/swing/tree | |
parent | eaec4980e139903ae9b274d1abcf3a13946603a8 (diff) | |
download | gcc-4f9533c7722fa07511a94d005227961f4a4dec23.zip gcc-4f9533c7722fa07511a94d005227961f4a4dec23.tar.gz gcc-4f9533c7722fa07511a94d005227961f4a4dec23.tar.bz2 |
Imported GNU Classpath 0.90
Imported GNU Classpath 0.90
* scripts/makemake.tcl: LocaleData.java moved to gnu/java/locale.
* sources.am: Regenerated.
* gcj/javaprims.h: Regenerated.
* Makefile.in: Regenerated.
* gcj/Makefile.in: Regenerated.
* include/Makefile.in: Regenerated.
* testsuite/Makefile.in: Regenerated.
* gnu/java/lang/VMInstrumentationImpl.java: New override.
* gnu/java/net/local/LocalSocketImpl.java: Likewise.
* gnu/classpath/jdwp/VMMethod.java: Likewise.
* gnu/classpath/jdwp/VMVirtualMachine.java: Update to latest
interface.
* java/lang/Thread.java: Add UncaughtExceptionHandler.
* java/lang/reflect/Method.java: Implements GenericDeclaration and
isSynthetic(),
* java/lang/reflect/Field.java: Likewise.
* java/lang/reflect/Constructor.java
* java/lang/Class.java: Implements Type, GenericDeclaration,
getSimpleName() and getEnclosing*() methods.
* java/lang/Class.h: Add new public methods.
* java/lang/Math.java: Add signum(), ulp() and log10().
* java/lang/natMath.cc (log10): New function.
* java/security/VMSecureRandom.java: New override.
* java/util/logging/Logger.java: Updated to latest classpath
version.
* java/util/logging/LogManager.java: New override.
From-SVN: r113887
Diffstat (limited to 'libjava/classpath/javax/swing/tree')
12 files changed, 2918 insertions, 1692 deletions
diff --git a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java index adece10..8dbdd2f 100644 --- a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java @@ -38,10 +38,13 @@ exception statement from your version. */ package javax.swing.tree; +import gnu.classpath.NotImplementedException; + import java.awt.Rectangle; import java.util.Enumeration; import javax.swing.event.TreeModelEvent; +import javax.swing.tree.VariableHeightLayoutCache.NodeRecord; /** * class AbstractLayoutCache @@ -49,361 +52,397 @@ import javax.swing.event.TreeModelEvent; * @author Andrew Selkirk */ public abstract class AbstractLayoutCache - implements RowMapper + implements RowMapper { - /** - * class NodeDimensions - */ - public abstract static class NodeDimensions - { - /** - * Creates <code>NodeDimensions</code> object. - */ - public NodeDimensions() - { - // Do nothing here. - } - - /** - * getNodeDimensions - * - * @param value0 TODO - * @param value1 TODO - * @param value2 TODO - * @param value3 TODO - * @param value4 TODO - * @return Rectangle - */ - public abstract Rectangle getNodeDimensions(Object value0, int value1, - int value2, boolean value3, Rectangle value4); - } - - /** - * 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; - } - - /** - * getNodeDimensions - * - * @param value TODO - * @param row TODO - * @param depth TODO - * @param expanded TODO - * @param bounds TODO - * - * @return Rectangle - */ - protected Rectangle getNodeDimensions(Object value, int row, int depth, - boolean expanded, Rectangle bounds) - { - if (bounds == null) - return new Rectangle(); - return null; - // TODO - } - - /** - * 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; - } - - /** - * getRowHeight - * - * @return the row height - */ - public int getRowHeight() - { - return rowHeight; - } - - /** - * setSelectionModel - * - * @param model the model - */ - public void setSelectionModel(TreeSelectionModel model) - { - treeSelectionModel = model; - } - - /** - * getSelectionModel - * - * @return the model - */ - public TreeSelectionModel getSelectionModel() - { - return treeSelectionModel; - } - - /** - * getPreferredHeight - * - * @return int - */ - public int getPreferredHeight() - { - return 0; // TODO - } - - /** - * getPreferredWidth - * - * @param value0 TODO - * - * @return int - */ - public int getPreferredWidth(Rectangle value0) - { - return 0; // TODO - } - - /** - * 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 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); - - /** - * getRowsForPaths - * - * @param paths the tree paths - * - * @return an array of rows - */ - public int[] getRowsForPaths(TreePath[] paths) - { - return null; // TODO - } - - /** - * isFixedRowHeight - * - * @return boolean - */ - protected boolean isFixedRowHeight() - { - return false; // TODO - } + /** + * 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) + { + if (nodeDimensions == null) + throw new InternalError("The NodeDimensions are not set"); + return nodeDimensions.getNodeDimensions(value, row, depth, expanded, bounds); + } + + /** + * 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) + { + treeSelectionModel = model; + } + + /** + * 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 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 = 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 false; + } } diff --git a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java index d974772..be8317f 100644 --- a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java +++ b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java @@ -1,5 +1,5 @@ /* DefaultMutableTreeNode.java -- - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -53,7 +53,7 @@ import java.util.Vector; /** - * DefaultMutableTreeNode + * A default implementation of the {@link MutableTreeNode} interface. * * @author Andrew Selkirk * @author Robert Schuster (robertschuster@fsfe.org) @@ -64,18 +64,19 @@ public class DefaultMutableTreeNode private static final long serialVersionUID = -4298474751201349152L; /** - * EMPTY_ENUMERATION + * An empty enumeration, returned by {@link #children()} if a node has no + * children. */ public static final Enumeration EMPTY_ENUMERATION = EmptyEnumeration.getInstance(); /** - * parent + * The parent of this node (possibly <code>null</code>). */ protected MutableTreeNode parent; /** - * children + * The child nodes for this node (may be empty). */ protected Vector children = new Vector(); @@ -91,7 +92,7 @@ public class DefaultMutableTreeNode /** * Creates a <code>DefaultMutableTreeNode</code> object. - * This node allows to add child nodes. + * This is equivalent to <code>DefaultMutableTreeNode(null, true)</code>. */ public DefaultMutableTreeNode() { @@ -100,9 +101,10 @@ public class DefaultMutableTreeNode /** * Creates a <code>DefaultMutableTreeNode</code> object with the given - * user object attached to it. This node allows to add child nodes. + * user object attached to it. This is equivalent to + * <code>DefaultMutableTreeNode(userObject, true)</code>. * - * @param userObject the user object + * @param userObject the user object (<code>null</code> permitted). */ public DefaultMutableTreeNode(Object userObject) { @@ -113,7 +115,7 @@ public class DefaultMutableTreeNode * Creates a <code>DefaultMutableTreeNode</code> object with the given * user object attached to it. * - * @param userObject the user object + * @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 */ @@ -124,28 +126,22 @@ public class DefaultMutableTreeNode } /** - * clone + * 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 Object + * @return A clone of the node. */ public Object clone() { - try - { - return super.clone(); - // TODO: Do we need to do more here ? - } - catch (CloneNotSupportedException e) - { - // This never happens. - return null; - } + return new DefaultMutableTreeNode(this.userObject, this.allowsChildren); } /** - * Returns a string representation of this node + * 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 human-readable String representing this node + * @return A string representation of the node (possibly <code>null</code>). */ public String toString() { @@ -156,20 +152,30 @@ public class DefaultMutableTreeNode } /** - * Adds a new child node to this node. + * 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 + * @param child the child node (<code>null</code> not permitted). * - * @throws IllegalArgumentException if <code>child</code> is null - * @throws IllegalStateException if the node does not allow children + * @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 (! allowsChildren) - throw new IllegalStateException(); + if (isNodeAncestor(child)) + throw new IllegalArgumentException("Cannot add ancestor node."); children.add(child); child.setParent(this); @@ -178,7 +184,7 @@ public class DefaultMutableTreeNode /** * Returns the parent node of this node. * - * @return the parent node + * @return The parent node (possibly <code>null</code>). */ public TreeNode getParent() { @@ -186,23 +192,39 @@ public class DefaultMutableTreeNode } /** - * Removes the child with the given index from this node + * Removes the child with the given index from this node. * - * @param index the index + * @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) { - children.remove(index); + MutableTreeNode child = (MutableTreeNode) children.remove(index); + child.setParent(null); } /** - * Removes the given child from this node. + * Removes the given child from this node and sets its parent to + * <code>null</code>. * - * @param node the child node + * @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); } /** @@ -235,11 +257,23 @@ public class DefaultMutableTreeNode /** * Inserts given child node at the given index. * - * @param node the child node + * @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); } @@ -300,24 +334,33 @@ public class DefaultMutableTreeNode } /** - * Returns the child index for a given node. - * - * @param node this node - * - * @return the index + * 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); } /** - * setAllowsChildren + * 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 TODO + * @param allowsChildren the flag. */ public void setAllowsChildren(boolean allowsChildren) { + if (!allowsChildren) + removeAllChildren(); this.allowsChildren = allowsChildren; } @@ -366,15 +409,24 @@ public class DefaultMutableTreeNode */ public void removeAllChildren() { - children.removeAllElements(); + for (int i = getChildCount() - 1; i >= 0; i--) + remove(i); } /** - * isNodeAncestor - * - * @param node TODO - * - * @return boolean + * 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) { @@ -383,19 +435,26 @@ public class DefaultMutableTreeNode TreeNode current = this; - while (current != null - && current != node) + while (current != null && current != node) current = current.getParent(); return current == node; } /** - * isNodeDescendant - * - * @param node TODO - * - * @return boolean + * 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) { @@ -722,11 +781,13 @@ public class DefaultMutableTreeNode } /** - * isNodeChild + * 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 TODO + * @param node the node (<code>null</code> permitted). * - * @return boolean + * @return A boolean. */ public boolean isNodeChild(TreeNode node) { @@ -737,9 +798,11 @@ public class DefaultMutableTreeNode } /** - * getFirstChild + * Returns the first child node belonging to this tree node. * - * @return TreeNode + * @return The first child node. + * + * @throws NoSuchElementException if this tree node has no children. */ public TreeNode getFirstChild() { @@ -747,9 +810,11 @@ public class DefaultMutableTreeNode } /** - * getLastChild + * Returns the last child node belonging to this tree node. * - * @return TreeNode + * @return The last child node. + * + * @throws NoSuchElementException if this tree node has no children. */ public TreeNode getLastChild() { @@ -757,16 +822,20 @@ public class DefaultMutableTreeNode } /** - * getChildAfter + * 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 TODO + * @param node a child of this node (<code>null</code> not permitted). * - * @return TreeNode + * @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) + if (node == null || node.getParent() != this) throw new IllegalArgumentException(); int index = getIndex(node) + 1; @@ -778,16 +847,20 @@ public class DefaultMutableTreeNode } /** - * getChildBefore + * 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 TODO + * @param node a child of this node (<code>null</code> not permitted). * - * @return TreeNode + * @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) + if (node == null || node.getParent() != this) throw new IllegalArgumentException(); int index = getIndex(node) - 1; @@ -799,25 +872,31 @@ public class DefaultMutableTreeNode } /** - * isNodeSibling + * 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 TODO + * @param node the node (<code>null</code> permitted). * - * @return boolean + * @return A boolean. */ public boolean isNodeSibling(TreeNode node) { if (node == null) return false; - + if (node == this) + return true; return (node.getParent() == getParent() && getParent() != null); } /** - * getSiblingCount + * 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 int + * @return The sibling count. */ public int getSiblingCount() { @@ -828,9 +907,11 @@ public class DefaultMutableTreeNode } /** - * getNextSibling + * 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 DefaultMutableTreeNode + * @return The next sibling, or <code>null</code>. */ public DefaultMutableTreeNode getNextSibling() { @@ -846,9 +927,11 @@ public class DefaultMutableTreeNode } /** - * getPreviousSibling + * 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 DefaultMutableTreeNode + * @return The previous sibling, or <code>null</code>. */ public DefaultMutableTreeNode getPreviousSibling() { @@ -864,9 +947,10 @@ public class DefaultMutableTreeNode } /** - * isLeaf + * Returns <code>true</code> if this tree node is a lead node (that is, it + * has no children), and <code>false</otherwise>. * - * @return boolean + * @return A boolean. */ public boolean isLeaf() { @@ -874,9 +958,11 @@ public class DefaultMutableTreeNode } /** - * getFirstLeaf + * 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 DefaultMutableTreeNode + * @return The first leaf node. */ public DefaultMutableTreeNode getFirstLeaf() { @@ -889,9 +975,11 @@ public class DefaultMutableTreeNode } /** - * getLastLeaf + * 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 DefaultMutableTreeNode + * @return The first leaf node. */ public DefaultMutableTreeNode getLastLeaf() { @@ -908,33 +996,37 @@ public class DefaultMutableTreeNode } /** - * getNextLeaf + * Returns the next leaf node after this tree node. * - * @return DefaultMutableTreeNode + * @return The next leaf node, or <code>null</code>. */ public DefaultMutableTreeNode getNextLeaf() { - if (parent == null) - return null; - - // TODO: Fix implementation. + // 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; - //return parent.getChildAfter(this); } /** - * getPreviousLeaf + * Returns the previous leaf node before this tree node. * - * @return DefaultMutableTreeNode + * @return The previous leaf node, or <code>null</code>. */ public DefaultMutableTreeNode getPreviousLeaf() { - if (parent == null) - return null; - - // TODO: Fix implementation. + // 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; - //return parent.getChildBefore(this); } /** diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java index e28c926..cc19501 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java @@ -77,17 +77,6 @@ public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor, TreeSelectionListener { /** - * The gap between the icon and editing component during editing. - */ - static int ICON_TEXT_GAP = 3; - - /** - * The left margin of the editing container (the gap between the tree and - * the editing component of the editing icon. - */ - static int TREE_ICON_GAP = ICON_TEXT_GAP; - - /** * The number of the fast mouse clicks, required to start the editing * session. */ @@ -141,7 +130,7 @@ public class DefaultTreeCellEditor { // From the previous version, the left margin is taken as half // of the icon width. - editingIcon.paintIcon(this, g, TREE_ICON_GAP, 0); + editingIcon.paintIcon(this, g, 0, 0); } super.paint(g); } @@ -157,7 +146,7 @@ public class DefaultTreeCellEditor // Move the component to the left, leaving room for the editing icon: if (editingIcon != null) - eOffset = TREE_ICON_GAP + editingIcon.getIconWidth() + ICON_TEXT_GAP; + eOffset = editingIcon.getIconWidth(); else eOffset = 0; @@ -166,7 +155,7 @@ public class DefaultTreeCellEditor c.setLocation(eOffset, 0); // Span the editing component near over all window width. - c.setSize(bounds.width - eOffset - TREE_ICON_GAP, bounds.height); + c.setSize(bounds.width - eOffset, bounds.height); /* * @specnote the Sun sets some more narrow editing component width (it is * not documented how does it is calculated). However as our text field is @@ -542,7 +531,8 @@ public class DefaultTreeCellEditor * 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 + * @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) diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java index df70ba7..5e93145 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java @@ -407,7 +407,7 @@ public class DefaultTreeCellRenderer this.hasFocus = hasFocus; setHorizontalAlignment(LEFT); setOpaque(false); - setVerticalAlignment(TOP); + setVerticalAlignment(CENTER); setEnabled(true); super.setFont(UIManager.getFont("Tree.font")); @@ -445,8 +445,7 @@ public class DefaultTreeCellRenderer /** * Paints the value. The background is filled based on selected. * - * @param g - * the graphics device. + * @param g the graphics device. */ public void paint(Graphics g) { @@ -468,17 +467,27 @@ public class DefaultTreeCellRenderer getHorizontalTextPosition(), vr, ir, tr, getIconTextGap()); + // Reusing one rectangle. + Rectangle bounds = getBounds(ir); + + bounds.x = tr.x - insets.left; + bounds.width = tr.width + insets.left+insets.right; + g.setColor(super.getBackground()); - g.fillRect(tr.x, tr.y, tr.width, tr.height - insets.top - insets.bottom); + g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); - // paint border - Color b = getBorderSelectionColor(); - if (b != null) + super.paint(g); + + // Paint the border of the focused element only (lead selection) + if (hasFocus) { - g.setColor(b); - g.drawRect(tr.x, tr.y, tr.width, tr.height - insets.top - insets.bottom); + Color b = getBorderSelectionColor(); + if (b != null) + { + g.setColor(b); + g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height - 1); + } } - super.paint(g); } /** diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java index 818f548..c1ca679 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java @@ -37,6 +37,8 @@ exception statement from your version. */ package javax.swing.tree; +import gnu.classpath.NotImplementedException; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -46,7 +48,6 @@ import java.util.EventListener; import javax.swing.event.EventListenerList; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; -import javax.swing.tree.DefaultMutableTreeNode; /** * DefaultTreeModel @@ -74,26 +75,29 @@ public class DefaultTreeModel protected boolean asksAllowsChildren; /** - * Constructor DefaultTreeModel + * Constructor DefaultTreeModel where any node can have children. * * @param root the tree root. */ public DefaultTreeModel(TreeNode root) { - if (root == null) - root = new DefaultMutableTreeNode(); - setRoot(root); + this (root, false); } /** - * Constructor DefaultTreeModel + * Create the DefaultTreeModel that may check if the nodes can have + * children or not. * - * @param root the tree root. - * @param asksAllowsChildren TODO + * @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 root, boolean asksAllowsChildren) + public DefaultTreeModel(TreeNode aRoot, boolean asksAllowsChildren) { - setRoot(root); + if (aRoot == null) + aRoot = new DefaultMutableTreeNode(); + this.root = aRoot; this.asksAllowsChildren = asksAllowsChildren; } @@ -222,25 +226,60 @@ public class DefaultTreeModel } /** - * 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. + * <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() { - // TODO + // 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. + * 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 - TODO + * @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) { - // TODO + 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); } /** @@ -390,7 +429,17 @@ public class DefaultTreeModel */ public void nodeStructureChanged(TreeNode node) { - // TODO + 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); } /** diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java index de27dad..0676f7e 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java @@ -1,5 +1,5 @@ /* DefaultTreeSelectionModel.java - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -35,6 +35,7 @@ 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; @@ -42,7 +43,10 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.Arrays; import java.util.EventListener; +import java.util.HashSet; +import java.util.Iterator; import java.util.Vector; import javax.swing.DefaultListSelectionModel; @@ -52,703 +56,968 @@ import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; /** - * DefaultTreeSelectionModel + * 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 + implements Cloneable, Serializable, TreeSelectionModel { - static final long serialVersionUID = 3288129636638950196L; - - /** - * 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; - - /** - * Constructs a new DefaultTreeSelectionModel. - */ - public DefaultTreeSelectionModel() - { - setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); - listenerList = new EventListenerList(); - } - - /** - * Creates a clone of this DefaultTreeSelectionModel with the same - * selection. - * - * @exception CloneNotSupportedException should not be thrown here - * - * @return a clone of this DefaultTreeSelectionModel - */ - public Object clone() throws CloneNotSupportedException - { - return null; // TODO - } - - /** - * Returns a string that shows this object's properties. - * - * @return a string that shows this object's properties - */ - public String toString() - { - return null; // TODO - } - - /** - * 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 rowMapper the RowMapper to set - * - * @see RowMapper - */ - public void setRowMapper(RowMapper rowMapper) - { - // TODO - } - - /** - * 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) - { - selectionMode = mode; - } - - /** - * 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) - { - selection = new TreePath[] { - path }; - } - - /** - * 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) - { - // TODO - } - - /** - * 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. - * - * @param path the path to add to the selection - */ - public void addSelectionPath(TreePath path) - { - if (!isPathSelected(path)) - { - if (isSelectionEmpty()) - setSelectionPath(path); - else - { - TreePath[] temp = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, temp, 0, selection.length); - temp[temp.length - 1] = path; - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - } - leadPath = path; - fireValueChanged(new TreeSelectionEvent(this, path, true, - leadPath, path)); - } - } - - /** - * 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) - { - if (paths != null) - { - TreePath v0 = null; - for (int i = 0; i < paths.length; i++) - { - v0 = paths[i]; - if (!isPathSelected(v0)) - { - if (isSelectionEmpty()) - setSelectionPath(v0); - else - { - TreePath[] temp = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, temp, 0, - selection.length); - temp[temp.length - 1] = v0; - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - } - leadPath = paths[paths.length - 1]; - fireValueChanged(new TreeSelectionEvent(this, v0, true, - leadPath, paths[0])); - } - } - } - } - - /** - * 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) - { - int index = -1; - if (isPathSelected(path)) - { - for (int i = 0; i < selection.length; i++) - { - if (selection[i].equals(path)) - { - index = i; - break; - } - } - TreePath[] temp = new TreePath[selection.length - 1]; - System.arraycopy(selection, 0, temp, 0, index); - System.arraycopy(selection, index + 1, temp, index, - selection.length - index - 1); - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - - fireValueChanged(new TreeSelectionEvent(this, path, false, - leadPath, 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) - { - int index = -1; - TreePath v0 = null; - for (int i = 0; i < paths.length; i++) - { - v0 = paths[i]; - if (isPathSelected(v0)) - { - for (int x = 0; x < selection.length; x++) - { - if (selection[i].equals(v0)) - { - index = x; - break; - } - } - TreePath[] temp = new TreePath[selection.length - 1]; - System.arraycopy(selection, 0, temp, 0, index); - System.arraycopy(selection, index + 1, temp, index, - selection.length - index - 1); - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - - fireValueChanged(new TreeSelectionEvent(this, v0, false, - leadPath, paths[0])); - } - } - } - } - - /** - * 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. - */ - public void clearSelection() - { - leadPath = null; - selection = null; - } - - /** - * 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 EventListener[] getListeners(Class listenerType) - { - return listenerList.getListeners(listenerType); - } - - /** - * Returns the currently selected rows. - * - * @return the currently selected rows - */ - public int[] getSelectionRows() - { - if (rowMapper == null) - return null; - else - return rowMapper.getRowsForPaths(selection); - } - - /** - * Returns the smallest row index from the selection. - * - * @return the smallest row index from the selection - */ - public int getMinSelectionRow() - { - if ((rowMapper == null) || (selection == null) - || (selection.length == 0)) - return -1; - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - int minRow = Integer.MAX_VALUE; - for (int index = 0; index < rows.length; index++) - minRow = Math.min(minRow, rows[index]); - return minRow; - } - } - - /** - * Returns the largest row index from the selection. - * - * @return the largest row index from the selection - */ - public int getMaxSelectionRow() - { - if ((rowMapper == null) || (selection == null) - || (selection.length == 0)) - return -1; - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - int maxRow = -1; - for (int index = 0; index < rows.length; index++) - maxRow = Math.max(maxRow, rows[index]); - return maxRow; - } - } - - /** - * 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 - */ - public boolean isRowSelected(int row) - { - return false; // TODO - } - - /** - * Updates the mappings from TreePaths to row indices. - */ - public void resetRowSelection() - { - // TODO - } - - /** - * getLeadSelectionRow - * - * @return int - */ - public int getLeadSelectionRow() - { - if ((rowMapper == null) || (leadPath == null)) - return -1; - else - return rowMapper.getRowsForPaths(new TreePath[] { - leadPath })[0]; - } - - /** - * 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) - { - changeSupport.addPropertyChangeListener(listener); - } - - /** - * Removes a <code>PropertyChangeListener</code> object from this model. - * - * @param listener the listener to remove. - */ - public void removePropertyChangeListener(PropertyChangeListener listener) - { - changeSupport.removePropertyChangeListener(listener); - } - - /** - * Returns all added <code>PropertyChangeListener</code> objects. - * - * @return an array of listeners. - * - * @since 1.4 - */ - public PropertyChangeListener[] getPropertyChangeListeners() - { - return changeSupport.getPropertyChangeListeners(); - } - - /** - * 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() - { - // TODO - } - - /** - * Returns <code>true</code> if the paths are contiguous or 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) - { - return false; // TODO - } - - /** - * 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) - { - return false; // TODO - } - - /** - * 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) - { - return false; // TODO - } - - /** - * notifyPathChange - * - * @param value0 TODO - * @param value1 TODO - */ - protected void notifyPathChange(Vector value0, TreePath value1) - { - // TODO - } - - /** - * Updates the lead index instance field. - */ - protected void updateLeadIndex() - { - // TODO - } - - /** - * Deprecated and not used. - */ - protected void insureUniqueness() - { - // TODO - } + + /** + * 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; + + /** + * Constructs a new DefaultTreeSelectionModel. + */ + public DefaultTreeSelectionModel() + { + setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); + listenerList = new EventListenerList(); + } + + /** + * 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(); + + // Clone the selection and the list selection model. + cloned.selection = (TreePath[]) selection.clone(); + if (listSelectionModel!=null) + cloned.listSelectionModel = + (DefaultListSelectionModel) listSelectionModel.clone(); + 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 + { + StringBuffer b = new StringBuffer("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; + } + + /** + * 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) + { + selectionMode = mode; + insureRowContinuity(); + } + + /** + * 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) + { + // The most frequently only one cell in the tree is selected. + TreePath[] ose = selection; + selection = new TreePath[] { path }; + TreePath oldLead = leadPath; + leadIndex = 0; + leadRow = getRow(path); + leadPath = path; + + TreeSelectionEvent event; + + if (ose != null && ose.length > 0) + { + // The first item in the path list is the selected path. + // The remaining items are unselected pathes. + TreePath[] changed = new TreePath[ose.length + 1]; + boolean[] news = new boolean[changed.length]; + news[0] = true; + changed[0] = path; + System.arraycopy(ose, 0, changed, 1, ose.length); + event = new TreeSelectionEvent(this, changed, news, oldLead, path); + } + else + { + event = new TreeSelectionEvent(this, path, true, oldLead, path); + } + fireValueChanged(event); + } + + /** + * 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 + { + // Generic non optimized implementation. + int[] rows = mapper.getRowsForPaths(new TreePath[] { path }); + if (rows.length == 0) + return - 1; + else + return rows[0]; + } + } + + /** + * 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) + { + // Must be called, as defined in JDK API 1.4. + insureUniqueness(); + clearSelection(); + addSelectionPaths(paths); + } + + /** + * 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 (! isPathSelected(path)) + { + if (selectionMode == SINGLE_TREE_SELECTION || isSelectionEmpty() + || ! canPathBeAdded(path)) + setSelectionPath(path); + else + { + TreePath[] temp = new TreePath[selection.length + 1]; + System.arraycopy(selection, 0, temp, 0, selection.length); + temp[temp.length - 1] = path; + selection = new TreePath[temp.length]; + System.arraycopy(temp, 0, selection, 0, temp.length); + } + } + + if (path!=leadPath) + { + TreePath oldLead = leadPath; + leadPath = path; + leadRow = getRow(path); + leadIndex = selection.length - 1; + fireValueChanged(new TreeSelectionEvent(this, path, true, oldLead, + leadPath)); + } + } + + /** + * 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) + { + // Must be called, as defined in JDK API 1.4. + insureUniqueness(); + + if (paths != null) + { + TreePath v0 = null; + for (int i = 0; i < paths.length; i++) + { + v0 = paths[i]; + if (! isPathSelected(v0)) + { + if (isSelectionEmpty()) + setSelectionPath(v0); + else + { + TreePath[] temp = new TreePath[selection.length + 1]; + System.arraycopy(selection, 0, temp, 0, selection.length); + temp[temp.length - 1] = v0; + selection = new TreePath[temp.length]; + System.arraycopy(temp, 0, selection, 0, temp.length); + } + TreePath oldLead = leadPath; + leadPath = paths[paths.length - 1]; + leadRow = getRow(leadPath); + leadIndex = selection.length - 1; + + fireValueChanged(new TreeSelectionEvent(this, v0, true, + oldLead, leadPath)); + } + } + insureRowContinuity(); + } + } + + /** + * 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 (isSelectionEmpty()) + return; + + int index = - 1; + if (isPathSelected(path)) + { + for (int i = 0; i < selection.length; i++) + { + if (selection[i].equals(path)) + { + index = i; + break; + } + } + TreePath[] temp = new TreePath[selection.length - 1]; + System.arraycopy(selection, 0, temp, 0, index); + System.arraycopy(selection, index + 1, temp, index, selection.length + - index - 1); + selection = new TreePath[temp.length]; + System.arraycopy(temp, 0, selection, 0, temp.length); + + // If the removed path was the lead path, set the lead path to null. + TreePath oldLead = leadPath; + if (path!=null && leadPath!=null && path.equals(leadPath)) + leadPath = null; + + fireValueChanged(new TreeSelectionEvent(this, path, false, oldLead, + leadPath)); + insureRowContinuity(); + } + } + + /** + * 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 (isSelectionEmpty()) + return; + if (paths != null) + { + int index = - 1; + TreePath v0 = null; + TreePath oldLead = leadPath; + for (int i = 0; i < paths.length; i++) + { + v0 = paths[i]; + if (isPathSelected(v0)) + { + for (int x = 0; x < selection.length; x++) + { + if (selection[i].equals(v0)) + { + index = x; + break; + } + if (leadPath != null && leadPath.equals(v0)) + leadPath = null; + } + TreePath[] temp = new TreePath[selection.length - 1]; + System.arraycopy(selection, 0, temp, 0, index); + System.arraycopy(selection, index + 1, temp, index, + selection.length - index - 1); + selection = new TreePath[temp.length]; + System.arraycopy(temp, 0, selection, 0, temp.length); + + fireValueChanged(new TreeSelectionEvent(this, v0, false, + oldLead, leadPath)); + } + } + insureRowContinuity(); + } + } + + /** + * 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 (! isSelectionEmpty()) + { + TreeSelectionEvent event = new TreeSelectionEvent( + this, selection, new boolean[selection.length], leadPath, null); + leadPath = null; + selection = null; + fireValueChanged(event); + } + else + { + leadPath = null; + selection = null; + } + } + + /** + * 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 EventListener[] getListeners(Class listenerType) + { + return listenerList.getListeners(listenerType); + } + + /** + * Returns the currently selected rows. + * + * @return the currently selected rows + */ + public int[] getSelectionRows() + { + if (rowMapper == null) + return null; + else + return rowMapper.getRowsForPaths(selection); + } + + /** + * Returns the smallest row index from the selection. + * + * @return the smallest row index from the selection + */ + public int getMinSelectionRow() + { + if ((rowMapper == null) || (selection == null) || (selection.length == 0)) + return - 1; + else + { + int[] rows = rowMapper.getRowsForPaths(selection); + int minRow = Integer.MAX_VALUE; + for (int index = 0; index < rows.length; index++) + minRow = Math.min(minRow, rows[index]); + return minRow; + } + } + + /** + * Returns the largest row index from the selection. + * + * @return the largest row index from the selection + */ + public int getMaxSelectionRow() + { + if ((rowMapper == null) || (selection == null) || (selection.length == 0)) + return - 1; + else + { + int[] rows = rowMapper.getRowsForPaths(selection); + int maxRow = - 1; + for (int index = 0; index < rows.length; index++) + maxRow = Math.max(maxRow, rows[index]); + return maxRow; + } + } + + /** + * 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 false if nothing is selected. + if (isSelectionEmpty()) + return false; + + RowMapper mapper = getRowMapper(); + + if (mapper instanceof AbstractLayoutCache) + { + // The absolute majority of cases, unless the TreeUI is very + // seriously rewritten + AbstractLayoutCache ama = (AbstractLayoutCache) mapper; + TreePath path = ama.getPathForRow(row); + return isPathSelected(path); + } + else + { + // Generic non optimized implementation. + int[] rows = mapper.getRowsForPaths(selection); + for (int i = 0; i < rows.length; i++) + if (rows[i] == row) + return true; + return false; + } + } + + /** + * Updates the mappings from TreePaths to row indices. + */ + public void resetRowSelection() + { + // Nothing to do here. + } + + /** + * 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) + { + changeSupport.addPropertyChangeListener(listener); + } + + /** + * Removes a <code>PropertyChangeListener</code> object from this model. + * + * @param listener the listener to remove. + */ + public void removePropertyChangeListener(PropertyChangeListener listener) + { + changeSupport.removePropertyChangeListener(listener); + } + + /** + * Returns all added <code>PropertyChangeListener</code> objects. + * + * @return an array of listeners. + * @since 1.4 + */ + public PropertyChangeListener[] getPropertyChangeListeners() + { + return changeSupport.getPropertyChangeListeners(); + } + + /** + * 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 (selection == null || selection.length < 2) + return; + else if (selectionMode == CONTIGUOUS_TREE_SELECTION) + { + if (rowMapper == null) + // This is the best we can do without the row mapper: + selectOne(); + else + { + int[] rows = rowMapper.getRowsForPaths(selection); + Arrays.sort(rows); + int i; + for (i = 1; i < rows.length; i++) + { + if (rows[i - 1] != rows[i] - 1) + // Break if no longer continuous. + break; + } + + if (i < rows.length) + { + TreePath[] ns = new TreePath[i]; + for (int j = 0; j < ns.length; j++) + ns[i] = getPath(j); + setSelectionPaths(ns); + } + } + } + else if (selectionMode == SINGLE_TREE_SELECTION) + selectOne(); + } + + /** + * Keep only one (normally last or leading) path in the selection. + */ + private void selectOne() + { + if (leadIndex > 0 && leadIndex < selection.length) + setSelectionPath(selection[leadIndex]); + else + setSelectionPath(selection[selection.length -1]); + } + + /** + * Get path for the given row that must be in the current selection. + */ + private TreePath getPath(int row) + { + if (rowMapper instanceof AbstractLayoutCache) + return ((AbstractLayoutCache) rowMapper).getPathForRow(row); + else + { + int[] rows = rowMapper.getRowsForPaths(selection); + for (int i = 0; i < rows.length; i++) + if (rows[i] == row) + return selection[i]; + } + throw new InternalError(row + " not in selection"); + } + + /** + * 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[] rows = rowMapper.getRowsForPaths(paths); + + // The patches may not be sorted. + Arrays.sort(rows); + + for (int i = 1; i < rows.length; i++) + { + if (rows[i-1] != rows[i] - 1) + return false; + } + 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 (rowMapper == null || isSelectionEmpty() + || selectionMode == DISCONTIGUOUS_TREE_SELECTION) + return true; + + TreePath [] all = new TreePath[paths.length + selection.length]; + System.arraycopy(paths, 0, all, 0, paths.length); + System.arraycopy(selection, 0, all, paths.length, selection.length); + + return arePathsContiguous(all); + } + + /** + * Checks if the single path can be added to selection. + */ + private boolean canPathBeAdded(TreePath path) + { + if (rowMapper == null || isSelectionEmpty() + || selectionMode == DISCONTIGUOUS_TREE_SELECTION) + return true; + + TreePath[] all = new TreePath[selection.length + 1]; + System.arraycopy(selection, 0, all, 0, selection.length); + all[all.length - 1] = path; + + return arePathsContiguous(all); + } + + /** + * 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 set = new HashSet(); + 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 iter = set.iterator(); + + for (int i = 0; i < remaining.length; i++) + remaining[i] = (TreePath) 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 vPathes the vector of the changed patches + * @param oldLeadSelection the old selection index + */ + protected void notifyPathChange(Vector vPathes, TreePath oldLeadSelection) + { + TreePath[] pathes = new TreePath[vPathes.size()]; + for (int i = 0; i < pathes.length; i++) + pathes[i] = (TreePath) vPathes.get(i); + + boolean[] news = new boolean[pathes.length]; + for (int i = 0; i < news.length; i++) + news[i] = isPathSelected(pathes[i]); + + TreeSelectionEvent event = new TreeSelectionEvent(this, pathes, news, + oldLeadSelection, + leadPath); + fireValueChanged(event); + } + + /** + * Updates the lead selection row number after changing the lead selection + * path. + */ + protected void updateLeadIndex() + { + if (isSelectionEmpty()) + { + leadRow = leadIndex = - 1; + } + else + { + leadRow = getRow(leadPath); + for (int i = 0; i < selection.length; i++) + { + if (selection[i].equals(leadPath)) + { + leadIndex = i; + break; + } + } + leadIndex = leadRow; + } + } + + /** + * 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/FixedHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java index 535417e..96655ce 100644 --- a/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java @@ -1,5 +1,5 @@ -/* FixedHeightLayoutCache.java -- -Copyright (C) 2002, 2004 Free Software Foundation, Inc. +/* FixedHeightLayoutCache.java -- Fixed cell height tree layout cache +Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -37,208 +37,592 @@ 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; + /** - * FixedHeightLayoutCache + * 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 Andrew Selkirk + * @author Audrius Meskauskas + * @author Andrew Selkirk */ public class FixedHeightLayoutCache - extends AbstractLayoutCache + 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 lpath = new LinkedList(); + 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 expanded = new HashSet(); + + /** + * Maps nodes to the row numbers. + */ + Hashtable nodes = new Hashtable(); + + /** + * Maps row numbers to nodes. + */ + Hashtable row2node = new Hashtable(); + + /** + * 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 = (NodeRecord) 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 = (NodeRecord) 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 = (NodeRecord) 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 en = nodes.elements(); + + int dist = Integer.MAX_VALUE; + + while (en.hasMoreElements() && dist > 0) + { + r = (NodeRecord) 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 pathes that start from the given + * parent path. + * + * @param parentPath the parent path + * @return the enumeration over pathes + */ + public Enumeration getVisiblePathsFrom(TreePath parentPath) + { + if (dirty) + update(); + Vector p = new Vector(parentPath.getPathCount()); + Object node; + NodeRecord nr; + + for (int i = 0; i < parentPath.getPathCount(); i++) + { + node = parentPath.getPathComponent(i); + nr = (NodeRecord) nodes.get(node); + if (nr.row >= 0) + p.add(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 en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = (NodeRecord) en.nextElement(); + Rectangle r = nr.getBounds(); + totalHeight += r.height; + } + return totalHeight; + } - // ------------------------------------------------------------- - // Variables -------------------------------------------------- - // ------------------------------------------------------------- - - // ------------------------------------------------------------- - // Initialization --------------------------------------------- - // ------------------------------------------------------------- - - /** - * Constructor FixedHeightLayoutCache - */ - public FixedHeightLayoutCache() - { - // TODO - } // FixedHeightLayoutCache() - - // ------------------------------------------------------------- - // Methods ---------------------------------------------------- - // ------------------------------------------------------------- - - /** - * getRowCount - * - * @returns int - */ - public int getRowCount() - { - return 0; // TODO - } // getRowCount() - - /** - * invalidatePathBounds - * - * @param value0 TODO - */ - public void invalidatePathBounds(TreePath value0) - { - // TODO - } // invalidatePathBounds() - - /** - * invalidateSizes - */ - public void invalidateSizes() - { - // TODO - } // invalidateSizes() - - /** - * isExpanded - * - * @param value0 TODO - * @returns boolean - */ - public boolean isExpanded(TreePath value0) - { - return false; // TODO - } // isExpanded() - - /** - * getBounds - * - * @param value0 TODO - * @param value1 TODO - * @returns Rectangle - */ - public Rectangle getBounds(TreePath value0, Rectangle value1) - { - return null; // TODO - } // getBounds() - - /** - * getPathForRow - * - * @param row TODO - * @returns TreePath - */ - public TreePath getPathForRow(int row) - { - //TODO - return null; - } // getPathForRow() - - /** - * getRowForPath - * - * @param value0 TODO - * @returns int - */ - public int getRowForPath(TreePath value0) - { - return 0; - } // getRowForPath() - - /** - * getPathClosestTo - * - * @param value0 TODO - * @param value1 TODO - * @returns TreePath - */ - public TreePath getPathClosestTo(int value0, int value1) - { - return null; // TODO - } // getPathClosestTo() - - /** - * getVisibleChildCount - * - * @param value0 TODO - * @returns int - */ - public int getVisibleChildCount(TreePath value0) - { - return 0; // TODO - } // getVisibleChildCount() - - /** - * getVisiblePathsFrom - * - * @param value0 TODO - * @returns Enumeration - */ - public Enumeration getVisiblePathsFrom(TreePath value0) - { - return null; // TODO - } // getVisiblePathsFrom() - - /** - * setExpandedState - * - * @param value0 TODO - * @param value1 TODO - */ - public void setExpandedState(TreePath value0, boolean value1) - { - // TODO - } // setExpandedState() - - /** - * getExpandedState - * - * @param value0 TODO - * @returns boolean - */ - public boolean getExpandedState(TreePath value0) - { - return false; // TODO - } // getExpandedState() - - /** - * treeNodesChanged - * - * @param value0 TODO - */ - public void treeNodesChanged(TreeModelEvent value0) - { - // TODO - } // treeNodesChanged() - - /** - * treeNodesInserted - * - * @param value0 TODO - */ - public void treeNodesInserted(TreeModelEvent value0) - { - // TODO - } // treeNodesInserted() - - /** - * treeNodesRemoved - * - * @param value0 TODO - */ - public void treeNodesRemoved(TreeModelEvent value0) - { - // TODO - } // treeNodesRemoved() - - /** - * treeStructureChanged - * - * @param value0 TODO - */ - public void treeStructureChanged(TreeModelEvent value0) - { - // TODO - } // treeStructureChanged() - -} // FixedHeightLayoutCache + /** + * Get the maximal width. + */ + public int getPreferredWidth(Rectangle value) + { + if (dirty) + update(); + + maximalWidth = 0; + Enumeration en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = (NodeRecord) 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/TreeCellEditor.java b/libjava/classpath/javax/swing/tree/TreeCellEditor.java index 691cbc1..8951f31 100644 --- a/libjava/classpath/javax/swing/tree/TreeCellEditor.java +++ b/libjava/classpath/javax/swing/tree/TreeCellEditor.java @@ -1,5 +1,5 @@ /* TreeCellEditor.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -44,20 +44,26 @@ import javax.swing.CellEditor; import javax.swing.JTree; /** - * TreeCellEditor public interface + * 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 { /** - * getTreeCellEditorComponent - * @param tree TODO - * @param value TODO - * @param isSelected TODO - * @param expanded TODO - * @param leaf TODO - * @param row TODO - * @return TODO + * 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, diff --git a/libjava/classpath/javax/swing/tree/TreeCellRenderer.java b/libjava/classpath/javax/swing/tree/TreeCellRenderer.java index a1808c9..b7eeb9c 100644 --- a/libjava/classpath/javax/swing/tree/TreeCellRenderer.java +++ b/libjava/classpath/javax/swing/tree/TreeCellRenderer.java @@ -1,5 +1,5 @@ /* TreeCellRenderer.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -43,27 +43,31 @@ import java.awt.Component; import javax.swing.JTree; /** - * TreeCellRenderer public interface + * A <code>TreeCellRenderer</code> is used by the {@link JTree} component to + * paint individual tree elements (nodes). + * * @author Andrew Selkirk */ public interface TreeCellRenderer { - /** - * getTreeCellRendererComponent - * @param tree TODO - * @param value TODO - * @param selected TODO - * @param expanded TODO - * @param leaf TODO - * @param row TODO - * @param hasFocus TODO - * @returns TODO + * 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/TreeNode.java b/libjava/classpath/javax/swing/tree/TreeNode.java index fb8f880..94f2c69 100644 --- a/libjava/classpath/javax/swing/tree/TreeNode.java +++ b/libjava/classpath/javax/swing/tree/TreeNode.java @@ -1,5 +1,5 @@ /* TreeNode.java -- - Copyright (C) 2002 Free Software Foundation, Inc. + Copyright (C) 2002, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -41,59 +41,72 @@ package javax.swing.tree; import java.util.Enumeration; /** - * TreeNode public interface + * A tree node. + * * @author Andrew Selkirk */ public interface TreeNode { - //------------------------------------------------------------- - // Methods ---------------------------------------------------- - //------------------------------------------------------------- - - /** - * getParent - * @returns TreeNode - */ - TreeNode getParent(); - - /** - * getIndex - * @param node TODO - * @returns int - */ - int getIndex(TreeNode node); - - /** - * getChildAt - * @param index TODO - * @returns TreeNode - */ - TreeNode getChildAt(int index); - - /** - * getChildCount - * @returns int - */ - int getChildCount(); - - /** - * getAllowsChildren - * @returns boolean - */ - boolean getAllowsChildren(); - - /** - * isLeaf - * @returns boolean - */ - boolean isLeaf(); - - /** - * children - * @returns Enumeration - */ - Enumeration children(); - - -} // 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. + */ + Enumeration children(); + +} diff --git a/libjava/classpath/javax/swing/tree/TreePath.java b/libjava/classpath/javax/swing/tree/TreePath.java index 37ec632..4671c4b 100644 --- a/libjava/classpath/javax/swing/tree/TreePath.java +++ b/libjava/classpath/javax/swing/tree/TreePath.java @@ -1,5 +1,5 @@ /* TreePath.java -- - Copyright (C) 2002, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2005, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -53,9 +53,15 @@ public class TreePath implements Serializable static final long serialVersionUID = 4380036194768077479L; /** - * path + * The actual patch. The {@link DefaultTreeSelectionModel#clone()} + * assumes that the TreePath is immutable, so it is marked final here. */ - private Object[] path = null; + private final Object[] path; + + /** + * The parent path (to be reused). + */ + private transient TreePath parentPath; /** @@ -153,8 +159,8 @@ public class TreePath implements Serializable * * @param object the object (<code>null</code> permitted). * - * @returns <code>true</code> if <code>obj</code> is equal to this tree path, - * and <code>false</code> otherwise. + * @return <code>true</code> if <code>obj</code> is equal to this tree path, + * and <code>false</code> otherwise. */ public boolean equals(Object object) { @@ -196,7 +202,7 @@ public class TreePath implements Serializable /** * Returns an array containing the path elements. * - * @returns An array containing the path elements. + * @return An array containing the path elements. */ public Object[] getPath() { @@ -216,7 +222,7 @@ public class TreePath implements Serializable /** * Returns the number of elements in the path. * - * @returns The number of elements in the path. + * @return The number of elements in the path. */ public int getPathCount() { @@ -248,8 +254,8 @@ public class TreePath implements Serializable * * @param path the path to check (<code>null</code> permitted). * - * @returns <code>true</code> if <code>path</code> is a descendant of this - * path, and <code>false</code> otherwise + * @return <code>true</code> if <code>path</code> is a descendant of this + * path, and <code>false</code> otherwise */ public boolean isDescendant(TreePath path) { @@ -272,7 +278,7 @@ public class TreePath implements Serializable * * @param element the element. * - * @returns A tree path. + * @return A tree path. */ public TreePath pathByAddingChild(Object element) { @@ -284,8 +290,8 @@ public class TreePath implements Serializable * as this path, except for the last one. If this path contains only one * element, the method returns <code>null</code>. * - * @returns The parent path, or <code>null</code> if this path has only one - * element. + * @return The parent path, or <code>null</code> if this path has only one + * element. */ public TreePath getParentPath() { @@ -293,7 +299,12 @@ public class TreePath implements Serializable // is what the JDK does. if (path.length <= 1) return null; - - return new TreePath(this.getPath(), path.length - 1); + + // 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/VariableHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java index 2c9136c..a9ed552 100644 --- a/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java @@ -1,5 +1,5 @@ /* VariableHeightLayoutCache.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -35,219 +35,579 @@ 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; /** - * VariableHeightLayoutCache - * @author Andrew Selkirk + * 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 { - - //------------------------------------------------------------- - // Variables -------------------------------------------------- - //------------------------------------------------------------- - - - //------------------------------------------------------------- - // Initialization --------------------------------------------- - //------------------------------------------------------------- - - /** - * Constructor VariableHeightLayoutCache - */ - public VariableHeightLayoutCache() { - // TODO - } // VariableHeightLayoutCache() - - - //------------------------------------------------------------- - // Methods ---------------------------------------------------- - //------------------------------------------------------------- - - /** - * setModel - * @param value0 TODO - */ - public void setModel(TreeModel value0) { - // TODO - } // setModel() - - /** - * setRootVisible - * @param value0 TODO - */ - public void setRootVisible(boolean value0) { - // TODO - } // setRootVisible() - - /** - * setNodeDimensions - * @param value0 TODO - */ - public void setNodeDimensions(NodeDimensions value0) { - // TODO - } // setNodeDimensions() - - /** - * setExpandedState - * @param value0 TODO - * @param value1 TODO - */ - public void setExpandedState(TreePath value0, boolean value1) { - // TODO - } // setExpandedState() - - /** - * getExpandedState - * @param value0 TODO - * @returns boolean - */ - public boolean getExpandedState(TreePath value0) { - return false; // TODO - } // getExpandedState() - - /** - * getBounds - * @param value0 TODO - * @param value1 TODO - * @returns Rectangle - */ - public Rectangle getBounds(TreePath value0, Rectangle value1) { - return null; // TODO - } // getBounds() - - /** - * getPathForRow - * @param value0 TODO - * @returns TreePath - */ - public TreePath getPathForRow(int value0) { - return null; // TODO - } // getPathForRow() - - /** - * getRowForPath - * @param value0 TODO - * @returns int - */ - public int getRowForPath(TreePath value0) { - return 0; // TODO - } // getRowForPath() - - /** - * getRowCount - * @returns int - */ - public int getRowCount() { - return 0; // TODO - } // getRowCount() - - /** - * invalidatePathBounds - * @param value0 TODO - */ - public void invalidatePathBounds(TreePath value0) { - // TODO - } // invalidatePathBounds() - - /** - * getPreferredHeight - * @returns int - */ - public int getPreferredHeight() { - return 0; // TODO - } // getPreferredHeight() - - /** - * getPreferredWidth - * @param value0 TODO - * @returns int - */ - public int getPreferredWidth(Rectangle value0) { - return 0; // TODO - } // getPreferredWidth() - - /** - * getPathClosestTo - * @param value0 TODO - * @param value1 TODO - * @returns TreePath - */ - public TreePath getPathClosestTo(int value0, int value1) { - return null; // TODO - } // getPathClosestTo() - - /** - * getVisiblePathsFrom - * @param value0 TODO - * @returns Enumeration - */ - public Enumeration getVisiblePathsFrom(TreePath value0) { - return null; // TODO - } // getVisiblePathsFrom() - - /** - * getVisibleChildCount - * @param value0 TODO - * @returns int - */ - public int getVisibleChildCount(TreePath value0) { - return 0; // TODO - } // getVisibleChildCount() - - /** - * invalidateSizes - */ - public void invalidateSizes() { - // TODO - } // invalidateSizes() - - /** - * isExpanded - * @param value0 TODO - * @returns boolean - */ - public boolean isExpanded(TreePath value0) { - return false; // TODO - } // isExpanded() - - /** - * treeNodesChanged - * @param value0 TODO - */ - public void treeNodesChanged(TreeModelEvent value0) { - // TODO - } // treeNodesChanged() - - /** - * treeNodesInserted - * @param value0 TODO - */ - public void treeNodesInserted(TreeModelEvent value0) { - // TODO - } // treeNodesInserted() - - /** - * treeNodesRemoved - * @param value0 TODO - */ - public void treeNodesRemoved(TreeModelEvent value0) { - // TODO - } // treeNodesRemoved() - - /** - * treeStructureChanged - * @param value0 TODO - */ - public void treeStructureChanged(TreeModelEvent value0) { - // TODO - } // treeStructureChanged() - - -} // VariableHeightLayoutCache +public class VariableHeightLayoutCache + extends AbstractLayoutCache +{ + /** + * 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 lpath = new LinkedList(); + 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 expanded = new HashSet(); + + /** + * Maps nodes to the row numbers. + */ + Hashtable nodes = new Hashtable(); + + /** + * Maps row numbers to nodes. + */ + Hashtable row2node = new Hashtable(); + + /** + * 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; + + 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 + 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 = (NodeRecord) 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 = (NodeRecord) 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 = (NodeRecord) 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 en = nodes.elements(); + + int dist = Integer.MAX_VALUE; + + while (en.hasMoreElements() && dist > 0) + { + r = (NodeRecord) 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 pathes that start from the given + * parent path. + * + * @param parentPath the parent path + * @return the enumeration over pathes + */ + public Enumeration getVisiblePathsFrom(TreePath parentPath) + { + if (dirty) + update(); + Vector p = new Vector(parentPath.getPathCount()); + Object node; + NodeRecord nr; + + for (int i = 0; i < parentPath.getPathCount(); i++) + { + node = parentPath.getPathComponent(i); + nr = (NodeRecord) nodes.get(node); + if (nr.row >= 0) + p.add(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 en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = (NodeRecord) 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 en = nodes.elements(); + while (en.hasMoreElements()) + { + NodeRecord nr = (NodeRecord) en.nextElement(); + Rectangle r = nr.getBounds(); + if (r.x + r.width > maximalWidth) + maximalWidth = r.x + r.width; + } + return maximalWidth; + } +} |