001 /*
002 * $Id: TreeModelSupport.java 3100 2008-10-14 22:33:10Z rah003 $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx.tree;
022
023 import javax.swing.event.EventListenerList;
024 import javax.swing.event.TreeModelEvent;
025 import javax.swing.event.TreeModelListener;
026 import javax.swing.tree.TreeModel;
027 import javax.swing.tree.TreePath;
028
029 import org.jdesktop.swingx.util.Contract;
030
031 /**
032 * Support for change notification, usable by {@code TreeModel}s.
033 *
034 * The changed/inserted/removed is expressed in terms of a {@code TreePath},
035 * it's up to the client model to build it as appropriate.
036 *
037 * This is inspired by {@code AbstractTreeModel} from Christian Kaufhold,
038 * www.chka.de.
039 *
040 * TODO - implement and test precondition failure of added/removed notification
041 *
042 * @author JW
043 */
044 public final class TreeModelSupport {
045 protected EventListenerList listeners;
046
047 private TreeModel treeModel;
048
049 /**
050 * Creates the support class for the given {@code TreeModel}.
051 *
052 * @param model the model to support
053 * @throws NullPointerException if {@code model} is {@code null}
054 */
055 public TreeModelSupport(TreeModel model) {
056 if (model == null)
057 throw new NullPointerException("model must not be null");
058 listeners = new EventListenerList();
059 this.treeModel = model;
060 }
061
062 //---------------------- structural changes on subtree
063
064 /**
065 * Notifies registered TreeModelListeners that the tree's root has
066 * been replaced. Can cope with a null root.
067 */
068 public void fireNewRoot() {
069
070 Object root = treeModel.getRoot();
071
072 /*
073 * Undocumented. I think it is the only reasonable/possible solution to
074 * use use null as path if there is no root. TreeModels without root
075 * aren't important anyway, since JTree doesn't support them (yet).
076 */
077 TreePath path = (root != null) ? new TreePath(root) : null;
078 fireTreeStructureChanged(path);
079 }
080
081 /**
082 * Call when a node has changed its leaf state.<p>
083 *
084 * PENDING: rename? Do we need it?
085 * @param path the path to the node with changed leaf state.
086 */
087 public void firePathLeafStateChanged(TreePath path) {
088 fireTreeStructureChanged(path);
089 }
090
091 /**
092 * Notifies registered TreeModelListeners that the structure
093 * below the node identified by the given path has been
094 * completely changed.
095 * <p>
096 * NOTE: the subtree path maybe null if the root is null.
097 * If not null, it must contain at least one element (the root).
098 *
099 * @param subTreePath the path to the root of the subtree
100 * whose structure was changed.
101 * @throws NullPointerException if the path is not null but empty
102 * or contains null elements.
103 */
104 public void fireTreeStructureChanged(TreePath subTreePath) {
105 if (subTreePath != null) {
106 Contract.asNotNull(subTreePath.getPath(),
107 "path must not contain null elements");
108 }
109 Object[] pairs = listeners.getListenerList();
110
111 TreeModelEvent e = null;
112
113 for (int i = pairs.length - 2; i >= 0; i -= 2) {
114 if (pairs[i] == TreeModelListener.class) {
115 if (e == null)
116 e = createStructureChangedEvent(subTreePath);
117
118 ((TreeModelListener) pairs[i + 1]).treeStructureChanged(e);
119 }
120 }
121 }
122
123 //----------------------- node modifications, no mutations
124
125 /**
126 * Notifies registered TreeModelListeners that the
127 * the node identified by the given path has been modified.
128 *
129 * @param path the path to the node that has been modified,
130 * must not be null and must not contain null path elements.
131 *
132 */
133 public void firePathChanged(TreePath path) {
134 Object node = path.getLastPathComponent();
135 TreePath parentPath = path.getParentPath();
136
137 if (parentPath == null)
138 fireChildrenChanged(path, null, null);
139 else {
140 Object parent = parentPath.getLastPathComponent();
141
142 fireChildChanged(parentPath, treeModel
143 .getIndexOfChild(parent, node), node);
144 }
145 }
146
147 /**
148 * Notifies registered TreeModelListeners that the given child of
149 * the node identified by the given parent path has been modified.
150 * The parent path must not be null, nor empty nor contain null
151 * elements.
152 *
153 * @param parentPath the path to the parent of the modified children.
154 * @param index the position of the child
155 * @param child child node that has been modified, must not be null
156 */
157 public void fireChildChanged(TreePath parentPath, int index, Object child) {
158 fireChildrenChanged(parentPath, new int[] { index },
159 new Object[] { child });
160 }
161
162 /**
163 * Notifies registered TreeModelListeners that the given children of
164 * the node identified by the given parent path have been modified.
165 * The parent path must not be null, nor empty nor contain null
166 * elements. Note that the index array must contain the position of the
167 * corresponding child in the the children array. The indices must be in
168 * ascending order. <p>
169 *
170 * The exception to these rules is if the root itself has been
171 * modified (which has no parent by definition). In this case
172 * the path must be the path to the root and both indices and children
173 * arrays must be null.
174 *
175 * @param parentPath the path to the parent of the modified children.
176 * @param indices the positions of the modified children
177 * @param children the modified children
178 */
179 public void fireChildrenChanged(TreePath parentPath, int[] indices,
180 Object[] children) {
181 Contract.asNotNull(parentPath.getPath(),
182 "path must not be null and must not contain null elements");
183 Object[] pairs = listeners.getListenerList();
184
185 TreeModelEvent e = null;
186
187 for (int i = pairs.length - 2; i >= 0; i -= 2) {
188 if (pairs[i] == TreeModelListener.class) {
189 if (e == null)
190 e = createTreeModelEvent(parentPath, indices, children);
191
192 ((TreeModelListener) pairs[i + 1]).treeNodesChanged(e);
193 }
194 }
195 }
196
197
198 //------------------------ mutations (insert/remove nodes)
199
200
201 /**
202 * Notifies registered TreeModelListeners that the child has been added to
203 * the the node identified by the given parent path at the given position.
204 * The parent path must not be null, nor empty nor contain null elements.
205 *
206 * @param parentPath the path to the parent of added child.
207 * @param index the position of the added children
208 * @param child the added child
209 */
210 public void fireChildAdded(TreePath parentPath, int index, Object child) {
211 fireChildrenAdded(parentPath, new int[] { index },
212 new Object[] { child });
213 }
214
215 /**
216 * Notifies registered TreeModelListeners that the child has been removed
217 * from the node identified by the given parent path from the given position.
218 * The parent path must not be null, nor empty nor contain null elements.
219 *
220 * @param parentPath the path to the parent of removed child.
221 * @param index the position of the removed children before the removal
222 * @param child the removed child
223 */
224 public void fireChildRemoved(TreePath parentPath, int index, Object child) {
225 fireChildrenRemoved(parentPath, new int[] { index },
226 new Object[] { child });
227 }
228
229 /**
230 * Notifies registered TreeModelListeners that the given children have been
231 * added to the the node identified by the given parent path at the given
232 * locations. The parent path and the child array must not be null, nor
233 * empty nor contain null elements. Note that the index array must contain
234 * the position of the corresponding child in the the children array. The
235 * indices must be in ascending order.
236 * <p>
237 *
238 * @param parentPath the path to the parent of the added children.
239 * @param indices the positions of the added children.
240 * @param children the added children.
241 */
242 public void fireChildrenAdded(TreePath parentPath, int[] indices,
243 Object[] children) {
244 Object[] pairs = listeners.getListenerList();
245
246 TreeModelEvent e = null;
247
248 for (int i = pairs.length - 2; i >= 0; i -= 2) {
249 if (pairs[i] == TreeModelListener.class) {
250 if (e == null)
251 e = createTreeModelEvent(parentPath, indices, children);
252
253 ((TreeModelListener) pairs[i + 1]).treeNodesInserted(e);
254 }
255 }
256 }
257
258 /**
259 * Notifies registered TreeModelListeners that the given children have been
260 * removed to the the node identified by the given parent path from the
261 * given locations. The parent path and the child array must not be null,
262 * nor empty nor contain null elements. Note that the index array must
263 * contain the position of the corresponding child in the the children
264 * array. The indices must be in ascending order.
265 * <p>
266 *
267 * @param parentPath the path to the parent of the removed children.
268 * @param indices the positions of the removed children before the removal
269 * @param children the removed children
270 */
271 public void fireChildrenRemoved(TreePath parentPath, int[] indices,
272 Object[] children) {
273 Object[] pairs = listeners.getListenerList();
274
275 TreeModelEvent e = null;
276
277 for (int i = pairs.length - 2; i >= 0; i -= 2) {
278 if (pairs[i] == TreeModelListener.class) {
279 if (e == null)
280 e = createTreeModelEvent(parentPath, indices, children);
281 ((TreeModelListener) pairs[i + 1]).treeNodesRemoved(e);
282 }
283 }
284 }
285
286 //------------------- factory methods of TreeModelEvents
287
288 /**
289 * Creates and returns a TreeModelEvent for structureChanged
290 * event notification. The given path may be null to indicate
291 * setting a null root. In all other cases, the first path element
292 * must contain the root and the last path element the rootNode of the
293 * structural change. Specifically, a TreePath with a single element
294 * (which is the root) denotes a structural change of the complete tree.
295 *
296 * @param parentPath the path to the root of the changed structure,
297 * may be null to indicate setting a null root.
298 * @return a TreeModelEvent for structureChanged notification.
299 *
300 * @see javax.swing.event.TreeModelEvent
301 * @see javax.swing.event.TreeModelListener
302 */
303 private TreeModelEvent createStructureChangedEvent(TreePath parentPath) {
304 return createTreeModelEvent(parentPath, null, null);
305 }
306
307 /**
308 * Creates and returns a TreeModelEvent for changed/inserted/removed
309 * event notification.
310 *
311 * @param parentPath path to parent of modified node
312 * @param indices the indices of the modified children (before the change)
313 * @param children the array of modified children
314 * @return a TreeModelEvent for changed/inserted/removed notification
315 *
316 * @see javax.swing.event.TreeModelEvent
317 * @see javax.swing.event.TreeModelListener
318 */
319 private TreeModelEvent createTreeModelEvent(TreePath parentPath,
320 int[] indices, Object[] children) {
321 return new TreeModelEvent(treeModel, parentPath, indices, children);
322 }
323
324
325 //------------------------ handling listeners
326
327 public void addTreeModelListener(TreeModelListener l) {
328 listeners.add(TreeModelListener.class, l);
329 }
330
331 public TreeModelListener[] getTreeModelListeners() {
332 return listeners.getListeners(TreeModelListener.class);
333 }
334
335 public void removeTreeModelListener(TreeModelListener l) {
336 listeners.remove(TreeModelListener.class, l);
337 }
338 }