001/*
002  The Broad Institute
003  SOFTWARE COPYRIGHT NOTICE AGREEMENT
004  This software and its documentation are copyright (2003-2008) by the
005  Broad Institute/Massachusetts Institute of Technology. All rights are
006  reserved.
007
008  This software is supplied without any warranty or guaranteed support
009  whatsoever. Neither the Broad Institute nor MIT can be responsible for its
010  use, misuse, or functionality.
011*/
012
013package ca.bc.webarts.widgets.treetable;
014// package org.genomespace.gsui.ui.treetable;
015
016
017import javax.swing.event.EventListenerList;
018import javax.swing.event.TreeModelEvent;
019import javax.swing.event.TreeModelListener;
020import javax.swing.tree.TreeNode;
021import javax.swing.tree.TreePath;
022import javax.swing.tree.DefaultMutableTreeNode;
023
024//import org.genomespace.gsui.ui.table.*;
025//import org.genomespace.gsui.ui.tasks.*;
026
027/**
028 *  Description of the Class
029 *
030 * @author    Joshua Gould
031 */
032public abstract class AbstractSortableTreeTableModel implements SortableTreeTableModel
033{
034
035  protected EventListenerList listenerList = new EventListenerList();
036
037  public void addTreeModelListener( TreeModelListener l )
038  {
039    listenerList.add( TreeModelListener.class, l );
040  }
041
042  public void removeTreeModelListener( TreeModelListener l )
043  {
044    listenerList.remove( TreeModelListener.class, l );
045  }
046
047  public void valueForPathChanged( TreePath path, Object newValue )
048  {
049
050  }
051
052  public abstract void sortOrderChanged( SortEvent e );
053
054
055  /**
056   * Invoke this method after you've inserted some TreeNodes into node.
057   * childIndices should be the index of the new elements and must be sorted
058   * in ascending order.
059   *
060   * @param node
061   *            Description of the Parameter
062   * @param childIndices
063   *            Description of the Parameter
064   */
065  public void nodesWereInserted( TreeNode node, int[] childIndices )
066  {
067    if ( listenerList != null && node != null && childIndices != null && childIndices.length > 0 )
068    {
069      int cCount = childIndices.length;
070      Object[] newChildren = new Object[cCount];
071
072      for ( int counter = 0; counter < cCount; counter++ )
073      {
074        newChildren[counter] = node.getChildAt( childIndices[counter] );
075      }
076
077      fireTreeNodesInserted( this, getPathToRoot( node ), childIndices, newChildren );
078
079    }
080  }
081
082  /**
083   * Invoke this method after you've changed how node is to be
084   * represented in the tree.
085   */
086  public void nodeChanged( TreeNode node )
087  {
088    if ( listenerList != null && node != null )
089    {
090      TreeNode parent = node.getParent();
091
092      if ( parent != null )
093      {
094        int anIndex = parent.getIndex( node );
095        if ( anIndex != -1 )
096        {
097          int[] cIndexs = new int[1];
098
099          cIndexs[0] = anIndex;
100          nodesChanged( parent, cIndexs );
101        }
102      }
103      else if ( node == getRoot() )
104      {
105        nodesChanged( node, null );
106      }
107    }
108  }
109
110  /**
111   * Invoke this method after you've removed some TreeNodes from node.
112   * childIndices should be the index of the removed elements and must be
113   * sorted in ascending order. And removedChildren should be the array of the
114   * children objects that were removed.
115   *
116   * @param node
117   *            Description of the Parameter
118   * @param childIndices
119   *            Description of the Parameter
120   * @param removedChildren
121   *            Description of the Parameter
122   */
123  public void nodesWereRemoved( TreeNode node, int[] childIndices, Object[] removedChildren )
124  {
125    if ( node != null && childIndices != null )
126    {
127      fireTreeNodesRemoved( this, getPathToRoot( node ), childIndices, removedChildren );
128    }
129  }
130
131  /**
132   * Invoke this method after you've changed how the children identified by
133   * childIndicies are to be represented in the tree.
134   *
135   * @param node
136   *            Description of the Parameter
137   * @param childIndices
138   *            Description of the Parameter
139   */
140  public void nodesChanged( TreeNode node, int[] childIndices )
141  {
142    if ( node != null )
143    {
144      if ( childIndices != null )
145      {
146        int cCount = childIndices.length;
147
148        if ( cCount > 0 )
149        {
150          Object[] cChildren = new Object[cCount];
151
152          for ( int counter = 0; counter < cCount; counter++ )
153          {
154            cChildren[counter] = node.getChildAt( childIndices[counter] );
155          }
156          fireTreeNodesChanged( this, getPathToRoot( node ), childIndices, cChildren );
157        }
158      }
159      else if ( node == getRoot() )
160      {
161        fireTreeNodesChanged( this, getPathToRoot( node ), null, null );
162      }
163    }
164  }
165
166  /**
167   * Invoke this method if you've totally changed the children of node and its
168   * childrens children... This will post a treeStructureChanged event.
169   *
170   * @param node
171   *            Description of the Parameter
172   */
173  public void nodeStructureChanged( TreeNode node )
174  {
175    if ( node != null )
176    {
177      fireTreeStructureChanged( this, getPathToRoot( node ), null, null );
178    }
179  }
180
181  /*
182         * Notify all listeners that have registered interest for notification on
183         * this event type. The event instance is lazily created using the
184         * parameters passed into the fire method.
185         *
186         * @see EventListenerList
187  */
188  protected void fireTreeNodesChanged( Object source, Object[] path, int[] childIndices, Object[] children )
189  {
190    // Guaranteed to return a non-null array
191    try
192    {
193      Object[] listeners = listenerList.getListenerList();
194      TreeModelEvent e = null;
195      // Process the listeners last to first, notifying
196      // those that are interested in this event
197      for ( int i = listeners.length - 2; i >= 0; i -= 2 )
198      {
199        if ( listeners[i] == TreeModelListener.class )
200        {
201          // Lazily create the event:
202          if ( e == null )
203          {
204            e = new TreeModelEvent( source, path, childIndices, children );
205          }
206          ( ( TreeModelListener ) listeners[ i + 1] ).treeNodesChanged( e );
207        }
208      }
209    }
210    catch ( Throwable t )
211    {
212      t.printStackTrace();
213    }
214  }
215
216  /*
217         * Notify all listeners that have registered interest for notification on
218         * this event type. The event instance is lazily created using the
219         * parameters passed into the fire method.
220         *
221         * @see EventListenerList
222  */
223  protected void fireTreeNodesInserted( Object source, Object[] path, int[] childIndices, Object[] children )
224  {
225    try
226    {
227      // Guaranteed to return a non-null array
228      Object[] listeners = listenerList.getListenerList();
229      TreeModelEvent e = null;
230      // Process the listeners last to first, notifying
231      // those that are interested in this event
232      for ( int i = listeners.length - 2; i >= 0; i -= 2 )
233      {
234        if ( listeners[i] == TreeModelListener.class )
235        {
236          // Lazily create the event:
237          if ( e == null )
238          {
239            e = new TreeModelEvent( source, path, childIndices, children );
240          }
241          ( ( TreeModelListener ) listeners[ i + 1] ).treeNodesInserted( e );
242        }
243      }
244    }
245    catch ( Throwable t )
246    {
247      t.printStackTrace();
248    }
249  }
250
251  /*
252         * Notify all listeners that have registered interest for notification on
253         * this event type. The event instance is lazily created using the
254         * parameters passed into the fire method.
255         *
256         * @see EventListenerList
257  */
258  protected void fireTreeNodesRemoved( Object source, Object[] path, int[] childIndices, Object[] children )
259  {
260    // Guaranteed to return a non-null array
261    try
262    {
263      Object[] listeners = listenerList.getListenerList();
264      TreeModelEvent e = null;
265      // Process the listeners last to first, notifying
266      // those that are interested in this event
267      for ( int i = listeners.length - 2; i >= 0; i -= 2 )
268      {
269        if ( listeners[i] == TreeModelListener.class )
270        {
271          // Lazily create the event:
272          if ( e == null )
273          {
274            e = new TreeModelEvent( source, path, childIndices, children );
275          }
276          ( ( TreeModelListener ) listeners[ i + 1] ).treeNodesRemoved( e );
277        }
278      }
279    }
280    catch ( Throwable t )
281    {
282      t.printStackTrace();
283    }
284  }
285
286  /*
287         * Notify all listeners that have registered interest for notification on
288         * this event type. The event instance is lazily created using the
289         * parameters passed into the fire method.
290         *
291         * @see EventListenerList
292  */
293  protected void fireTreeStructureChanged( Object source, Object[] path, int[] childIndices, Object[] children )
294  {
295    try
296    {
297      // Guaranteed to return a non-null array
298      Object[] listeners = listenerList.getListenerList();
299      TreeModelEvent e = null;
300      // Process the listeners last to first, notifying
301      // those that are interested in this event
302      for ( int i = listeners.length - 2; i >= 0; i -= 2 )
303      {
304        if ( listeners[i] == TreeModelListener.class )
305        {
306          // Lazily create the event:
307          if ( e == null )
308          {
309            e = new TreeModelEvent( source, path, childIndices, children );
310          }
311          ( ( TreeModelListener ) listeners[ i + 1] ).treeStructureChanged( e );
312        }
313      }
314    }
315    catch ( Throwable t )
316    {
317      t.printStackTrace();
318    }
319  }
320
321  protected void fireTreeStructureChanged( Object source, Object[] path )
322  {
323    // int[] childIndices, Object[] children) {
324    // Guaranteed to return a non-null array
325    try
326    {
327      Object[] listeners = listenerList.getListenerList();
328      TreeModelEvent e = null;
329      // Process the listeners last to first, notifying
330      // those that are interested in this event
331      for ( int i = listeners.length - 2; i >= 0; i -= 2 )
332      {
333        if ( listeners[i] == TreeModelListener.class )
334        {
335          // Lazily create the event:
336          if ( e == null )
337          {
338            e = new TreeModelEvent( source, path );
339            // childIndices, children);
340          }
341          ( ( TreeModelListener ) listeners[ i + 1] ).treeStructureChanged( e );
342        }
343      }
344    }
345    catch ( Throwable t )
346    {
347      t.printStackTrace();
348    }
349  }
350
351  public void setValueAt( Object value, Object node, int column )
352  {
353
354  }
355
356  /**
357   * Builds the parents of node up to and including the root node, where the
358   * original node is the last element in the returned array. The length of
359   * the returned array gives the node's depth in the tree.
360   *
361   * @param aNode
362   *            the TreeNode to get the path for
363   * @return The pathToRoot value
364   */
365  public Object[] getPathToRoot( TreeNode aNode )
366  {
367    return getPathToRoot( aNode, 0 );
368  }
369
370  /**
371   * Returns an array of all the tree model listeners registered on this
372   * model.
373   *
374   * @return all of this model's <code>TreeModelListener</code> s or an
375   *         empty array if no tree model listeners are currently registered
376   * @see #addTreeModelListener
377   * @see #removeTreeModelListener
378   * @since 1.4
379   */
380  public TreeModelListener[] getTreeModelListeners()
381  {
382    return ( TreeModelListener[] ) listenerList.getListeners( TreeModelListener.class );
383  }
384
385  public boolean isCellEditable( Object node, int column )
386  {
387    return false;
388  }
389
390  public int getChildCount( Object parent )
391  {
392    return ( ( DefaultMutableTreeNode ) parent ).getChildCount();
393  }
394
395  public int getIndexOfChild( Object parent, Object child )
396  {
397    return ( ( DefaultMutableTreeNode ) parent ) .getIndex( ( DefaultMutableTreeNode ) child );
398  }
399
400  public abstract Class getColumnClass( int column );
401
402  public abstract Object getRoot();
403
404  public boolean isLeaf( Object node )
405  {
406    return ( ( DefaultMutableTreeNode ) node ).isLeaf();
407  }
408
409  public Object getChild( Object parent, int index )
410  {
411    return ( ( DefaultMutableTreeNode ) parent ).getChildAt( index );
412  }
413
414  public abstract int getColumnCount();
415
416  public abstract String getColumnName( int column );
417
418  public abstract Object getValueAt( Object node, int column );
419
420  /**
421   * Builds the parents of node up to and including the root node, where the
422   * original node is the last element in the returned array. The length of
423   * the returned array gives the node's depth in the tree.
424   *
425   * @param aNode
426   *            the TreeNode to get the path for
427   * @param depth
428   *            an int giving the number of steps already taken towards the
429   *            root (on recursive calls), used to size the returned array
430   * @return an array of TreeNodes giving the path from the root to the
431   *         specified node
432   */
433  protected Object[] getPathToRoot( TreeNode aNode, int depth )
434  {
435    Object[] retNodes;
436    // This method recurses, traversing towards the root in order
437    // size the array. On the way back, it fills in the nodes,
438    // starting from the root and working back to the original node.
439
440    /*
441                 * Check for null, in case someone passed in a null node, or they passed
442                 * in an element that isn't rooted at root.
443    */
444    if ( aNode == null )
445    {
446      if ( depth == 0 )
447      {
448        return null;
449      }
450      else
451      {
452        retNodes = new Object[depth];
453      }
454    }
455    else
456    {
457      depth++;
458      if ( aNode == getRoot() )
459      {
460        retNodes = new Object[depth];
461      }
462      else
463      {
464        retNodes = getPathToRoot( aNode.getParent(), depth );
465      }
466      retNodes[ retNodes.length - depth] = aNode;
467    }
468    return retNodes;
469  }
470
471}
472