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