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 }