diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/javax/swing/JTree.java | |
download | cbb-gcc-4.6.4-upstream.tar.bz2 cbb-gcc-4.6.4-upstream.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/javax/swing/JTree.java')
-rw-r--r-- | libjava/classpath/javax/swing/JTree.java | 3186 |
1 files changed, 3186 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/JTree.java b/libjava/classpath/javax/swing/JTree.java new file mode 100644 index 000000000..d89adad5e --- /dev/null +++ b/libjava/classpath/javax/swing/JTree.java @@ -0,0 +1,3186 @@ +/* JTree.java + Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeListener; +import java.io.Serializable; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Locale; +import java.util.Vector; + +import javax.accessibility.Accessible; +import javax.accessibility.AccessibleAction; +import javax.accessibility.AccessibleComponent; +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; +import javax.accessibility.AccessibleSelection; +import javax.accessibility.AccessibleState; +import javax.accessibility.AccessibleStateSet; +import javax.accessibility.AccessibleText; +import javax.accessibility.AccessibleValue; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.event.TreeWillExpandListener; +import javax.swing.plaf.TreeUI; +import javax.swing.text.Position; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.DefaultTreeSelectionModel; +import javax.swing.tree.ExpandVetoException; +import javax.swing.tree.TreeCellEditor; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +public class JTree extends JComponent implements Scrollable, Accessible +{ + + /** + * This class implements accessibility support for the JTree class. It + * provides an implementation of the Java Accessibility API appropriate + * to tree user-interface elements. + */ + protected class AccessibleJTree extends JComponent.AccessibleJComponent + implements AccessibleSelection, TreeSelectionListener, TreeModelListener, + TreeExpansionListener + { + + /** + * This class implements accessibility support for the JTree child. It provides + * an implementation of the Java Accessibility API appropriate to tree nodes. + */ + protected class AccessibleJTreeNode extends AccessibleContext + implements Accessible, AccessibleComponent, AccessibleSelection, + AccessibleAction + { + + private JTree tree; + private TreePath tp; + private Accessible acc; + private AccessibleStateSet states; + private Vector selectionList; + private Vector actionList; + private TreeModel mod; + private Cursor cursor; + + /** + * Constructs an AccessibleJTreeNode + * + * @param t - the current tree + * @param p - the current path to be dealt with + * @param ap - the accessible object to use + */ + public AccessibleJTreeNode(JTree t, TreePath p, Accessible ap) + { + states = new AccessibleStateSet(); + selectionList = new Vector(); + actionList = new Vector(); + mod = tree.getModel(); + cursor = JTree.this.getCursor(); + + tree = t; + tp = p; + acc = ap; + + // Add all the children of this path that may already be + // selected to the selection list. + TreePath[] selected = tree.getSelectionPaths(); + for (int i = 0; i < selected.length; i++) + { + TreePath sel = selected[i]; + if ((sel.getParentPath()).equals(tp)) + selectionList.add(sel); + } + + // Add all the actions available for a node to + // the action list. + actionList.add("EXPAND"); + actionList.add("COLLAPSE"); + actionList.add("EDIT"); + actionList.add("SELECT"); + actionList.add("DESELECT"); + } + + /** + * Adds the specified selected item in the object to the object's + * selection. + * + * @param i - the i-th child of this node. + */ + public void addAccessibleSelection(int i) + { + if (mod != null) + { + Object child = mod.getChild(tp.getLastPathComponent(), i); + if (child != null) + { + if (!states.contains(AccessibleState.MULTISELECTABLE)) + clearAccessibleSelection(); + selectionList.add(child); + tree.addSelectionPath(tp.pathByAddingChild(child)); + } + } + } + + /** + * Adds the specified focus listener to receive focus events + * from this component. + * + * @param l - the new focus listener + */ + public void addFocusListener(FocusListener l) + { + tree.addFocusListener(l); + } + + /** + * Add a PropertyChangeListener to the listener list. + * + * @param l - the new property change listener + */ + public void addPropertyChangeListener(PropertyChangeListener l) + { + // Nothing to do here. + } + + /** + * Clears the selection in the object, so that nothing in the + * object is selected. + */ + public void clearAccessibleSelection() + { + selectionList.clear(); + } + + /** + * Checks whether the specified point is within this object's + * bounds, where the point's x and y coordinates are defined to be + * relative to the coordinate system of the object. + * + * @param p - the point to check + * @return true if p is in the bounds + */ + public boolean contains(Point p) + { + return getBounds().contains(p); + } + + /** + * Perform the specified Action on the tree node. + * + * @param i - the i-th action to perform + * @return true if the the action was performed; else false. + */ + public boolean doAccessibleAction(int i) + { + if (i >= actionList.size() || i < 0) + return false; + + if (actionList.get(i).equals("EXPAND")) + tree.expandPath(tp); + else if (actionList.get(i).equals("COLLAPSE")) + tree.collapsePath(tp); + else if (actionList.get(i).equals("SELECT")) + tree.addSelectionPath(tp); + else if (actionList.get(i).equals("DESELECT")) + tree.removeSelectionPath(tp); + else if (actionList.get(i).equals("EDIT")) + tree.startEditingAtPath(tp); + else + return false; + return true; + } + + /** + * Get the AccessibleAction associated with this object. + * + * @return the action + */ + public AccessibleAction getAccessibleAction() + { + return this; + } + + /** + * Returns the number of accessible actions available in this tree node. + * + * @return the number of actions + */ + public int getAccessibleActionCount() + { + return actionList.size(); + } + + /** + * Return a description of the specified action of the tree node. + * + * @param i - the i-th action's description + * @return a description of the action + */ + public String getAccessibleActionDescription(int i) + { + if (i < 0 || i >= actionList.size()) + return (actionList.get(i)).toString(); + return super.getAccessibleDescription(); + } + + /** + * Returns the Accessible child, if one exists, contained at the + * local coordinate Point. + * + * @param p - the point of the accessible + * @return the accessible at point p if it exists + */ + public Accessible getAccessibleAt(Point p) + { + TreePath acc = tree.getClosestPathForLocation(p.x, p.y); + if (acc != null) + return new AccessibleJTreeNode(tree, acc, this); + return null; + } + + /** + * Return the specified Accessible child of the object. + * + * @param i - the i-th child of the current path + * @return the child if it exists + */ + public Accessible getAccessibleChild(int i) + { + if (mod != null) + { + Object child = mod.getChild(tp.getLastPathComponent(), i); + if (child != null) + return new AccessibleJTreeNode(tree, tp.pathByAddingChild(child), + acc); + } + return null; + } + + /** + * Returns the number of accessible children in the object. + * + * @return the number of children the current node has + */ + public int getAccessibleChildrenCount() + { + TreeModel mod = getModel(); + if (mod != null) + return mod.getChildCount(tp.getLastPathComponent()); + return 0; + } + + /** + * Get the AccessibleComponent associated with this object. + * + * @return the accessible component if it is supported. + */ + public AccessibleComponent getAccessibleComponent() + { + return this; + } + + /** + * Get the AccessibleContext associated with this tree node. + * + * @return an instance of this class + */ + public AccessibleContext getAccessibleContext() + { + return this; + } + + /** + * Get the accessible description of this object. + * + * @return the accessible description + */ + public String getAccessibleDescription() + { + return super.getAccessibleDescription(); + } + + /** + * Get the index of this object in its accessible parent. + * + * @return the index of this in the parent. + */ + public int getAccessibleIndexInParent() + { + AccessibleContext parent = getAccessibleParent().getAccessibleContext(); + if (parent != null) + for (int i = 0; i < parent.getAccessibleChildrenCount(); i++) + { + if ((parent.getAccessibleChild(i)).equals(this)) + return i; + } + return -1; + } + + /** + * Get the accessible name of this object. + * + * @return the accessible name + */ + public String getAccessibleName() + { + return super.getAccessibleName(); + } + + /** + * Get the Accessible parent of this object. + * + * @return the accessible parent if it exists. + */ + public Accessible getAccessibleParent() + { + return super.getAccessibleParent(); + } + + /** + * Get the role of this object. + * + * @return the accessible role + */ + public AccessibleRole getAccessibleRole() + { + return AccessibleJTree.this.getAccessibleRole(); + } + + /** + * Get the AccessibleSelection associated with this object if one exists. + * + * @return the accessible selection for this. + */ + public AccessibleSelection getAccessibleSelection() + { + return this; + } + + /** + * Returns an Accessible representing the specified selected item + * in the object. + * + * @return the accessible representing a certain selected item. + */ + public Accessible getAccessibleSelection(int i) + { + if (i > 0 && i < getAccessibleSelectionCount()) + return new AccessibleJTreeNode(tree, + tp.pathByAddingChild(selectionList.get(i)), acc); + return null; + } + + /** + * Returns the number of items currently selected. + * + * @return the number of items selected. + */ + public int getAccessibleSelectionCount() + { + return selectionList.size(); + } + + /** + * Get the state set of this object. + * + * @return the state set for this object + */ + public AccessibleStateSet getAccessibleStateSet() + { + if (isVisible()) + states.add(AccessibleState.VISIBLE); + if (tree.isCollapsed(tp)) + states.add(AccessibleState.COLLAPSED); + if (tree.isEditable()) + states.add(AccessibleState.EDITABLE); + if (mod != null && + !mod.isLeaf(tp.getLastPathComponent())) + states.add(AccessibleState.EXPANDABLE); + if (tree.isExpanded(tp)) + states.add(AccessibleState.EXPANDED); + if (isFocusable()) + states.add(AccessibleState.FOCUSABLE); + if (hasFocus()) + states.add(AccessibleState.FOCUSED); + if (tree.getSelectionModel().getSelectionMode() != + TreeSelectionModel.SINGLE_TREE_SELECTION) + states.add(AccessibleState.MULTISELECTABLE); + if (tree.isOpaque()) + states.add(AccessibleState.OPAQUE); + if (tree.isPathSelected(tp)) + states.add(AccessibleState.SELECTED); + if (isShowing()) + states.add(AccessibleState.SHOWING); + + states.add(AccessibleState.SELECTABLE); + return states; + } + + /** + * Get the AccessibleText associated with this object if one exists. + * + * @return the accessible text + */ + public AccessibleText getAccessibleText() + { + return super.getAccessibleText(); + } + + /** + * Get the AccessibleValue associated with this object if one exists. + * + * @return the accessible value if it exists + */ + public AccessibleValue getAccessibleValue() + { + return super.getAccessibleValue(); + } + + /** + * Get the background color of this object. + * + * @return the color of the background. + */ + public Color getBackground() + { + return tree.getBackground(); + } + + /** + * Gets the bounds of this object in the form of a Rectangle object. + * + * @return the bounds of the current node. + */ + public Rectangle getBounds() + { + return tree.getPathBounds(tp); + } + + /** + * Gets the Cursor of this object. + * + * @return the cursor for the current node + */ + public Cursor getCursor() + { + return cursor; + } + + /** + * Gets the Font of this object. + * + * @return the font for the current node + */ + public Font getFont() + { + return tree.getFont(); + } + + /** + * Gets the FontMetrics of this object. + * + * @param f - the current font. + * @return the font metrics for the given font. + */ + public FontMetrics getFontMetrics(Font f) + { + return tree.getFontMetrics(f); + } + + /** + * Get the foreground color of this object. + * + * @return the foreground for this object. + */ + public Color getForeground() + { + return tree.getForeground(); + } + + /** + * Gets the locale of the component. + * + * @return the locale of the component. + */ + public Locale getLocale() + { + return tree.getLocale(); + } + + /** + * Gets the location of the object relative to the + * parent in the form of a point specifying the object's + * top-left corner in the screen's coordinate space. + * + * @return the location of the current node. + */ + public Point getLocation() + { + return getLocationInJTree(); + } + + /** + * Returns the location in the tree. + * + * @return the location in the JTree. + */ + protected Point getLocationInJTree() + { + Rectangle bounds = tree.getPathBounds(tp); + return new Point(bounds.x, bounds.y); + } + + /** + * Returns the location of the object on the screen. + * + * @return the location of the object on the screen. + */ + public Point getLocationOnScreen() + { + Point loc = getLocation(); + SwingUtilities.convertPointToScreen(loc, tree); + return loc; + } + + /** + * Returns the size of this object in the form of a Dimension object. + * + * @return the size of the object + */ + public Dimension getSize() + { + Rectangle b = getBounds(); + return b.getSize(); + } + + /** + * Returns true if the current child of this object is selected. + * + * @param i - the child of the current node + * @return true if the child is selected. + */ + public boolean isAccessibleChildSelected(int i) + { + Object child = mod.getChild(tp.getLastPathComponent(), i); + if (child != null) + return tree.isPathSelected(tp.pathByAddingChild(child)); + return false; + } + + /** + * Determines if the object is enabled. + * + * @return true if the tree is enabled + */ + public boolean isEnabled() + { + return tree.isEnabled(); + } + + /** + * Returns whether this object can accept focus or not. + * + * @return true, it is always focus traversable + */ + public boolean isFocusTraversable() + { + return true; + } + + /** + * Determines if the object is showing. + * + * @return true if the object is visible and the + * parent is visible. + */ + public boolean isShowing() + { + return isVisible() && tree.isShowing(); + } + + /** + * Determines if the object is visible. + * + * @return true if the object is visible. + */ + public boolean isVisible() + { + return tree.isVisible(tp); + } + + /** + * Removes the specified selected item in the object from the + * object's selection. + * + * @param i - the specified item to remove + */ + public void removeAccessibleSelection(int i) + { + if (mod != null) + { + Object child = mod.getChild(tp.getLastPathComponent(), i); + if (child != null) + { + if (!states.contains(AccessibleState.MULTISELECTABLE)) + clearAccessibleSelection(); + if (selectionList.contains(child)) + { + selectionList.remove(child); + tree.removeSelectionPath(tp.pathByAddingChild(child)); + } + } + } + } + + /** + * Removes the specified focus listener so it no longer receives focus + * events from this component. + * + * @param l - the focus listener to remove + */ + public void removeFocusListener(FocusListener l) + { + tree.removeFocusListener(l); + } + + /** + * Remove a PropertyChangeListener from the listener list. + * + * @param l - the property change listener to remove. + */ + public void removePropertyChangeListener(PropertyChangeListener l) + { + // Nothing to do here. + } + + /** + * Requests focus for this object. + */ + public void requestFocus() + { + tree.requestFocus(); + } + + /** + * Causes every selected item in the object to be selected if the object + * supports multiple selections. + */ + public void selectAllAccessibleSelection() + { + Object parent = tp.getLastPathComponent(); + if (mod != null) + { + for (int i = 0; i < mod.getChildCount(parent); i++) + { + Object child = mod.getChild(parent, i); + if (child != null) + { + if (!states.contains(AccessibleState.MULTISELECTABLE)) + clearAccessibleSelection(); + if (selectionList.contains(child)) + { + selectionList.add(child); + tree.addSelectionPath(tp.pathByAddingChild(child)); + } + } + } + } + } + + /** + * Set the accessible description of this object. + * + * @param s - the string to set the accessible description to. + */ + public void setAccessibleDescription(String s) + { + super.setAccessibleDescription(s); + } + + /** + * Set the localized accessible name of this object. + * + * @param s - the string to set the accessible name to. + */ + public void setAccessibleName(String s) + { + super.setAccessibleName(s); + } + + /** + * Set the background color of this object. + * + * @param c - the color to set the background to. + */ + public void setBackground(Color c) + { + // Nothing to do here. + } + + /** + * Sets the bounds of this object in the form of a Rectangle object. + * + * @param r - the bounds to set the object o + */ + public void setBounds(Rectangle r) + { + // Nothing to do here. + } + + /** + * Sets the Cursor of this object. + * + * @param c - the new cursor + */ + public void setCursor(Cursor c) + { + cursor = c; + } + + /** + * Sets the enabled state of the object. + * + * @param b - boolean to enable or disable object + */ + public void setEnabled(boolean b) + { + // Nothing to do here. + } + + /** + * Sets the Font of this object. + * + * @param f - the new font. + */ + public void setFont(Font f) + { + // Nothing to do here. + } + + /** + * Sets the foreground color of this object. + * + * @param c - the new foreground color. + */ + public void setForeground(Color c) + { + // Nothing to do here. + } + + /** + * Sets the location of the object relative to the parent. + * + * @param p - the new location for the object. + */ + public void setLocation(Point p) + { + // Nothing to do here. + } + + /** + * Resizes this object so that it has width and height. + * + * @param d - the new size for the object. + */ + public void setSize(Dimension d) + { + // Nothing to do here. + } + + /** + * Sets the visible state of the object. + * + * @param b - sets the objects visibility. + */ + public void setVisible(boolean b) + { + // Nothing to do here. + } + } + + /** + * Constructor + */ + public AccessibleJTree() + { + // Nothing to do here. + } + + /** + * Adds the specified selected item in the object to the object's selection. + * + * @param i - the row to add to the tree's selection + */ + public void addAccessibleSelection(int i) + { + addSelectionInterval(i, i); + } + + /** + * Clears the selection in the object, so that nothing in the object is selected. + */ + public void clearAccessibleSelection() + { + clearSelection(); + } + + /** + * Fire a visible data property change notification. + */ + public void fireVisibleDataPropertyChange() + { + treeDidChange(); + } + + /** + * Returns the Accessible child, if one exists, contained at the local + * coordinate Point. + * + * @param p - the point of the accessible to get. + * @return the accessible at point p. + */ + public Accessible getAccessibleAt(Point p) + { + TreePath tp = getClosestPathForLocation(p.x, p.y); + if (tp != null) + return new AccessibleJTreeNode(JTree.this, tp, null); + return null; + } + + /** + * Return the nth Accessible child of the object. + * + * @param i - the accessible child to get + * @return the i-th child + */ + public Accessible getAccessibleChild(int i) + { + return null; + } + + /** + * Returns the number of top-level children nodes of this JTree. + * + * @return the number of top-level children + */ + public int getAccessibleChildrenCount() + { + TreeModel model = getModel(); + if (model != null) + return model.getChildCount(model.getRoot()); + return 0; + } + + /** + * Get the index of this object in its accessible parent. + * + * @return the index of this object. + */ + public int getAccessibleIndexInParent() + { + return 0; + } + + /** + * Get the role of this object. + * + * @return the role of this object + */ + public AccessibleRole getAccessibleRole() + { + return AccessibleRole.TREE; + } + + /** + * Get the AccessibleSelection associated with this object. + * + * @return the accessible selection of the tree + */ + public AccessibleSelection getAccessibleSelection() + { + TreeModel mod = getModel(); + if (mod != null) + return (new AccessibleJTreeNode(JTree.this, + new TreePath(mod.getRoot()), null)).getAccessibleSelection(); + return null; + } + + /** + * Returns an Accessible representing the specified selected item in the object. + * + * @return the i-th accessible in the selection + */ + public Accessible getAccessibleSelection(int i) + { + TreeModel mod = getModel(); + if (mod != null) + return (new AccessibleJTreeNode(JTree.this, + new TreePath(mod.getRoot()), null)).getAccessibleSelection(i); + return null; + } + + /** + * Returns the number of items currently selected. + * + * @return the number of selected accessibles. + */ + public int getAccessibleSelectionCount() + { + return getSelectionCount(); + } + + /** + * Returns true if the current child of this object is selected. + * + * @param i - the child of this object + * @return true if the i-th child is selected. + */ + public boolean isAccessibleChildSelected(int i) + { + // Nothing to do here. + return false; + } + + /** + * Removes the specified selected item in the object from the object's + * selection. + * + * @param i - the i-th selected item to remove + */ + public void removeAccessibleSelection(int i) + { + removeSelectionInterval(i, i); + } + + /** + * Causes every selected item in the object to be selected if the object + * supports multiple selections. + */ + public void selectAllAccessibleSelection() + { + if (getSelectionModel().getSelectionMode() != + TreeSelectionModel.SINGLE_TREE_SELECTION) + addSelectionInterval(0, getVisibleRowCount()); + } + + /** + * Tree Collapsed notification + * + * @param e - the event + */ + public void treeCollapsed(TreeExpansionEvent e) + { + fireTreeCollapsed(e.getPath()); + } + + /** + * Tree Model Expansion notification. + * + * @param e - the event + */ + public void treeExpanded(TreeExpansionEvent e) + { + fireTreeExpanded(e.getPath()); + } + + /** + * Tree Model Node change notification. + * + * @param e - the event + */ + public void treeNodesChanged(TreeModelEvent e) + { + // Nothing to do here. + } + + /** + * Tree Model Node change notification. + * + * @param e - the event + */ + public void treeNodesInserted(TreeModelEvent e) + { + // Nothing to do here. + } + + /** + * Tree Model Node change notification. + * + * @param e - the event + */ + public void treeNodesRemoved(TreeModelEvent e) + { + // Nothing to do here. + } + + /** + * Tree Model structure change change notification. + * + * @param e - the event + */ + public void treeStructureChanged(TreeModelEvent e) + { + // Nothing to do here. + } + + /** + * Tree Selection Listener value change method. + * + * @param e - the event + */ + public void valueChanged(TreeSelectionEvent e) + { + fireValueChanged(e); + } + } + + public static class DynamicUtilTreeNode extends DefaultMutableTreeNode + { + protected Object childValue; + + protected boolean loadedChildren; + + /** + * Currently not set or used by this class. It might be set and used in + * later versions of this class. + */ + protected boolean hasChildren; + + public DynamicUtilTreeNode(Object value, Object children) + { + super(value); + childValue = children; + loadedChildren = false; + } + + public int getChildCount() + { + loadChildren(); + return super.getChildCount(); + } + + protected void loadChildren() + { + if (!loadedChildren) + { + createChildren(this, childValue); + loadedChildren = true; + } + } + + public Enumeration children() + { + loadChildren(); + return super.children(); + } + + /** + * Returns the child node at position <code>pos</code>. Subclassed + * here to load the children if necessary. + * + * @param pos the position of the child node to fetch + * + * @return the childnode at the specified position + */ + public TreeNode getChildAt(int pos) + { + loadChildren(); + return super.getChildAt(pos); + } + + public boolean isLeaf() + { + return childValue == null || !(childValue instanceof Hashtable + || childValue instanceof Vector + || childValue.getClass().isArray()); + } + + public static void createChildren(DefaultMutableTreeNode parent, + Object children) + { + if (children instanceof Hashtable) + { + Hashtable tab = (Hashtable) children; + Enumeration e = tab.keys(); + while (e.hasMoreElements()) + { + Object key = e.nextElement(); + Object val = tab.get(key); + parent.add(new DynamicUtilTreeNode(key, val)); + } + } + else if (children instanceof Vector) + { + Iterator i = ((Vector) children).iterator(); + while (i.hasNext()) + { + Object n = i.next(); + parent.add(new DynamicUtilTreeNode(n, n)); + } + } + else if (children != null && children.getClass().isArray()) + { + Object[] arr = (Object[]) children; + for (int i = 0; i < arr.length; ++i) + parent.add(new DynamicUtilTreeNode(arr[i], arr[i])); + } + } + } + + /** + * Listens to the model of the JTree and updates the property + * <code>expandedState</code> if nodes are removed or changed. + */ + protected class TreeModelHandler implements TreeModelListener + { + + /** + * Creates a new instance of TreeModelHandler. + */ + protected TreeModelHandler() + { + // Nothing to do here. + } + + /** + * Notifies when a node has changed in some ways. This does not include + * that a node has changed its location or changed it's children. It + * only means that some attributes of the node have changed that might + * affect its presentation. + * + * This method is called after the actual change occured. + * + * @param ev the TreeModelEvent describing the change + */ + public void treeNodesChanged(TreeModelEvent ev) + { + // Nothing to do here. + } + + /** + * Notifies when a node is inserted into the tree. + * + * This method is called after the actual change occured. + * + * @param ev the TreeModelEvent describing the change + */ + public void treeNodesInserted(TreeModelEvent ev) + { + // nothing to do here + } + + /** + * Notifies when a node is removed from the tree. + * + * This method is called after the actual change occured. + * + * @param ev the TreeModelEvent describing the change + */ + public void treeNodesRemoved(TreeModelEvent ev) + { + if (ev != null) + { + TreePath parent = ev.getTreePath(); + Object[] children = ev.getChildren(); + TreeSelectionModel sm = getSelectionModel(); + if (children != null) + { + TreePath path; + Vector toRemove = new Vector(); + // Collect items that we must remove. + for (int i = children.length - 1; i >= 0; i--) + { + path = parent.pathByAddingChild(children[i]); + if (nodeStates.containsKey(path)) + toRemove.add(path); + // Clear selection while we are at it. + if (sm != null) + removeDescendantSelectedPaths(path, true); + } + if (toRemove.size() > 0) + removeDescendantToggledPaths(toRemove.elements()); + TreeModel model = getModel(); + if (model == null || model.isLeaf(parent.getLastPathComponent())) + nodeStates.remove(parent); + } + } + } + + /** + * Notifies when the structure of the tree is changed. + * + * This method is called after the actual change occured. + * + * @param ev the TreeModelEvent describing the change + */ + public void treeStructureChanged(TreeModelEvent ev) + { + if (ev != null) + { + TreePath parent = ev.getTreePath(); + if (parent != null) + { + if (parent.getPathCount() == 1) + { + // We have a new root, clear everything. + clearToggledPaths(); + Object root = treeModel.getRoot(); + if (root != null && treeModel.isLeaf(root)) + nodeStates.put(parent, Boolean.TRUE); + } + else if (nodeStates.containsKey(parent)) + { + Vector toRemove = new Vector(); + boolean expanded = isExpanded(parent); + toRemove.add(parent); + removeDescendantToggledPaths(toRemove.elements()); + if (expanded) + { + TreeModel model = getModel(); + if (model != null + || model.isLeaf(parent.getLastPathComponent())) + collapsePath(parent); + else + nodeStates.put(parent, Boolean.TRUE); + } + } + removeDescendantSelectedPaths(parent, false); + } + } + } + } + + /** + * This redirects TreeSelectionEvents and rewrites the source of it to be + * this JTree. This is typically done when the tree model generates an + * event, but the JTree object associated with that model should be listed + * as the actual source of the event. + */ + protected class TreeSelectionRedirector implements TreeSelectionListener, + Serializable + { + /** The serial version UID. */ + private static final long serialVersionUID = -3505069663646241664L; + + /** + * Creates a new instance of TreeSelectionRedirector + */ + protected TreeSelectionRedirector() + { + // Nothing to do here. + } + + /** + * Notifies when the tree selection changes. + * + * @param ev the TreeSelectionEvent that describes the change + */ + public void valueChanged(TreeSelectionEvent ev) + { + TreeSelectionEvent rewritten = + (TreeSelectionEvent) ev.cloneWithSource(JTree.this); + fireValueChanged(rewritten); + } + } + + /** + * A TreeModel that does not allow anything to be selected. + */ + protected static class EmptySelectionModel extends DefaultTreeSelectionModel + { + /** The serial version UID. */ + private static final long serialVersionUID = -5815023306225701477L; + + /** + * The shared instance of this model. + */ + protected static final EmptySelectionModel sharedInstance = + new EmptySelectionModel(); + + /** + * Creates a new instance of EmptySelectionModel. + */ + protected EmptySelectionModel() + { + // Nothing to do here. + } + + /** + * Returns the shared instance of EmptySelectionModel. + * + * @return the shared instance of EmptySelectionModel + */ + public static EmptySelectionModel sharedInstance() + { + return sharedInstance; + } + + /** + * This catches attempts to set a selection and sets nothing instead. + * + * @param paths not used here + */ + public void setSelectionPaths(TreePath[] paths) + { + // We don't allow selections in this class. + } + + /** + * This catches attempts to add something to the selection. + * + * @param paths not used here + */ + public void addSelectionPaths(TreePath[] paths) + { + // We don't allow selections in this class. + } + + /** + * This catches attempts to remove something from the selection. + * + * @param paths not used here + */ + public void removeSelectionPaths(TreePath[] paths) + { + // We don't allow selections in this class. + } + } + + private static final long serialVersionUID = 7559816092864483649L; + + public static final String CELL_EDITOR_PROPERTY = "cellEditor"; + + public static final String CELL_RENDERER_PROPERTY = "cellRenderer"; + + public static final String EDITABLE_PROPERTY = "editable"; + + public static final String INVOKES_STOP_CELL_EDITING_PROPERTY = + "invokesStopCellEditing"; + + public static final String LARGE_MODEL_PROPERTY = "largeModel"; + + public static final String ROOT_VISIBLE_PROPERTY = "rootVisible"; + + public static final String ROW_HEIGHT_PROPERTY = "rowHeight"; + + public static final String SCROLLS_ON_EXPAND_PROPERTY = "scrollsOnExpand"; + + public static final String SELECTION_MODEL_PROPERTY = "selectionModel"; + + public static final String SHOWS_ROOT_HANDLES_PROPERTY = "showsRootHandles"; + + public static final String TOGGLE_CLICK_COUNT_PROPERTY = "toggleClickCount"; + + public static final String TREE_MODEL_PROPERTY = "model"; + + public static final String VISIBLE_ROW_COUNT_PROPERTY = "visibleRowCount"; + + /** @since 1.3 */ + public static final String ANCHOR_SELECTION_PATH_PROPERTY = + "anchorSelectionPath"; + + /** @since 1.3 */ + public static final String LEAD_SELECTION_PATH_PROPERTY = "leadSelectionPath"; + + /** @since 1.3 */ + public static final String EXPANDS_SELECTED_PATHS_PROPERTY = + "expandsSelectedPaths"; + + private static final Object EXPANDED = Boolean.TRUE; + + private static final Object COLLAPSED = Boolean.FALSE; + + private boolean dragEnabled; + + private boolean expandsSelectedPaths; + + private TreePath anchorSelectionPath; + + /** + * This contains the state of all nodes in the tree. Al/ entries map the + * TreePath of a note to to its state. Valid states are EXPANDED and + * COLLAPSED. Nodes not in this Hashtable are assumed state COLLAPSED. + * + * This is package private to avoid accessor methods. + */ + Hashtable nodeStates = new Hashtable(); + + protected transient TreeCellEditor cellEditor; + + protected transient TreeCellRenderer cellRenderer; + + protected boolean editable; + + protected boolean invokesStopCellEditing; + + protected boolean largeModel; + + protected boolean rootVisible; + + protected int rowHeight; + + protected boolean scrollsOnExpand; + + protected transient TreeSelectionModel selectionModel; + + protected boolean showsRootHandles; + + protected int toggleClickCount; + + protected transient TreeModel treeModel; + + protected int visibleRowCount; + + /** + * Handles TreeModelEvents to update the expandedState. + */ + protected transient TreeModelListener treeModelListener; + + /** + * Redirects TreeSelectionEvents so that the source is this JTree. + */ + protected TreeSelectionRedirector selectionRedirector = + new TreeSelectionRedirector(); + + /** + * Indicates if the rowHeight property has been set by a client + * program or by the UI. + * + * @see #setUIProperty(String, Object) + * @see LookAndFeel#installProperty(JComponent, String, Object) + */ + private boolean clientRowHeightSet = false; + + /** + * Indicates if the scrollsOnExpand property has been set by a client + * program or by the UI. + * + * @see #setUIProperty(String, Object) + * @see LookAndFeel#installProperty(JComponent, String, Object) + */ + private boolean clientScrollsOnExpandSet = false; + + /** + * Indicates if the showsRootHandles property has been set by a client + * program or by the UI. + * + * @see #setUIProperty(String, Object) + * @see LookAndFeel#installProperty(JComponent, String, Object) + */ + private boolean clientShowsRootHandlesSet = false; + + /** + * Creates a new <code>JTree</code> object. + */ + public JTree() + { + this(getDefaultTreeModel()); + } + + /** + * Creates a new <code>JTree</code> object. + * + * @param value the initial nodes in the tree + */ + public JTree(Hashtable<?, ?> value) + { + this(createTreeModel(value)); + } + + /** + * Creates a new <code>JTree</code> object. + * + * @param value the initial nodes in the tree + */ + public JTree(Object[] value) + { + this(createTreeModel(value)); + } + + /** + * Creates a new <code>JTree</code> object. + * + * @param model the model to use + */ + public JTree(TreeModel model) + { + setRootVisible(true); + setSelectionModel( new DefaultTreeSelectionModel() ); + + // The root node appears expanded by default. + nodeStates = new Hashtable(); + + // The cell renderer gets set by the UI. + cellRenderer = null; + + // Install the UI before installing the model. This way we avoid double + // initialization of lots of UI and model stuff inside the UI and related + // classes. The necessary UI updates are performed via property change + // events to the UI. + updateUI(); + setModel(model); + } + + /** + * Creates a new <code>JTree</code> object. + * + * @param root the root node + */ + public JTree(TreeNode root) + { + this(root, false); + } + + /** + * Creates a new <code>JTree</code> object. + * + * @param root the root node + * @param asksAllowChildren if false, all nodes without children are leaf + * nodes. If true, only nodes that do not allow children are leaf + * nodes. + */ + public JTree(TreeNode root, boolean asksAllowChildren) + { + this(new DefaultTreeModel(root, asksAllowChildren)); + } + + /** + * Creates a new <code>JTree</code> object. + * + * @param value the initial nodes in the tree + */ + public JTree(Vector<?> value) + { + this(createTreeModel(value)); + } + + public int getRowForPath(TreePath path) + { + TreeUI ui = getUI(); + + if (ui != null) + return ui.getRowForPath(this, path); + + return -1; + } + + public TreePath getPathForRow(int row) + { + TreeUI ui = getUI(); + return ui != null ? ui.getPathForRow(this, row) : null; + } + + /** + * Get the pathes that are displayes between the two given rows. + * + * @param index0 the starting row, inclusive + * @param index1 the ending row, inclusive + * + * @return the array of the tree pathes + */ + protected TreePath[] getPathBetweenRows(int index0, int index1) + { + TreeUI ui = getUI(); + + if (ui == null) + return null; + + int minIndex = Math.min(index0, index1); + int maxIndex = Math.max(index0, index1); + TreePath[] paths = new TreePath[maxIndex - minIndex + 1]; + + for (int i = minIndex; i <= maxIndex; ++i) + paths[i - minIndex] = ui.getPathForRow(this, i); + + return paths; + } + + /** + * Creates a new <code>TreeModel</code> object. + * + * @param value the values stored in the model + */ + protected static TreeModel createTreeModel(Object value) + { + return new DefaultTreeModel(new DynamicUtilTreeNode(value, value)); + } + + /** + * Return the UI associated with this <code>JTree</code> object. + * + * @return the associated <code>TreeUI</code> object + */ + public TreeUI getUI() + { + return (TreeUI) ui; + } + + /** + * Sets the UI associated with this <code>JTree</code> object. + * + * @param ui the <code>TreeUI</code> to associate + */ + public void setUI(TreeUI ui) + { + super.setUI(ui); + } + + /** + * This method resets the UI used to the Look and Feel defaults.. + */ + public void updateUI() + { + setUI((TreeUI) UIManager.getUI(this)); + } + + /** + * This method returns the String ID of the UI class of Separator. + * + * @return The UI class' String ID. + */ + public String getUIClassID() + { + return "TreeUI"; + } + + /** + * Gets the AccessibleContext associated with this + * <code>JTree</code>. + * + * @return the associated context + */ + public AccessibleContext getAccessibleContext() + { + return new AccessibleJTree(); + } + + /** + * Returns the preferred viewport size. + * + * @return the preferred size + */ + public Dimension getPreferredScrollableViewportSize() + { + return getPreferredSize(); + } + + /** + * Return the preferred scrolling amount (in pixels) for the given scrolling + * direction and orientation. This method handles a partially exposed row by + * returning the distance required to completely expose the item. + * + * @param visibleRect the currently visible part of the component. + * @param orientation the scrolling orientation + * @param direction the scrolling direction (negative - up, positive -down). + * The values greater than one means that more mouse wheel or similar + * events were generated, and hence it is better to scroll the longer + * distance. + * @author Audrius Meskauskas (audriusa@bioinformatics.org) + */ + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, + int direction) + { + int delta = 0; + + // Round so that the top would start from the row boundary + if (orientation == SwingConstants.VERTICAL) + { + int row = getClosestRowForLocation(0, visibleRect.y); + if (row != -1) + { + Rectangle b = getRowBounds(row); + if (b.y != visibleRect.y) + { + if (direction < 0) + delta = Math.max(0, visibleRect.y - b.y); + else + delta = b.y + b.height - visibleRect.y; + } + else + { + if (direction < 0) + { + if (row != 0) + { + b = getRowBounds(row - 1); + delta = b.height; + } + } + else + delta = b.height; + } + } + } + else + // The RI always returns 4 for HORIZONTAL scrolling. + delta = 4; + return delta; + } + + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) + { + int block; + if (orientation == SwingConstants.VERTICAL) + block = visibleRect.height; + else + block = visibleRect.width; + return block; + } + + public boolean getScrollableTracksViewportHeight() + { + if (getParent() instanceof JViewport) + return ((JViewport) getParent()).getHeight() > getPreferredSize().height; + return false; + } + + public boolean getScrollableTracksViewportWidth() + { + if (getParent() instanceof JViewport) + return ((JViewport) getParent()).getWidth() > getPreferredSize().width; + return false; + } + + /** + * Adds a <code>TreeExpansionListener</code> object to the tree. + * + * @param listener the listener to add + */ + public void addTreeExpansionListener(TreeExpansionListener listener) + { + listenerList.add(TreeExpansionListener.class, listener); + } + + /** + * Removes a <code>TreeExpansionListener</code> object from the tree. + * + * @param listener the listener to remove + */ + public void removeTreeExpansionListener(TreeExpansionListener listener) + { + listenerList.remove(TreeExpansionListener.class, listener); + } + + /** + * Returns all added <code>TreeExpansionListener</code> objects. + * + * @return an array of listeners + */ + public TreeExpansionListener[] getTreeExpansionListeners() + { + return (TreeExpansionListener[]) getListeners(TreeExpansionListener.class); + } + + /** + * Notifies all listeners that the tree was collapsed. + * + * @param path the path to the node that was collapsed + */ + public void fireTreeCollapsed(TreePath path) + { + TreeExpansionEvent event = new TreeExpansionEvent(this, path); + TreeExpansionListener[] listeners = getTreeExpansionListeners(); + + for (int index = 0; index < listeners.length; ++index) + listeners[index].treeCollapsed(event); + } + + /** + * Notifies all listeners that the tree was expanded. + * + * @param path the path to the node that was expanded + */ + public void fireTreeExpanded(TreePath path) + { + TreeExpansionEvent event = new TreeExpansionEvent(this, path); + TreeExpansionListener[] listeners = getTreeExpansionListeners(); + + for (int index = 0; index < listeners.length; ++index) + listeners[index].treeExpanded(event); + } + + /** + * Adds a <code>TreeSelctionListener</code> object to the tree. + * + * @param listener the listener to add + */ + public void addTreeSelectionListener(TreeSelectionListener listener) + { + listenerList.add(TreeSelectionListener.class, listener); + } + + /** + * Removes a <code>TreeSelectionListener</code> object from the tree. + * + * @param listener the listener to remove + */ + public void removeTreeSelectionListener(TreeSelectionListener listener) + { + listenerList.remove(TreeSelectionListener.class, listener); + } + + /** + * Returns all added <code>TreeSelectionListener</code> objects. + * + * @return an array of listeners + */ + public TreeSelectionListener[] getTreeSelectionListeners() + { + return (TreeSelectionListener[]) + getListeners(TreeSelectionListener.class); + } + + /** + * Notifies all listeners when the selection of the tree changed. + * + * @param event the event to send + */ + protected void fireValueChanged(TreeSelectionEvent event) + { + TreeSelectionListener[] listeners = getTreeSelectionListeners(); + + for (int index = 0; index < listeners.length; ++index) + listeners[index].valueChanged(event); + } + + /** + * Adds a <code>TreeWillExpandListener</code> object to the tree. + * + * @param listener the listener to add + */ + public void addTreeWillExpandListener(TreeWillExpandListener listener) + { + listenerList.add(TreeWillExpandListener.class, listener); + } + + /** + * Removes a <code>TreeWillExpandListener</code> object from the tree. + * + * @param listener the listener to remove + */ + public void removeTreeWillExpandListener(TreeWillExpandListener listener) + { + listenerList.remove(TreeWillExpandListener.class, listener); + } + + /** + * Returns all added <code>TreeWillExpandListener</code> objects. + * + * @return an array of listeners + */ + public TreeWillExpandListener[] getTreeWillExpandListeners() + { + return (TreeWillExpandListener[]) + getListeners(TreeWillExpandListener.class); + } + + /** + * Notifies all listeners that the tree will collapse. + * + * @param path the path to the node that will collapse + */ + public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException + { + TreeExpansionEvent event = new TreeExpansionEvent(this, path); + TreeWillExpandListener[] listeners = getTreeWillExpandListeners(); + + for (int index = 0; index < listeners.length; ++index) + listeners[index].treeWillCollapse(event); + } + + /** + * Notifies all listeners that the tree will expand. + * + * @param path the path to the node that will expand + */ + public void fireTreeWillExpand(TreePath path) throws ExpandVetoException + { + TreeExpansionEvent event = new TreeExpansionEvent(this, path); + TreeWillExpandListener[] listeners = getTreeWillExpandListeners(); + + for (int index = 0; index < listeners.length; ++index) + listeners[index].treeWillExpand(event); + } + + /** + * Returns the model of this <code>JTree</code> object. + * + * @return the associated <code>TreeModel</code> + */ + public TreeModel getModel() + { + return treeModel; + } + + /** + * Sets the model to use in <code>JTree</code>. + * + * @param model the <code>TreeModel</code> to use + */ + public void setModel(TreeModel model) + { + if (treeModel == model) + return; + + // Remove listeners from old model. + if (treeModel != null && treeModelListener != null) + treeModel.removeTreeModelListener(treeModelListener); + + // add treeModelListener to the new model + if (treeModelListener == null) + treeModelListener = createTreeModelListener(); + if (model != null) // as setModel(null) is allowed + model.addTreeModelListener(treeModelListener); + + TreeModel oldValue = treeModel; + treeModel = model; + clearToggledPaths(); + + if (treeModel != null) + { + if (treeModelListener == null) + treeModelListener = createTreeModelListener(); + if (treeModelListener != null) + treeModel.addTreeModelListener(treeModelListener); + Object root = treeModel.getRoot(); + if (root != null && !treeModel.isLeaf(root)) + { + nodeStates.put(new TreePath(root), Boolean.TRUE); + } + } + + firePropertyChange(TREE_MODEL_PROPERTY, oldValue, model); + } + + /** + * Checks if this <code>JTree</code> object is editable. + * + * @return <code>true</code> if this tree object is editable, + * <code>false</code> otherwise + */ + public boolean isEditable() + { + return editable; + } + + /** + * Sets the <code>editable</code> property. + * + * @param flag <code>true</code> to make this tree object editable, + * <code>false</code> otherwise + */ + public void setEditable(boolean flag) + { + if (editable == flag) + return; + + boolean oldValue = editable; + editable = flag; + firePropertyChange(EDITABLE_PROPERTY, oldValue, editable); + } + + /** + * Checks if the root element is visible. + * + * @return <code>true</code> if the root element is visible, + * <code>false</code> otherwise + */ + public boolean isRootVisible() + { + return rootVisible; + } + + public void setRootVisible(boolean flag) + { + if (rootVisible == flag) + return; + + // If the root is currently selected, unselect it + if (rootVisible && !flag) + { + TreeSelectionModel model = getSelectionModel(); + // The root is always shown in the first row + TreePath rootPath = getPathForRow(0); + model.removeSelectionPath(rootPath); + } + + boolean oldValue = rootVisible; + rootVisible = flag; + firePropertyChange(ROOT_VISIBLE_PROPERTY, oldValue, flag); + + } + + public boolean getShowsRootHandles() + { + return showsRootHandles; + } + + public void setShowsRootHandles(boolean flag) + { + clientShowsRootHandlesSet = true; + + if (showsRootHandles == flag) + return; + + boolean oldValue = showsRootHandles; + showsRootHandles = flag; + firePropertyChange(SHOWS_ROOT_HANDLES_PROPERTY, oldValue, flag); + } + + public TreeCellEditor getCellEditor() + { + return cellEditor; + } + + public void setCellEditor(TreeCellEditor editor) + { + if (cellEditor == editor) + return; + + TreeCellEditor oldValue = cellEditor; + cellEditor = editor; + firePropertyChange(CELL_EDITOR_PROPERTY, oldValue, editor); + } + + public TreeCellRenderer getCellRenderer() + { + return cellRenderer; + } + + public void setCellRenderer(TreeCellRenderer newRenderer) + { + if (cellRenderer == newRenderer) + return; + + TreeCellRenderer oldValue = cellRenderer; + cellRenderer = newRenderer; + firePropertyChange(CELL_RENDERER_PROPERTY, oldValue, newRenderer); + } + + public TreeSelectionModel getSelectionModel() + { + return selectionModel; + } + + public void setSelectionModel(TreeSelectionModel model) + { + if (selectionModel == model) + return; + + if( model == null ) + model = EmptySelectionModel.sharedInstance(); + + if (selectionModel != null) + selectionModel.removeTreeSelectionListener(selectionRedirector); + + TreeSelectionModel oldValue = selectionModel; + selectionModel = model; + + selectionModel.addTreeSelectionListener(selectionRedirector); + + firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue, model); + revalidate(); + repaint(); + } + + public int getVisibleRowCount() + { + return visibleRowCount; + } + + public void setVisibleRowCount(int rows) + { + if (visibleRowCount == rows) + return; + + int oldValue = visibleRowCount; + visibleRowCount = rows; + firePropertyChange(VISIBLE_ROW_COUNT_PROPERTY, oldValue, rows); + } + + public boolean isLargeModel() + { + return largeModel; + } + + public void setLargeModel(boolean large) + { + if (largeModel == large) + return; + + boolean oldValue = largeModel; + largeModel = large; + firePropertyChange(LARGE_MODEL_PROPERTY, oldValue, large); + } + + public int getRowHeight() + { + return rowHeight; + } + + public void setRowHeight(int height) + { + clientRowHeightSet = true; + + if (rowHeight == height) + return; + + int oldValue = rowHeight; + rowHeight = height; + firePropertyChange(ROW_HEIGHT_PROPERTY, oldValue, height); + } + + public boolean isFixedRowHeight() + { + return rowHeight > 0; + } + + public boolean getInvokesStopCellEditing() + { + return invokesStopCellEditing; + } + + public void setInvokesStopCellEditing(boolean invoke) + { + if (invokesStopCellEditing == invoke) + return; + + boolean oldValue = invokesStopCellEditing; + invokesStopCellEditing = invoke; + firePropertyChange(INVOKES_STOP_CELL_EDITING_PROPERTY, + oldValue, invoke); + } + + /** + * @since 1.3 + */ + public int getToggleClickCount() + { + return toggleClickCount; + } + + /** + * @since 1.3 + */ + public void setToggleClickCount(int count) + { + if (toggleClickCount == count) + return; + + int oldValue = toggleClickCount; + toggleClickCount = count; + firePropertyChange(TOGGLE_CLICK_COUNT_PROPERTY, oldValue, count); + } + + public void scrollPathToVisible(TreePath path) + { + if (path == null) + return; + Rectangle rect = getPathBounds(path); + scrollRectToVisible(rect); + } + + public void scrollRowToVisible(int row) + { + scrollPathToVisible(getPathForRow(row)); + } + + public boolean getScrollsOnExpand() + { + return scrollsOnExpand; + } + + public void setScrollsOnExpand(boolean scroll) + { + clientScrollsOnExpandSet = true; + if (scrollsOnExpand == scroll) + return; + + boolean oldValue = scrollsOnExpand; + scrollsOnExpand = scroll; + firePropertyChange(SCROLLS_ON_EXPAND_PROPERTY, oldValue, scroll); + } + + public void setSelectionPath(TreePath path) + { + clearSelectionPathStates(); + selectionModel.setSelectionPath(path); + } + + public void setSelectionPaths(TreePath[] paths) + { + clearSelectionPathStates(); + selectionModel.setSelectionPaths(paths); + } + + /** + * This method, and all calls to it, should be removed once the + * DefaultTreeModel fires events properly. Maintenance of the nodeStates + * table should really be done in the TreeModelHandler. + */ + private void clearSelectionPathStates() + { + TreePath[] oldPaths = selectionModel.getSelectionPaths(); + if (oldPaths != null) + for (int i = 0; i < oldPaths.length; i++) + nodeStates.remove(oldPaths[i]); + } + + public void setSelectionRow(int row) + { + TreePath path = getPathForRow(row); + + if (path != null) + setSelectionPath(path); + } + + public void setSelectionRows(int[] rows) + { + // Make sure we have an UI so getPathForRow() does not return null. + if (rows == null || getUI() == null) + return; + + TreePath[] paths = new TreePath[rows.length]; + + for (int i = rows.length - 1; i >= 0; --i) + paths[i] = getPathForRow(rows[i]); + + setSelectionPaths(paths); + } + + public void setSelectionInterval(int index0, int index1) + { + TreePath[] paths = getPathBetweenRows(index0, index1); + + if (paths != null) + setSelectionPaths(paths); + } + + public void addSelectionPath(TreePath path) + { + selectionModel.addSelectionPath(path); + } + + public void addSelectionPaths(TreePath[] paths) + { + selectionModel.addSelectionPaths(paths); + } + + public void addSelectionRow(int row) + { + TreePath path = getPathForRow(row); + + if (path != null) + selectionModel.addSelectionPath(path); + } + + public void addSelectionRows(int[] rows) + { + // Make sure we have an UI so getPathForRow() does not return null. + if (rows == null || getUI() == null) + return; + + TreePath[] paths = new TreePath[rows.length]; + + for (int i = rows.length - 1; i >= 0; --i) + paths[i] = getPathForRow(rows[i]); + + addSelectionPaths(paths); + } + + /** + * Select all rows between the two given indexes, inclusive. The method + * will not select the inner leaves and braches of the currently collapsed + * nodes in this interval. + * + * @param index0 the starting row, inclusive + * @param index1 the ending row, inclusive + */ + public void addSelectionInterval(int index0, int index1) + { + TreePath[] paths = getPathBetweenRows(index0, index1); + + if (paths != null) + addSelectionPaths(paths); + } + + public void removeSelectionPath(TreePath path) + { + clearSelectionPathStates(); + selectionModel.removeSelectionPath(path); + } + + public void removeSelectionPaths(TreePath[] paths) + { + clearSelectionPathStates(); + selectionModel.removeSelectionPaths(paths); + } + + public void removeSelectionRow(int row) + { + TreePath path = getPathForRow(row); + + if (path != null) + removeSelectionPath(path); + } + + public void removeSelectionRows(int[] rows) + { + if (rows == null || getUI() == null) + return; + + TreePath[] paths = new TreePath[rows.length]; + + for (int i = rows.length - 1; i >= 0; --i) + paths[i] = getPathForRow(rows[i]); + + removeSelectionPaths(paths); + } + + public void removeSelectionInterval(int index0, int index1) + { + TreePath[] paths = getPathBetweenRows(index0, index1); + + if (paths != null) + removeSelectionPaths(paths); + } + + public void clearSelection() + { + selectionModel.clearSelection(); + setLeadSelectionPath(null); + } + + public TreePath getLeadSelectionPath() + { + if (selectionModel == null) + return null; + else + return selectionModel.getLeadSelectionPath(); + } + + /** + * @since 1.3 + */ + public void setLeadSelectionPath(TreePath path) + { + if (selectionModel != null) + { + TreePath oldValue = selectionModel.getLeadSelectionPath(); + if (path == oldValue || path != null && path.equals(oldValue)) + return; + + // Repaint the previous and current rows with the lead selection path. + if (path != null) + { + repaint(getPathBounds(path)); + selectionModel.addSelectionPath(path); + } + + if (oldValue != null) + repaint(getPathBounds(oldValue)); + + firePropertyChange(LEAD_SELECTION_PATH_PROPERTY, oldValue, path); + } + } + + /** + * @since 1.3 + */ + public TreePath getAnchorSelectionPath() + { + return anchorSelectionPath; + } + + /** + * @since 1.3 + */ + public void setAnchorSelectionPath(TreePath path) + { + if (anchorSelectionPath == path) + return; + + TreePath oldValue = anchorSelectionPath; + anchorSelectionPath = path; + firePropertyChange(ANCHOR_SELECTION_PATH_PROPERTY, oldValue, path); + } + + public int getLeadSelectionRow() + { + return selectionModel.getLeadSelectionRow(); + } + + public int getMaxSelectionRow() + { + return selectionModel.getMaxSelectionRow(); + } + + public int getMinSelectionRow() + { + return selectionModel.getMinSelectionRow(); + } + + public int getSelectionCount() + { + return selectionModel.getSelectionCount(); + } + + public TreePath getSelectionPath() + { + return selectionModel.getSelectionPath(); + } + + public TreePath[] getSelectionPaths() + { + return selectionModel.getSelectionPaths(); + } + + public int[] getSelectionRows() + { + return selectionModel.getSelectionRows(); + } + + public boolean isPathSelected(TreePath path) + { + return selectionModel.isPathSelected(path); + } + + /** + * Returns <code>true</code> when the specified row is selected, + * <code>false</code> otherwise. This call is delegated to the + * {@link TreeSelectionModel#isRowSelected(int)} method. + * + * @param row the row to check + * + * @return <code>true</code> when the specified row is selected, + * <code>false</code> otherwise + */ + public boolean isRowSelected(int row) + { + return selectionModel.isRowSelected(row); + } + + public boolean isSelectionEmpty() + { + return selectionModel.isSelectionEmpty(); + } + + /** + * Return the value of the <code>dragEnabled</code> property. + * + * @return the value + * + * @since 1.4 + */ + public boolean getDragEnabled() + { + return dragEnabled; + } + + /** + * Set the <code>dragEnabled</code> property. + * + * @param enabled new value + * + * @since 1.4 + */ + public void setDragEnabled(boolean enabled) + { + dragEnabled = enabled; + } + + public int getRowCount() + { + TreeUI ui = getUI(); + + if (ui != null) + return ui.getRowCount(this); + + return 0; + } + + public void collapsePath(TreePath path) + { + try + { + fireTreeWillCollapse(path); + } + catch (ExpandVetoException ev) + { + // We do nothing if attempt has been vetoed. + } + setExpandedState(path, false); + fireTreeCollapsed(path); + } + + public void collapseRow(int row) + { + if (row < 0 || row >= getRowCount()) + return; + + TreePath path = getPathForRow(row); + + if (path != null) + collapsePath(path); + } + + public void expandPath(TreePath path) + { + // Don't expand if path is null + // or is already expanded. + if (path == null || isExpanded(path)) + return; + + try + { + fireTreeWillExpand(path); + } + catch (ExpandVetoException ev) + { + // We do nothing if attempt has been vetoed. + } + + setExpandedState(path, true); + fireTreeExpanded(path); + } + + public void expandRow(int row) + { + if (row < 0 || row >= getRowCount()) + return; + + TreePath path = getPathForRow(row); + + if (path != null) + expandPath(path); + } + + public boolean isCollapsed(TreePath path) + { + return !isExpanded(path); + } + + public boolean isCollapsed(int row) + { + if (row < 0 || row >= getRowCount()) + return false; + + TreePath path = getPathForRow(row); + + if (path != null) + return isCollapsed(path); + + return false; + } + + public boolean isExpanded(TreePath path) + { + if (path == null) + return false; + + Object state = nodeStates.get(path); + + if ((state == null) || (state != EXPANDED)) + return false; + + TreePath parent = path.getParentPath(); + + if (parent != null) + return isExpanded(parent); + + return true; + } + + public boolean isExpanded(int row) + { + if (row < 0 || row >= getRowCount()) + return false; + + TreePath path = getPathForRow(row); + + if (path != null) + return isExpanded(path); + + return false; + } + + /** + * @since 1.3 + */ + public boolean getExpandsSelectedPaths() + { + return expandsSelectedPaths; + } + + /** + * @since 1.3 + */ + public void setExpandsSelectedPaths(boolean flag) + { + if (expandsSelectedPaths == flag) + return; + + boolean oldValue = expandsSelectedPaths; + expandsSelectedPaths = flag; + firePropertyChange(EXPANDS_SELECTED_PATHS_PROPERTY, oldValue, flag); + } + + public Rectangle getPathBounds(TreePath path) + { + TreeUI ui = getUI(); + + if (ui == null) + return null; + + return ui.getPathBounds(this, path); + } + + public Rectangle getRowBounds(int row) + { + TreePath path = getPathForRow(row); + + if (path != null) + return getPathBounds(path); + + return null; + } + + public boolean isEditing() + { + TreeUI ui = getUI(); + + if (ui != null) + return ui.isEditing(this); + + return false; + } + + public boolean stopEditing() + { + TreeUI ui = getUI(); + + if (isEditing()) + if (ui != null) + return ui.stopEditing(this); + + return false; + } + + public void cancelEditing() + { + TreeUI ui = getUI(); + + if (isEditing()) + if (ui != null) + ui.cancelEditing(this); + } + + public void startEditingAtPath(TreePath path) + { + TreeUI ui = getUI(); + + if (ui != null) + ui.startEditingAtPath(this, path); + } + + public TreePath getEditingPath() + { + TreeUI ui = getUI(); + + if (ui != null) + return ui.getEditingPath(this); + + return null; + } + + public TreePath getPathForLocation(int x, int y) + { + TreePath path = getClosestPathForLocation(x, y); + + if (path != null) + { + Rectangle rect = getPathBounds(path); + + if ((rect != null) && rect.contains(x, y)) + return path; + } + + return null; + } + + public int getRowForLocation(int x, int y) + { + TreePath path = getPathForLocation(x, y); + + if (path != null) + return getRowForPath(path); + + return -1; + } + + public TreePath getClosestPathForLocation(int x, int y) + { + TreeUI ui = getUI(); + + if (ui != null) + return ui.getClosestPathForLocation(this, x, y); + + return null; + } + + public int getClosestRowForLocation(int x, int y) + { + TreePath path = getClosestPathForLocation(x, y); + + if (path != null) + return getRowForPath(path); + + return -1; + } + + public Object getLastSelectedPathComponent() + { + TreePath path = getSelectionPath(); + + if (path != null) + return path.getLastPathComponent(); + + return null; + } + + private void doExpandParents(TreePath path, boolean state) + { + TreePath parent = path.getParentPath(); + + if (!isExpanded(parent) && parent != null) + doExpandParents(parent, false); + + nodeStates.put(path, state ? EXPANDED : COLLAPSED); + } + + protected void setExpandedState(TreePath path, boolean state) + { + if (path == null) + return; + + doExpandParents(path, state); + } + + protected void clearToggledPaths() + { + nodeStates.clear(); + } + + protected Enumeration<TreePath> getDescendantToggledPaths(TreePath parent) + { + if (parent == null) + return null; + + Enumeration nodes = nodeStates.keys(); + Vector result = new Vector(); + + while (nodes.hasMoreElements()) + { + TreePath path = (TreePath) nodes.nextElement(); + + if (path.isDescendant(parent)) + result.addElement(path); + } + + return result.elements(); + } + + public boolean hasBeenExpanded(TreePath path) + { + if (path == null) + return false; + + return nodeStates.get(path) != null; + } + + public boolean isVisible(TreePath path) + { + if (path == null) + return false; + + TreePath parent = path.getParentPath(); + + if (parent == null) + return true; // Is root node. + + return isExpanded(parent); + } + + public void makeVisible(TreePath path) + { + if (path == null) + return; + + expandPath(path.getParentPath()); + } + + public boolean isPathEditable(TreePath path) + { + return isEditable(); + } + + /** + * Creates and returns an instance of {@link TreeModelHandler}. + * + * @return an instance of {@link TreeModelHandler} + */ + protected TreeModelListener createTreeModelListener() + { + return new TreeModelHandler(); + } + + /** + * Returns a sample TreeModel that can be used in a JTree. This can be used + * in Bean- or GUI-Builders to show something interesting. + * + * @return a sample TreeModel that can be used in a JTree + */ + protected static TreeModel getDefaultTreeModel() + { + DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root node"); + DefaultMutableTreeNode child1 = new DefaultMutableTreeNode("Child node 1"); + DefaultMutableTreeNode child11 = + new DefaultMutableTreeNode("Child node 1.1"); + DefaultMutableTreeNode child12 = + new DefaultMutableTreeNode("Child node 1.2"); + DefaultMutableTreeNode child13 = + new DefaultMutableTreeNode("Child node 1.3"); + DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Child node 2"); + DefaultMutableTreeNode child21 = + new DefaultMutableTreeNode("Child node 2.1"); + DefaultMutableTreeNode child22 = + new DefaultMutableTreeNode("Child node 2.2"); + DefaultMutableTreeNode child23 = + new DefaultMutableTreeNode("Child node 2.3"); + DefaultMutableTreeNode child24 = + new DefaultMutableTreeNode("Child node 2.4"); + + DefaultMutableTreeNode child3 = new DefaultMutableTreeNode("Child node 3"); + root.add(child1); + root.add(child2); + root.add(child3); + child1.add(child11); + child1.add(child12); + child1.add(child13); + child2.add(child21); + child2.add(child22); + child2.add(child23); + child2.add(child24); + return new DefaultTreeModel(root); + } + + /** + * Converts the specified value to a String. This is used by the renderers + * of this JTree and its nodes. + * + * This implementation simply returns <code>value.toString()</code> and + * ignores all other parameters. Subclass this method to control the + * conversion. + * + * @param value the value that is converted to a String + * @param selected indicates if that value is selected or not + * @param expanded indicates if that value is expanded or not + * @param leaf indicates if that value is a leaf node or not + * @param row the row of the node + * @param hasFocus indicates if that node has focus or not + */ + public String convertValueToText(Object value, boolean selected, + boolean expanded, boolean leaf, int row, boolean hasFocus) + { + return value.toString(); + } + + /** + * A String representation of this JTree. This is intended to be used for + * debugging. The returned string may be empty but may not be + * <code>null</code>. + * + * @return a String representation of this JTree + */ + protected String paramString() + { + // TODO: this is completely legal, but it would possibly be nice + // to return some more content, like the tree structure, some properties + // etc ... + return ""; + } + + /** + * Returns all TreePath objects which are a descendants of the given path + * and are exapanded at the moment of the execution of this method. If the + * state of any node is beeing toggled while this method is executing this + * change may be left unaccounted. + * + * @param path The parent of this request + * + * @return An Enumeration containing TreePath objects + */ + public Enumeration<TreePath> getExpandedDescendants(TreePath path) + { + Enumeration paths = nodeStates.keys(); + Vector relevantPaths = new Vector(); + while (paths.hasMoreElements()) + { + TreePath nextPath = (TreePath) paths.nextElement(); + if (nodeStates.get(nextPath) == EXPANDED + && path.isDescendant(nextPath)) + { + relevantPaths.add(nextPath); + } + } + return relevantPaths.elements(); + } + + /** + * Returns the next table element (beginning from the row + * <code>startingRow</code> that starts with <code>prefix</code>. + * Searching is done in the direction specified by <code>bias</code>. + * + * @param prefix the prefix to search for in the cell values + * @param startingRow the index of the row where to start searching from + * @param bias the search direction, either {@link Position.Bias#Forward} or + * {@link Position.Bias#Backward} + * + * @return the path to the found element or -1 if no such element has been + * found + * + * @throws IllegalArgumentException if prefix is <code>null</code> or + * startingRow is not valid + * + * @since 1.4 + */ + public TreePath getNextMatch(String prefix, int startingRow, + Position.Bias bias) + { + if (prefix == null) + throw new IllegalArgumentException("The argument 'prefix' must not be" + + " null."); + if (startingRow < 0) + throw new IllegalArgumentException("The argument 'startingRow' must not" + + " be less than zero."); + + int size = getRowCount(); + if (startingRow > size) + throw new IllegalArgumentException("The argument 'startingRow' must not" + + " be greater than the number of" + + " elements in the TreeModel."); + + TreePath foundPath = null; + if (bias == Position.Bias.Forward) + { + for (int i = startingRow; i < size; i++) + { + TreePath path = getPathForRow(i); + Object o = path.getLastPathComponent(); + // FIXME: in the following call to convertValueToText the + // last argument (hasFocus) should be done right. + String item = convertValueToText(o, isRowSelected(i), + isExpanded(i), treeModel.isLeaf(o), + i, false); + if (item.startsWith(prefix)) + { + foundPath = path; + break; + } + } + } + else + { + for (int i = startingRow; i >= 0; i--) + { + TreePath path = getPathForRow(i); + Object o = path.getLastPathComponent(); + // FIXME: in the following call to convertValueToText the + // last argument (hasFocus) should be done right. + String item = convertValueToText(o, isRowSelected(i), + isExpanded(i), treeModel.isLeaf(o), i, false); + if (item.startsWith(prefix)) + { + foundPath = path; + break; + } + } + } + return foundPath; + } + + /** + * Removes any paths in the current set of selected paths that are + * descendants of <code>path</code>. If <code>includePath</code> is set + * to <code>true</code> and <code>path</code> itself is selected, then + * it will be removed too. + * + * @param path the path from which selected descendants are to be removed + * @param includeSelected if <code>true</code> then <code>path</code> itself + * will also be remove if it's selected + * + * @return <code>true</code> if something has been removed, + * <code>false</code> otherwise + * + * @since 1.3 + */ + protected boolean removeDescendantSelectedPaths(TreePath path, + boolean includeSelected) + { + boolean removedSomething = false; + TreePath[] selected = getSelectionPaths(); + for (int index = 0; index < selected.length; index++) + { + if ((selected[index] == path && includeSelected) + || (selected[index].isDescendant(path))) + { + removeSelectionPath(selected[index]); + removedSomething = true; + } + } + return removedSomething; + } + + /** + * Removes any descendants of the TreePaths in toRemove that have been + * expanded. + * + * @param toRemove - Enumeration of TreePaths that need to be removed from + * cache of toggled tree paths. + */ + protected void removeDescendantToggledPaths(Enumeration<TreePath> toRemove) + { + while (toRemove.hasMoreElements()) + { + TreePath current = toRemove.nextElement(); + Enumeration descendants = getDescendantToggledPaths(current); + + while (descendants.hasMoreElements()) + { + TreePath currentDes = (TreePath) descendants.nextElement(); + if (isExpanded(currentDes)) + nodeStates.remove(currentDes); + } + } + } + + /** + * <p> + * Sent when the tree has changed enough that we need to resize the bounds, + * but not enough that we need to remove the expanded node set (e.g nodes were + * expanded or collapsed, or nodes were inserted into the tree). You should + * never have to invoke this, the UI will invoke this as it needs to. + * </p> + * <p> + * If the tree uses {@link DefaultTreeModel}, you must call + * {@link DefaultTreeModel#reload(TreeNode)} or + * {@link DefaultTreeModel#reload()} after adding or removing nodes. Following + * the official Java 1.5 API standard, just calling treeDidChange, repaint() + * or revalidate() does <i>not</i> update the tree appearance properly. + * + * @see DefaultTreeModel#reload() + */ + public void treeDidChange() + { + repaint(); + } + + /** + * Helper method for + * {@link LookAndFeel#installProperty(JComponent, String, Object)}. + * + * @param propertyName the name of the property + * @param value the value of the property + * + * @throws IllegalArgumentException if the specified property cannot be set + * by this method + * @throws ClassCastException if the property value does not match the + * property type + * @throws NullPointerException if <code>c</code> or + * <code>propertyValue</code> is <code>null</code> + */ + void setUIProperty(String propertyName, Object value) + { + if (propertyName.equals("rowHeight")) + { + if (! clientRowHeightSet) + { + setRowHeight(((Integer) value).intValue()); + clientRowHeightSet = false; + } + } + else if (propertyName.equals("scrollsOnExpand")) + { + if (! clientScrollsOnExpandSet) + { + setScrollsOnExpand(((Boolean) value).booleanValue()); + clientScrollsOnExpandSet = false; + } + } + else if (propertyName.equals("showsRootHandles")) + { + if (! clientShowsRootHandlesSet) + { + setShowsRootHandles(((Boolean) value).booleanValue()); + clientShowsRootHandlesSet = false; + } + } + else + { + super.setUIProperty(propertyName, value); + } + } +} |