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 }