001 /* 002 * Created on 20/6/2004 003 * 004 * Copyright (C) 2004 Denis Krukovsky. All rights reserved. 005 * ==================================================================== 006 * The Software License (based on Apache Software License, Version 1.1) 007 * 008 * Redistribution and use in source and binary forms, with or without 009 * modification, are permitted provided that the following conditions 010 * are met: 011 * 012 * 1. Redistributions of source code must retain the above copyright 013 * notice, this list of conditions and the following disclaimer. 014 * 015 * 2. Redistributions in binary form must reproduce the above copyright 016 * notice, this list of conditions and the following disclaimer in 017 * the documentation and/or other materials provided with the 018 * distribution. 019 * 020 * 3. The end-user documentation included with the redistribution, 021 * if any, must include the following acknowledgment: 022 * "This product includes software developed by 023 * Denis Krukovsky (dkrukovsky at yahoo.com)." 024 * Alternately, this acknowledgment may appear in the software itself, 025 * if and wherever such third-party acknowledgments normally appear. 026 * 027 * 4. The names "dot useful" and "Denis Krukovsky" must not be used to 028 * endorse or promote products derived from this software without 029 * prior written permission. For written permission, please 030 * contact dkrukovsky at yahoo.com. 031 * 032 * 5. Products derived from this software may not be called "useful", 033 * nor may "useful" appear in their name, without prior written 034 * permission of Denis Krukovsky. 035 * 036 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 037 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 038 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 039 * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR 040 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 041 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 042 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 043 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 044 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 045 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 046 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 047 * SUCH DAMAGE. 048 * ==================================================================== 049 */ 050 051 package org.dotuseful.ui.tree; 052 053 import javax.swing.event.EventListenerList; 054 import javax.swing.event.TreeModelEvent; 055 import javax.swing.event.TreeModelListener; 056 import javax.swing.tree.DefaultMutableTreeNode; 057 import javax.swing.tree.MutableTreeNode; 058 059 /** 060 * AutomatedTreeNode extends DefaultMutableTreeNode adding support for automatic notification of node 061 * changes. AutomatedTreeNodes are 062 * used with AutomatedTreeModel. In this model each node considered as a little tree which can fire TreeModel events about its 063 * changes. Each parent node registers itself as a listener of its child nodes events and transfers events to its own 064 * listeners (which is its parent) up to the root node. You can use AutomatedTreeModel which automatically handles events and 065 * fires them as usual TreeModel. 066 * <p> 067 * A sample of code that uses DefaultTreeModel and DefaultTreeNodes 068 * <code> 069 * setUserObject( event.getObject() ); 070 * //getting a tree model from somewhere 071 * DefaultTreeModel model = ( DefaultTreeModel ) titleTree.getModel(); 072 * model.nodeChanged( node ); 073 * </code> 074 * <p> 075 * A sample of code that uses AutomatedTreeModel and AutomatedTreeNodes 076 * <code> 077 * setUserObject( event.getObject() ); 078 * //Everything else automated 079 * </code> 080 * <b>This is not a thread safe class.</b> If you intend to use 081 * an AutomatedTreeNode (or a tree of TreeNodes) in more than one thread, you 082 * need to do your own synchronizing. A good convention to adopt is 083 * synchronizing on the root node of a tree. 084 * <p> 085 * 086 * @author dkrukovsky 087 */ 088 public class AutomatedTreeNode extends DefaultMutableTreeNode implements TreeModelListener { 089 /** Listeners. */ 090 protected EventListenerList listenerList = new EventListenerList(); 091 092 /** 093 * Creates an AutomatedTreeNode that has no parent and no children, but which 094 * allows children. 095 */ 096 public AutomatedTreeNode() { 097 } 098 099 /** 100 * Creates an AutomatedTreeNode node with no parent, no children, but which allows 101 * children, and initializes it with the specified user object. 102 * 103 * @param userObject an Object provided by the user that constitutes 104 * the node's data 105 */ 106 public AutomatedTreeNode(Object userObject) { 107 super(userObject); 108 } 109 110 /** 111 * Creates an AutomatedTreeNode with no parent, no children, initialized with 112 * the specified user object, and that allows children only if 113 * specified. 114 * 115 * @param userObject an Object provided by the user that constitutes 116 * the node's data 117 * @param allowsChildren if true, the node is allowed to have child 118 * nodes -- otherwise, it is always a leaf node 119 */ 120 public AutomatedTreeNode(Object userObject, boolean allowsChildren) { 121 super(userObject, allowsChildren); 122 } 123 124 /** 125 * Removes <code>newChild</code> from its present parent (if it has a 126 * parent), sets the child's parent to this node, adds the 127 * child to this node's child array at index <code>childIndex</code>, fires a <code>nodesWereInserted</code> event, 128 * and then adds itself as a <code>TreeModelListener</code> to <code>newChild</code>. 129 * <code>newChild</code> must not be null and must not be an ancestor of this node. 130 * @param newChild the MutableTreeNode to insert under this node 131 * @param childIndex the index in this node's child array where this node is to be inserted 132 * @exception ArrayIndexOutOfBoundsException if <code>childIndex</code> is out of bounds 133 * @exception IllegalArgumentException if <code>newChild</code> is null or is an ancestor of this node 134 * @exception IllegalStateException if this node does not allow children 135 * @see #isNodeDescendant 136 */ 137 138 /** 139 * Removes <code>newChild</code> from its present parent (if it has a 140 * parent), sets the child's parent to this node, adds the 141 * child to this node's child array at index <code>childIndex</code>, 142 * fires a <code>nodesWereInserted</code> event, and then adds itself as 143 * a <code>TreeModelListener</code> to <code>newChild</code>. 144 * <code>newChild</code> must not be null and must not be an ancestor of 145 * this node. 146 * 147 * @param newChild the MutableTreeNode to insert under this node 148 * @param childIndex the index in this node's child array 149 * where this node is to be inserted 150 * @exception ArrayIndexOutOfBoundsException if 151 * <code>childIndex</code> is out of bounds 152 * @exception IllegalArgumentException if 153 * <code>newChild</code> is null or is an 154 * ancestor of this node 155 * @exception IllegalStateException if this node does not allow 156 * children 157 * @see #isNodeDescendant 158 */ 159 public void insert(final MutableTreeNode newChild, final int childIndex) { 160 super.insert(newChild, childIndex); 161 int[] newIndexs = new int[1]; 162 newIndexs[0] = childIndex; 163 nodesWereInserted(newIndexs); 164 ((AutomatedTreeNode)newChild).addTreeModelListener(this); 165 } 166 167 /** 168 * Removes the child at the specified index from this node's children 169 * and sets that node's parent to null. The child node to remove 170 * must be a <code>MutableTreeNode</code>. 171 * 172 * @param childIndex the index in this node's child array 173 * of the child to remove 174 * @exception ArrayIndexOutOfBoundsException if 175 * <code>childIndex</code> is out of bounds 176 */ 177 public void remove(final int childIndex) { 178 Object[] removedArray = new Object[1]; 179 AutomatedTreeNode node = (AutomatedTreeNode)getChildAt(childIndex); 180 node.removeTreeModelListener(this); 181 removedArray[0] = node; 182 super.remove(childIndex); 183 nodesWereRemoved( 184 new int[] { childIndex }, removedArray); 185 } 186 187 /** 188 * Sets the user object for this node to <code>userObject</code>. 189 * 190 * @param userObject the Object that constitutes this node's 191 * user-specified data 192 * @see #toString 193 */ 194 public void setUserObject(Object userObject) { 195 super.setUserObject(userObject); 196 nodeChanged(); 197 } 198 199 /** 200 * <p>Invoked after a node (or a set of siblings) has changed in some 201 * way. The node(s) have not changed locations in the tree or 202 * altered their children arrays, but other attributes have 203 * changed and may affect presentation. Example: the name of a 204 * file has changed, but it is in the same location in the file 205 * system.</p> 206 */ 207 public void treeNodesChanged(TreeModelEvent e) { 208 fireTreeNodesChanged(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren()); 209 } 210 211 /** <p>Invoked after nodes have been inserted into the tree.</p> */ 212 public void treeNodesInserted(TreeModelEvent e) { 213 fireTreeNodesInserted(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren()); 214 } 215 216 /** 217 * <p>Invoked after nodes have been removed from the tree. Note that 218 * if a subtree is removed from the tree, this method may only be 219 * invoked once for the root of the removed subtree, not once for 220 * each individual set of siblings removed.</p> 221 */ 222 public void treeNodesRemoved(TreeModelEvent e) { 223 fireTreeNodesRemoved(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren()); 224 } 225 226 /** 227 * <p>Invoked after the tree has drastically changed structure from a 228 * given node down. If the path returned by e.getPath() is of length 229 * one and the first element does not identify the current root node 230 * the first element should become the new root of the tree.<p> 231 */ 232 public void treeStructureChanged(TreeModelEvent e) { 233 fireTreeStructureChanged(e.getSource(), e.getPath(), e.getChildIndices(), e.getChildren()); 234 } 235 236 /** 237 * Invoke this method after the node changed how it is to be 238 * represented in the tree. 239 */ 240 protected void nodeChanged() { 241 if (listenerList != null) { 242 AutomatedTreeNode parent = (AutomatedTreeNode)getParent(); 243 if (parent != null) { 244 int anIndex = parent.getIndex(this); 245 if (anIndex != -1) { 246 int[] cIndexs = new int[1]; 247 cIndexs[0] = anIndex; 248 //parent.nodesChanged(cIndexs); 249 Object[] cChildren = new Object[1]; 250 cChildren[0] = this; 251 fireTreeNodesChanged(parent.getPath(), cIndexs, cChildren); 252 } 253 } 254 else if (this == getRoot()) { 255 fireTreeNodesChanged(getPath(), null, null); 256 } 257 } 258 } 259 260 /** 261 * This method invoked after you've inserted some AutomatedTreeNodes into 262 * node. childIndices should be the index of the new elements and 263 * must be sorted in ascending order. 264 */ 265 protected void nodesWereInserted(int[] childIndices) { 266 if (listenerList != null && childIndices != null && childIndices.length > 0) { 267 int cCount = childIndices.length; 268 Object[] newChildren = new Object[cCount]; 269 for (int counter = 0; counter < cCount; counter++) 270 newChildren[counter] = getChildAt(childIndices[counter]); 271 fireTreeNodesInserted(childIndices, newChildren); 272 } 273 } 274 275 /** 276 * This method invoked after you've removed some AutomatedTreeNodes from 277 * node. childIndices should be the index of the removed elements and 278 * must be sorted in ascending order. And removedChildren should be 279 * the array of the children objects that were removed. 280 */ 281 protected void nodesWereRemoved(int[] childIndices, Object[] removedChildren) { 282 if (childIndices != null) { 283 fireTreeNodesRemoved(childIndices, removedChildren); 284 } 285 } 286 287 /** 288 * Invoke this method if you've totally changed the children of 289 * node and its childrens children... This will post a 290 * treeStructureChanged event. 291 */ 292 protected void nodeStructureChanged() { 293 fireTreeStructureChanged(null, null); 294 } 295 296 /** 297 * Adds a listener for the TreeModelEvent posted after the node changes. 298 * 299 * @see #removeTreeModelListener 300 * @param l the listener to add 301 */ 302 public void addTreeModelListener(TreeModelListener l) { 303 listenerList.add(TreeModelListener.class, l); 304 } 305 306 /** 307 * Removes a listener previously added with <B>addTreeModelListener()</B>. 308 * 309 * @see #addTreeModelListener 310 * @param l the listener to remove 311 */ 312 public void removeTreeModelListener(TreeModelListener l) { 313 listenerList.remove(TreeModelListener.class, l); 314 } 315 316 /** 317 * Notifies all listeners that have registered interest for 318 * notification on this event type by firing a treeNodesChanged() method. 319 * 320 * @param childIndices the indices of the changed elements 321 * @param children the changed elements 322 * @see EventListenerList 323 */ 324 protected void fireTreeNodesChanged(int[] childIndices, Object[] children) { 325 fireTreeNodesChanged(getPath(), childIndices, children); 326 } 327 328 /** 329 * Notifies all listeners that have registered interest for 330 * notification on this event type by firing a treeNodesChanged() method. 331 * 332 * @param path the path to the root node 333 * @param childIndices the indices of the changed elements 334 * @param children the changed elements 335 * @see EventListenerList 336 */ 337 protected void fireTreeNodesChanged(Object[] path, int[] childIndices, Object[] children) { 338 fireTreeNodesChanged(this, path, childIndices, children); 339 } 340 341 /** 342 * Notifies all listeners that have registered interest for 343 * notification on this event type by firing a treeNodesChanged() method. 344 * 345 * @param source the node being changed 346 * @param path the path to the root node 347 * @param childIndices the indices of the changed elements 348 * @param children the changed elements 349 * @see EventListenerList 350 */ 351 protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { 352 // Guaranteed to return a non-null array 353 Object[] listeners = listenerList.getListenerList(); 354 TreeModelEvent e = null; 355 // Process the listeners last to first, notifying 356 // those that are interested in this event 357 for (int i = listeners.length - 2; i >= 0; i -= 2) { 358 if (listeners[i] == TreeModelListener.class) { 359 // Lazily create the event: 360 if (e == null) 361 e = new TreeModelEvent(source, path, childIndices, children); 362 ((TreeModelListener)listeners[i + 1]).treeNodesChanged(e); 363 } 364 } 365 } 366 367 /** 368 * Notifies all listeners that have registered interest for 369 * notification on this event type by firing a treeNodesInserted() method. 370 * 371 * @param childIndices the indices of the changed elements 372 * @param children the changed elements 373 * @see EventListenerList 374 */ 375 protected void fireTreeNodesInserted(int[] childIndices, Object[] children) { 376 fireTreeNodesInserted(this, getPath(), childIndices, children); 377 } 378 379 /** 380 * Notifies all listeners that have registered interest for 381 * notification on this event type by firing a treeNodesInserted() method. 382 * 383 * @param source the node being changed 384 * @param path the path to the root node 385 * @param childIndices the indices of the changed elements 386 * @param children the changed elements 387 * @see EventListenerList 388 */ 389 protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { 390 // Guaranteed to return a non-null array 391 Object[] listeners = listenerList.getListenerList(); 392 TreeModelEvent e = null; 393 // Process the listeners last to first, notifying 394 // those that are interested in this event 395 for (int i = listeners.length - 2; i >= 0; i -= 2) { 396 if (listeners[i] == TreeModelListener.class) { 397 // Lazily create the event: 398 if (e == null) 399 e = new TreeModelEvent(source, path, childIndices, children); 400 ((TreeModelListener)listeners[i + 1]).treeNodesInserted(e); 401 } 402 } 403 } 404 405 /** 406 * Notifies all listeners that have registered interest for 407 * notification on this event type by firing a treeNodesRemoved() method. 408 * 409 * @param childIndices the indices of the changed elements 410 * @param children the changed elements 411 * @see EventListenerList 412 */ 413 protected void fireTreeNodesRemoved(int[] childIndices, Object[] children) { 414 fireTreeNodesRemoved(this, getPath(), childIndices, children); 415 } 416 417 /** 418 * Notifies all listeners that have registered interest for 419 * notification on this event type by firing a treeNodesRemoved() method. 420 * 421 * @param source the node being changed 422 * @param path the path to the root node 423 * @param childIndices the indices of the changed elements 424 * @param children the changed elements 425 * @see EventListenerList 426 */ 427 protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { 428 // Guaranteed to return a non-null array 429 Object[] listeners = listenerList.getListenerList(); 430 TreeModelEvent e = null; 431 // Process the listeners last to first, notifying 432 // those that are interested in this event 433 for (int i = listeners.length - 2; i >= 0; i -= 2) { 434 if (listeners[i] == TreeModelListener.class) { 435 // Lazily create the event: 436 if (e == null) 437 e = new TreeModelEvent(source, path, childIndices, children); 438 ((TreeModelListener)listeners[i + 1]).treeNodesRemoved(e); 439 } 440 } 441 } 442 443 /** 444 * Notifies all listeners that have registered interest for 445 * notification on this event type by firing a treeStructureChanged() method. 446 * 447 * @param childIndices the indices of the changed elements 448 * @param children the changed elements 449 * @see EventListenerList 450 */ 451 protected void fireTreeStructureChanged(int[] childIndices, Object[] children) { 452 fireTreeStructureChanged(this, getPath(), childIndices, children); 453 } 454 455 /** 456 * Notifies all listeners that have registered interest for 457 * notification on this event type by firing a treeStructureChanged() method. 458 * 459 * @param source the node being changed 460 * @param path the path to the root node 461 * @param childIndices the indices of the changed elements 462 * @param children the changed elements 463 * @see EventListenerList 464 */ 465 protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { 466 // Guaranteed to return a non-null array 467 Object[] listeners = listenerList.getListenerList(); 468 TreeModelEvent e = null; 469 // Process the listeners last to first, notifying 470 // those that are interested in this event 471 for (int i = listeners.length - 2; i >= 0; i -= 2) { 472 if (listeners[i] == TreeModelListener.class) { 473 // Lazily create the event: 474 if (e == null) 475 e = new TreeModelEvent(source, path, childIndices, children); 476 ((TreeModelListener)listeners[i + 1]).treeStructureChanged(e); 477 } 478 } 479 } 480 }