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: KTreeModelTreeAdapter.java,v $
023   Revision 1.1  2004/05/31 07:30:26  markl
024   Final cleanup and bugfixes of kiwi.ui.model.
025
026   Revision 1.5  2003/01/19 09:33:06  markl
027   Javadoc & comment header updates.
028
029   Revision 1.4  2001/03/12 04:54:33  markl
030   Source code cleanup.
031
032   Revision 1.3  1999/02/26 10:24:12  markl
033   Made some fixes in the handling of tree structure change and node removal
034   events
035
036   Revision 1.2  1999/01/10 03:12:19  markl
037   added GPL header & RCS tag
038   ----------------------------------------------------------------------------
039*/
040
041package kiwi.ui.model;
042
043import kiwi.event.*;
044
045import java.util.*;
046import javax.swing.*;
047import javax.swing.event.*;
048import javax.swing.tree.*;
049
050/** An adapter that allows a Swing <code>JTree</code> to be used with the
051  * <code>StaticTreeModel</code> and <code>DynamicTreeModel</code> data models.
052  * The adapter translates messages and events between the <code>JTree</code>
053  * and the <code>ITreeModel</code> implementation.
054  *
055  * @see javax.swing.JTree
056  * @see kiwi.ui.model.KTreeModel
057  *
058  * @author Mark Lindner
059  * @since Kiwi 2.0
060  */
061
062public class KTreeModelTreeAdapter implements TreeModel, KTreeModelListener
063  {
064  private KTreeModel model = null;
065  private Vector listeners;
066  private JTree jtree;
067  private TreeWillExpandListener treeWillExpandListener = null;
068  private TreeExpansionListener treeExpansionListener = null;
069  private static final int emptyIndexList[] = new int[0];
070  private static final Object emptyObjectList[] = new Object[0];
071  
072  /** Construct a new tree model tree adapter. Creates a new adapter for the
073    * specified JFC tree component.
074    *
075    * @param jtree The JFC tree component to be associated with this adapter.
076    */
077
078  public KTreeModelTreeAdapter(JTree jtree)
079    {
080    this.jtree = jtree;
081    jtree.setShowsRootHandles(true);
082    listeners = new Vector();
083
084    jtree.addTreeWillExpandListener(treeWillExpandListener
085                                    = new TreeWillExpandListener()
086      {
087      public void treeWillExpand(TreeExpansionEvent evt)
088        {
089        Object node = evt.getPath().getLastPathComponent();
090
091        if(model != null)
092          model.preloadChildren(node);
093        }
094      
095      public void treeWillCollapse(TreeExpansionEvent evt)
096        {
097        }
098      });
099
100    jtree.addTreeExpansionListener(treeExpansionListener
101                                   = new TreeExpansionListener()
102      {
103      public void treeExpanded(TreeExpansionEvent evt)
104        {
105        }
106      
107      public void treeCollapsed(TreeExpansionEvent evt)
108        {
109        Object node = evt.getPath().getLastPathComponent();
110
111        if(model != null)
112          model.releaseChildren(node);
113        }
114      });
115    }
116
117  /** Set the tree model to be used by this adapter. The adapter adds itself
118    * as a listener of the tree model. If a model was already set prior to this
119    * call, it is replaced, and the adapter removes itself as a listener from
120    * that model.
121    *
122    * @param model The model to set.
123    * @see #getTreeModel
124    */
125
126  public void setTreeModel(KTreeModel model)
127    {
128    if(this.model != null)
129      this.model.removeTreeModelListener(this);
130    
131    this.model = model;
132    model.addTreeModelListener(this);
133
134//    model.preloadChildren(model.getRoot()); // should this be here?
135
136    fireTreeStructureChanged(model.getRoot());
137    }
138
139  /** Get the tree model in use by this adapter. Returns the tree model
140    * currently associated with this adapter (may be <code>null</code> if no
141    * model has been set).
142    *
143    * @see #setTreeModel
144    */
145
146  public KTreeModel getTreeModel()
147    {
148    return(model);
149    }
150
151  /** Add a tree model listener. Adds a <code>TreeModelListener</code> to this
152    * adapter's list of tree model listeners.
153    *
154    * @param listener The listener to add.
155    * @see #removeTreeModelListener
156    */
157
158  public void addTreeModelListener(TreeModelListener listener)
159    {
160    listeners.addElement(listener);
161    }
162
163  /** Remove a tree model listener. Removes a <code>TreeModelListener</code>
164    * from this adapter's list of tree model listeners.
165    *
166    * @param listener The listener to remove.
167    * @see #addTreeModelListener
168    */
169
170  public void removeTreeModelListener(TreeModelListener listener)
171    {
172    listeners.removeElement(listener);
173    }
174
175  /** Invoked when nodes are added to the model. */
176  
177  public void nodesAdded(KTreeModelEvent evt)
178    {
179    fireTreeNodesInserted(evt.getNode(), evt.getStartIndex(),
180                          evt.getEndIndex());
181    }
182
183  /** Invoked when nodes are removed from the model. */
184
185  public void nodesRemoved(KTreeModelEvent evt)
186    {
187    fireTreeNodesRemoved(evt.getNode(), evt.getStartIndex(),
188                         evt.getEndIndex());
189    }
190
191  /** Invoked when nodes are changed in the model. */
192
193  public void nodesChanged(KTreeModelEvent evt)
194    {
195    fireTreeNodesChanged(evt.getNode(), evt.getStartIndex(),
196                         evt.getEndIndex());
197    }
198
199  /** Invoked when the structure of a portion of the model is changed. */
200
201  public void structureChanged(KTreeModelEvent evt)
202    {
203    fireTreeStructureChanged(evt.getNode());
204    }
205
206  /** Invoked when the structure of the entire model has changed. */
207  
208  public void dataChanged(KTreeModelEvent evt)
209    {
210    fireTreeStructureChanged(null);
211    }
212
213  /** Get the child of a node. (Implementation of <code>TreeModel</code>.)
214    */
215
216  public Object getChild(Object parent, int index)
217    {
218    if(model == null)
219      return(null);
220    
221    return(model.getChild(parent, index));
222    }
223
224  /** Get the child count of a node. (Implementation of
225    * <code>TreeModel</code>.)
226    */
227
228  public int getChildCount(Object parent)
229    {
230    if(model == null)
231      return(0);
232    
233    return(model.getChildCount(parent));
234    }
235
236  /** Determine if a node is a leaf. (Implementation of
237    * <code>TreeModel</code>.)
238    */
239
240  public boolean isLeaf(Object node)
241    {
242    if(model == null)
243      return(true);
244    
245    return(! model.isExpandable(node));
246    }
247
248  /** Handle <code>JTree</code> events. (Implementation of
249   * <code>TreeModel</code>.)
250   */
251
252  public void valueForPathChanged(TreePath path, Object newValue)
253    {
254    }
255
256  /** Get the index of a child within its parent. (Implementation of
257    * <code>TreeModel</code>.)
258    */
259
260  public int getIndexOfChild(Object parent, Object child)
261    {
262    if(model == null)
263      return(-1);
264
265    return(model.getIndexOfChild(parent, child));
266    }
267
268  /** Get the root of the tree. (Implementation of <code>TreeModel</code>.)
269    */
270
271  public Object getRoot()
272    {
273    if(model == null)
274      return(null);
275
276    return(model.getRoot());
277    }
278
279  /** Compute a <code>TreePath</code> for a given node.
280    *
281    * @param node The node.
282    * @return A <code>TreePath</code> to the given node.
283    * @see javax.swing.tree.TreePath
284    */
285
286  public TreePath getPathForNode(Object node)
287    {
288    Vector v = new Vector();
289    Object n = node;
290
291    if(n != null)
292      {
293      v.insertElementAt(n, 0);
294      while((n = model.getParent(n)) != null)
295        v.insertElementAt(n, 0);
296      }
297
298    Object path[] = new Object[v.size()];
299    v.copyInto(path);
300
301    return(new TreePath(path));
302    }
303
304  /** Fire a <i>tree nodes inserted</i> event. This event notifies the
305    * <code>JTree</code> that new nodes have been added to the tree data model.
306    *
307    * @param parent The parent of the new nodes.
308    * @param startIndex The offset of the first node in the range of nodes that
309    * were added.
310    * @param endIndex The offset of the last node in the range of nodes that
311    * were added.
312    */
313
314  protected void fireTreeNodesInserted(Object parent, int startIndex,
315                                       int endIndex)
316    {
317    TreeModelEvent evt = null;
318
319    Enumeration e = listeners.elements();
320    while(e.hasMoreElements())
321      {
322      if(evt == null)
323        {
324        int indices[] = emptyIndexList;
325        Object objects[] = emptyObjectList;
326
327        if((startIndex >= 0) && (endIndex >= 0))
328          {
329          int len = endIndex - startIndex + 1;
330          indices = new int[len];
331          objects = new Object[len];
332
333          for(int i = 0, pos = startIndex; i < len; i++, pos++)
334            {
335            indices[i] = pos;
336            objects[i] = model.getChild(parent, pos);
337            }
338          }
339        
340        evt = new TreeModelEvent(this, getPathForNode(parent), indices,
341                                 objects);
342        }
343      
344      TreeModelListener l = (TreeModelListener)e.nextElement();
345
346      l.treeNodesInserted(evt);
347      }
348    }
349
350  /** Fire a <i>tree nodes removed</i> event. This event notifies the
351    * <code>JTree</code> that a node has been removed from the tree data model.
352    *
353    * @param parent The parent of the nodes that were removed.
354    * @param startIndex The offset of the first node in the range of nodes that
355    * were removed.
356    * @param endIndex The offset of the last node in the range of nodes that
357    * were removed.
358    */
359
360  protected void fireTreeNodesRemoved(Object parent, int startIndex,
361                                      int endIndex)
362    {
363    TreeModelEvent evt = null;
364
365    Enumeration e = listeners.elements();
366    while(e.hasMoreElements())
367      {
368      if(evt == null)
369        {
370        int indices[] = emptyIndexList;
371        Object objects[] = emptyObjectList;
372
373        if((startIndex >= 0) && (endIndex >= 0))
374          {
375          int len = endIndex - startIndex + 1;
376          indices = new int[len];
377          objects = new Object[len];
378
379          for(int i = 0, pos = startIndex; i < len; i++, pos++)
380            {
381            indices[i] = pos;
382            objects[i] = model.getChild(parent, pos);
383            }
384          }
385
386        evt = new TreeModelEvent(this, getPathForNode(parent), indices,
387                                 objects);
388        }
389      TreeModelListener l = (TreeModelListener)e.nextElement();
390      l.treeNodesRemoved(evt);
391      }
392    }
393        
394  /** Fire a <i>tree nodes changed</i> event. This event notifies the
395    * <code>JTree</code> that a node has changed in some way.
396    *
397    * @param parent The parent of the nodes that changed.
398    * @param startIndex The offset of the first node in the range of nodes that
399    * changed.
400    * @param endIndex The offset of the last node in the range of nodes that
401    * changed.
402    */
403
404  protected void fireTreeNodesChanged(Object parent, int startIndex,
405                                      int endIndex)
406    {
407    TreeModelEvent evt = null;
408
409    Enumeration e = listeners.elements();
410    while(e.hasMoreElements())
411      {
412      if(evt == null)
413        {
414        int indices[] = emptyIndexList;
415        Object objects[] = emptyObjectList;
416
417        if((startIndex >= 0) && (endIndex >= 0))
418          {
419          int len = endIndex - startIndex + 1;
420          indices = new int[len];
421          objects = new Object[len];
422
423          for(int i = 0, pos = startIndex; i < len; i++, pos++)
424            {
425            indices[i] = pos;
426            objects[i] = model.getChild(parent, pos);
427            }
428          }
429
430        evt = new TreeModelEvent(this, getPathForNode(parent), indices,
431                                 objects);
432        }
433      
434      TreeModelListener l = (TreeModelListener)e.nextElement();
435      l.treeNodesChanged(evt);
436      }
437    }
438
439  /** Fire a <i>tree structure changed</i> event. This event notifies the
440    * <code>JTree</code> that the subtree rooted at a given node has changed in
441    * some major way.
442    *
443    * @param node The node that is the root of the subtree that changed.
444    */
445
446  protected void fireTreeStructureChanged(Object node)
447    {
448    TreeModelEvent evt = null;
449
450    Enumeration e = listeners.elements();
451    while(e.hasMoreElements())
452      {
453      if(evt == null)
454        evt = new TreeModelEvent(this, (node == null) ? null
455                                 : getPathForNode(node));
456
457      TreeModelListener l = (TreeModelListener)e.nextElement();
458      l.treeStructureChanged(evt);
459      }
460    }
461  
462  /** Dispose of the adapter. Causes the adapter to detach its listeners from
463    * its associated <code>JTree</code> component, and then null out its
464    * references to the <code>JTree</code> and to the associated
465    * <code>KTreeModel</code>.
466    */
467  
468  public void dispose()
469    {
470    if(treeWillExpandListener != null)
471      jtree.removeTreeWillExpandListener(treeWillExpandListener);
472    if(treeExpansionListener != null)
473      jtree.removeTreeExpansionListener(treeExpansionListener);
474    treeWillExpandListener = null;
475    treeExpansionListener = null;
476
477    if(model != null)
478      model.removeTreeModelListener(this);
479    
480    model = null;
481    jtree = null;
482    }
483
484  }
485
486/* end of source file */