Skip to main content
summaryrefslogtreecommitdiffstats
blob: 9fe9fca0e8e5ac8cc44200b49dacd906c6f88036 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/*******************************************************************************
 * 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.common.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.common.utility.internal.model.listener.awt.AWTListChangeListenerWrapper;
import org.eclipse.jpt.common.utility.model.event.ListAddEvent;
import org.eclipse.jpt.common.utility.model.event.ListChangeEvent;
import org.eclipse.jpt.common.utility.model.event.ListClearEvent;
import org.eclipse.jpt.common.utility.model.event.ListMoveEvent;
import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent;
import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent;
import org.eclipse.jpt.common.utility.model.listener.ListChangeListener;
import org.eclipse.jpt.common.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();
		}

	}

}

Back to the top