001    /*
002     * $Id: AbstractTreeTableModel.java,v 1.3 2005/10/10 18:01:37 rbair Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx.treetable;
023    
024    import java.util.EventListener;
025    
026    import javax.swing.event.EventListenerList;
027    import javax.swing.event.TreeModelEvent;
028    import javax.swing.event.TreeModelListener;
029    import javax.swing.tree.TreeNode;
030    import javax.swing.tree.TreePath;
031    
032    // There is no javax.swing.tree.AbstractTreeModel; There ought to be one.
033    
034    /**
035     * AbstractTreeTableModel provides an implementation of
036     * {@link org.jdesktop.swingx.treetable.TreeTableModel} as a convenient starting
037     * point in defining custom data models for {@link org.jdesktop.swingx.JXTreeTable}.
038     *
039     * @author Ramesh Gupta
040     */
041    public abstract class AbstractTreeTableModel implements TreeTableModel {
042        /**
043         * Value returned by {@link org.jdesktop.swingx.treetable.TreeTableModel#getColumnClass(int) getColumnClass}
044         * for the {@link org.jdesktop.swingx.JXTreeTable#isHierarchical(int) hierarchical} column.
045         */
046        public final static Class hierarchicalColumnClass = TreeTableModel.class;
047    
048        /**
049         * Root node of the model
050         */
051        protected Object root;
052    
053        /**
054         * Event listener list
055         */
056        protected EventListenerList listenerList = new EventListenerList();
057    
058        /**
059         * Constructs an <code>AbstractTreeTableModel</code> with a null root node
060         */
061        public AbstractTreeTableModel() {
062            this(null);
063        }
064    
065        /**
066         * Constructs an <code>AbstractTreeTableModel</code> with the specified node
067         * as the root node.
068         *
069         * @param root root node
070         */
071        public AbstractTreeTableModel(Object root) {
072            this.root = root;
073        }
074    
075        /**
076         * {@inheritDoc}
077         */
078        public Class getColumnClass(int column) {
079            // Assume that the first column will contain hierarchical nodes.
080            return column == 0 ? hierarchicalColumnClass : Object.class;
081        }
082    
083        /**
084         * {@inheritDoc}
085         */
086        public String getColumnName(int column) {
087            return "Column " + column; // Cheap implementation
088        }
089    
090        /**
091         * {@inheritDoc}
092         */
093        public Object getRoot() { // From the TreeNode interface
094            return root;
095        }
096    
097        /**
098         * Returns the child of <I>parent</I> at index <I>index</I> in the parent's
099         * child array.  <I>parent</I> must be a node previously obtained from
100         * this data source. This should not return null if <i>index</i>
101         * is a valid index for <i>parent</i> (that is <i>index</i> >= 0 &&
102         * <i>index</i> < getChildCount(<i>parent</i>)).
103         *
104         * @param   parent  a node in the tree, obtained from this data source
105         * @return  the child of <I>parent</I> at index <I>index</I>, or null if the
106         * specified parent node is not a <code>TreeNode</code>.
107         */
108        public Object getChild(Object parent, int index) {
109            // meant to be overridden
110            try {
111                return ((TreeNode) parent).getChildAt(index);
112            }
113            catch (ClassCastException ex) { // not a TreeNode?
114                return null;
115            }
116        }
117    
118        /**
119         * Returns the number of children in the specified parent node.
120         *
121         * @param parent node whose child count is being requested
122         * @return the number of children in the specified parent node
123         */
124        public int getChildCount(Object parent) {
125            // meant to be overridden
126            try {
127                return ((TreeNode) parent).getChildCount();
128            }
129            catch (ClassCastException ex) { // not a TreeNode?
130                return 0;
131            }
132        }
133    
134        /**
135         * {@inheritDoc}
136         */
137        public int getColumnCount() {
138            // meant to be overridden
139            return 1; // Cheap (and woefully inadequate) implementation
140        }
141    
142        /**
143         * Returns the index of child in parent.
144         * If either the parent or child is <code>null</code>, returns -1.
145         * @param parent a note in the tree, obtained from this data source
146         * @param child the node we are interested in
147         * @return the index of the child in the parent, or -1
148         *    if either the parent or the child is <code>null</code>
149         */
150        public int getIndexOfChild(Object parent, Object child) {
151            if (parent == null || child == null)
152                return -1;
153    
154            try {
155                return ((TreeNode) parent).getIndex((TreeNode) child);
156            }
157            catch (ClassCastException ex) { // not a TreeNode?
158                // This is not called in the JTree's default mode.
159                // Use a naive implementation.
160                for (int i = 0; i < getChildCount(parent); i++) {
161                    if (getChild(parent, i).equals(child)) {
162                        return i;
163                    }
164                }
165                return -1;
166            }
167        }
168    
169        /**
170         * {@inheritDoc}
171         */
172        public boolean isCellEditable(Object node, int column) {
173            // RG: Fix Issue 49 -- Cell not editable, by default.
174            // Subclasses might override this to return true.
175            return false;
176        }
177    
178        /**
179         * Returns true if the specified node is a leaf node; false otherwise.
180         *
181         * @param node node to test
182         * @return true if the specified node is a leaf node; false otherwise
183         */
184        public boolean isLeaf(Object node) {
185            try {
186                return ((TreeNode) node).isLeaf();
187            }
188            catch (ClassCastException ex) { // not a TreeNode?
189                return getChildCount(node) == 0;
190            }
191        }
192    
193        /**
194         * Called when value for the item identified by path has been changed.
195         * If newValue signifies a truly new value the model should
196         * post a <code>treeNodesChanged</code> event.
197         *
198         * @param path path to the node that has changed
199         * @param newValue the new value from the <code>TreeCellEditor</code>
200         */
201        public void valueForPathChanged(TreePath path, Object newValue) {
202            /**@todo Implement this javax.swing.tree.TreeModel method*/
203        }
204    
205        public void addTreeModelListener(TreeModelListener l) {
206            listenerList.add(TreeModelListener.class, l);
207        }
208    
209        public void removeTreeModelListener(TreeModelListener l) {
210            listenerList.remove(TreeModelListener.class, l);
211        }
212    
213        public TreeModelListener[] getTreeModelListeners() {
214            return (TreeModelListener[]) listenerList.getListeners(
215                TreeModelListener.class);
216        }
217    
218        /*
219         * Notify all listeners that have registered interest for
220         * notification on this event type.  The event instance
221         * is lazily created using the parameters passed into
222         * the fire method.
223         * @see EventListenerList
224         */
225        protected void fireTreeNodesChanged(Object source, Object[] path,
226                                            int[] childIndices, Object[] children) {
227            // Guaranteed to return a non-null array
228            Object[] listeners = listenerList.getListenerList();
229            TreeModelEvent e = null;
230            // Process the listeners last to first, notifying
231            // those that are interested in this event
232            for (int i = listeners.length - 2; i >= 0; i -= 2) {
233                if (listeners[i] == TreeModelListener.class) {
234                    // Lazily create the event:
235                    if (e == null)
236                        e = new TreeModelEvent(source, path,
237                                               childIndices, children);
238                    ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
239                }
240            }
241        }
242    
243        /*
244         * Notify all listeners that have registered interest for
245         * notification on this event type.  The event instance
246         * is lazily created using the parameters passed into
247         * the fire method.
248         * @see EventListenerList
249         */
250        protected void fireTreeNodesInserted(Object source, Object[] path,
251                                             int[] childIndices, Object[] children) {
252            // Guaranteed to return a non-null array
253            Object[] listeners = listenerList.getListenerList();
254            TreeModelEvent e = null;
255            // Process the listeners last to first, notifying
256            // those that are interested in this event
257            for (int i = listeners.length - 2; i >= 0; i -= 2) {
258                if (listeners[i] == TreeModelListener.class) {
259                    // Lazily create the event:
260                    if (e == null)
261                        e = new TreeModelEvent(source, path,
262                                               childIndices, children);
263                    ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
264                }
265            }
266        }
267    
268        /*
269         * Notify all listeners that have registered interest for
270         * notification on this event type.  The event instance
271         * is lazily created using the parameters passed into
272         * the fire method.
273         * @see EventListenerList
274         */
275        protected void fireTreeNodesRemoved(Object source, Object[] path,
276                                            int[] childIndices, Object[] children) {
277            // Guaranteed to return a non-null array
278            Object[] listeners = listenerList.getListenerList();
279            TreeModelEvent e = null;
280            // Process the listeners last to first, notifying
281            // those that are interested in this event
282            for (int i = listeners.length - 2; i >= 0; i -= 2) {
283                if (listeners[i] == TreeModelListener.class) {
284                    // Lazily create the event:
285                    if (e == null)
286                        e = new TreeModelEvent(source, path,
287                                               childIndices, children);
288                    ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
289                }
290            }
291        }
292    
293        /*
294         * Notify all listeners that have registered interest for
295         * notification on this event type.  The event instance
296         * is lazily created using the parameters passed into
297         * the fire method.
298         * @see EventListenerList
299         */
300        protected void fireTreeStructureChanged(Object source, Object[] path,
301                                                int[] childIndices,
302                                                Object[] children) {
303            // Guaranteed to return a non-null array
304            Object[] listeners = listenerList.getListenerList();
305            TreeModelEvent e = null;
306            // Process the listeners last to first, notifying
307            // those that are interested in this event
308            for (int i = listeners.length - 2; i >= 0; i -= 2) {
309                if (listeners[i] == TreeModelListener.class) {
310                    // Lazily create the event:
311                    if (e == null) {
312                        e = new TreeModelEvent(source, path,
313                                               childIndices, children);
314                    }
315                    ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
316                }
317            }
318        }
319    
320        /**
321         * Returns an array of all the objects currently registered
322         * as <code><em>Foo</em>Listener</code>s
323         * upon this model.
324         * <code><em>Foo</em>Listener</code>s are registered using the
325         * <code>add<em>Foo</em>Listener</code> method.
326         *
327         * <p>
328         *
329         * You can specify the <code>listenerType</code> argument
330         * with a class literal,
331         * such as
332         * <code><em>Foo</em>Listener.class</code>.
333         * For example, you can query a
334         * <code>DefaultTreeModel</code> <code>m</code>
335         * for its tree model listeners with the following code:
336         *
337         * <pre>TreeModelListener[] tmls = (TreeModelListener[])(m.getListeners(TreeModelListener.class));</pre>
338         *
339         * If no such listeners exist, this method returns an empty array.
340         *
341         * @param listenerType the type of listeners requested; this parameter
342         *          should specify an interface that descends from
343         *          <code>java.util.EventListener</code>
344         * @return an array of all objects registered as
345         *          <code><em>Foo</em>Listener</code>s on this component,
346         *          or an empty array if no such
347         *          listeners have been added
348         * @exception ClassCastException if <code>listenerType</code>
349         *          doesn't specify a class or interface that implements
350         *          <code>java.util.EventListener</code>
351         *
352         * @see #getTreeModelListeners
353         *
354         * @since 1.3
355         */
356        public EventListener[] getListeners(Class listenerType) {
357            return listenerList.getListeners(listenerType);
358        }
359    
360        // Left to be implemented in the subclass:
361    
362        /**
363         * public Object getChild(Object parent, int index)
364         * public int getChildCount(Object parent)
365         * public int getColumnCount()
366         * public String getColumnName(int column)
367         * public Object getValueAt(Object node, int column)
368         * public void setValueAt(Object value, Object node, int column)
369         */
370    }