Coverage Report - net.wotonomy.ui.swing.TreeAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
TreeAssociation
0% 
0% 
2.238
 
 1  0
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Intersect Software Corporation
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, see http://www.gnu.org
 17  
 */
 18  
 
 19  
 package net.wotonomy.ui.swing;
 20  
 
 21  
 import java.awt.EventQueue;
 22  
 import java.awt.Rectangle;
 23  
 import java.awt.event.FocusEvent;
 24  
 import java.awt.event.FocusListener;
 25  
 import java.util.Enumeration;
 26  
 import java.util.Iterator;
 27  
 import java.util.Vector;
 28  
 
 29  
 import javax.swing.JTree;
 30  
 import javax.swing.event.TreeExpansionEvent;
 31  
 import javax.swing.event.TreeExpansionListener;
 32  
 import javax.swing.event.TreeSelectionEvent;
 33  
 import javax.swing.event.TreeWillExpandListener;
 34  
 import javax.swing.tree.ExpandVetoException;
 35  
 import javax.swing.tree.TreePath;
 36  
 
 37  
 import net.wotonomy.foundation.NSArray;
 38  
 import net.wotonomy.foundation.NSMutableArray;
 39  
 import net.wotonomy.ui.EODisplayGroup;
 40  
 
 41  
 /**
 42  
 * TreeAssociation is a TreeModelAssociation further
 43  
 * customized for JTrees.  It binds a JTree to a display group's
 44  
 * list of displayable objects, each of which may have
 45  
 * a list of child objects managed by another display
 46  
 * group, and so on.  TreeAssociation works exactly
 47  
 * like a ListAssociation, with the additional capability
 48  
 * to specify a "Children" aspect, that will allow child
 49  
 * objects to be retrieved from a parent display group.
 50  
 * Note that the children aspect requires the bound
 51  
 * display group to have a DataSource that can vend a
 52  
 * DataSource appropriate for the bound key  That data
 53  
 * source is then used to create data sources for
 54  
 * child nodes, and so on.
 55  
 *
 56  
 * <ul>
 57  
 *
 58  
 * <li>titles: a property convertable to a string for
 59  
 * display in the nodes of the tree.  The objects in
 60  
 * the bound display group will be used to populate the
 61  
 * initial root nodes of the tree (more accurately,
 62  
 * children of the offscreen root node in the tree).</li>
 63  
 *
 64  
 * <li>children: a property of a node value that returns
 65  
 * zero, one or many objects, each of which will correspond
 66  
 * to a child node for the corresponding node in the tree.
 67  
 * The data source of the bound display group is replaced 
 68  
 * a data source that populates the display group with
 69  
 * the visible nodes in the tree component as determined by 
 70  
 * calling fetchObjectsIntoChildrenGroup.
 71  
 * If this aspect is not bound, the tree behaves like a list.
 72  
 * <br><br>
 73  
 * Binding this aspect with a null display group is the same
 74  
 * as binding it with the titles display group.  
 75  
 * In this configuration the contents of the titles 
 76  
 * display group will be replaced with the visible nodes in the
 77  
 * tree component, as specified above, replacing the existing 
 78  
 * data source.  
 79  
 * <br><br>
 80  
 * In that case, the display groups for the nodes in
 81  
 * the tree will still use the original data source
 82  
 * for resolving their children key, and programmatically
 83  
 * setting the contents of the display group will still 
 84  
 * repopulate the root nodes of the tree.
 85  
 * </li>
 86  
 *
 87  
 * <li>isLeaf: a property of a node value that returns
 88  
 * a value convertable to a boolean value (aside from
 89  
 * an actual boolean value, zeroes evaluate to true,
 90  
 * as does any String containing "yes" or "true" or that
 91  
 * is convertable to a number equal to zero; other values
 92  
 * evaluate to false).  
 93  
 * <br><br>
 94  
 * If the isLeaf aspect is not bound,
 95  
 * the tree must force nodes to load their children to
 96  
 * determine whether they are leaf nodes (in effect
 97  
 * loading the grandchildren for any expanded node).
 98  
 * If bound, child loading is deferred until the node
 99  
 * is actually expanded.  
 100  
 * <br><br>
 101  
 * For example, binding this value to a null
 102  
 * display group and the key "false" will result in a
 103  
 * deferred-loading tree that works much like Windows
 104  
 * Explorer's network volume browser - all nodes appear
 105  
 * with "pluses" until they are expanded.
 106  
 * <br><br>
 107  
 * Note that the display group is ignored: the property
 108  
 * will be applied directly to the object corresponding
 109  
 * to the node.</li>
 110  
 *
 111  
 * </ul>
 112  
 *
 113  
 * All other usage is as TreeModelAssociation.
 114  
 *
 115  
 * @author michael@mpowers.net
 116  
 * @author $Author: cgruber $
 117  
 * @version $Revision: 904 $
 118  
 */
 119  
 public class TreeAssociation extends TreeModelAssociation
 120  
     implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable
 121  
 {
 122  
     private boolean isExpanding;
 123  
     private Vector nodeQueue;
 124  
     private Vector structureQueue;
 125  
     private boolean isRunning;
 126  
     
 127  
     /**
 128  
     * Constructor expecting a JTree.
 129  
     */
 130  
     public TreeAssociation ( Object anObject )
 131  
     {
 132  0
         super( anObject );
 133  0
         init();
 134  0
     }
 135  
 
 136  
     /**
 137  
     * Constructor expecting a JTree or similar component
 138  
     * and specifying a label for the root node.
 139  
     */
 140  
     public TreeAssociation( Object anObject, Object aRootLabel )
 141  
     {
 142  0
         super( anObject );
 143  0
         init();
 144  0
         rootLabel = aRootLabel;
 145  0
         rootNode.setUserObject( aRootLabel );
 146  0
     }
 147  
     
 148  
     // convenience
 149  
     private JTree component()
 150  
     {
 151  0
         return (JTree) object();
 152  
     }
 153  
 
 154  
     /**
 155  
     * Called by both constructors.
 156  
     */
 157  
     protected void init()
 158  
     {
 159  0
         isExpanding = false;
 160  0
         isRunning = false;
 161  0
         nodeQueue = new Vector();
 162  0
         structureQueue = new Vector();
 163  0
         component().addFocusListener( this );
 164  0
         component().addTreeExpansionListener( this );
 165  0
         component().addTreeWillExpandListener( this );
 166  0
     }
 167  
 
 168  
     /**
 169  
     * Returns whether this class can control the specified
 170  
     * object.
 171  
     */
 172  
     public static boolean isUsableWithObject ( Object anObject )
 173  
     {
 174  0
         return ( anObject instanceof JTree );
 175  
     }
 176  
 
 177  
     /**
 178  
     * Overridden to not fire events during initial population.
 179  
     */
 180  
     public void establishConnection ()
 181  
     {
 182  0
         isExpanding = true;
 183  0
         super.establishConnection();
 184  0
         isExpanding = false;
 185  0
     }
 186  
     
 187  
     // interface TreeSelectionListener
 188  
 
 189  
     public void valueChanged(TreeSelectionEvent e)
 190  
     {
 191  0
         if ( ! isListening ) return;
 192  
 // NOTE: This approach causes focus rectangle to perceptibly trail
 193  
 // the selection rectangle, presumably because we're called after the
 194  
 // new selection has been processed but before the focus is processed.
 195  
 // Users don't like it, so we're going with a preference to use
 196  
 // invokeLater.
 197  
 /*
 198  
         // paint immediately before updating the display group and
 199  
         //   before any potentially lengthy second-order effects happen:
 200  
         //   this improves user-perceived responsiveness of big apps
 201  
         if ( object() instanceof javax.swing.JComponent )
 202  
         {
 203  
             javax.swing.JComponent component = (javax.swing.JComponent)object();
 204  
             component.paintImmediately( component.getBounds() );
 205  
         }
 206  
         selectFromSelectionModel();
 207  
 */
 208  
 
 209  
 // NOTE: This approach uses invoke later to cause the update of
 210  
 // the display group (which could be lengthly if that in turn
 211  
 // causes other things to update) to happen after the tree repaints.
 212  
 // Users like this because it "feels faster", but developers have
 213  
 // to remember that if they listen to tree selection events they
 214  
 // will have to do a similar invoke later if they check the display
 215  
 // group.
 216  
 
 217  0
         Runnable select = new Runnable()
 218  
         {
 219  0
             public void run()
 220  
             {
 221  0
                 selectFromSelectionModel();
 222  0
             }
 223  
         };
 224  
         
 225  0
         if ( selectionPaintedImmediately )
 226  
         {
 227  0
             if ( object() instanceof java.awt.Component )
 228  
             {
 229  0
                 ((java.awt.Component)object()).repaint();
 230  
             }
 231  0
             EventQueue.invokeLater( select );
 232  0
         }
 233  
         else
 234  
         {
 235  0
             select.run();
 236  
         }
 237  0
     }
 238  
 
 239  
     /**
 240  
     * Overridden to check whether the node is visible 
 241  
     * in the tree on screen.  Offscreen in a scrollpane
 242  
     * does not count.
 243  
     */
 244  
     public boolean isVisible(Object node)
 245  
     {
 246  0
         JTree tree = (JTree) object();
 247  0
         TreePath path = ((DisplayGroupNode)node).treePath();
 248  0
         if ( tree.isVisible( path ) )
 249  
         {
 250  0
             Rectangle rowRect = tree.getPathBounds( path );
 251  0
             if ( rowRect != null )
 252  
             {
 253  0
                 Rectangle visible = tree.getVisibleRect();
 254  0
                 if ( visible != null )
 255  
                 {
 256  
 //System.out.println( "isVisible: intersects: " + visible.intersects( rowRect ) );        
 257  0
                     return visible.intersects( rowRect );
 258  
                 }
 259  
             }
 260  
         }
 261  
 //System.out.println( "isVisible: false" );        
 262  0
         return false;
 263  
     }
 264  
 
 265  
     /**
 266  
     * Fires a tree nodes changed event to all listeners.
 267  
     * Provided as a convenience if you need to make manual
 268  
     * changes to the tree model.
 269  
     */
 270  
     public void fireTreeNodesChanged(final Object source,
 271  
                                      final Object[] path,
 272  
                                      final int[] childIndices,
 273  
                                      final Object[] children)
 274  
     {
 275  0
         if ( !isExpanding )
 276  
         {
 277  0
             for ( int i = 0; i < children.length; i++ )
 278  
             {
 279  0
                 nodeQueue.add( children[i] );
 280  
             }
 281  0
             if ( !isRunning )
 282  
             {
 283  0
                 isRunning = true;
 284  0
                 EventQueue.invokeLater( this );
 285  
             }
 286  
         }
 287  0
     }
 288  
 
 289  
     /**
 290  
     * Fires a tree nodes inserted event to all listeners.
 291  
     * Provided as a convenience if you need to make manual
 292  
     * changes to the tree model.
 293  
     */
 294  
     public void fireTreeNodesInserted(Object source,
 295  
                                      Object[] path,
 296  
                                      int[] childIndices,
 297  
                                      Object[] children)
 298  
     {
 299  0
         if ( !isExpanding )
 300  
         {
 301  0
             super.fireTreeNodesInserted( source, path, childIndices, children );
 302  0
             EventQueue.invokeLater( this );
 303  
         }
 304  0
     }
 305  
 
 306  
     /**
 307  
     * Fires a tree nodes removed event to all listeners.
 308  
     * Provided as a convenience if you need to make manual
 309  
     * changes to the tree model.
 310  
     */
 311  
     public void fireTreeNodesRemoved(Object source,
 312  
                                     Object[] path,
 313  
                                     int[] childIndices,
 314  
                                     Object[] children)
 315  
     {
 316  0
         if ( !isExpanding )
 317  
         {
 318  0
             super.fireTreeNodesRemoved( source, path, childIndices, children );
 319  0
             EventQueue.invokeLater( this );
 320  
         }
 321  0
     }
 322  
 
 323  
     /**
 324  
     * Fires a tree structure changed event to all listeners.
 325  
     * Provided as a convenience if you need to make manual
 326  
     * changes to the tree model.
 327  
     */
 328  
     public void fireTreeStructureChanged(Object source,
 329  
                                     Object[] path,
 330  
                                     int[] childIndices,
 331  
                                     Object[] children)
 332  
     {
 333  0
         if ( !isExpanding )
 334  
         {
 335  0
             structureQueue.add( path[path.length-1] );
 336  0
             if ( !isRunning )
 337  
             {
 338  0
                 isRunning = true;
 339  0
                 EventQueue.invokeLater( this );
 340  
             }
 341  
         }
 342  0
     }
 343  
 
 344  
     /**
 345  
     * Overridden to return all visible rows in the tree.
 346  
     */
 347  
     public NSArray objectsFetchedIntoChildrenGroup()
 348  
     {
 349  0
         JTree tree = (JTree) object();
 350  0
         NSMutableArray objectList = new NSMutableArray();
 351  
         
 352  0
         int count = tree.getRowCount();
 353  0
         for ( int i = 0; i < count; i++ )
 354  
         {
 355  0
             objectList.add(  
 356  0
                 ((DisplayGroupNode) tree.getPathForRow( i ).getLastPathComponent()).object() );
 357  
         }
 358  
 
 359  
 //new net.wotonomy.ui.swing.util.StackTraceInspector( Integer.toString( objectList.size() )  );
 360  0
         return objectList;
 361  
     }
 362  
 
 363  
     // interface FocusListener
 364  
     
 365  
         /**
 366  
         * Notifies of beginning of edit.
 367  
         */
 368  
     public void focusGained(FocusEvent evt)
 369  
     {
 370  
                 Object o;
 371  
                 EODisplayGroup displayGroup;
 372  0
         Enumeration e = aspects().objectEnumerator();
 373  0
                 while ( e.hasMoreElements() )
 374  
                 {
 375  0
                         displayGroup =
 376  0
                                 displayGroupForAspect( e.nextElement().toString() );
 377  0
                         if ( displayGroup != null )
 378  
                         {
 379  0
                                 displayGroup.associationDidBeginEditing( this );
 380  0
                         }
 381  
                 }
 382  0
     }
 383  
 
 384  
         /**
 385  
         * Updates object on focus lost and notifies of end of edit.
 386  
         */
 387  
     public void focusLost(FocusEvent evt)
 388  
     {
 389  0
         if ( ! component().isEditing() )
 390  
         {
 391  
             Object o;
 392  
             EODisplayGroup displayGroup;
 393  0
             Enumeration e = aspects().objectEnumerator();
 394  0
             while ( e.hasMoreElements() )
 395  
             {
 396  0
                 displayGroup =
 397  0
                     displayGroupForAspect( e.nextElement().toString() );
 398  0
                 if ( displayGroup != null )
 399  
                 {
 400  0
                     displayGroup.associationDidEndEditing( this );
 401  0
                 }
 402  
             }
 403  
         }
 404  0
     }
 405  
     
 406  
     // interface TreeWillExpandListener
 407  
     
 408  
     public void treeWillExpand(TreeExpansionEvent event)
 409  
                     throws ExpandVetoException
 410  
     {
 411  0
         isExpanding = true;
 412  0
     }
 413  
                     
 414  
     public void treeWillCollapse(TreeExpansionEvent event)
 415  
                       throws ExpandVetoException                    
 416  
     {
 417  
         // do nothing
 418  0
     }
 419  
                     
 420  
     // interface TreeExpansionListener
 421  
     
 422  
     /**
 423  
     * Updates the children display group, if any.
 424  
     */
 425  
     public void treeExpanded(TreeExpansionEvent event)
 426  
     { //System.out.println( "treeExpanded: " + event.getPath().getLastPathComponent() );        
 427  0
         isExpanding = false;
 428  0
         if ( childrenDisplayGroup != null )
 429  
         {
 430  0
             removeAsListener(); // prevent data source refetch: see fetchObjects()
 431  0
             childrenDisplayGroup.fetch();
 432  0
             addAsListener();
 433  
         }
 434  0
     }
 435  
     
 436  
     /**
 437  
     * Updates the children display group, if any.
 438  
     */
 439  
     public void treeCollapsed(TreeExpansionEvent event)
 440  
     {
 441  0
         if ( childrenDisplayGroup != null )
 442  
         {
 443  0
             removeAsListener(); // prevent data source refetch: see fetchObjects()
 444  0
             childrenDisplayGroup.fetch();
 445  0
             addAsListener();
 446  
         }
 447  0
     }
 448  
     
 449  
     // interface Runnable
 450  
     
 451  
     /**
 452  
     * Fires any queued node changed and structure changed events.
 453  
     * Typically invoked on a delayed event loop.
 454  
     */
 455  
     public void run()
 456  
     {
 457  
         DisplayGroupNode node;    
 458  
         int index;
 459  
         Iterator i;
 460  
         
 461  0
         i = nodeQueue.iterator();
 462  0
         while ( i.hasNext() )
 463  
         {
 464  0
             node = (DisplayGroupNode) i.next();
 465  0
             index = ((DisplayGroupNode)node.parentGroup).getIndex( node );
 466  0
             if ( ( index != -1 )
 467  0
             && ( node.treePath().getParentPath() != null ) )
 468  
             {
 469  0
                 super.fireTreeNodesChanged( 
 470  0
                     node,
 471  0
                     node.treePath().getParentPath().getPath(),
 472  0
                     new int[] { index },
 473  0
                     new Object[] { node } );
 474  0
             }
 475  
         }
 476  0
         nodeQueue.clear();
 477  
         
 478  0
         i = structureQueue.iterator();
 479  0
         while ( i.hasNext() )
 480  
         {
 481  0
             node = (DisplayGroupNode) i.next();
 482  0
             super.fireTreeStructureChanged( 
 483  0
                 node,
 484  0
                 node.treePath().getPath(),
 485  0
                 null,
 486  0
                 null );
 487  0
         }
 488  0
         structureQueue.clear();
 489  
         
 490  0
         isRunning = false;
 491  
 /*      
 492  
         EventQueue.invokeLater( new Runnable() { public void run() {
 493  
             ((JTree)object()).treeDidChange();
 494  
             ((JTree)object()).getParent().invalidate();
 495  
             ((JTree)object()).getParent().validate();
 496  
             ((JTree)object()).getParent().update( ((JTree)object()).getGraphics() );
 497  
             
 498  
 //            ((JTree)object()).getParent().doLayout();
 499  
 //            ((JTree)object()).getParent().repaint();
 500  
 //            ((JTree)object()).repaint();
 501  
 //            ((JTree)object()).updateUI();
 502  
         } } );
 503  
 */       
 504  0
     }
 505  
 }
 506  
 
 507  
 /*
 508  
  * $Log$
 509  
  * Revision 1.2  2006/02/18 23:19:05  cgruber
 510  
  * Update imports and maven dependencies.
 511  
  *
 512  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 513  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 514  
  *
 515  
  * Revision 1.55  2004/02/05 02:18:50  mpowers
 516  
  * Super was calling back into this class before init() was called.
 517  
  *
 518  
  * Revision 1.54  2003/08/06 23:07:52  chochos
 519  
  * general code cleanup (mostly, removing unused imports)
 520  
  *
 521  
  * Revision 1.53  2003/06/03 14:49:48  mpowers
 522  
  * Now correctly calculating isVisible based on the component visible rect.
 523  
  * Now deferring node changed events to a later event queue to allow repaints
 524  
  * to happen after all changes have taken effect.
 525  
  *
 526  
  * Revision 1.52  2002/05/03 21:41:18  mpowers
 527  
  * No longer clearing the selection model when updating from display group:
 528  
  * we now only modify if a change needs to be made.
 529  
  * No longer listening for selection change during firing of delete events:
 530  
  * delete events cause JTree's to update their selection model.
 531  
  * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges()
 532  
  * must happen after the screen is painted, or the selection is not displayed.
 533  
  *
 534  
  * Revision 1.51  2002/04/19 21:18:45  mpowers
 535  
  * Removed tree event coalescing, which was causing way too many problems.
 536  
  * The fireChangeEvent algorithm is way faster than before, so we should
 537  
  * still be better off than before.  At least now, we don't have to track
 538  
  * whether the view component has encountered a particular node.
 539  
  *
 540  
  * Revision 1.49  2002/04/12 21:05:58  mpowers
 541  
  * Now distinguishing changes in titles group even better.
 542  
  *
 543  
  * Revision 1.48  2002/04/12 20:36:31  mpowers
 544  
  * Now distinguishing between changes made on titles group by tree expansion
 545  
  * versus external changes which should cause us to repopulate root nodes.
 546  
  *
 547  
  * Revision 1.47  2002/04/10 21:20:04  mpowers
 548  
  * Better handling for tree nodes when working with editing contexts.
 549  
  * Better handling for invalidation.  No longer broadcasting events
 550  
  * when nodes have not been "registered" in the tree.
 551  
  *
 552  
  * Revision 1.46  2002/03/27 20:44:53  mpowers
 553  
  * Added isVisible test for node.
 554  
  *
 555  
  * Revision 1.45  2002/03/08 23:19:57  mpowers
 556  
  * Refactoring of DelegatingTreeDataSource to facilitate binding of titles
 557  
  * and children aspects to the same display group.
 558  
  *
 559  
  * Revision 1.44  2002/03/07 23:04:36  mpowers
 560  
  * Refining TreeColumnAssociation.
 561  
  *
 562  
  * Revision 1.43  2002/03/06 13:04:15  mpowers
 563  
  * Implemented cascading qualifiers in tree nodes.
 564  
  *
 565  
  * Revision 1.42  2002/03/05 23:18:28  mpowers
 566  
  * Added documentation.
 567  
  * Added isSelectionPaintedImmediate and isSelectionTracking attributes
 568  
  * to TableAssociation.
 569  
  * Added getTableAssociation to TableColumnAssociation.
 570  
  *
 571  
  * Revision 1.41  2002/03/01 23:42:08  mpowers
 572  
  * Implemented TreeColumnAssociation, and updated documentation.
 573  
  *
 574  
  * Revision 1.40  2002/03/01 20:41:39  mpowers
 575  
  * Now a focus listener and an expansion listener.
 576  
  *
 577  
  * Revision 1.39  2002/02/27 23:19:17  mpowers
 578  
  * Refactoring of TreeAssociation to create TreeModelAssociation parent.
 579  
  *
 580  
  *
 581  
  */
 582