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 }