001    /*
002     * Created on 20/6/2004
003     *
004     * Copyright (C) 2004 Denis Krukovsky. All rights reserved.
005     * ====================================================================
006     * The Software License (based on Apache Software License, Version 1.1)
007     *
008     * Redistribution and use in source and binary forms, with or without
009     * modification, are permitted provided that the following conditions
010     * are met:
011     *
012     * 1. Redistributions of source code must retain the above copyright
013     *    notice, this list of conditions and the following disclaimer.
014     *
015     * 2. Redistributions in binary form must reproduce the above copyright
016     *    notice, this list of conditions and the following disclaimer in
017     *    the documentation and/or other materials provided with the
018     *    distribution.
019     *
020     * 3. The end-user documentation included with the redistribution,
021     *    if any, must include the following acknowledgment:
022     *       "This product includes software developed by
023     *        Denis Krukovsky (dkrukovsky at yahoo.com)."
024     *    Alternately, this acknowledgment may appear in the software itself,
025     *    if and wherever such third-party acknowledgments normally appear.
026     *
027     * 4. The names "dot useful" and "Denis Krukovsky" must not be used to
028     *    endorse or promote products derived from this software without
029     *    prior written permission. For written permission, please
030     *    contact dkrukovsky at yahoo.com.
031     *
032     * 5. Products derived from this software may not be called "useful",
033     *    nor may "useful" appear in their name, without prior written
034     *    permission of Denis Krukovsky.
035     *
036     * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037     * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039     * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
040     * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043     * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045     * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047     * SUCH DAMAGE.
048     * ====================================================================
049     */
050    
051    package org.dotuseful.ui.tree;
052    
053    import javax.swing.event.EventListenerList;
054    import javax.swing.event.TreeModelEvent;
055    import javax.swing.event.TreeModelListener;
056    import javax.swing.tree.DefaultMutableTreeNode;
057    import javax.swing.tree.MutableTreeNode;
058    
059    /**
060     * AutomatedTreeNode extends DefaultMutableTreeNode adding support for automatic notification of node
061     * changes. AutomatedTreeNodes are
062     * used with AutomatedTreeModel. In this model each node considered as a little tree which can fire TreeModel events about its
063     * changes. Each parent node registers itself as a listener of its child nodes events and transfers events to its own
064     * listeners (which is its parent) up to the root node. You can use AutomatedTreeModel which automatically handles events and
065     * fires them as usual TreeModel.
066     * <p>
067     * A sample of code that uses DefaultTreeModel and DefaultTreeNodes
068     * <code>
069     * setUserObject( event.getObject() );
070     * //getting a tree model from somewhere
071     * DefaultTreeModel model = ( DefaultTreeModel ) titleTree.getModel();
072     * model.nodeChanged( node );
073     * </code> 
074     * <p>
075     * A sample of code that uses AutomatedTreeModel and AutomatedTreeNodes
076     * <code>
077     * setUserObject( event.getObject() );
078     * //Everything else automated
079     * </code>
080     * <b>This is not a thread safe class.</b> If you intend to use
081     * an AutomatedTreeNode (or a tree of TreeNodes) in more than one thread, you
082     * need to do your own synchronizing. A good convention to adopt is
083     * synchronizing on the root node of a tree.
084     * <p>
085     *
086     * @author dkrukovsky
087     */
088    public class AutomatedTreeNode extends DefaultMutableTreeNode implements TreeModelListener {
089        /** Listeners. */
090        protected EventListenerList listenerList = new EventListenerList();
091    
092        /**
093         * Creates an AutomatedTreeNode that has no parent and no children, but which
094         * allows children.
095         */
096        public AutomatedTreeNode() {
097        }
098    
099        /**
100         * Creates an AutomatedTreeNode node with no parent, no children, but which allows
101         * children, and initializes it with the specified user object.
102         *
103         * @param userObject an Object provided by the user that constitutes
104         *                   the node's data
105         */
106        public AutomatedTreeNode(Object userObject) {
107            super(userObject);
108        }
109    
110        /**
111         * Creates an AutomatedTreeNode with no parent, no children, initialized with
112         * the specified user object, and that allows children only if
113         * specified.
114         *
115         * @param userObject an Object provided by the user that constitutes
116         *        the node's data
117         * @param allowsChildren if true, the node is allowed to have child
118         *        nodes -- otherwise, it is always a leaf node
119         */
120        public AutomatedTreeNode(Object userObject, boolean allowsChildren) {
121            super(userObject, allowsChildren);
122        }
123    
124        /**
125         * Removes <code>newChild</code> from its present parent (if it has a
126         * parent), sets the child's parent to this node, adds the
127         * child to this node's child array at index <code>childIndex</code>, fires a <code>nodesWereInserted</code> event,
128         * and then adds itself as a <code>TreeModelListener</code> to <code>newChild</code>.
129         * <code>newChild</code> must not be null and must not be an ancestor of this node.
130         * @param   newChild        the MutableTreeNode to insert under this node
131         * @param   childIndex      the index in this node's child array where this node is to be inserted
132         * @exception       ArrayIndexOutOfBoundsException  if <code>childIndex</code> is out of bounds
133         * @exception       IllegalArgumentException        if <code>newChild</code> is null or is an ancestor of this node
134         * @exception       IllegalStateException   if this node does not allow children
135         * @see     #isNodeDescendant
136         */
137    
138        /**
139         * Removes <code>newChild</code> from its present parent (if it has a
140         * parent), sets the child's parent to this node, adds the
141         * child to this node's child array at index <code>childIndex</code>,
142         * fires a <code>nodesWereInserted</code> event, and then adds itself as
143         * a <code>TreeModelListener</code> to <code>newChild</code>.
144         * <code>newChild</code> must not be null and must not be an ancestor of
145         * this node.
146         *
147         * @param   newChild        the MutableTreeNode to insert under this node
148         * @param   childIndex      the index in this node's child array
149         *                          where this node is to be inserted
150         * @exception       ArrayIndexOutOfBoundsException  if
151         *                          <code>childIndex</code> is out of bounds
152         * @exception       IllegalArgumentException        if
153         *                          <code>newChild</code> is null or is an
154         *                          ancestor of this node
155         * @exception       IllegalStateException   if this node does not allow
156         *                                          children
157         * @see     #isNodeDescendant
158         */
159        public void insert(final MutableTreeNode newChild, final int childIndex) {
160            super.insert(newChild, childIndex);
161            int[] newIndexs = new int[1];
162            newIndexs[0] = childIndex;
163            nodesWereInserted(newIndexs);
164            ((AutomatedTreeNode)newChild).addTreeModelListener(this);
165        }
166    
167        /**
168         * Removes the child at the specified index from this node's children
169         * and sets that node's parent to null. The child node to remove
170         * must be a <code>MutableTreeNode</code>.
171         *
172         * @param   childIndex      the index in this node's child array
173         *                          of the child to remove
174         * @exception       ArrayIndexOutOfBoundsException  if
175         *                          <code>childIndex</code> is out of bounds
176         */
177        public void remove(final int childIndex) {
178            Object[] removedArray = new Object[1];
179            AutomatedTreeNode node = (AutomatedTreeNode)getChildAt(childIndex);
180            node.removeTreeModelListener(this);
181            removedArray[0] = node;
182            super.remove(childIndex);
183            nodesWereRemoved(
184                new int[] { childIndex }, removedArray);
185        }
186    
187        /**
188         * Sets the user object for this node to <code>userObject</code>.
189         *
190         * @param   userObject      the Object that constitutes this node's
191         *                          user-specified data
192         * @see     #toString
193         */
194        public void setUserObject(Object userObject) {
195            super.setUserObject(userObject);
196            nodeChanged();
197        }
198    
199        /**
200         * <p>Invoked after a node (or a set of siblings) has changed in some
201         * way. The node(s) have not changed locations in the tree or
202         * altered their children arrays, but other attributes have
203         * changed and may affect presentation. Example: the name of a
204         * file has changed, but it is in the same location in the file
205         * system.</p>
206         */
207        public void treeNodesChanged(TreeModelEvent e) {
208            fireTreeNodesChanged(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren());
209        }
210    
211        /** <p>Invoked after nodes have been inserted into the tree.</p> */
212        public void treeNodesInserted(TreeModelEvent e) {
213            fireTreeNodesInserted(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren());
214        }
215    
216        /**
217         * <p>Invoked after nodes have been removed from the tree.  Note that
218         * if a subtree is removed from the tree, this method may only be
219         * invoked once for the root of the removed subtree, not once for
220         * each individual set of siblings removed.</p>
221         */
222        public void treeNodesRemoved(TreeModelEvent e) {
223            fireTreeNodesRemoved(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren());
224        }
225    
226        /**
227         * <p>Invoked after the tree has drastically changed structure from a
228         * given node down.  If the path returned by e.getPath() is of length
229         * one and the first element does not identify the current root node
230         * the first element should become the new root of the tree.<p>
231         */
232        public void treeStructureChanged(TreeModelEvent e) {
233            fireTreeStructureChanged(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren());
234        }
235    
236        /**
237         * Invoke this method after the node changed how it is to be
238         * represented in the tree.
239         */
240        protected void nodeChanged() {
241            if (listenerList != null) {
242                AutomatedTreeNode parent = (AutomatedTreeNode)getParent();
243                if (parent != null) {
244                    int anIndex = parent.getIndex(this);
245                    if (anIndex != -1) {
246                        int[] cIndexs = new int[1];
247                        cIndexs[0] = anIndex;
248                        //parent.nodesChanged(cIndexs);
249                        Object[] cChildren = new Object[1];
250                        cChildren[0] = this;
251                        fireTreeNodesChanged(parent.getPath(), cIndexs, cChildren);
252                    }
253                }
254                else if (this == getRoot()) {
255                    fireTreeNodesChanged(getPath(), null, null);
256                }
257            }
258        }
259    
260        /**
261         * This method invoked after you've inserted some AutomatedTreeNodes into
262         * node.  childIndices should be the index of the new elements and
263         * must be sorted in ascending order.
264         */
265        protected void nodesWereInserted(int[] childIndices) {
266            if (listenerList != null && childIndices != null && childIndices.length > 0) {
267                int cCount = childIndices.length;
268                Object[] newChildren = new Object[cCount];
269                for (int counter = 0; counter < cCount; counter++)
270                    newChildren[counter] = getChildAt(childIndices[counter]);
271                fireTreeNodesInserted(childIndices, newChildren);
272            }
273        }
274    
275        /**
276         * This method invoked after you've removed some AutomatedTreeNodes from
277         * node.  childIndices should be the index of the removed elements and
278         * must be sorted in ascending order. And removedChildren should be
279         * the array of the children objects that were removed.
280         */
281        protected void nodesWereRemoved(int[] childIndices, Object[] removedChildren) {
282            if (childIndices != null) {
283                fireTreeNodesRemoved(childIndices, removedChildren);
284            }
285        }
286    
287        /**
288         * Invoke this method if you've totally changed the children of
289         * node and its childrens children...  This will post a
290         * treeStructureChanged event.
291         */
292        protected void nodeStructureChanged() {
293            fireTreeStructureChanged(null, null);
294        }
295    
296        /**
297         * Adds a listener for the TreeModelEvent posted after the node changes.
298         *
299         * @see     #removeTreeModelListener
300         * @param   l       the listener to add
301         */
302        public void addTreeModelListener(TreeModelListener l) {
303            listenerList.add(TreeModelListener.class, l);
304        }
305    
306        /**
307         * Removes a listener previously added with <B>addTreeModelListener()</B>.
308         *
309         * @see     #addTreeModelListener
310         * @param   l       the listener to remove
311         */
312        public void removeTreeModelListener(TreeModelListener l) {
313            listenerList.remove(TreeModelListener.class, l);
314        }
315    
316        /**
317         * Notifies all listeners that have registered interest for
318         * notification on this event type by firing a treeNodesChanged() method.
319         *
320         * @param childIndices the indices of the changed elements
321         * @param children the changed elements
322         * @see EventListenerList
323         */
324        protected void fireTreeNodesChanged(int[] childIndices, Object[] children) {
325            fireTreeNodesChanged(getPath(), childIndices, children);
326        }
327    
328        /**
329         * Notifies all listeners that have registered interest for
330         * notification on this event type by firing a treeNodesChanged() method.
331         *
332         * @param path the path to the root node
333         * @param childIndices the indices of the changed elements
334         * @param children the changed elements
335         * @see EventListenerList
336         */
337        protected void fireTreeNodesChanged(Object[] path, int[] childIndices, Object[] children) {
338            fireTreeNodesChanged(this, path, childIndices, children);
339        }
340    
341        /**
342         * Notifies all listeners that have registered interest for
343         * notification on this event type by firing a treeNodesChanged() method.
344         *
345         * @param source the node being changed
346         * @param path the path to the root node
347         * @param childIndices the indices of the changed elements
348         * @param children the changed elements
349         * @see EventListenerList
350         */
351        protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
352            // Guaranteed to return a non-null array
353            Object[] listeners = listenerList.getListenerList();
354            TreeModelEvent e = null;
355            // Process the listeners last to first, notifying
356            // those that are interested in this event
357            for (int i = listeners.length - 2; i >= 0; i -= 2) {
358                if (listeners[i] == TreeModelListener.class) {
359                    // Lazily create the event:
360                    if (e == null)
361                        e = new TreeModelEvent(source, path, childIndices, children);
362                    ((TreeModelListener)listeners[i + 1]).treeNodesChanged(e);
363                }
364            }
365        }
366    
367        /**
368         * Notifies all listeners that have registered interest for
369         * notification on this event type by firing a treeNodesInserted() method.
370         *
371         * @param childIndices the indices of the changed elements
372         * @param children the changed elements
373         * @see EventListenerList
374         */
375        protected void fireTreeNodesInserted(int[] childIndices, Object[] children) {
376            fireTreeNodesInserted(this, getPath(), childIndices, children);
377        }
378    
379        /**
380         * Notifies all listeners that have registered interest for
381         * notification on this event type by firing a treeNodesInserted() method.
382         *
383         * @param source the node being changed
384         * @param path the path to the root node
385         * @param childIndices the indices of the changed elements
386         * @param children the changed elements
387         * @see EventListenerList
388         */
389        protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) {
390            // Guaranteed to return a non-null array
391            Object[] listeners = listenerList.getListenerList();
392            TreeModelEvent e = null;
393            // Process the listeners last to first, notifying
394            // those that are interested in this event
395            for (int i = listeners.length - 2; i >= 0; i -= 2) {
396                if (listeners[i] == TreeModelListener.class) {
397                    // Lazily create the event:
398                    if (e == null)
399                        e = new TreeModelEvent(source, path, childIndices, children);
400                    ((TreeModelListener)listeners[i + 1]).treeNodesInserted(e);
401                }
402            }
403        }
404    
405        /**
406         * Notifies all listeners that have registered interest for
407         * notification on this event type by firing a treeNodesRemoved() method.
408         *
409         * @param childIndices the indices of the changed elements
410         * @param children the changed elements
411         * @see EventListenerList
412         */
413        protected void fireTreeNodesRemoved(int[] childIndices, Object[] children) {
414            fireTreeNodesRemoved(this, getPath(), childIndices, children);
415        }
416    
417        /**
418         * Notifies all listeners that have registered interest for
419         * notification on this event type by firing a treeNodesRemoved() method.
420         *
421         * @param source the node being changed
422         * @param path the path to the root node
423         * @param childIndices the indices of the changed elements
424         * @param children the changed elements
425         * @see EventListenerList
426         */
427        protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) {
428            // Guaranteed to return a non-null array
429            Object[] listeners = listenerList.getListenerList();
430            TreeModelEvent e = null;
431            // Process the listeners last to first, notifying
432            // those that are interested in this event
433            for (int i = listeners.length - 2; i >= 0; i -= 2) {
434                if (listeners[i] == TreeModelListener.class) {
435                    // Lazily create the event:
436                    if (e == null)
437                        e = new TreeModelEvent(source, path, childIndices, children);
438                    ((TreeModelListener)listeners[i + 1]).treeNodesRemoved(e);
439                }
440            }
441        }
442    
443        /**
444         * Notifies all listeners that have registered interest for
445         * notification on this event type by firing a treeStructureChanged() method.
446         *
447         * @param childIndices the indices of the changed elements
448         * @param children the changed elements
449         * @see EventListenerList
450         */
451        protected void fireTreeStructureChanged(int[] childIndices, Object[] children) {
452            fireTreeStructureChanged(this, getPath(), childIndices, children);
453        }
454    
455        /**
456         * Notifies all listeners that have registered interest for
457         * notification on this event type by firing a treeStructureChanged() method.
458         *
459         * @param source the node being changed
460         * @param path the path to the root node
461         * @param childIndices the indices of the changed elements
462         * @param children the changed elements
463         * @see EventListenerList
464         */
465        protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
466            // Guaranteed to return a non-null array
467            Object[] listeners = listenerList.getListenerList();
468            TreeModelEvent e = null;
469            // Process the listeners last to first, notifying
470            // those that are interested in this event
471            for (int i = listeners.length - 2; i >= 0; i -= 2) {
472                if (listeners[i] == TreeModelListener.class) {
473                    // Lazily create the event:
474                    if (e == null)
475                        e = new TreeModelEvent(source, path, childIndices, children);
476                    ((TreeModelListener)listeners[i + 1]).treeStructureChanged(e);
477                }
478            }
479        }
480    }