001 /* 002 * $Id: DefaultTreeTableModel.java 3187 2009-01-20 16:19:08Z kschaefe $ 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 package org.jdesktop.swingx.treetable; 022 023 import java.util.ArrayList; 024 import java.util.List; 025 026 import javax.swing.tree.TreePath; 027 028 /** 029 * {@code DefaultTreeTableModel} is a concrete implementation of 030 * {@code AbstractTreeTableModel} and is provided purely as a convenience. 031 * Applications that use {@code JXTreeTable} are expected to provide their own 032 * implementation of a {@code TreeTableModel}. 033 * <p> 034 * The {@code DefaultTreeTableModel} is designed to be used with 035 * {@code TreeTableNode}s. Specifically, users should extend 036 * {@code AbstractMutableTreeTableNode} to provide custom implementations for 037 * data display. 038 * <p> 039 * Users who do not provide a list of column identifiers must provide a root 040 * that contains at least one column. Without specified identifiers the model 041 * will attempt to calculate the columns required for display by querying the 042 * root node. Normally, the root node can be little more than a shell (in 043 * displays that hide it), but without identifiers, the model relies on the root 044 * node metadata for display. 045 * 046 * @author Ramesh Gupta 047 * @author Karl Schaefer 048 */ 049 public class DefaultTreeTableModel extends AbstractTreeTableModel { 050 /** The <code>List</code> of column identifiers. */ 051 protected List<?> columnIdentifiers; 052 053 private boolean useAutoCalculatedIdentifiers; 054 055 /** 056 * Creates a new {@code DefaultTreeTableModel} with a {@code null} root. 057 */ 058 public DefaultTreeTableModel() { 059 this(null); 060 } 061 062 /** 063 * Creates a new {@code DefaultTreeTableModel} with the specified 064 * {@code root}. 065 * 066 * @param root 067 * the root node of the tree 068 */ 069 public DefaultTreeTableModel(TreeTableNode root) { 070 this(root, null); 071 } 072 073 /** 074 * Creates a new {@code DefaultTreeTableModel} with the specified {@code 075 * root} and column names. 076 * 077 * @param root 078 * the root node of the tree 079 * @param columnNames 080 * the names of the columns used by this model 081 * @see #setColumnIdentifiers(List) 082 */ 083 public DefaultTreeTableModel(TreeTableNode root, List<?> columnNames) { 084 super(root); 085 086 setColumnIdentifiers(columnNames); 087 } 088 089 private boolean isValidTreeTableNode(Object node) { 090 boolean result = false; 091 092 if (node instanceof TreeTableNode) { 093 TreeTableNode ttn = (TreeTableNode) node; 094 095 while (!result && ttn != null) { 096 result = ttn == root; 097 098 ttn = ttn.getParent(); 099 } 100 } 101 102 return result; 103 } 104 105 /** 106 * Replaces the column identifiers in the model. If the number of 107 * <code>newIdentifier</code>s is greater than the current number of 108 * columns, new columns are added to the end of each row in the model. If 109 * the number of <code>newIdentifier</code>s is less than the current 110 * number of columns, all the extra columns at the end of a row are 111 * discarded. 112 * <p> 113 * 114 * @param columnIdentifiers 115 * vector of column identifiers. If <code>null</code>, set the 116 * model to zero columns 117 */ 118 // from DefaultTableModel 119 public void setColumnIdentifiers(List<?> columnIdentifiers) { 120 useAutoCalculatedIdentifiers = columnIdentifiers == null; 121 122 this.columnIdentifiers = useAutoCalculatedIdentifiers 123 ? getAutoCalculatedIdentifiers(getRoot()) 124 : columnIdentifiers; 125 126 modelSupport.fireNewRoot(); 127 } 128 129 private static List<String> getAutoCalculatedIdentifiers( 130 TreeTableNode exemplar) { 131 List<String> autoCalculatedIndentifiers = new ArrayList<String>(); 132 133 if (exemplar != null) { 134 for (int i = 0, len = exemplar.getColumnCount(); i < len; i++) { 135 // forces getColumnName to use super.getColumnName 136 autoCalculatedIndentifiers.add(null); 137 } 138 } 139 140 return autoCalculatedIndentifiers; 141 } 142 143 /** 144 * Returns the root of the tree. Returns {@code null} only if the tree has 145 * no nodes. 146 * 147 * @return the root of the tree 148 * 149 * @throws ClassCastException 150 * if {@code root} is not a {@code TreeTableNode}. Even though 151 * subclasses have direct access to {@code root}, they should 152 * avoid accessing it directly. 153 * @see AbstractTreeTableModel#root 154 * @see #setRoot(TreeTableNode) 155 */ 156 @Override 157 public TreeTableNode getRoot() { 158 return (TreeTableNode) root; 159 } 160 161 /** 162 * Gets the value for the {@code node} at {@code column}. 163 * 164 * @impl delegates to {@code TreeTableNode.getValueAt(int)} 165 * @param node 166 * the node whose value is to be queried 167 * @param column 168 * the column whose value is to be queried 169 * @return the value Object at the specified cell 170 * @throws IllegalArgumentException 171 * if {@code node} is not an instance of {@code TreeTableNode} 172 * or is not managed by this model, or {@code column} is not a 173 * valid column index 174 */ 175 public Object getValueAt(Object node, int column) { 176 if (!isValidTreeTableNode(node)) { 177 throw new IllegalArgumentException( 178 "node must be a valid node managed by this model"); 179 } 180 181 if (column < 0 || column >= getColumnCount()) { 182 throw new IllegalArgumentException("column must be a valid index"); 183 } 184 185 TreeTableNode ttn = (TreeTableNode) node; 186 187 if (column >= ttn.getColumnCount()) { 188 return null; 189 } 190 191 return ttn.getValueAt(column); 192 } 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override 198 public void setValueAt(Object value, Object node, int column) { 199 if (!isValidTreeTableNode(node)) { 200 throw new IllegalArgumentException( 201 "node must be a valid node managed by this model"); 202 } 203 204 if (column < 0 || column >= getColumnCount()) { 205 throw new IllegalArgumentException("column must be a valid index"); 206 } 207 208 TreeTableNode ttn = (TreeTableNode) node; 209 210 if (column < ttn.getColumnCount()) { 211 ttn.setValueAt(value, column); 212 213 modelSupport.firePathChanged(new TreePath(getPathToRoot(ttn))); 214 } 215 } 216 217 /** 218 * {@inheritDoc} 219 */ 220 public int getColumnCount() { 221 return columnIdentifiers.size(); 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 // Can we make getColumnClass final and avoid the complex DTM copy? -- kgs 228 @Override 229 public String getColumnName(int column) { 230 // Copied from DefaultTableModel. 231 Object id = null; 232 233 // This test is to cover the case when 234 // getColumnCount has been subclassed by mistake ... 235 if (column < columnIdentifiers.size() && (column >= 0)) { 236 id = columnIdentifiers.get(column); 237 } 238 239 return (id == null) ? super.getColumnName(column) : id.toString(); 240 } 241 242 /** 243 * {@inheritDoc} 244 */ 245 public Object getChild(Object parent, int index) { 246 if (!isValidTreeTableNode(parent)) { 247 throw new IllegalArgumentException( 248 "parent must be a TreeTableNode managed by this model"); 249 } 250 251 return ((TreeTableNode) parent).getChildAt(index); 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 public int getChildCount(Object parent) { 258 if (!isValidTreeTableNode(parent)) { 259 throw new IllegalArgumentException( 260 "parent must be a TreeTableNode managed by this model"); 261 } 262 263 return ((TreeTableNode) parent).getChildCount(); 264 } 265 266 /** 267 * {@inheritDoc} 268 */ 269 public int getIndexOfChild(Object parent, Object child) { 270 if (!isValidTreeTableNode(parent) || !isValidTreeTableNode(child)) { 271 return -1; 272 } 273 274 return ((TreeTableNode) parent).getIndex((TreeTableNode) child); 275 } 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override 281 public boolean isCellEditable(Object node, int column) { 282 if (!isValidTreeTableNode(node)) { 283 throw new IllegalArgumentException( 284 "node must be a valid node managed by this model"); 285 } 286 287 if (column < 0 || column >= getColumnCount()) { 288 throw new IllegalArgumentException("column must be a valid index"); 289 } 290 291 TreeTableNode ttn = (TreeTableNode) node; 292 293 if (column >= ttn.getColumnCount()) { 294 return false; 295 } 296 297 return ttn.isEditable(column); 298 } 299 300 /** 301 * {@inheritDoc} 302 */ 303 @Override 304 public boolean isLeaf(Object node) { 305 if (!isValidTreeTableNode(node)) { 306 throw new IllegalArgumentException( 307 "node must be a TreeTableNode managed by this model"); 308 } 309 310 return ((TreeTableNode) node).isLeaf(); 311 } 312 313 /** 314 * Gets the path from the root to the specified node. 315 * 316 * @param aNode 317 * the node to query 318 * @return an array of {@code TreeTableNode}s, where 319 * {@code arr[0].equals(getRoot())} and 320 * {@code arr[arr.length - 1].equals(aNode)}, or an empty array if 321 * the node is not found. 322 * @throws NullPointerException 323 * if {@code aNode} is {@code null} 324 */ 325 public TreeTableNode[] getPathToRoot(TreeTableNode aNode) { 326 List<TreeTableNode> path = new ArrayList<TreeTableNode>(); 327 TreeTableNode node = aNode; 328 329 while (node != root) { 330 path.add(0, node); 331 332 node = (TreeTableNode) node.getParent(); 333 } 334 335 if (node == root) { 336 path.add(0, node); 337 } 338 339 return path.toArray(new TreeTableNode[0]); 340 } 341 342 /** 343 * Sets the root for this table model. If no column identifiers have been 344 * specified, this will rebuild the identifier list, using {@code root} as 345 * an examplar of the table. 346 * 347 * @param root 348 * the node to set as root 349 */ 350 public void setRoot(TreeTableNode root) { 351 this.root = root; 352 353 if (useAutoCalculatedIdentifiers) { 354 // rebuild the list 355 //this already fires an event don't duplicate 356 setColumnIdentifiers(null); 357 } else { 358 modelSupport.fireNewRoot(); 359 } 360 } 361 362 /** 363 * Invoked this to insert newChild at location index in parents children. 364 * This will then message nodesWereInserted to create the appropriate event. 365 * This is the preferred way to add children as it will create the 366 * appropriate event. 367 */ 368 public void insertNodeInto(MutableTreeTableNode newChild, 369 MutableTreeTableNode parent, int index) { 370 parent.insert(newChild, index); 371 372 modelSupport.fireChildAdded(new TreePath(getPathToRoot(parent)), index, 373 newChild); 374 } 375 376 /** 377 * Message this to remove node from its parent. This will message 378 * nodesWereRemoved to create the appropriate event. This is the preferred 379 * way to remove a node as it handles the event creation for you. 380 */ 381 public void removeNodeFromParent(MutableTreeTableNode node) { 382 MutableTreeTableNode parent = (MutableTreeTableNode) node.getParent(); 383 384 if (parent == null) { 385 throw new IllegalArgumentException("node does not have a parent."); 386 } 387 388 int index = parent.getIndex(node); 389 node.removeFromParent(); 390 391 modelSupport.fireChildRemoved(new TreePath(getPathToRoot(parent)), 392 index, node); 393 } 394 395 /** 396 * Called when value for the item identified by path has been changed. If 397 * newValue signifies a truly new value the model should post a {@code 398 * treeNodesChanged} event. 399 * <p> 400 * This changes the object backing the {@code TreeTableNode} described by 401 * the path. This change does not alter a nodes children in any way. If you 402 * need to change structure of the node, use one of the provided mutator 403 * methods. 404 * 405 * @param path 406 * path to the node that has changed 407 * @param newValue 408 * the new value 409 * @throws NullPointerException 410 * if {@code path} is {@code null} 411 * @throws IllegalArgumentException 412 * if {@code path} is not a path managed by this model 413 * @throws ClassCastException 414 * if {@code path.getLastPathComponent()} is not a {@code 415 * TreeTableNode} 416 */ 417 @Override 418 public void valueForPathChanged(TreePath path, Object newValue) { 419 if (path.getPathComponent(0) != root) { 420 throw new IllegalArgumentException("invalid path"); 421 } 422 423 TreeTableNode node = (TreeTableNode) path.getLastPathComponent(); 424 node.setUserObject(newValue); 425 426 modelSupport.firePathChanged(path); 427 } 428 429 /** 430 * Sets the user object for a node. Client code must use this method, so 431 * that the model can notify listeners that a change has occurred. 432 * <p> 433 * This method is a convenient cover for 434 * {@link #valueForPathChanged(TreePath, Object)}. 435 * 436 * @param node 437 * the node to modify 438 * @param userObject 439 * the new user object to set 440 * @throws NullPointerException 441 * if {@code node} is {@code null} 442 * @throws IllegalArgumentException 443 * if {@code node} is not a node managed by this model 444 */ 445 public void setUserObject(TreeTableNode node, Object userObject) { 446 valueForPathChanged(new TreePath(getPathToRoot(node)), userObject); 447 } 448 }