001/* ---------------------------------------------------------------------------- 002 The Kiwi Toolkit - A Java Class Library 003 Copyright (C) 1998-2004 Mark A. Lindner 004 005 This library is free software; you can redistribute it and/or 006 modify it under the terms of the GNU General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This library is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 General Public License for more details. 014 015 You should have received a copy of the GNU General Public License 016 along with this library; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 018 02111-1307, USA. 019 020 The author may be contacted at: mark_a_lindner@yahoo.com 021 ---------------------------------------------------------------------------- 022 $Log: DefaultKTreeModel.java,v $ 023 Revision 1.1 2004/05/31 07:30:26 markl 024 Final cleanup and bugfixes of kiwi.ui.model. 025 026 ---------------------------------------------------------------------------- 027*/ 028 029package kiwi.ui.model; 030 031import java.awt.Image; 032import java.lang.reflect.Method; 033import java.util.*; 034import javax.swing.Icon; 035 036import kiwi.event.*; 037import kiwi.util.*; 038 039/** A default implementation of <code>KTreeModel</code>. 040 * 041 * @author Mark Lindner 042 * @since Kiwi 2.0 043 */ 044 045public class DefaultKTreeModel implements KTreeModel 046 { 047 private Vector listeners = new Vector(); 048 /** The object-to-node map. */ 049 protected TreeMap nodeMap; 050 protected KTreeModelSupport support; 051 protected static final TreeNode emptyList[] = new TreeNode[0]; 052 053 private static final Icon FOLDER_OPEN_ICON = KiwiUtils.getResourceManager() 054 .getIcon("folder-open.gif"); 055 private static final Icon FOLDER_CLOSED_ICON = KiwiUtils.getResourceManager() 056 .getIcon("folder-closed.gif"); 057 private static final Icon DOCUMENT_ICON = KiwiUtils.getResourceManager() 058 .getIcon("document.gif"); 059 060 /** The model's root node. */ 061 protected TreeNode rootNode; 062 063 /** Construct a new, static <code>DefaultKTreeModel</code>. */ 064 065 public DefaultKTreeModel() 066 { 067 support = new KTreeModelSupport(this); 068 nodeMap = new TreeMap(new HashCodeComparator()); 069 } 070 071 /** Create a node for an object. This method is provided for 072 * subclasses which maintain dynamic tree data. 073 */ 074 075 protected TreeNode makeNode(Object object, TreeNode parent) 076 { 077 TreeNode node = new TreeNode(object, parent); 078 nodeMap.put(object, node); 079 080 return(node); 081 } 082 083 /** Destroy a node, and recursively destroy all its descendants. This method 084 * is provided for subclasses which maintain dynamic tree data. 085 */ 086 087 protected void destroyNode(TreeNode node) 088 { 089 Iterator iter = node.getChildren(); 090 if(iter != null) 091 while(iter.hasNext()) 092 { 093 destroyNode((TreeNode)iter.next()); 094 } 095 096 nodeMap.remove(node.getObject()); 097 } 098 099 /* 100 */ 101 102 public synchronized Object getRoot() 103 { 104 return(rootNode == null ? null : rootNode.getObject()); 105 } 106 107 /* 108 */ 109 110 public synchronized void setRoot(Object root) 111 { 112 nodeMap.clear(); 113 114 rootNode = makeNode(root, null); 115 support.fireDataChanged(); 116 } 117 118 /* 119 */ 120 121 public synchronized int getChildCount(Object node) 122 { 123 TreeNode n = nodeForObject(node); 124 return(n.getChildCount()); 125 } 126 127 /* 128 */ 129 130 public synchronized Object getChild(Object parent, int index) 131 { 132 TreeNode p = nodeForObject(parent); 133 if(p == null) 134 return(null); 135 136 TreeNode child = p.getChild(index); 137 if(child == null) 138 return(null); 139 140 return(child.getObject()); 141 } 142 143 /* 144 */ 145 146 public synchronized Iterator getChildren(Object parent) 147 { 148 TreeNode p = nodeForObject(parent); 149 if(p == null) 150 return(null); 151 152 Iterator iter = p.getChildren(); 153 ArrayList list = new ArrayList(); 154 while(iter.hasNext()) 155 { 156 TreeNode node = (TreeNode)iter.next(); 157 list.add(node.getObject()); 158 } 159 160 return(list.iterator()); 161 } 162 163 /* 164 */ 165 166 public synchronized int getIndexOfChild(Object parent, Object node) 167 { 168 TreeNode p = nodeForObject(parent); 169 if(p == null) 170 return(-1); 171 172 TreeNode child = nodeForObject(node); 173 174 return((child == null) ? -1 : p.getIndexOfChild(child)); 175 } 176 177 /* 178 */ 179 180 public synchronized void removeChildren(Object parent) 181 { 182 TreeNode p = nodeForObject(parent); 183 if(p != null) 184 p.removeChildren(); 185 } 186 187 /* 188 */ 189 190 public synchronized void removeChild(Object parent, int index) 191 { 192 TreeNode p = nodeForObject(parent); 193 if(p != null) 194 p.removeChild(index); 195 } 196 197 /* 198 */ 199 200 public synchronized void addChild(Object parent, Object node) 201 { 202 TreeNode p = nodeForObject(parent); 203 if(p != null) 204 p.addChild(makeNode(node, p)); 205 } 206 207 /* 208 */ 209 210 public synchronized void addChild(Object parent, Object node, int index) 211 { 212 TreeNode p = nodeForObject(parent); 213 if(p != null) 214 p.addChild(makeNode(node, p), index); 215 } 216 217 /* 218 */ 219 220 public synchronized Object getParent(Object node) 221 { 222 TreeNode n = nodeForObject(node); 223 if(n == null) 224 return(null); 225 226 TreeNode p = n.getParent(); 227 228 return((p == null) ? null : p.getObject()); 229 } 230 231 /* 232 */ 233 234 public synchronized boolean isExpandable(Object node) 235 { 236 TreeNode n = nodeForObject(node); 237 238 return((n == null) ? false : n.isExpandable()); 239 } 240 241 /** May be overridden by subclasses to provide custom icons. 242 */ 243 244 public synchronized Icon getIcon(Object node, boolean isExpanded) 245 { 246 if(isExpandable(node)) 247 return(isExpanded ? FOLDER_OPEN_ICON : FOLDER_CLOSED_ICON); 248 else 249 return(DOCUMENT_ICON); 250 } 251 252 /** May be overridden by subclasses to provide custom labels. 253 */ 254 255 public synchronized String getLabel(Object node) 256 { 257 return(node.toString()); 258 } 259 260 /* 261 */ 262 263 public synchronized void updateNode(Object node) 264 { 265 TreeNode n = nodeForObject(node); 266 267 if(n != null) 268 { 269 TreeNode parent = n.getParent(); 270 int index = n.getIndexOfChild(n); 271 if(index >= 0) 272 support.fireNodeChanged(parent.getObject(), index); 273 } 274 } 275 276 /* 277 */ 278 279 public synchronized void updateChildren(Object parent) 280 { 281 TreeNode p = nodeForObject(parent); 282 if(p != null) 283 support.fireNodeStructureChanged(parent); 284 } 285 286 /* 287 */ 288 289 public synchronized void releaseChildren(Object parent) 290 { 291 /* no-op */ 292 } 293 294 /** Get the value of an arbitrary property for a given node. 295 * 296 * @param node The node. 297 * @param property The name of the property. 298 * 299 * @return The value of the specified property, or <code>null</code> if 300 * there is no value for this property. 301 */ 302 303 public Object getValueForProperty(Object node, String property) 304 { 305 return(null); 306 } 307 308 /* 309 */ 310 311 /** Add a <code>TreeModelListener</code> to this model's list of listeners. 312 * 313 * @param listener The listener to be added. 314 * @see #removeTreeModelListener 315 */ 316 317 public void addTreeModelListener(KTreeModelListener listener) 318 { 319 support.addTreeModelListener(listener); 320 } 321 322 /** Remove a <code>TreeModelListener</code> from this model's list of 323 * listeners. 324 * 325 * @param listener The listener to remove. 326 * @see #addTreeModelListener 327 */ 328 329 public void removeTreeModelListener(KTreeModelListener listener) 330 { 331 support.removeTreeModelListener(listener); 332 } 333 334 /** Look up a node for a given object. 335 */ 336 337 protected TreeNode nodeForObject(Object item) 338 { 339 return((TreeNode)nodeMap.get(item)); 340 } 341 342 /* 343 */ 344 345 public void preloadChildren(Object item) 346 { 347 TreeNode node = (TreeNode)nodeMap.get(item); 348 if(node != null) 349 { 350 if(! node.hasChildrenLoaded()) 351 loadChildren(node); 352 } 353 } 354 355 /** Load the children for a given node. This method is provided for 356 * subclasses which obtain tree data from an external source. The default 357 * implementation is a no-op. 358 * 359 * @param node The parent node. 360 */ 361 362 protected void loadChildren(TreeNode node) 363 { 364 /* in this case, a no-op */ 365 } 366 367 /** Internal wrapper object for nodes. */ 368 369 protected class TreeNode 370 { 371 private Object object; 372 private TreeNode parent; 373 private ArrayList children = null; // null if not yet loaded 374 private boolean expandable = false; 375 private int childCount = 0; 376 377 /* construct a new tree node */ 378 379 TreeNode(Object object, TreeNode parent) 380 { 381 this.parent = parent; 382 this.object = object; 383 } 384 385 /* get the index of the given child in the list of children */ 386 387 int getIndexOfChild(TreeNode child) 388 { 389 if(children == null) 390 return(-1); 391 392 return(children.indexOf(child)); 393 } 394 395 /* add a new child to the list of children */ 396 397 void addChild(TreeNode child) 398 { 399 if(children == null) 400 children = new ArrayList(); 401 402 int ct = children.size(); 403 children.add(child); 404 support.fireNodeAdded(object, ct); 405 } 406 407 /* insert a given child at the given position in the list of children */ 408 409 void addChild(TreeNode child, int index) 410 { 411 if(index < 0) 412 index = 0; 413 else if(index > children.size()) 414 index = children.size(); 415 416 children.add(index, child); 417 support.fireNodeAdded(object, index); 418 } 419 420 /* replace the children with a new list of children */ 421 422 void setChildren(ArrayList children) 423 { 424 this.children = children; 425 support.fireNodeStructureChanged(object); 426 } 427 428 /* remove a child from the list of children */ 429 430 void removeChild(TreeNode child) 431 { 432 int index = children.indexOf(child); 433 if(index >= 0 || index < children.size()) 434 { 435 children.remove(index); 436 destroyNode(child); 437 support.fireNodeRemoved(object, index); 438 } 439 } 440 441 /* */ 442 443 void removeChildren() 444 { 445 int ct = children.size(); 446 if(ct > 0) 447 { 448 Iterator iter = children.iterator(); 449 while(iter.hasNext()) 450 destroyNode((TreeNode)iter.next()); 451 452 children.clear(); 453 support.fireNodesRemoved(object, 0, --ct); 454 } 455 } 456 457 /* remove the child at the given position in the list of children */ 458 459 void removeChild(int index) 460 { 461 TreeNode node = (TreeNode)children.get(index); 462 destroyNode(node); 463 children.remove(index); 464 support.fireNodeRemoved(object, index); 465 } 466 467 /* drop the children */ 468 469 void releaseChildren() 470 { 471 if(children == null) 472 return; 473 474 Iterator iter = children.iterator(); 475 while(iter.hasNext()) 476 destroyNode((TreeNode)iter.next()); 477 478 children = null; 479 } 480 481 /* get the user object for this node */ 482 483 Object getObject() 484 { 485 return(object); 486 } 487 488 /* set the expandable flag */ 489 490 void setExpandable(boolean flag) 491 { 492 expandable = flag; 493 } 494 495 /* determine if this node is expandable */ 496 497 public boolean isExpandable() 498 { 499 return(expandable); 500 } 501 502 /* get the parent of this node */ 503 504 public TreeNode getParent() 505 { 506 return(parent); 507 } 508 509 /* get a child count for the node */ 510 511 int getChildCount() 512 { 513 // We need to remember the (most recent) child count even after 514 // the children are dropped; otherwise we'd report child count 515 // of 0 and the tree would render the parent without an expander 516 // control. 517 518 if(children != null) 519 childCount = children.size(); 520 521 return(childCount); 522 } 523 524 /* get the child at a given index */ 525 526 TreeNode getChild(int index) 527 { 528 return((children == null) ? null : (TreeNode)children.get(index)); 529 } 530 531 /* get an iterator to the children */ 532 533 Iterator getChildren() 534 { 535 return((children == null) ? null : children.iterator()); 536 } 537 538 /* are children loaded? */ 539 540 boolean hasChildrenLoaded() 541 { 542 return(children != null); 543 } 544 545 } 546 } 547 548/* end of source file */