/* * 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()); } } }