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 */