/* |
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package javax.swing.tree; |
|
import java.util.*; |
|
import java.beans.ConstructorProperties; |
|
import java.io.*; |
|
import javax.swing.event.*; |
|
/** |
|
* A simple tree data model that uses TreeNodes. |
|
* For further information and examples that use DefaultTreeModel, |
|
* see <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html">How to Use Trees</a> |
|
* in <em>The Java Tutorial.</em> |
|
* <p> |
|
* <strong>Warning:</strong> |
|
* Serialized objects of this class will not be compatible with |
|
* future Swing releases. The current serialization support is |
|
* appropriate for short term storage or RMI between applications running |
|
* the same version of Swing. As of 1.4, support for long term storage |
|
* of all JavaBeans™ |
|
* has been added to the <code>java.beans</code> package. |
|
* Please see {@link java.beans.XMLEncoder}. |
|
* |
|
* @author Rob Davis |
|
* @author Ray Ryan |
|
* @author Scott Violet |
|
*/ |
|
public class DefaultTreeModel implements Serializable, TreeModel { |
|
/** Root of the tree. */ |
|
protected TreeNode root; |
|
/** Listeners. */ |
|
protected EventListenerList listenerList = new EventListenerList(); |
|
/** |
|
* Determines how the <code>isLeaf</code> method figures |
|
* out if a node is a leaf node. If true, a node is a leaf |
|
* node if it does not allow children. (If it allows |
|
* children, it is not a leaf node, even if no children |
|
* are present.) That lets you distinguish between <i>folder</i> |
|
* nodes and <i>file</i> nodes in a file system, for example. |
|
* <p> |
|
* If this value is false, then any node which has no |
|
* children is a leaf node, and any node may acquire |
|
* children. |
|
* |
|
* @see TreeNode#getAllowsChildren |
|
* @see TreeModel#isLeaf |
|
* @see #setAsksAllowsChildren |
|
*/ |
|
protected boolean asksAllowsChildren; |
|
/** |
|
* Creates a tree in which any node can have children. |
|
* |
|
* @param root a TreeNode object that is the root of the tree |
|
* @see #DefaultTreeModel(TreeNode, boolean) |
|
*/ |
|
@ConstructorProperties({"root"}) |
|
public DefaultTreeModel(TreeNode root) { |
|
this(root, false); |
|
} |
|
/** |
|
* Creates a tree specifying whether any node can have children, |
|
* or whether only certain nodes can have children. |
|
* |
|
* @param root a TreeNode object that is the root of the tree |
|
* @param asksAllowsChildren a boolean, false if any node can |
|
* have children, true if each node is asked to see if |
|
* it can have children |
|
* @see #asksAllowsChildren |
|
*/ |
|
public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) { |
|
super(); |
|
this.root = root; |
|
this.asksAllowsChildren = asksAllowsChildren; |
|
} |
|
/** |
|
* Sets whether or not to test leafness by asking getAllowsChildren() |
|
* or isLeaf() to the TreeNodes. If newvalue is true, getAllowsChildren() |
|
* is messaged, otherwise isLeaf() is messaged. |
|
*/ |
|
public void setAsksAllowsChildren(boolean newValue) { |
|
asksAllowsChildren = newValue; |
|
} |
|
/** |
|
* Tells how leaf nodes are determined. |
|
* |
|
* @return true if only nodes which do not allow children are |
|
* leaf nodes, false if nodes which have no children |
|
* (even if allowed) are leaf nodes |
|
* @see #asksAllowsChildren |
|
*/ |
|
public boolean asksAllowsChildren() { |
|
return asksAllowsChildren; |
|
} |
|
/** |
|
* Sets the root to <code>root</code>. A null <code>root</code> implies |
|
* the tree is to display nothing, and is legal. |
|
*/ |
|
public void setRoot(TreeNode root) { |
|
Object oldRoot = this.root; |
|
this.root = root; |
|
if (root == null && oldRoot != null) { |
|
fireTreeStructureChanged(this, null); |
|
} |
|
else { |
|
nodeStructureChanged(root); |
|
} |
|
} |
|
/** |
|
* Returns the root of the tree. Returns null only if the tree has |
|
* no nodes. |
|
* |
|
* @return the root of the tree |
|
*/ |
|
public Object getRoot() { |
|
return root; |
|
} |
|
/** |
|
* Returns the index of child in parent. |
|
* If either the parent or child is <code>null</code>, returns -1. |
|
* @param parent a note in the tree, obtained from this data source |
|
* @param child the node we are interested in |
|
* @return the index of the child in the parent, or -1 |
|
* if either the parent or the child is <code>null</code> |
|
*/ |
|
public int getIndexOfChild(Object parent, Object child) { |
|
if(parent == null || child == null) |
|
return -1; |
|
return ((TreeNode)parent).getIndex((TreeNode)child); |
|
} |
|
/** |
|
* Returns the child of <I>parent</I> at index <I>index</I> in the parent's |
|
* child array. <I>parent</I> must be a node previously obtained from |
|
* this data source. This should not return null if <i>index</i> |
|
* is a valid index for <i>parent</i> (that is <i>index</i> >= 0 && |
|
* <i>index</i> < getChildCount(<i>parent</i>)). |
|
* |
|
* @param parent a node in the tree, obtained from this data source |
|
* @return the child of <I>parent</I> at index <I>index</I> |
|
*/ |
|
public Object getChild(Object parent, int index) { |
|
return ((TreeNode)parent).getChildAt(index); |
|
} |
|
/** |
|
* Returns the number of children of <I>parent</I>. Returns 0 if the node |
|
* is a leaf or if it has no children. <I>parent</I> must be a node |
|
* previously obtained from this data source. |
|
* |
|
* @param parent a node in the tree, obtained from this data source |
|
* @return the number of children of the node <I>parent</I> |
|
*/ |
|
public int getChildCount(Object parent) { |
|
return ((TreeNode)parent).getChildCount(); |
|
} |
|
/** |
|
* Returns whether the specified node is a leaf node. |
|
* The way the test is performed depends on the |
|
* <code>askAllowsChildren</code> setting. |
|
* |
|
* @param node the node to check |
|
* @return true if the node is a leaf node |
|
* |
|
* @see #asksAllowsChildren |
|
* @see TreeModel#isLeaf |
|
*/ |
|
public boolean isLeaf(Object node) { |
|
if(asksAllowsChildren) |
|
return !((TreeNode)node).getAllowsChildren(); |
|
return ((TreeNode)node).isLeaf(); |
|
} |
|
/** |
|
* Invoke this method if you've modified the {@code TreeNode}s upon which |
|
* this model depends. The model will notify all of its listeners that the |
|
* model has changed. |
|
*/ |
|
public void reload() { |
|
reload(root); |
|
} |
|
/** |
|
* This sets the user object of the TreeNode identified by path |
|
* and posts a node changed. If you use custom user objects in |
|
* the TreeModel you're going to need to subclass this and |
|
* set the user object of the changed node to something meaningful. |
|
*/ |
|
public void valueForPathChanged(TreePath path, Object newValue) { |
|
MutableTreeNode aNode = (MutableTreeNode)path.getLastPathComponent(); |
|
aNode.setUserObject(newValue); |
|
nodeChanged(aNode); |
|
} |
|
/** |
|
* Invoked this to insert newChild at location index in parents children. |
|
* This will then message nodesWereInserted to create the appropriate |
|
* event. This is the preferred way to add children as it will create |
|
* the appropriate event. |
|
*/ |
|
public void insertNodeInto(MutableTreeNode newChild, |
|
MutableTreeNode parent, int index){ |
|
parent.insert(newChild, index); |
|
int[] newIndexs = new int[1]; |
|
newIndexs[0] = index; |
|
nodesWereInserted(parent, newIndexs); |
|
} |
|
/** |
|
* Message this to remove node from its parent. This will message |
|
* nodesWereRemoved to create the appropriate event. This is the |
|
* preferred way to remove a node as it handles the event creation |
|
* for you. |
|
*/ |
|
public void removeNodeFromParent(MutableTreeNode node) { |
|
MutableTreeNode parent = (MutableTreeNode)node.getParent(); |
|
if(parent == null) |
|
throw new IllegalArgumentException("node does not have a parent."); |
|
int[] childIndex = new int[1]; |
|
Object[] removedArray = new Object[1]; |
|
childIndex[0] = parent.getIndex(node); |
|
parent.remove(childIndex[0]); |
|
removedArray[0] = node; |
|
nodesWereRemoved(parent, childIndex, removedArray); |
|
} |
|
/** |
|
* Invoke this method after you've changed how node is to be |
|
* represented in the tree. |
|
*/ |
|
public void nodeChanged(TreeNode node) { |
|
if(listenerList != null && node != null) { |
|
TreeNode parent = node.getParent(); |
|
if(parent != null) { |
|
int anIndex = parent.getIndex(node); |
|
if(anIndex != -1) { |
|
int[] cIndexs = new int[1]; |
|
cIndexs[0] = anIndex; |
|
nodesChanged(parent, cIndexs); |
|
} |
|
} |
|
else if (node == getRoot()) { |
|
nodesChanged(node, null); |
|
} |
|
} |
|
} |
|
/** |
|
* Invoke this method if you've modified the {@code TreeNode}s upon which |
|
* this model depends. The model will notify all of its listeners that the |
|
* model has changed below the given node. |
|
* |
|
* @param node the node below which the model has changed |
|
*/ |
|
public void reload(TreeNode node) { |
|
if(node != null) { |
|
fireTreeStructureChanged(this, getPathToRoot(node), null, null); |
|
} |
|
} |
|
/** |
|
* Invoke this method after you've inserted some TreeNodes into |
|
* node. childIndices should be the index of the new elements and |
|
* must be sorted in ascending order. |
|
*/ |
|
public void nodesWereInserted(TreeNode node, int[] childIndices) { |
|
if(listenerList != null && node != null && childIndices != null |
|
&& childIndices.length > 0) { |
|
int cCount = childIndices.length; |
|
Object[] newChildren = new Object[cCount]; |
|
for(int counter = 0; counter < cCount; counter++) |
|
newChildren[counter] = node.getChildAt(childIndices[counter]); |
|
fireTreeNodesInserted(this, getPathToRoot(node), childIndices, |
|
newChildren); |
|
} |
|
} |
|
/** |
|
* Invoke this method after you've removed some TreeNodes from |
|
* node. childIndices should be the index of the removed elements and |
|
* must be sorted in ascending order. And removedChildren should be |
|
* the array of the children objects that were removed. |
|
*/ |
|
public void nodesWereRemoved(TreeNode node, int[] childIndices, |
|
Object[] removedChildren) { |
|
if(node != null && childIndices != null) { |
|
fireTreeNodesRemoved(this, getPathToRoot(node), childIndices, |
|
removedChildren); |
|
} |
|
} |
|
/** |
|
* Invoke this method after you've changed how the children identified by |
|
* childIndicies are to be represented in the tree. |
|
*/ |
|
public void nodesChanged(TreeNode node, int[] childIndices) { |
|
if(node != null) { |
|
if (childIndices != null) { |
|
int cCount = childIndices.length; |
|
if(cCount > 0) { |
|
Object[] cChildren = new Object[cCount]; |
|
for(int counter = 0; counter < cCount; counter++) |
|
cChildren[counter] = node.getChildAt |
|
(childIndices[counter]); |
|
fireTreeNodesChanged(this, getPathToRoot(node), |
|
childIndices, cChildren); |
|
} |
|
} |
|
else if (node == getRoot()) { |
|
fireTreeNodesChanged(this, getPathToRoot(node), null, null); |
|
} |
|
} |
|
} |
|
/** |
|
* Invoke this method if you've totally changed the children of |
|
* node and its children's children... This will post a |
|
* treeStructureChanged event. |
|
*/ |
|
public void nodeStructureChanged(TreeNode node) { |
|
if(node != null) { |
|
fireTreeStructureChanged(this, getPathToRoot(node), null, null); |
|
} |
|
} |
|
/** |
|
* Builds the parents of node up to and including the root node, |
|
* where the original node is the last element in the returned array. |
|
* The length of the returned array gives the node's depth in the |
|
* tree. |
|
* |
|
* @param aNode the TreeNode to get the path for |
|
*/ |
|
public TreeNode[] getPathToRoot(TreeNode aNode) { |
|
return getPathToRoot(aNode, 0); |
|
} |
|
/** |
|
* Builds the parents of node up to and including the root node, |
|
* where the original node is the last element in the returned array. |
|
* The length of the returned array gives the node's depth in the |
|
* tree. |
|
* |
|
* @param aNode the TreeNode to get the path for |
|
* @param depth an int giving the number of steps already taken towards |
|
* the root (on recursive calls), used to size the returned array |
|
* @return an array of TreeNodes giving the path from the root to the |
|
* specified node |
|
*/ |
|
protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) { |
|
TreeNode[] retNodes; |
|
// This method recurses, traversing towards the root in order |
|
// size the array. On the way back, it fills in the nodes, |
|
// starting from the root and working back to the original node. |
|
/* Check for null, in case someone passed in a null node, or |
|
they passed in an element that isn't rooted at root. */ |
|
if(aNode == null) { |
|
if(depth == 0) |
|
return null; |
|
else |
|
retNodes = new TreeNode[depth]; |
|
} |
|
else { |
|
depth++; |
|
if(aNode == root) |
|
retNodes = new TreeNode[depth]; |
|
else |
|
retNodes = getPathToRoot(aNode.getParent(), depth); |
|
retNodes[retNodes.length - depth] = aNode; |
|
} |
|
return retNodes; |
|
} |
|
// |
|
// Events |
|
// |
|
/** |
|
* Adds a listener for the TreeModelEvent posted after the tree changes. |
|
* |
|
* @see #removeTreeModelListener |
|
* @param l the listener to add |
|
*/ |
|
public void addTreeModelListener(TreeModelListener l) { |
|
listenerList.add(TreeModelListener.class, l); |
|
} |
|
/** |
|
* Removes a listener previously added with <B>addTreeModelListener()</B>. |
|
* |
|
* @see #addTreeModelListener |
|
* @param l the listener to remove |
|
*/ |
|
public void removeTreeModelListener(TreeModelListener l) { |
|
listenerList.remove(TreeModelListener.class, l); |
|
} |
|
/** |
|
* Returns an array of all the tree model listeners |
|
* registered on this model. |
|
* |
|
* @return all of this model's <code>TreeModelListener</code>s |
|
* or an empty |
|
* array if no tree model listeners are currently registered |
|
* |
|
* @see #addTreeModelListener |
|
* @see #removeTreeModelListener |
|
* |
|
* @since 1.4 |
|
*/ |
|
public TreeModelListener[] getTreeModelListeners() { |
|
return listenerList.getListeners(TreeModelListener.class); |
|
} |
|
/** |
|
* Notifies all listeners that have registered interest for |
|
* notification on this event type. The event instance |
|
* is lazily created using the parameters passed into |
|
* the fire method. |
|
* |
|
* @param source the source of the {@code TreeModelEvent}; |
|
* typically {@code this} |
|
* @param path the path to the parent of the nodes that changed; use |
|
* {@code null} to identify the root has changed |
|
* @param childIndices the indices of the changed elements |
|
* @param children the changed elements |
|
*/ |
|
protected void fireTreeNodesChanged(Object source, Object[] path, |
|
int[] childIndices, |
|
Object[] children) { |
|
// Guaranteed to return a non-null array |
|
Object[] listeners = listenerList.getListenerList(); |
|
TreeModelEvent e = null; |
|
// Process the listeners last to first, notifying |
|
// those that are interested in this event |
|
for (int i = listeners.length-2; i>=0; i-=2) { |
|
if (listeners[i]==TreeModelListener.class) { |
|
// Lazily create the event: |
|
if (e == null) |
|
e = new TreeModelEvent(source, path, |
|
childIndices, children); |
|
((TreeModelListener)listeners[i+1]).treeNodesChanged(e); |
|
} |
|
} |
|
} |
|
/** |
|
* Notifies all listeners that have registered interest for |
|
* notification on this event type. The event instance |
|
* is lazily created using the parameters passed into |
|
* the fire method. |
|
* |
|
* @param source the source of the {@code TreeModelEvent}; |
|
* typically {@code this} |
|
* @param path the path to the parent the nodes were added to |
|
* @param childIndices the indices of the new elements |
|
* @param children the new elements |
|
*/ |
|
protected void fireTreeNodesInserted(Object source, Object[] path, |
|
int[] childIndices, |
|
Object[] children) { |
|
// Guaranteed to return a non-null array |
|
Object[] listeners = listenerList.getListenerList(); |
|
TreeModelEvent e = null; |
|
// Process the listeners last to first, notifying |
|
// those that are interested in this event |
|
for (int i = listeners.length-2; i>=0; i-=2) { |
|
if (listeners[i]==TreeModelListener.class) { |
|
// Lazily create the event: |
|
if (e == null) |
|
e = new TreeModelEvent(source, path, |
|
childIndices, children); |
|
((TreeModelListener)listeners[i+1]).treeNodesInserted(e); |
|
} |
|
} |
|
} |
|
/** |
|
* Notifies all listeners that have registered interest for |
|
* notification on this event type. The event instance |
|
* is lazily created using the parameters passed into |
|
* the fire method. |
|
* |
|
* @param source the source of the {@code TreeModelEvent}; |
|
* typically {@code this} |
|
* @param path the path to the parent the nodes were removed from |
|
* @param childIndices the indices of the removed elements |
|
* @param children the removed elements |
|
*/ |
|
protected void fireTreeNodesRemoved(Object source, Object[] path, |
|
int[] childIndices, |
|
Object[] children) { |
|
// Guaranteed to return a non-null array |
|
Object[] listeners = listenerList.getListenerList(); |
|
TreeModelEvent e = null; |
|
// Process the listeners last to first, notifying |
|
// those that are interested in this event |
|
for (int i = listeners.length-2; i>=0; i-=2) { |
|
if (listeners[i]==TreeModelListener.class) { |
|
// Lazily create the event: |
|
if (e == null) |
|
e = new TreeModelEvent(source, path, |
|
childIndices, children); |
|
((TreeModelListener)listeners[i+1]).treeNodesRemoved(e); |
|
} |
|
} |
|
} |
|
/** |
|
* Notifies all listeners that have registered interest for |
|
* notification on this event type. The event instance |
|
* is lazily created using the parameters passed into |
|
* the fire method. |
|
* |
|
* @param source the source of the {@code TreeModelEvent}; |
|
* typically {@code this} |
|
* @param path the path to the parent of the structure that has changed; |
|
* use {@code null} to identify the root has changed |
|
* @param childIndices the indices of the affected elements |
|
* @param children the affected elements |
|
*/ |
|
protected void fireTreeStructureChanged(Object source, Object[] path, |
|
int[] childIndices, |
|
Object[] children) { |
|
// Guaranteed to return a non-null array |
|
Object[] listeners = listenerList.getListenerList(); |
|
TreeModelEvent e = null; |
|
// Process the listeners last to first, notifying |
|
// those that are interested in this event |
|
for (int i = listeners.length-2; i>=0; i-=2) { |
|
if (listeners[i]==TreeModelListener.class) { |
|
// Lazily create the event: |
|
if (e == null) |
|
e = new TreeModelEvent(source, path, |
|
childIndices, children); |
|
((TreeModelListener)listeners[i+1]).treeStructureChanged(e); |
|
} |
|
} |
|
} |
|
/** |
|
* Notifies all listeners that have registered interest for |
|
* notification on this event type. The event instance |
|
* is lazily created using the parameters passed into |
|
* the fire method. |
|
* |
|
* @param source the source of the {@code TreeModelEvent}; |
|
* typically {@code this} |
|
* @param path the path to the parent of the structure that has changed; |
|
* use {@code null} to identify the root has changed |
|
*/ |
|
private void fireTreeStructureChanged(Object source, TreePath path) { |
|
// Guaranteed to return a non-null array |
|
Object[] listeners = listenerList.getListenerList(); |
|
TreeModelEvent e = null; |
|
// Process the listeners last to first, notifying |
|
// those that are interested in this event |
|
for (int i = listeners.length-2; i>=0; i-=2) { |
|
if (listeners[i]==TreeModelListener.class) { |
|
// Lazily create the event: |
|
if (e == null) |
|
e = new TreeModelEvent(source, path); |
|
((TreeModelListener)listeners[i+1]).treeStructureChanged(e); |
|
} |
|
} |
|
} |
|
/** |
|
* Returns an array of all the objects currently registered |
|
* as <code><em>Foo</em>Listener</code>s |
|
* upon this model. |
|
* <code><em>Foo</em>Listener</code>s are registered using the |
|
* <code>add<em>Foo</em>Listener</code> method. |
|
* |
|
* <p> |
|
* |
|
* You can specify the <code>listenerType</code> argument |
|
* with a class literal, |
|
* such as |
|
* <code><em>Foo</em>Listener.class</code>. |
|
* For example, you can query a |
|
* <code>DefaultTreeModel</code> <code>m</code> |
|
* for its tree model listeners with the following code: |
|
* |
|
* <pre>TreeModelListener[] tmls = (TreeModelListener[])(m.getListeners(TreeModelListener.class));</pre> |
|
* |
|
* If no such listeners exist, this method returns an empty array. |
|
* |
|
* @param listenerType the type of listeners requested; this parameter |
|
* should specify an interface that descends from |
|
* <code>java.util.EventListener</code> |
|
* @return an array of all objects registered as |
|
* <code><em>Foo</em>Listener</code>s on this component, |
|
* or an empty array if no such |
|
* listeners have been added |
|
* @exception ClassCastException if <code>listenerType</code> |
|
* doesn't specify a class or interface that implements |
|
* <code>java.util.EventListener</code> |
|
* |
|
* @see #getTreeModelListeners |
|
* |
|
* @since 1.3 |
|
*/ |
|
public <T extends EventListener> T[] getListeners(Class<T> listenerType) { |
|
return listenerList.getListeners(listenerType); |
|
} |
|
// Serialization support. |
|
private void writeObject(ObjectOutputStream s) throws IOException { |
|
Vector<Object> values = new Vector<Object>(); |
|
s.defaultWriteObject(); |
|
// Save the root, if its Serializable. |
|
if(root != null && root instanceof Serializable) { |
|
values.addElement("root"); |
|
values.addElement(root); |
|
} |
|
s.writeObject(values); |
|
} |
|
private void readObject(ObjectInputStream s) |
|
throws IOException, ClassNotFoundException { |
|
s.defaultReadObject(); |
|
Vector values = (Vector)s.readObject(); |
|
int indexCounter = 0; |
|
int maxCounter = values.size(); |
|
if(indexCounter < maxCounter && values.elementAt(indexCounter). |
|
equals("root")) { |
|
root = (TreeNode)values.elementAt(++indexCounter); |
|
indexCounter++; |
|
} |
|
} |
|
} // End of class DefaultTreeModel |