blob: 5a19127741f408ede9db9a05cf3c24a2f7fcef61 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2009 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal.model.value.swing;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.eclipse.jpt.utility.internal.model.listener.awt.AWTListChangeListenerWrapper;
import org.eclipse.jpt.utility.model.event.ListAddEvent;
import org.eclipse.jpt.utility.model.event.ListChangeEvent;
import org.eclipse.jpt.utility.model.event.ListClearEvent;
import org.eclipse.jpt.utility.model.event.ListMoveEvent;
import org.eclipse.jpt.utility.model.event.ListRemoveEvent;
import org.eclipse.jpt.utility.model.event.ListReplaceEvent;
import org.eclipse.jpt.utility.model.listener.ListChangeListener;
import org.eclipse.jpt.utility.model.value.ListValueModel;
/**
* This TreeModel implementation provides a tree with a "null" root that
* has a set of "primitive" children. These "primitive" children do not have
* children themselves, making the tree a maximum of 2 levels deep.
* This model automatically synchronizes the root's children with a
* ListValueModel that holds a collection of primitive (non-model) objects
* (e.g. Strings).
*
* This is useful for providing an "editable" list of primitives. Since the JDK
* does not provide us with an editable listbox, we must use an editable tree.
* We wrap everything in DefaultMutableTreeNodes.
*
* Subclasses must implement #primitiveChanged(int, Object) and update
* the model appropriately. This method is called when the user edits the
* list directly and presses <Enter>.
*
* The JTree using this model must be configured as "editable":
* tree.setEditable(true);
*/
// TODO convert to use an adapter instead of requiring subclass
public abstract class PrimitiveListTreeModel
extends DefaultTreeModel
{
/** a model on the list of primitives */
private final ListValueModel<?> listHolder;
/** a listener that handles the adding, removing, and replacing of the primitives */
private final ListChangeListener listChangeListener;
// ********** constructors **********
/**
* Public constructor - the list holder is required
*/
public PrimitiveListTreeModel(ListValueModel<?> listHolder) {
super(new DefaultMutableTreeNode(null, true)); // true = the root can have children
if (listHolder == null) {
throw new NullPointerException();
}
this.listHolder = listHolder;
this.listChangeListener = this.buildListChangeListener();
// postpone listening to the model until we have listeners ourselves
}
protected ListChangeListener buildListChangeListener() {
return new AWTListChangeListenerWrapper(this.buildListChangeListener_());
}
protected ListChangeListener buildListChangeListener_() {
return new PrimitiveListChangeListener();
}
// ********** behavior **********
/**
* Subclasses should override this method to update the
* model appropriately. The primitive at the specified index was
* edited directly by the user and the new value is as specified.
* Convert the value appropriately and place it in the model.
*/
protected abstract void primitiveChanged(int index, Object newValue);
// ********** TreeModel implementation **********
/**
* Override to change the underlying model instead of changing the node directly.
*/
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
TreeNode node = (TreeNode) path.getLastPathComponent();
int index = ((TreeNode) this.getRoot()).getIndex(node);
this.primitiveChanged(index, newValue);
}
/**
* Extend to start listening to the underlying model if necessary.
*/
@Override
public void addTreeModelListener(TreeModelListener l) {
if (this.getTreeModelListeners().length == 0) {
this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener);
this.synchronizeList();
}
super.addTreeModelListener(l);
}
/**
* Extend to stop listening to the underlying model if appropriate.
*/
@Override
public void removeTreeModelListener(TreeModelListener l) {
super.removeTreeModelListener(l);
if (this.getTreeModelListeners().length == 0) {
this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener);
}
}
// ********** behavior **********
/**
* Synchronize our list of nodes with the list of primitives
*/
void synchronizeList() {
this.clearList();
this.buildList();
}
void clearList() {
int childcount = this.root.getChildCount();
for (int i = childcount - 1; i >= 0; i--) {
this.removeNodeFromParent((MutableTreeNode)this.root.getChildAt(i));
}
}
private void buildList() {
for (Iterator<?> stream = this.listHolder.iterator(); stream.hasNext(); ) {
this.addPrimitive(stream.next());
}
}
/**
* Add the specified primitive to the end of the list.
*/
private void addPrimitive(Object primitive) {
this.insertPrimitive(this.root.getChildCount(), primitive);
}
/**
* Create a node for the specified primitive
* and insert it as a child of the root.
*/
void insertPrimitive(int index, Object primitive) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(primitive, false); // don't allow children on the child node
this.insertNodeInto(node, (MutableTreeNode) this.root, index);
}
/**
* Remove node at the specified index.
*/
MutableTreeNode removeNode(int index) {
MutableTreeNode node = (MutableTreeNode) this.root.getChildAt(index);
this.removeNodeFromParent(node);
return node;
}
/**
* Replace the user object of the node at childIndex.
*/
void replacePrimitive(int index, Object primitive) {
MutableTreeNode node = (MutableTreeNode) this.root.getChildAt(index);
node.setUserObject(primitive);
this.nodeChanged(node);
}
// ********** inner class **********
private class PrimitiveListChangeListener implements ListChangeListener {
PrimitiveListChangeListener() {
super();
}
public void itemsAdded(ListAddEvent event) {
int i = event.getIndex();
for (Object item : event.getItems()) {
PrimitiveListTreeModel.this.insertPrimitive(i++, item);
}
}
public void itemsRemoved(ListRemoveEvent event) {
for (int i = 0; i < event.getItemsSize(); i++) {
PrimitiveListTreeModel.this.removeNode(event.getIndex());
}
}
public void itemsReplaced(ListReplaceEvent event) {
int i = event.getIndex();
for (Object item : event.getNewItems()) {
PrimitiveListTreeModel.this.replacePrimitive(i++, item);
}
}
public void itemsMoved(ListMoveEvent event) {
ArrayList<MutableTreeNode> temp = new ArrayList<MutableTreeNode>(event.getLength());
for (int i = 0; i < event.getLength(); i++) {
temp.add(PrimitiveListTreeModel.this.removeNode(event.getSourceIndex()));
}
int i = event.getTargetIndex();
for (MutableTreeNode node : temp) {
PrimitiveListTreeModel.this.insertPrimitive(i++, node);
}
}
public void listCleared(ListClearEvent event) {
PrimitiveListTreeModel.this.clearList();
}
public void listChanged(ListChangeEvent event) {
PrimitiveListTreeModel.this.synchronizeList();
}
}
}