org.jdesktop.swingx.treetable.DefaultTreeTableModel Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id$
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.treetable;
import java.util.ArrayList;
import java.util.List;
import javax.swing.tree.TreePath;
/**
* {@code DefaultTreeTableModel} is a concrete implementation of
* {@code AbstractTreeTableModel} and is provided purely as a convenience for
* use with {@code TreeTableNode}s. Applications that use {@code JXTreeTable}
* without {@code TreeTableNode}s are expected to provide their own
* implementation of a {@code TreeTableModel}.
*
* The {@code DefaultTreeTableModel} is designed to be used with
* {@code TreeTableNode}s. Specifically, users should extend
* {@code AbstractMutableTreeTableNode} to provide custom implementations for
* data display.
*
* Users who do not provide a list of column identifiers must provide a root
* that contains at least one column. Without specified identifiers the model
* will attempt to calculate the columns required for display by querying the
* root node. Normally, the root node can be little more than a shell (in
* displays that hide it), but without identifiers, the model relies on the root
* node metadata for display.
*
* @author Ramesh Gupta
* @author Karl Schaefer
*/
public class DefaultTreeTableModel extends AbstractTreeTableModel {
/** The List
of column identifiers. */
protected List> columnIdentifiers;
private boolean useAutoCalculatedIdentifiers;
/**
* Creates a new {@code DefaultTreeTableModel} with a {@code null} root.
*/
public DefaultTreeTableModel() {
this(null);
}
/**
* Creates a new {@code DefaultTreeTableModel} with the specified
* {@code root}.
*
* @param root
* the root node of the tree
*/
public DefaultTreeTableModel(TreeTableNode root) {
this(root, null);
}
/**
* Creates a new {@code DefaultTreeTableModel} with the specified {@code
* root} and column names.
*
* @param root
* the root node of the tree
* @param columnNames
* the names of the columns used by this model
* @see #setColumnIdentifiers(List)
*/
public DefaultTreeTableModel(TreeTableNode root, List> columnNames) {
super(root);
setColumnIdentifiers(columnNames);
}
private boolean isValidTreeTableNode(Object node) {
boolean result = false;
if (node instanceof TreeTableNode) {
TreeTableNode ttn = (TreeTableNode) node;
while (!result && ttn != null) {
result = ttn == root;
ttn = ttn.getParent();
}
}
return result;
}
/**
* Replaces the column identifiers in the model. If the number of
* newIdentifier
s is greater than the current number of
* columns, new columns are added to the end of each row in the model. If
* the number of newIdentifier
s is less than the current
* number of columns, all the extra columns at the end of a row are
* discarded.
*
*
* @param columnIdentifiers
* vector of column identifiers. If null
, set the
* model to zero columns
*/
// from DefaultTableModel
public void setColumnIdentifiers(List> columnIdentifiers) {
useAutoCalculatedIdentifiers = columnIdentifiers == null;
this.columnIdentifiers = useAutoCalculatedIdentifiers
? getAutoCalculatedIdentifiers(getRoot())
: columnIdentifiers;
modelSupport.fireNewRoot();
}
private static List getAutoCalculatedIdentifiers(
TreeTableNode exemplar) {
List autoCalculatedIndentifiers = new ArrayList();
if (exemplar != null) {
for (int i = 0, len = exemplar.getColumnCount(); i < len; i++) {
// forces getColumnName to use super.getColumnName
autoCalculatedIndentifiers.add(null);
}
}
return autoCalculatedIndentifiers;
}
/**
* Returns the root of the tree. Returns {@code null} only if the tree has
* no nodes.
*
* @return the root of the tree
*
* @throws ClassCastException
* if {@code root} is not a {@code TreeTableNode}. Even though
* subclasses have direct access to {@code root}, they should
* avoid accessing it directly.
* @see AbstractTreeTableModel#root
* @see #setRoot(TreeTableNode)
*/
@Override
public TreeTableNode getRoot() {
return (TreeTableNode) root;
}
/**
* Gets the value for the {@code node} at {@code column}.
*
* @impl delegates to {@code TreeTableNode.getValueAt(int)}
* @param node
* the node whose value is to be queried
* @param column
* the column whose value is to be queried
* @return the value Object at the specified cell
* @throws IllegalArgumentException
* if {@code node} is not an instance of {@code TreeTableNode}
* or is not managed by this model, or {@code column} is not a
* valid column index
*/
@Override
public Object getValueAt(Object node, int column) {
if (!isValidTreeTableNode(node)) {
throw new IllegalArgumentException(
"node must be a valid node managed by this model");
}
if (column < 0 || column >= getColumnCount()) {
throw new IllegalArgumentException("column must be a valid index");
}
TreeTableNode ttn = (TreeTableNode) node;
if (column >= ttn.getColumnCount()) {
return null;
}
return ttn.getValueAt(column);
}
/**
* {@inheritDoc}
*/
@Override
public void setValueAt(Object value, Object node, int column) {
if (!isValidTreeTableNode(node)) {
throw new IllegalArgumentException(
"node must be a valid node managed by this model");
}
if (column < 0 || column >= getColumnCount()) {
throw new IllegalArgumentException("column must be a valid index");
}
TreeTableNode ttn = (TreeTableNode) node;
if (column < ttn.getColumnCount()) {
ttn.setValueAt(value, column);
modelSupport.firePathChanged(new TreePath(getPathToRoot(ttn)));
}
}
/**
* {@inheritDoc}
*/
@Override
public int getColumnCount() {
return columnIdentifiers.size();
}
/**
* {@inheritDoc}
*/
// Can we make getColumnClass final and avoid the complex DTM copy? -- kgs
@Override
public String getColumnName(int column) {
// Copied from DefaultTableModel.
Object id = null;
// This test is to cover the case when
// getColumnCount has been subclassed by mistake ...
if (column < columnIdentifiers.size() && (column >= 0)) {
id = columnIdentifiers.get(column);
}
return (id == null) ? super.getColumnName(column) : id.toString();
}
/**
* {@inheritDoc}
*/
@Override
public Object getChild(Object parent, int index) {
if (!isValidTreeTableNode(parent)) {
throw new IllegalArgumentException(
"parent must be a TreeTableNode managed by this model");
}
return ((TreeTableNode) parent).getChildAt(index);
}
/**
* {@inheritDoc}
*/
@Override
public int getChildCount(Object parent) {
if (!isValidTreeTableNode(parent)) {
throw new IllegalArgumentException(
"parent must be a TreeTableNode managed by this model");
}
return ((TreeTableNode) parent).getChildCount();
}
/**
* {@inheritDoc}
*/
@Override
public int getIndexOfChild(Object parent, Object child) {
if (!isValidTreeTableNode(parent) || !isValidTreeTableNode(child)) {
return -1;
}
return ((TreeTableNode) parent).getIndex((TreeTableNode) child);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCellEditable(Object node, int column) {
if (!isValidTreeTableNode(node)) {
throw new IllegalArgumentException(
"node must be a valid node managed by this model");
}
if (column < 0 || column >= getColumnCount()) {
throw new IllegalArgumentException("column must be a valid index");
}
TreeTableNode ttn = (TreeTableNode) node;
if (column >= ttn.getColumnCount()) {
return false;
}
return ttn.isEditable(column);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isLeaf(Object node) {
if (!isValidTreeTableNode(node)) {
throw new IllegalArgumentException(
"node must be a TreeTableNode managed by this model");
}
return ((TreeTableNode) node).isLeaf();
}
/**
* Gets the path from the root to the specified node.
*
* @param aNode
* the node to query
* @return an array of {@code TreeTableNode}s, where
* {@code arr[0].equals(getRoot())} and
* {@code arr[arr.length - 1].equals(aNode)}, or an empty array if
* the node is not found.
* @throws NullPointerException
* if {@code aNode} is {@code null}
*/
public TreeTableNode[] getPathToRoot(TreeTableNode aNode) {
List path = new ArrayList();
TreeTableNode node = aNode;
while (node != root) {
path.add(0, node);
node = node.getParent();
}
if (node == root) {
path.add(0, node);
}
return path.toArray(new TreeTableNode[0]);
}
/**
* Sets the root for this table model. If no column identifiers have been
* specified, this will rebuild the identifier list, using {@code root} as
* an examplar of the table.
*
* @param root
* the node to set as root
*/
public void setRoot(TreeTableNode root) {
this.root = root;
if (useAutoCalculatedIdentifiers) {
// rebuild the list
//this already fires an event don't duplicate
setColumnIdentifiers(null);
} else {
modelSupport.fireNewRoot();
}
}
/**
* Invoked this to insert newChild at location index in parents children.
* This will then message nodesWereInserted to create the appropriate event.
* This is the preferred way to add children as it will create the
* appropriate event.
*/
public void insertNodeInto(MutableTreeTableNode newChild,
MutableTreeTableNode parent, int index) {
parent.insert(newChild, index);
modelSupport.fireChildAdded(new TreePath(getPathToRoot(parent)), index,
newChild);
}
/**
* Message this to remove node from its parent. This will message
* nodesWereRemoved to create the appropriate event. This is the preferred
* way to remove a node as it handles the event creation for you.
*/
public void removeNodeFromParent(MutableTreeTableNode node) {
MutableTreeTableNode parent = (MutableTreeTableNode) node.getParent();
if (parent == null) {
throw new IllegalArgumentException("node does not have a parent.");
}
int index = parent.getIndex(node);
node.removeFromParent();
modelSupport.fireChildRemoved(new TreePath(getPathToRoot(parent)),
index, node);
}
/**
* Called when value for the item identified by path has been changed. If
* newValue signifies a truly new value the model should post a {@code
* treeNodesChanged} event.
*
* This changes the object backing the {@code TreeTableNode} described by
* the path. This change does not alter a nodes children in any way. If you
* need to change structure of the node, use one of the provided mutator
* methods.
*
* @param path
* path to the node that has changed
* @param newValue
* the new value
* @throws NullPointerException
* if {@code path} is {@code null}
* @throws IllegalArgumentException
* if {@code path} is not a path managed by this model
* @throws ClassCastException
* if {@code path.getLastPathComponent()} is not a {@code
* TreeTableNode}
*/
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
if (path.getPathComponent(0) != root) {
throw new IllegalArgumentException("invalid path");
}
TreeTableNode node = (TreeTableNode) path.getLastPathComponent();
node.setUserObject(newValue);
modelSupport.firePathChanged(path);
}
/**
* Sets the user object for a node. Client code must use this method, so
* that the model can notify listeners that a change has occurred.
*
* This method is a convenient cover for
* {@link #valueForPathChanged(TreePath, Object)}.
*
* @param node
* the node to modify
* @param userObject
* the new user object to set
* @throws NullPointerException
* if {@code node} is {@code null}
* @throws IllegalArgumentException
* if {@code node} is not a node managed by this model
*/
public void setUserObject(TreeTableNode node, Object userObject) {
valueForPathChanged(new TreePath(getPathToRoot(node)), userObject);
}
}