/*
* Created on 14.07.2008
*
*/
package org.jdesktop.swingx.treetable;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.jdesktop.swingx.tree.TreeModelSupport;
import org.jdesktop.swingx.treetable.TreeTableModel;
/**
* Adapts a TreeModel to a TreeTableModel. The per-node columnar data is
* provided by a NodeModel. If the columnar data should be editable, a
* NodeChangedMediator is required to handle the change notification in the
* adapted TreeModel.
* <p>
*
* Example usage: Assuming a default tree model with build from treeNodes which
* have Person objects as userObject, developers can implement a PersonNodeModel
* for the columnar data
*
* <pre><code>
* public class PersonNodeModel implements NodeModel {
* public Class<?> getColumnClass(int column) {
* if (column == 2) return Date.class;
* return String.class;
* }
*
* public int getColumnCount() {
* return 3;
* }
*
* public String getColumnName(int column) {
* switch(column) {
* case 0:
* return "Name";
* case 1:
* return "First Name";
* case 2:
* return "Birth Date";
* default:
* return null;
* }
* }
*
* public int getHierarchicalColumn() {
* return 0;
* }
*
* public Object getValueAt(Object node, int column) {
* Person person = getPerson(node);
* if (person == null) return note;
* switch (column) {
* case 0:
* return person.getFamilyName();
* case 1:
* return person.getFirstName();
* case 2:
* return person.getBirthDate();
* }
* return null;
* }
*
* private Person getPerson(Object node) {
* if (node instanceof DefaultMutableTreeNode) {
* node = ((DefaultMutableTreeNode) node).getUserObject();
* }
* if (node instanceof Person) return (Person) node;
* return null;
* }
*
* public boolean isCellEditable(Object node, int column) {
* return true;
* }
*
* public void setValueAt(Object value, Object node, int column) {
* Person person = getPerson(node);
* if (note == null) return;
* switch(column) {
* case 0:
* person.setFamilyName(String.valueOf(value));
* break;
* case 1:
* person.setFirstName((String) value);
* break;
* case 2:
* person.setBirthDate((Date) value);
*
* }
*
* }
*
*
* }
* </code></pre>
*
* For usage in a read-only JXTreeTable:
*
*
* <pre><code>
* TreeTableModel treeTableModel = new TreeTableModelAdapter(personTreeModel,
* new PersonNodeModel());
* treeTable.setModel(treeTableModel);
* </code></pre>
*
* For usage in an editable JXTreeTable, you'll need to configure the adapter
* with an appropriate NodeChangedMediator which takes care of change
* notification by the adapted TreeModel. For TreeModels of type
* DefaultTreeModel, there's a pre-defined default mediator (for custom types
* you'll have to implement a custom mediator which knows how to trigger changed events on
* behalf of the TreeModel):
*
* <pre><code>
* treeTableModel.setNodeChangedMediator(NodeChangedMediator.DEFAULT);
* </code></pre>
*/
public class TreeTableModelAdapter implements TreeTableModel {
/** The adapted treeModel. */
private TreeModel treeModel;
/** The columnar data. */
private NodeModel nodeModel;
/** The treeModelsupport which manages listener notification. */
private TreeModelSupport treeModelSupport;
/** Translator of events coming from the adapted TreeModel. */
private Broadcaster broadcaster;
/** The mediator responsible for node changed notification after edits.*/
private NodeChangedMediator mediator;
/**
* Instantiates a TreeTableModelAdapter from the given TreeModel
* and NodeModel. The NodeChangedMediator is set to null.
*
* @param treeModel the TreeModel to adapt.
* @param nodeModel the NodelModel which decorates the columnar properties.
*/
public TreeTableModelAdapter(TreeModel treeModel, NodeModel nodeModel) {
this(treeModel, nodeModel, null);
}
/**
* Instantiates a TreeTableModelAdapter from the given TreeModel,
* NodeModel and NodeChangedMediator. If the mediator is null,
* this adapter is not editable.
*
* @param treeModel the TreeModel to adapt.
* @param nodeModel the NodelModel which decorates the columnar properties.
* @param mediator the NodeChangedMediator responsible for nodeChanged notification.
*/
public TreeTableModelAdapter(TreeModel treeModel, NodeModel nodeModel,
NodeChangedMediator mediator) {
this.nodeModel = nodeModel;
this.treeModel = treeModel;
treeModelSupport = new TreeModelSupport(this);
broadcaster = new Broadcaster();
treeModel.addTreeModelListener(broadcaster);
setNodeChangedMediator(mediator);
}
/**
* Sets the NodeChangedMediator.
*
* @param mediator the mediator responsible for node changed notification
* on the adapted TreeModel.
*/
public void setNodeChangedMediator(NodeChangedMediator mediator) {
this.mediator = mediator;
}
// ---------------- TreeTableModel
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel.
*/
public Class<?> getColumnClass(int columnIndex) {
return nodeModel.getColumnClass(columnIndex);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel or return
* 0 if NodeModel is null.
*/
public int getColumnCount() {
return nodeModel != null ? nodeModel.getColumnCount() : 0;
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel.
*/
public String getColumnName(int column) {
return nodeModel.getColumnName(column);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel or return -1
* if nodeModel is null.
*/
public int getHierarchicalColumn() {
return nodeModel != null ? nodeModel.getHierarchicalColumn() : -1;
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel.
*/
public Object getValueAt(Object node, int column) {
return nodeModel.getValueAt(node, column);
}
/**
* Returns the adapter's editability. If true, it must
* guarantee to fire appropriate change notification on the
* adapted TreeModel. <p>
*
* This implementation returns true if it has a NodeChangeMediator, false otherwise.<p>
*
* PENDING: not flexible enough if has to fire on "adapted TreeModel"?
* Custom models might have their own ideas as to when they want to fire. This
* adapter relies on getting change notification back from the model, does
* nothing on its own to serve its notification responsibility. If we allow
* models to not fire (f.i. if properties which they consider to not constitute
* a nodeChanged) we'll need an enhanced mediator which provides a treePath to
* the root so we can fire a changed on our own behalf.
*
* @return the adapter's editable.
*/
protected boolean isEditable() {
return mediator != null;
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel if the adapter is
* editable, returns false if the adapter is not editable.
*
* @see #isEditable()
*/
public boolean isCellEditable(Object node, int column) {
return isEditable() && nodeModel.isCellEditable(node, column);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the associated NodeModel if the cell
* is editable. Does nothing if the cell is not editable.
*/
public void setValueAt(Object value, Object node, int column) {
if (!isCellEditable(node, column))
return;
nodeModel.setValueAt(value, node, column);
mediator.nodeChanged(treeModel, node);
}
// ----------------- TreeModel
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the adapted TreeModel.
*/
public Object getChild(Object parent, int index) {
return treeModel.getChild(parent, index);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the adapted TreeModel.
*/
public int getChildCount(Object parent) {
return treeModel.getChildCount(parent);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the adapted TreeModel.
*/
public int getIndexOfChild(Object parent, Object child) {
return treeModel.getIndexOfChild(parent, child);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the adapted TreeModel if available, or
* returns null if the TreeModel is null.
*/
public Object getRoot() {
return treeModel != null ? treeModel.getRoot() : null;
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the adapted TreeModel.
*/
public boolean isLeaf(Object node) {
return treeModel.isLeaf(node);
}
/**
* {@inheritDoc} <p>
*
* Implemented to delegate to the adapted TreeModel.
*/
public void valueForPathChanged(TreePath path, Object newValue) {
treeModel.valueForPathChanged(path, newValue);
}
/**
* {@inheritDoc} <p>
*
*/
public void addTreeModelListener(TreeModelListener l) {
treeModelSupport.addTreeModelListener(l);
}
/**
* {@inheritDoc} <p>
*
*/
public void removeTreeModelListener(TreeModelListener l) {
treeModelSupport.removeTreeModelListener(l);
}
/**
* The class responsible to translate TreeEvents received from the adapted
* TreeModel to events as appropriate for listeners of this adapter. All
* event state is taken as is, except for the source which is changed to
* this.
*/
private class Broadcaster implements TreeModelListener {
/**
* {@inheritDoc} <p>
*
*/
public void treeNodesChanged(TreeModelEvent e) {
treeModelSupport.fireChildrenChanged(e.getTreePath(), e
.getChildIndices(), e.getChildren());
}
/**
* {@inheritDoc} <p>
*
*/
public void treeNodesInserted(TreeModelEvent e) {
treeModelSupport.fireChildrenAdded(e.getTreePath(), e
.getChildIndices(), e.getChildren());
}
/**
* {@inheritDoc} <p>
*
*/
public void treeNodesRemoved(TreeModelEvent e) {
treeModelSupport.fireChildrenRemoved(e.getTreePath(), e
.getChildIndices(), e.getChildren());
}
/**
* {@inheritDoc} <p>
*
*/
public void treeStructureChanged(TreeModelEvent e) {
treeModelSupport.fireTreeStructureChanged(e.getTreePath());
}
}
}