Coverage Report - net.wotonomy.ui.swing.DisplayGroupNode
 
Classes in this File Line Coverage Branch Coverage Complexity
DisplayGroupNode
0% 
0% 
2.877
 
 1  0
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2001 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.Component;
 22  
 import java.lang.ref.Reference;
 23  
 import java.lang.ref.WeakReference;
 24  
 import java.util.Enumeration;
 25  
 import java.util.Iterator;
 26  
 import java.util.LinkedList;
 27  
 import java.util.List;
 28  
 import java.util.Vector;
 29  
 
 30  
 import javax.swing.tree.TreePath;
 31  
 
 32  
 import net.wotonomy.control.EODataSource;
 33  
 import net.wotonomy.control.EODelayedObserver;
 34  
 import net.wotonomy.control.EOEditingContext;
 35  
 import net.wotonomy.control.EOObjectStore;
 36  
 import net.wotonomy.control.EOObserverCenter;
 37  
 import net.wotonomy.control.EOQualifier;
 38  
 import net.wotonomy.control.PropertyDataSource;
 39  
 import net.wotonomy.foundation.NSArray;
 40  
 import net.wotonomy.foundation.NSDictionary;
 41  
 import net.wotonomy.foundation.NSMutableDictionary;
 42  
 import net.wotonomy.foundation.NSNotification;
 43  
 import net.wotonomy.foundation.internal.ValueConverter;
 44  
 import net.wotonomy.foundation.internal.WotonomyException;
 45  
 import net.wotonomy.ui.EODisplayGroup;
 46  
 import net.wotonomy.ui.swing.TreeModelAssociation.DelegatingTreeDataSource;
 47  
 
 48  
 /**
 49  
 * DisplayGroupNodes are used as nodes in the
 50  
 * TreeModelAssociation's implementation of TreeModel,
 51  
 * and is tightly coupled with TreeModelAssociation
 52  
 * and MasterDetailAssociation. <br><br>
 53  
 *
 54  
 * Even though it is no longer package access,
 55  
 * don't rely on this class because we want to
 56  
 * have the option of completely replacing this
 57  
 * approach in the future.
 58  
 *
 59  
 * @author michael@mpowers.net
 60  
 * @author $Author: cgruber $
 61  
 * @version $Revision: 904 $
 62  
 */
 63  
     abstract public class DisplayGroupNode
 64  
         extends EODisplayGroup
 65  
     {
 66  
         protected TreeModelAssociation parentAssociation;
 67  
         protected EODelayedObserver targetObserver;
 68  
         protected NSMutableDictionary childNodes;
 69  
         protected EODisplayGroup parentGroup;
 70  
         protected Object target;
 71  
         protected boolean isFetched;
 72  
         protected boolean isFetchNeeded;
 73  
         protected boolean useParentOrderings;
 74  
         protected boolean useParentQualifier;
 75  
 
 76  
         /**
 77  
         * Constructor for all nodes.
 78  
         * Root node must have a null target.
 79  
         */
 80  0
         public DisplayGroupNode(
 81  
             TreeModelAssociation aParentAssociation,
 82  
             EODisplayGroup aParentGroup,
 83  
             Object aTarget )
 84  0
         {
 85  
 //new net.wotonomy.ui.swing.util.StackTraceInspector( ""+aTarget );
 86  
 //System.out.println( "DisplayGroupNode.new: " + aTarget );            
 87  0
             parentAssociation = aParentAssociation;
 88  0
             target = null;
 89  0
             targetObserver = null;
 90  0
             parentGroup = aParentGroup;
 91  0
             childNodes = new NSMutableDictionary();
 92  0
             isFetched = false;
 93  0
             isFetchNeeded = false;
 94  0
             useParentOrderings = true;
 95  0
             useParentQualifier = true;
 96  
 
 97  0
             EODataSource parentSource = null;
 98  0
             if ( parentGroup != null )
 99  
             {
 100  0
                 parentSource = parentGroup.dataSource();
 101  0
             }
 102  
             else
 103  0
             if ( parentAssociation.titlesDisplayGroup != null )
 104  
             {
 105  0
                 parentSource = parentAssociation.titlesDisplayGroup.dataSource();
 106  
             }
 107  
 
 108  
             // create child datasource
 109  0
             if ( aTarget != null ) // not root node
 110  
             {
 111  0
                 if ( parentAssociation.childrenKey != null )
 112  
                 {
 113  0
                     if ( parentSource == null )
 114  
                     {
 115  0
                         throw new WotonomyException(
 116  0
                             "Need a data source when children aspect is bound." );
 117  
                     }
 118  
 
 119  0
                     NSArray displayedObjects = parentGroup.displayedObjects();
 120  0
                     EODataSource childSource = parentSource.dataSourceQualifiedByKey(
 121  0
                         parentAssociation.childrenKey );
 122  0
                     childSource.qualifyWithRelationshipKey(
 123  0
                         parentAssociation.childrenKey, aTarget );
 124  
 
 125  
                     // create new display group using child data source
 126  0
                     this.setDataSource( childSource );
 127  
 
 128  
                     // establish observer for target object
 129  0
                     setTarget( aTarget );
 130  0
                 }
 131  
                 else // only titles is bound
 132  
                 {
 133  
                     // establish observer for target object
 134  0
                     setTarget( aTarget );
 135  
                     
 136  0
                     setDataSource( new PropertyDataSource() 
 137  
                     {
 138  0
                         public NSArray fetchObjects()
 139  
                         {
 140  0
                             return new NSArray();
 141  
                         }
 142  
                     } );
 143  
                 }
 144  0
             }
 145  
             else // else root node
 146  
             {
 147  
                 // root node uses PropertyDataSource by default
 148  0
                 if ( parentSource == null )
 149  
                 {
 150  0
                     setDataSource( new PropertyDataSource() 
 151  
                     {
 152  0
                         public NSArray fetchObjects()
 153  
                         {
 154  0
                             if ( parentGroup != null )
 155  
                             {
 156  0
                                 return parentGroup.displayedObjects();
 157  
                             }
 158  0
                             return null;
 159  
                         }
 160  
                     } );
 161  0
                 }
 162  
                 else
 163  
                 {
 164  
                     // root node uses parent source directly
 165  0
                     setDataSource( parentSource );
 166  
                 }
 167  
             }
 168  0
         }
 169  
         
 170  
         /**
 171  
         * Overridden to unregister as an editor of the editing context,
 172  
         * since we don't directly present a user interface.
 173  
         */
 174  
         public void setDataSource ( EODataSource aDataSource )
 175  
         {
 176  0
             super.setDataSource( aDataSource );
 177  0
             if ( ( aDataSource != null )
 178  0
             &&   ( aDataSource.editingContext() != null ) )
 179  
             {
 180  0
                 aDataSource.editingContext().removeEditor( this );
 181  
             }
 182  0
         }
 183  
         
 184  
         /**
 185  
         * Returns whether the node should call fetch().
 186  
         */
 187  
         protected boolean isFetched()
 188  
         {
 189  0
             if ( isFetchNeeded() )
 190  
             {
 191  0
                 setFetchNeeded( false );
 192  0
                 fetch();
 193  
             }
 194  0
             return isFetched;
 195  
         }
 196  
 
 197  
         /**
 198  
         * Sets whether the node should call fetch().
 199  
         */
 200  
         protected void setFetched( boolean fetched )
 201  
         {
 202  
 //System.out.println( "DisplayGroupNode.setFetched: " + fetched + " : " + this + " : " + target );
 203  
 //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace();
 204  0
             isFetched = fetched;
 205  0
         }
 206  
 
 207  
         /**
 208  
         * Returns whether the node is in need of a refetch.
 209  
         */
 210  
         protected boolean isFetchNeeded()
 211  
         {
 212  0
             return isFetchNeeded;
 213  
         }
 214  
 
 215  
         /**
 216  
         * Returns whether the node should call fetch().
 217  
         */
 218  
         protected void setFetchNeeded( boolean fetchNeeded )
 219  
         {
 220  
 //System.out.println( "DisplayGroupNode.setFetchNeeded: " + fetchNeeded + " : " + this + " : " + target );
 221  
 //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace();
 222  0
             isFetchNeeded = fetchNeeded;
 223  0
         }
 224  
 
 225  
         /**
 226  
         * Subclasses should override this method to fire an appropriate insertion event.
 227  
         */
 228  
         protected void fireNodesInserted( Object[] path, int[] indexes, Object[] objects )
 229  
         {
 230  
 //System.out.println( "fireNodesInserted: " + this );            
 231  0
             parentAssociation.fireTreeNodesInserted(
 232  0
                 this, path, indexes, objects );
 233  0
         }
 234  
 
 235  
         /**
 236  
         * Subclasses should override this method to fire an appropriate change event.
 237  
         */
 238  
         protected void fireNodesChanged( Object[] path, int[] indexes, Object[] objects )
 239  
         {
 240  
 //System.out.println( "fireNodesChanged: " + this );            
 241  0
             parentAssociation.fireTreeNodesChanged(
 242  0
                 this, path, indexes, objects );
 243  0
         }
 244  
 
 245  
         /**
 246  
         * Subclasses should override this method to fire an appropriate deletion event.
 247  
         */
 248  
         protected void fireNodesRemoved( Object[] path, int[] indexes, Object[] objects )
 249  
         {
 250  
 //System.out.println( "fireNodesRemoved: " + this );            
 251  0
             parentAssociation.fireTreeNodesRemoved(
 252  0
                 this, path, indexes, objects );
 253  0
         }
 254  
 
 255  
         /**
 256  
         * Subclasses should override this method to fire an appropriate event.
 257  
         */
 258  
         protected void fireStructureChanged( Object[] path, int[] indexes, Object[] objects )
 259  
         {
 260  0
             parentAssociation.fireTreeStructureChanged(
 261  0
                 this, path, indexes, objects );
 262  0
         }
 263  
 
 264  
         /**
 265  
         * Overridden to broadcast a tree event after super executes.
 266  
         */
 267  
         public void insertObjectAtIndex ( Object anObject, int anIndex )
 268  
         {
 269  0
             int count = getChildCount(); // gets old count
 270  0
             if ( target == null )
 271  
             {
 272  
                 // if root node, forward to parent:
 273  
                 // circumventing delegating data source, if any
 274  0
                 EODataSource dataSource = parentGroup.dataSource();
 275  0
                 if ( dataSource instanceof DelegatingTreeDataSource )
 276  
                 {
 277  0
                     parentGroup.setDataSource( 
 278  0
                         ((DelegatingTreeDataSource)dataSource).delegateDataSource );
 279  
                 }
 280  0
                 parentGroup.insertObjectAtIndex( anObject, anIndex );
 281  0
                 if ( dataSource instanceof DelegatingTreeDataSource )
 282  
                 {
 283  0
                     parentGroup.setDataSource( dataSource );
 284  
                 }
 285  0
                 return; // prevent event from firing (?)
 286  
             }
 287  
             else // not root node
 288  
             {
 289  0
                 super.insertObjectAtIndex( anObject, anIndex );
 290  
             }
 291  0
         }
 292  
 
 293  
         /**
 294  
         * Overridden to broadcast a tree event after super executes.
 295  
         */
 296  
         public boolean deleteObjectAtIndex ( int anIndex )
 297  
         {
 298  
             boolean result;
 299  0
             Object node = getChildNodeAt( anIndex );
 300  0
             if ( target == null )
 301  
             {
 302  
                 // if root node, forward to parent:
 303  0
                 result = parentGroup.deleteObjectAtIndex( anIndex );
 304  0
             }
 305  
             else // not root node
 306  
             {
 307  0
                 result = super.deleteObjectAtIndex( anIndex );
 308  
             }
 309  
 
 310  0
             return result;
 311  
         }
 312  
 
 313  
         /**
 314  
         * Returns the child node that corresponds to the
 315  
         * specified index, creating it if necessary.
 316  
         * The index must be within bounds or an exception
 317  
         * is thrown.
 318  
         */
 319  
         public DisplayGroupNode getChildNodeAt( int anIndex )
 320  
         {
 321  0
             boolean wasFetched = isFetched();
 322  0
             if ( ! wasFetched ) fetch();
 323  0
             Object o = displayedObjects.objectAtIndex( anIndex );
 324  0
             DisplayGroupNode result = getChildNodeForObject( o );
 325  0
             if ( result == null )
 326  
             {
 327  0
                 result = createChildNodeForObject( o );
 328  
             }
 329  0
             return result;
 330  
         }
 331  
 
 332  
         /**
 333  
         * Returns a child node that corresponds to the
 334  
         * specified object, returning null if not found.
 335  
         */
 336  
         protected DisplayGroupNode getChildNodeForObject( Object anObject )
 337  
         {
 338  0
             return (DisplayGroupNode) 
 339  0
                 childNodes.objectForKey( new ReferenceKey( anObject ) );
 340  
         }
 341  
 
 342  
         /**
 343  
         * Creates a child node that corresponds to the
 344  
         * specified object.
 345  
         */
 346  
         private DisplayGroupNode createChildNodeForObject( Object anObject )
 347  
         {
 348  0
             DisplayGroupNode result = parentAssociation.createNode( this, anObject );
 349  0
             childNodes.setObjectForKey( result, new ReferenceKey( anObject ) );
 350  0
             return result;
 351  
         }
 352  
 
 353  
         /**
 354  
         * Returns a tree path of all DisplayGroupNodes leading
 355  
         * to this node, including the root node (but excluding the
 356  
         * titles display group).
 357  
         */
 358  
         public TreePath treePath()
 359  
         {
 360  0
             List path = new LinkedList();
 361  0
             EODisplayGroup node = this;
 362  0
             while ( node instanceof DisplayGroupNode )
 363  
             {
 364  
                 // insert at head of list
 365  0
                 path.add( 0, node );
 366  0
                 node = ((DisplayGroupNode)node).parentGroup;
 367  0
             }
 368  0
             return new TreePath( path.toArray() );
 369  
         }
 370  
 
 371  
         /**
 372  
         * Overridden to return the parent group's
 373  
         * sort ordering if useParentOrderings is true.
 374  
         * useParentOrderings is true by default.
 375  
         */
 376  
         public NSArray sortOrderings()
 377  
         {
 378  0
             if ( ( useParentOrderings )
 379  0
             && ( parentGroup != null ) )
 380  
             {
 381  0
                 return parentGroup.sortOrderings();
 382  
             }
 383  0
             return super.sortOrderings();
 384  
         }
 385  
 
 386  
         /**
 387  
         * Overridden to set useParentOrderings to false,
 388  
         * or true if aList is null.
 389  
         */
 390  
         public void setSortOrderings ( List aList )
 391  
         {
 392  0
             if ( aList == null )
 393  
             {
 394  0
                 useParentOrderings = true;
 395  0
             }
 396  
             else
 397  
             {
 398  0
                 useParentOrderings = false;
 399  0
                 super.setSortOrderings( aList );
 400  
             }
 401  0
         }
 402  
 
 403  
         /**
 404  
         * Overridden to return the parent group's
 405  
         * qualifier if useParentQualifier is true.
 406  
         * useParentQualifier is true by default.
 407  
         */
 408  
         public EOQualifier qualifier()
 409  
         {
 410  0
             if ( ( useParentQualifier )
 411  0
             && ( parentGroup != null ) )
 412  
             {
 413  0
                 return parentGroup.qualifier();
 414  
             }
 415  0
             return super.qualifier();
 416  
         }
 417  
 
 418  
         /**
 419  
         * Overridden to set useParentQualifier to false,
 420  
         * or true if aList is null.
 421  
         */
 422  
         public void setQualifier ( EOQualifier aQualifier )
 423  
         {
 424  0
             if ( aQualifier == null )
 425  
             {
 426  0
                 useParentQualifier = true;
 427  0
             }
 428  
             else
 429  
             {
 430  0
                 useParentQualifier = false;
 431  0
                 super.setQualifier( aQualifier );
 432  
             }
 433  0
         }
 434  
 
 435  
         /**
 436  
         * Overridden to set isFetched to true.
 437  
         */
 438  
         public boolean fetch()
 439  
         { 
 440  
 //System.out.println( "DisplayGroupNode.fetch: " + this + " : " );
 441  
 //if ( getClass().getName().indexOf( "Activity" ) != -1 )
 442  
 //{
 443  
 //    new net.wotonomy.ui.swing.util.StackTraceInspector( this.toString() );
 444  
 //}
 445  
             // set flag
 446  0
             setFetched( true );
 447  
 
 448  
             // skip root node
 449  0
             if ( target == null ) return true;
 450  
 
 451  
             // requalify
 452  0
             dataSource().qualifyWithRelationshipKey(
 453  0
                 parentAssociation.childrenKey, target );
 454  
 
 455  
             // call to super
 456  0
             return super.fetch();
 457  
 
 458  
 //boolean result = super.fetch();
 459  
 //System.out.println( displayedObjects() );
 460  
 //return result;
 461  
         }
 462  
 
 463  
         /**
 464  
         * Returns the object at the appropriate index
 465  
         * in the parent display group.
 466  
         */
 467  
         public Object object()
 468  
         {
 469  
             // if root node
 470  0
             if ( target == null )
 471  
             {
 472  0
                 return parentAssociation.rootLabel();
 473  
             }
 474  0
             return target;
 475  
         }
 476  
 
 477  
         /**
 478  
         * Returns the string value of the title property
 479  
         * on the object in the parent display group corresponding
 480  
         * to this index.  The tree renderer asks JTrees to
 481  
         * call this method to retrieve a value for display.
 482  
         */
 483  
         public String toString()
 484  
         {
 485  0
             Object result = getUserObject();
 486  0
             if ( result == null ) result = "[null]";
 487  0
             return result.toString();
 488  
         }
 489  
 
 490  
         // parts of interface TreeNode
 491  
 
 492  
         public int getChildCount()
 493  
         {
 494  0
             if ( ! isFetched() ) fetch();
 495  
 //if ( toString().indexOf("154.16406")!=-1){           
 496  
 //System.out.println( "getChildCount: " + displayedObjects.count() + " : " + this );            
 497  
 //new RuntimeException().printStackTrace();
 498  
 //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace();
 499  
 //}
 500  0
             return displayedObjects.count();
 501  
         }
 502  
 
 503  
         public int getIndex(DisplayGroupNode node)
 504  
         {
 505  0
             if ( ! isFetched() ) fetch();
 506  0
             return displayedObjects.indexOfObject(
 507  0
                 ((DisplayGroupNode)node).target );
 508  
         }
 509  
 
 510  
         public boolean getAllowsChildren()
 511  
         {
 512  0
             return true;
 513  
         }
 514  
 
 515  
         public boolean isLeaf()
 516  
         {
 517  
             // if not root node and isLeaf aspect is bound
 518  0
             if ( ( target != null )
 519  0
             &&   ( parentGroup != null )
 520  0
             &&   ( parentAssociation.leafKey != null ) )
 521  
             {
 522  
                 Object value;
 523  0
                 if ( parentAssociation.leafDisplayGroup != null )
 524  
                 {
 525  0
                     value = parentGroup.valueForObject(
 526  0
                         target, parentAssociation.leafKey );
 527  0
                 }
 528  
                 else
 529  
                 {
 530  0
                     value = parentAssociation.leafKey;
 531  
                 }
 532  
 
 533  
                 // getBoolean returns true for zero, among other things
 534  0
                 Object result = ValueConverter.getBoolean( value );
 535  0
                 if ( result != null )
 536  
                 {
 537  0
                     return ((Boolean)result).booleanValue();
 538  
                 }
 539  
             }
 540  
 
 541  
             // otherwise, we have to fetch and return count
 542  0
             return ( getChildCount() == 0 );
 543  
         }
 544  
 
 545  
         public Enumeration children()
 546  
         {
 547  0
             int count = getChildCount();
 548  0
             Vector v = new Vector();
 549  0
             for ( int i = 0; i < count; i++ )
 550  
             {
 551  0
                 v.add( getChildNodeAt( i ) );
 552  
             }
 553  0
             return v.elements();
 554  
         }
 555  
 
 556  
         // parts of interface MutableTreeNode
 557  
 
 558  
         public void insert(DisplayGroupNode aChild, int anIndex)
 559  
         {
 560  0
             insertObjectAtIndex(
 561  0
                 ((DisplayGroupNode)aChild).object(), anIndex );
 562  0
         }
 563  
 
 564  
         public void remove(int index)
 565  
         {
 566  0
             deleteObjectAtIndex( index );
 567  0
         }
 568  
 
 569  
         /**
 570  
         * Removes the node at the index corresponding
 571  
         * to the index of the object.
 572  
         */
 573  
         public void remove(DisplayGroupNode node)
 574  
         {
 575  0
             remove( getIndex( node ) );
 576  0
         }
 577  
 
 578  
         /**
 579  
         * Removes our object from the parent display group.
 580  
         */
 581  
         public void removeFromParent()
 582  
         {
 583  0
             int index = parentGroup.displayedObjects().indexOfIdenticalObject( target );
 584  0
             if ( index != NSArray.NotFound )
 585  
             {
 586  0
                 parentGroup.deleteObjectAtIndex( index );
 587  0
             }
 588  
             else
 589  
             {
 590  0
                 throw new WotonomyException(
 591  0
                     "Object not found in parent group: " + target );
 592  
             }
 593  0
         }
 594  
 
 595  
         /**
 596  
         * Removes our object from the parent display group
 597  
         * and adds it to the end of the specified node's children.
 598  
         */
 599  
         public void setParent(DisplayGroupNode newParent)
 600  
         {
 601  0
             removeFromParent();
 602  0
             newParent.insertObjectAtIndex(
 603  0
                 object(), newParent.displayedObjects.size() );
 604  0
         }
 605  
 
 606  
         /**
 607  
         * Returns the value of the displayed property in the parent display group
 608  
         * at the index that corresponds to the index of this node.
 609  
         */
 610  
         public Object getUserObject()
 611  
         {
 612  0
             return valueForKey( parentAssociation.titlesKey );
 613  
         }
 614  
 
 615  
         /**
 616  
         * Sets the value of the displayed property in the parent display group
 617  
         * at the index that corresponds to the index of this node.
 618  
         */
 619  
         public void setUserObject( Object aValue )
 620  
         {
 621  0
             setValueForKey( aValue, parentAssociation.titlesKey );
 622  0
         }
 623  
 
 624  
         /**
 625  
         * Returns a value from the object in the parent display group
 626  
         * at the index that corresponds to the index of this node.
 627  
         * For the root node, if the titles key is specified, the root
 628  
         * label is returned, otherwise null is returned.
 629  
         */
 630  
         public Object valueForKey( String aKey )
 631  
         {
 632  
             // if root node
 633  0
             if ( target == null )
 634  
             {
 635  
                 // compare by ref is okay for strings
 636  0
                 if ( aKey == parentAssociation.titlesKey )
 637  
                 {
 638  0
                     return parentAssociation.rootLabel();
 639  
                 }
 640  0
                 return null;
 641  
             }
 642  0
             return parentGroup.valueForObject( target, aKey );
 643  
         }
 644  
 
 645  
         /**
 646  
         * Sets a value on the object in the parent display group
 647  
         * at the index that corresponds to the index of this node.
 648  
         * For the root node, this method only works if aKey is the
 649  
         * titlesAspect's key, otherwise does nothing.
 650  
         */
 651  
         public void setValueForKey(Object aValue, String aKey)
 652  
         {
 653  
             // if root node, return.
 654  0
             if ( target == null )
 655  
             {
 656  
                 // compare by ref is okay for strings
 657  0
                 if ( aKey == parentAssociation.titlesKey )
 658  
                 {
 659  0
                     parentAssociation.setRootLabel( aValue );
 660  
 
 661  
                     // how to handle root node? tree event docs don't say.
 662  0
                     fireNodesChanged ( treePath().getPath(),
 663  0
                         new int[] { 0 },
 664  0
                         new Object[] { this } );
 665  
                 }
 666  0
                 return;
 667  
             }
 668  
 
 669  0
             parentGroup.setValueForObject(
 670  0
                 aValue, target, aKey );
 671  0
         }
 672  
 
 673  
         /**
 674  
         * Perform any clean up in this method.
 675  
         * The node will not be reused after this method is called.
 676  
         * This implementation removes itself from the parent's
 677  
         * set of child nodes, sets target and datasource to null,
 678  
         * and then calls disposeChildNodes().
 679  
         */
 680  
         protected void dispose()
 681  
         { //System.out.println( "dispose: " + this.getClass().getName() + " : " + this );
 682  0
             if ( parentGroup != null )
 683  
             { 
 684  0
                 ((DisplayGroupNode)parentGroup).childNodes.remove( 
 685  0
                     new ReferenceKey( target ) );
 686  
             }
 687  0
             setTarget( (Object) null );
 688  0
             setDataSource( null );
 689  0
             disposeChildNodes();
 690  0
         }
 691  
         
 692  
         /**
 693  
         * Calls dispose() on all child nodes.
 694  
         */
 695  
         protected void disposeChildNodes()
 696  
         {
 697  0
             Iterator i = new LinkedList(childNodes.values()).iterator();
 698  0
             while ( i.hasNext() )
 699  
             {
 700  0
                 ((DisplayGroupNode) i.next()).dispose();
 701  0
             }
 702  0
         }
 703  
         
 704  
         /**
 705  
         * Called after the target object posts a change notification.
 706  
         * This implementation re-fetches which triggers
 707  
         * updateDisplayedObjects to broadcast any tree events.
 708  
         * This method marks the parent object as changed if:
 709  
         * (1) this object is not registered in the editing context
 710  
         * of the titles display group's data source (if any), AND
 711  
         * (2) the children key is not in the list of attributes
 712  
         * of the parent object's EOClassDescription.
 713  
         */
 714  
         public void targetChanged()
 715  
         {
 716  
             // if not root node
 717  0
             if ( target != null )
 718  
             {
 719  
                 // if we're not root and not fetched, stop here.
 720  
                 //FIXME: with this, some nodes have old values when moved.
 721  
                 //FIXME: without this, nodes are unnecessarily fetched.
 722  
                 //FIXME: might have parent modify isFetched of certain child nodes.
 723  0
                 if ( isFetched() )
 724  
                 {
 725  0
                     fetch();
 726  0
                 }
 727  
                 else // not fetched - just update the display
 728  
                 {
 729  0
                     updateDisplayedObjects();
 730  
                 }
 731  
 /*
 732  
 //disabling this for performance reasons:
 733  
 //might reenable later or find an alternate approach
 734  
                 // check to see if we need to mark the parent object as changed
 735  
                 EOEditingContext context = dataSource().editingContext();
 736  
                 if ( ( context == null )
 737  
                 || ( context.globalIDForObject( target ) == null ) )
 738  
                 {
 739  
                     DisplayGroupNode parentNode = (DisplayGroupNode) parentGroup;
 740  
                     if ( parentNode.target != null )
 741  
                     {
 742  
                         // only notify if childrenKey is an attribute of parentDesc
 743  
                         //     (and therefore not a toOne or toMany relationship)
 744  
                         EOClassDescription parentDesc =
 745  
                             EOClassDescription.classDescriptionForClass(
 746  
                                 parentNode.target.getClass() );
 747  
                         if ( parentDesc.attributeKeys().contains( parentAssociation.childrenKey ) )
 748  
                         {
 749  
                             // only notify if no context is already observing the object
 750  
                             //     and we are an attribute key
 751  
                             EOObserverCenter.notifyObserversObjectWillChange( parentNode.target );
 752  
                         }
 753  
                     }
 754  
                 }
 755  
 */
 756  0
             }
 757  
             else // root node
 758  
             {
 759  0
                 setObjectArray( parentAssociation.titlesDisplayGroup.displayedObjects() );
 760  
             }
 761  
 
 762  
             // finally, broadcast change event for this node
 763  
             //   even though we're not sure if the displayed value changed.
 764  0
             fireNodeChanged();
 765  0
         }
 766  
 
 767  
         /**
 768  
         * Fires a change event for this node.
 769  
         */
 770  
         public void fireNodeChanged()
 771  
         {
 772  
             // if not root node
 773  0
             if ( target != null )
 774  
             {
 775  0
                 int index = ((DisplayGroupNode)parentGroup).getIndex( this );
 776  0
                 if ( ( index != -1 )
 777  0
                 && ( treePath().getParentPath() != null ) )
 778  
                 {
 779  0
                     fireNodesChanged (
 780  0
                     treePath().getParentPath().getPath(),
 781  0
                     new int[] { index },
 782  0
                     new Object[] { this } );
 783  
                 }
 784  
             }
 785  0
         }
 786  
 
 787  0
 Object[] previouslyDisplayedObjects = new Object[0];
 788  
         /**
 789  
         * Overridden to call to super, fire any tree events, and then 
 790  
         * call updateDisplayedObjects on all fetched child nodes.
 791  
         * This method compares this node's displayed objects against
 792  
         * the list of child nodes, synchronizes them, and then broadcasts
 793  
         * only the necessary events to bring the view component up to date.
 794  
         */
 795  
         public void updateDisplayedObjects()
 796  
         { 
 797  
 //System.out.println( "updateDisplayedObjects: " + " : " + this );
 798  
 //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace();
 799  
 //new RuntimeException().printStackTrace();
 800  0
             super.updateDisplayedObjects();
 801  
 
 802  
             // diff lists
 803  0
             boolean proceed = true;
 804  0
             Object[] oldObjects = previouslyDisplayedObjects;
 805  0
             Object[] newObjects = displayedObjects.toArray();
 806  0
             if ( oldObjects.length == newObjects.length )
 807  
             {
 808  0
                 proceed = false;
 809  0
                 for ( int i = 0; i < newObjects.length; i++ )
 810  
                 {
 811  0
                     if ( oldObjects[i] != newObjects[i] )
 812  
                     {
 813  0
                         proceed = true;
 814  0
                         break;
 815  
                     }
 816  
                 }
 817  
             }
 818  
             
 819  
             // this should be set before firing the change events
 820  
             //    in case some clients end up calling this again.
 821  0
             previouslyDisplayedObjects = newObjects;
 822  
 
 823  
             DisplayGroupNode node;
 824  0
             Iterator i = childNodes.values().iterator();
 825  0
             while ( i.hasNext() )
 826  
             {
 827  0
                 node = (DisplayGroupNode) i.next();
 828  0
                 if ( !node.isFetchNeeded() )
 829  
                 {
 830  0
                     node.updateDisplayedObjects();
 831  0
                 }
 832  
             }
 833  
             
 834  0
             if ( proceed )
 835  
             {
 836  
 //System.out.println( "DisplayGroupNode.firingEventsForChanges: " );                
 837  
 //new RuntimeException().printStackTrace();
 838  0
                 fireEventsForChanges( oldObjects, newObjects );
 839  
             }
 840  
 
 841  0
         }
 842  
 
 843  
         /**
 844  
         * Called by processRecentChanges to analyze the 
 845  
         * differences between the lists and broadcast the
 846  
         * appropriate events.
 847  
         */
 848  
         protected void fireEventsForChanges(
 849  
             Object[] oldObjects, Object[] newObjects )
 850  
         {
 851  
             // structure changed causes havoc while 
 852  
             //  establishing connection in some cases
 853  
             //if ( oldObjects.length == 0 || newObjects.length == 0 )
 854  
             //{
 855  
             //    fireStructureChanged( treePath().getPath(), null, null );
 856  
             //    return;
 857  
             //}           
 858  
             
 859  0
             int insertCount = 0;            
 860  0
             int deleteCount = 0;            
 861  0
             Object[] inserts = new Object[ newObjects.length ]; 
 862  0
             Object[] deletes = new Object[ oldObjects.length ]; 
 863  
             
 864  
             int i;
 865  0
             int n = -1, o = -1; // last match
 866  0
             int n1 = 0, o1 = 0; // current match test
 867  0
             int n2 = 0, o2 = 0; // scan ahead
 868  
 
 869  0
             while ( o1 < oldObjects.length && n1 < newObjects.length )
 870  
             {  
 871  0
                 if ( newObjects[n1] == oldObjects[o1] )
 872  
                 {  
 873  
                     // mark as match and continue
 874  0
                     o = o1;
 875  0
                     n = n1;
 876  0
                 }
 877  
                 else 
 878  
                 {
 879  
                     // scan ahead for the next match, if any
 880  0
                     o2 = o1;
 881  0
                     n2 = n1;
 882  
 
 883  0
                     while ( o2 < oldObjects.length || n2 < newObjects.length )
 884  
                     {  
 885  0
                         if ( o2 < oldObjects.length && newObjects[n1] == oldObjects[o2] )
 886  
                         {
 887  
                             // run o1 to o2: mark as deletes
 888  0
                             for ( i = o1; i < o2; i++ )
 889  
                             { // System.out.println( "delete : " + i );
 890  0
                                 deletes[i] = oldObjects[i];
 891  0
                                 deleteCount++;
 892  
                             }
 893  0
                             o1 = o2; // reset test
 894  0
                             o = o1; // set match
 895  0
                             n = n1; // set match
 896  0
                             break;
 897  
                         }
 898  0
                         if ( n2 < newObjects.length && newObjects[n2] == oldObjects[o1] )
 899  
                         {
 900  
                             // run n1 to n2: mark as inserts
 901  0
                             for ( i = n1; i < n2; i++ )
 902  
                             { // System.out.println( "insert : " + i  );
 903  0
                                 inserts[i] = newObjects[i];
 904  0
                                 insertCount++;
 905  
                             }
 906  0
                             n1 = n2; // reset test
 907  0
                             n = n1; // set match
 908  0
                             o = o1; // set match
 909  0
                             break;
 910  
                         }
 911  0
                         o2++;
 912  0
                         n2++;
 913  0
                     }
 914  
                 }
 915  0
                 if (n != n1)
 916  
                 {
 917  0
                     inserts[n1] = newObjects[n1];
 918  0
                     insertCount++;
 919  0
                     deletes[o1] = oldObjects[o1];
 920  0
                     deleteCount++;
 921  
                     //increment even though no match:
 922  
                     //the new object was marked as inserted and
 923  
                     //the old object was marked as deleted.
 924  0
                     n = n1;
 925  0
                     o = o1;
 926  
                 }
 927  0
                 o1++;
 928  0
                 n1++;
 929  0
             }
 930  
             
 931  
             // run o to end of oldObjects: mark as deletes
 932  0
             for ( i = o+1; i < oldObjects.length; i++ )
 933  
             { // System.out.println( "delete : " + i  );
 934  0
                 deletes[i] = oldObjects[i];
 935  0
                 deleteCount++;
 936  
             }
 937  
         
 938  
             // run n to end of newObjects: mark as inserts
 939  0
             for ( i = n+1; i < newObjects.length; i++ )
 940  
             { // System.out.println( "insert : " + i  );
 941  0
                 inserts[i] = newObjects[i];
 942  0
                 insertCount++;
 943  
             }
 944  
 
 945  
 //System.out.println( "done   : " 
 946  
 //+ o + " : " + o1 + " : " + o2 + " :: " + n + " : " + n1 + " : " + n2 );
 947  
 //System.out.println( new NSArray( newObjects ) );
 948  
 //System.out.println( new NSArray( inserts ) );
 949  
 //System.out.println( new NSArray( deletes ) );
 950  
 //System.out.println( new NSArray( oldObjects ) );
 951  
                 
 952  
             int c;
 953  
             Object[] nodes;
 954  
             int[] indices;
 955  
 
 956  
             // broadcast delete event
 957  0
             c = 0;
 958  0
             nodes = new Object[ deleteCount ];
 959  0
             indices = new int[ deleteCount ];
 960  0
             for ( i = 0; i < deletes.length; i++ )
 961  
             {
 962  0
                 if ( deletes[i] != null )
 963  
                 {
 964  0
                     indices[c] = i;
 965  0
                     nodes[c] = getChildNodeForObject( deletes[i] );
 966  0
                     c++;
 967  
                 }
 968  
             }
 969  0
             if ( c > 0 )
 970  
             {
 971  
 //                fireNodeChanged(); // force the jtree to get the correct child count
 972  0
                 fireNodesRemoved( treePath().getPath(), indices, nodes );
 973  
             }
 974  0
             deletes = nodes; // retain for dispose check
 975  
             
 976  
             // broadcast insert event
 977  0
             c = 0;
 978  0
             nodes = new Object[ insertCount ];
 979  0
             indices = new int[ insertCount ];
 980  0
             for ( i = 0; i < inserts.length; i++ )
 981  
             {
 982  0
                 if ( inserts[i] != null )
 983  
                 {
 984  0
                     indices[c] = i;
 985  0
                     nodes[c] = getChildNodeForObject( inserts[i] );
 986  0
                     if ( nodes[c] == null )
 987  
                     {
 988  0
                         nodes[c] = createChildNodeForObject( newObjects[i] );
 989  
                     }
 990  0
                     c++;
 991  
                 }
 992  
             }
 993  0
             if ( c > 0 )
 994  
             {
 995  0
                 fireNodesInserted( treePath().getPath(), indices, nodes );
 996  
             }
 997  
 
 998  
             // dispose any delete nodes not on insert list
 999  
             int j;
 1000  
             boolean found;
 1001  0
             for ( i = 0; i < deletes.length; i++ )
 1002  
             {
 1003  0
                 for ( j = 0; j < nodes.length; j++ )
 1004  
                 {
 1005  0
                     if ( deletes[i] == nodes[j] ) break;
 1006  
                 }
 1007  
                 
 1008  
                 // did not break early, so not found, so dispose
 1009  0
                 if ( j == nodes.length )
 1010  
                 {
 1011  0
                     ((DisplayGroupNode)deletes[i]).dispose();
 1012  
                 }
 1013  
             }
 1014  0
         }
 1015  
 
 1016  
         /**
 1017  
         * Sets the target object and creates an registers a target observer.
 1018  
         * If target was not previously null, the existing observer is unregistered.
 1019  
         * Protected access so subclasses and TreeModelAssociation can update our target.
 1020  
         */
 1021  
         public void setTarget( Object aTarget )
 1022  
         {
 1023  0
             if ( target != null )
 1024  
             {
 1025  0
                 EOObserverCenter.removeObserver( targetObserver, target );
 1026  0
                 targetObserver.discardPendingNotification();
 1027  
             }
 1028  
 
 1029  0
             if ( aTarget != null )
 1030  
             {
 1031  0
                 target = aTarget;
 1032  0
                 targetObserver = new TargetObserver( this );
 1033  0
                 EOObserverCenter.addObserver( targetObserver, target );
 1034  
             }
 1035  0
         }
 1036  
 
 1037  
     /**
 1038  
     * Returns the parent display group, or null if parent is root.
 1039  
     */
 1040  
     public DisplayGroupNode getParentGroup()
 1041  
     {
 1042  0
         if ( parentGroup instanceof DisplayGroupNode )
 1043  
         {
 1044  0
             return (DisplayGroupNode)parentGroup;
 1045  
         }
 1046  
         // presumably the root node
 1047  0
         return null;
 1048  
     }
 1049  
         
 1050  
     /**
 1051  
     * Gets all descendants of the this node.
 1052  
     */
 1053  
     public List getDescendants()
 1054  
     {
 1055  0
         return getDescendants( this, true );
 1056  
     }
 1057  
 
 1058  
     /**
 1059  
     * Gets only the descendants of the this node
 1060  
     * whose children has been loaded - no fetching
 1061  
     * will occur.  Useful for load-on-demand trees.
 1062  
     */
 1063  
     public List getLoadedDescendants()
 1064  
     {
 1065  0
         return getDescendants( this, false );
 1066  
     }
 1067  
 
 1068  
         // breadth first traversal implementation
 1069  
 
 1070  
     /**
 1071  
     * Returns a list of all descendants of the
 1072  
     * specified node. Unfetched nodes are traversed
 1073  
     * only if forceLoad is true.
 1074  
     * This implementation is a breadth-first traversal
 1075  
     * of the nodes starting at the specified node.
 1076  
     */
 1077  
     static private List getDescendants( DisplayGroupNode aNode, boolean forceLoad )
 1078  
     {
 1079  0
         if ( !forceLoad && !aNode.isFetched ) return NSArray.EmptyArray;
 1080  
         
 1081  0
         LinkedList result = new LinkedList();
 1082  0
         LinkedList queue = new LinkedList();
 1083  
 
 1084  0
         queue.add( aNode );
 1085  0
         while ( ! queue.isEmpty() )
 1086  
         {
 1087  0
             checkNode( (DisplayGroupNode) queue.removeFirst(),
 1088  0
                 queue, result, forceLoad );
 1089  0
         }
 1090  
 
 1091  0
         return result;
 1092  
     }
 1093  
 
 1094  
     /**
 1095  
     * Adds each fetched child node of the specified node to
 1096  
     * the result set (optionally forcing the child node to load)
 1097  
     * and adding child node to the end of the queue.
 1098  
     */
 1099  
     static private void checkNode( DisplayGroupNode aNode,
 1100  
         LinkedList aQueue, LinkedList aResult, boolean forceLoad )
 1101  
     {
 1102  
         DisplayGroupNode child;
 1103  0
         int count = aNode.getChildCount();
 1104  
 
 1105  0
         for ( int i = 0; i < count; i++ )
 1106  
         {
 1107  0
             child = aNode.getChildNodeAt( i );
 1108  
 
 1109  
             // add to queue if node has fetched children
 1110  0
             if ( ( !child.isFetched ) && ( forceLoad ) )
 1111  
             {
 1112  0
                 child.fetch();
 1113  
             }
 1114  0
             if ( child.isFetched )
 1115  
             {
 1116  0
                 aQueue.addLast( child );
 1117  
             }
 1118  
 
 1119  0
             aResult.add( child );
 1120  
         }
 1121  0
     }
 1122  
 
 1123  
     /**
 1124  
     * Overridden to not fetch on InvalidateAllObjectsInStoreNotification
 1125  
     * unless we've already been fetched, preserving the load-on-demand
 1126  
     * functionality.
 1127  
     */
 1128  
     public void objectsInvalidatedInEditingContext( NSNotification aNotification )
 1129  
     {
 1130  0
         if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification
 1131  0
             .equals( aNotification.name() ) )
 1132  
         {
 1133  
 //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: " + aNotification.name() );        
 1134  0
                 if ( parentAssociation.isVisible( this ) && targetObserver != null ) 
 1135  
                 {
 1136  0
                     targetObserver.objectWillChange( target ); // force ui to update
 1137  0
                     fireNodeChanged();
 1138  0
                 }
 1139  
                 else // make sure we fetch children when we do become visible
 1140  0
                     setFetchNeeded( true ); 
 1141  0
                 return;
 1142  
         }
 1143  
         else
 1144  0
         if ( ( EOEditingContext.ObjectsChangedInEditingContextNotification
 1145  0
             .equals( aNotification.name() ) )
 1146  0
         ||   ( EOEditingContext.EditingContextDidSaveChangesNotification
 1147  0
             .equals( aNotification.name() ) ) )
 1148  
         {
 1149  
             int index;
 1150  
             Enumeration e;
 1151  0
             boolean didChange = false;
 1152  0
             NSDictionary userInfo = aNotification.userInfo();
 1153  
 
 1154  
             // if our target object was deleted
 1155  0
             NSArray deletes = (NSArray) userInfo.objectForKey(
 1156  0
                 EOObjectStore.DeletedKey );
 1157  0
             if ( deletes.indexOfIdenticalObject( target ) != NSArray.NotFound )
 1158  
             {
 1159  
 //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: delete: " + this + " : " + aNotification.name() );        
 1160  0
                 if ( parentAssociation.isVisible( this ) && targetObserver != null ) 
 1161  
                 {
 1162  0
                     targetObserver.objectWillChange( target ); // force ui to update
 1163  0
                     fireNodeChanged();
 1164  0
                 }
 1165  
                 else // make sure we fetch children when we do become visible
 1166  0
                     setFetchNeeded( true ); 
 1167  0
                 return;
 1168  
             }
 1169  
 
 1170  
             // if our target object was invalidated
 1171  0
             NSArray invalidates = (NSArray) userInfo.objectForKey(
 1172  0
                 EOObjectStore.InvalidatedKey );
 1173  0
             if ( invalidates != null &&
 1174  0
                  invalidates.indexOfIdenticalObject( target ) != NSArray.NotFound )
 1175  
             {
 1176  
 //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: invalidate: " + this + " : " + aNotification.name() );        
 1177  0
                 if ( parentAssociation.isVisible( this ) && targetObserver != null ) 
 1178  
                 {
 1179  0
                     targetObserver.objectWillChange( target ); // force ui to update
 1180  0
                     fireNodeChanged();
 1181  0
                 }
 1182  
                 else // make sure we fetch children when we do become visible
 1183  0
                     setFetchNeeded( true ); 
 1184  0
                 return;
 1185  
             }
 1186  
             
 1187  
             // if our target object was updated, set fetchNeeded plus fire changed event
 1188  0
             NSArray updates = (NSArray) userInfo.objectForKey(
 1189  0
                 EOObjectStore.UpdatedKey );
 1190  0
             if ( updates.indexOfIdenticalObject( target ) != NSArray.NotFound )
 1191  
             {
 1192  0
                 if ( parentAssociation.isVisible( this ) && targetObserver != null ) 
 1193  
                 {
 1194  0
                     targetObserver.objectWillChange( target ); // force ui to update
 1195  0
                     fireNodeChanged();
 1196  0
                     if ( object() instanceof Component ) ((Component)object()).repaint();
 1197  
                 }
 1198  
                 else // make sure we fetch children when we do become visible
 1199  0
                     setFetchNeeded( true ); 
 1200  0
                 return;
 1201  
             }
 1202  
         }
 1203  
 
 1204  0
         super.objectsInvalidatedInEditingContext( aNotification );
 1205  
 
 1206  0
     }
 1207  
 
 1208  
     // inner classes
 1209  
 
 1210  
     /**
 1211  
     * Private class used to force a hashmap to
 1212  
     * perform key comparisons by reference.
 1213  
     */
 1214  
     private class ReferenceKey
 1215  
     {
 1216  
         private int hashCode;
 1217  
         private Object referent;
 1218  
 
 1219  0
         public ReferenceKey( Object anObject )
 1220  0
         {
 1221  0
             referent = anObject;
 1222  0
             hashCode = anObject.hashCode();
 1223  0
         }
 1224  
 
 1225  
         /**
 1226  
         * Returns the actual key's hash code.
 1227  
         */
 1228  
         public int hashCode()
 1229  
         {
 1230  0
             return hashCode;
 1231  
         }
 1232  
 
 1233  
         /**
 1234  
         * Compares by reference.
 1235  
         */
 1236  
         public boolean equals( Object anObject )
 1237  
         {
 1238  0
             if ( anObject instanceof ReferenceKey )
 1239  
             {
 1240  0
                 return ((ReferenceKey)anObject).referent == referent;
 1241  
             }
 1242  0
             return false;
 1243  
         }
 1244  
     }
 1245  
 
 1246  
     /**
 1247  
     * A private class to observe the target object of this node.
 1248  
     */
 1249  
     private class TargetObserver extends EODelayedObserver
 1250  
     {
 1251  
         Reference ref;
 1252  
 
 1253  
         /**
 1254  
         * Pass in the display group node that will be updated
 1255  
         * when the target changes.
 1256  
         */
 1257  0
         public TargetObserver( DisplayGroupNode aDisplayGroup )
 1258  0
         {
 1259  0
             ref = new WeakReference( aDisplayGroup );
 1260  0
         }
 1261  
 
 1262  
         /**
 1263  
         * Repopulate our display group, and calculate the deltas
 1264  
         * so we can broadcast appropriate events.
 1265  
         */
 1266  
         public void subjectChanged ()
 1267  
         {
 1268  0
             DisplayGroupNode node = (DisplayGroupNode) ref.get();
 1269  0
             if ( node == null ) return; // node is null if gc'd.
 1270  
             //FIXME: should un-register self from observer center??
 1271  
 
 1272  0
             node.targetChanged();
 1273  0
         }
 1274  
     }
 1275  
 
 1276  
 }
 1277  
 /*
 1278  
  * $Log$
 1279  
  * Revision 1.2  2006/02/18 23:19:05  cgruber
 1280  
  * Update imports and maven dependencies.
 1281  
  *
 1282  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 1283  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 1284  
  *
 1285  
  * Revision 1.64  2003/08/06 23:07:52  chochos
 1286  
  * general code cleanup (mostly, removing unused imports)
 1287  
  *
 1288  
  * Revision 1.63  2003/06/06 14:20:07  mpowers
 1289  
  * getLoadedDescendants was forcing a fetch of the node it was called on.
 1290  
  *
 1291  
  * Revision 1.62  2003/06/03 14:48:33  mpowers
 1292  
  * Clean-up of notification handling for updates/invalidation/etc.
 1293  
  * Now fetching immediately on notification if the node is visible.
 1294  
  * This averts the infamous IndexOutOfBoundsException that occurs
 1295  
  * if fetching happens during repaint, because the BasicTreeUI is
 1296  
  * caching the number of child nodes before painting begins.
 1297  
  *
 1298  
  * Revision 1.61  2003/01/18 23:33:29  mpowers
 1299  
  * Fixing the build.
 1300  
  *
 1301  
  * Revision 1.60  2002/05/31 15:03:10  mpowers
 1302  
  * Fixes for the previous fix.  Fat props to yjcheung.
 1303  
  *
 1304  
  * Revision 1.59  2002/05/28 15:31:36  mpowers
 1305  
  * Fix for updateDisplayedObjects for a subtle case where a node appears in
 1306  
  * the position that another node was moved from.
 1307  
  *
 1308  
  * Revision 1.58  2002/05/24 14:42:02  mpowers
 1309  
  * Prevent repeat events from firing if firing events loops back.
 1310  
  *
 1311  
  * Revision 1.57  2002/04/23 19:12:28  mpowers
 1312  
  * Reimplemented fireEventsForChanges.  Fitter and happier.
 1313  
  *
 1314  
  * Revision 1.56  2002/04/19 21:18:45  mpowers
 1315  
  * Removed tree event coalescing, which was causing way too many problems.
 1316  
  * The fireChangeEvent algorithm is way faster than before, so we should
 1317  
  * still be better off than before.  At least now, we don't have to track
 1318  
  * whether the view component has encountered a particular node.
 1319  
  *
 1320  
  * Revision 1.55  2002/04/19 20:53:22  mpowers
 1321  
  * Now firing event fewer events in fireEventsForChanges.
 1322  
  *
 1323  
  * Revision 1.54  2002/04/15 21:52:50  mpowers
 1324  
  * Tightening up TreeModelAssociation and DisplayGroupNode.
 1325  
  * Now only firing root structure changed once.
 1326  
  * Now disposing of root's children.
 1327  
  * Better event coalescing.
 1328  
  *
 1329  
  * Revision 1.53  2002/04/12 20:35:20  mpowers
 1330  
  * Now correctly setting parent display group and data source on creation.
 1331  
  *
 1332  
  * Revision 1.52  2002/04/10 21:20:04  mpowers
 1333  
  * Better handling for tree nodes when working with editing contexts.
 1334  
  * Better handling for invalidation.  No longer broadcasting events
 1335  
  * when nodes have not been "registered" in the tree.
 1336  
  *
 1337  
  * Revision 1.51  2002/04/03 20:13:36  mpowers
 1338  
  * Now differentiating between node instantiation caused by model expansion
 1339  
  * (user initiated) and by modifications to the model.
 1340  
  * Dispose now disposes all children.
 1341  
  *
 1342  
  * Revision 1.50  2002/03/23 16:20:27  mpowers
 1343  
  * Optimized processRecentChanges, minimized tree events.
 1344  
  *
 1345  
  * Revision 1.49  2002/03/11 03:15:06  mpowers
 1346  
  * Optimized processRecentChanges, minimize event firing, coalescing changes.
 1347  
  * Still need a better diff algorithm to avoid removing nodes.
 1348  
  *
 1349  
  * Revision 1.48  2002/03/10 00:59:39  mpowers
 1350  
  * Interim version: coalesces calls to process recent changes.
 1351  
  * Still does not handle rearranged nodes.
 1352  
  *
 1353  
  * Revision 1.47  2002/03/09 17:33:45  mpowers
 1354  
  * Nodes now track their child nodes by reference, not index.
 1355  
  *
 1356  
  * Revision 1.46  2002/03/08 23:19:07  mpowers
 1357  
  * Added getParentGroup to DisplayGroupNode.
 1358  
  *
 1359  
  * Revision 1.45  2002/03/06 13:04:16  mpowers
 1360  
  * Implemented cascading qualifiers in tree nodes.
 1361  
  *
 1362  
  * Revision 1.44  2002/02/27 23:19:17  mpowers
 1363  
  * Refactoring of TreeAssociation to create TreeModelAssociation parent.
 1364  
  *
 1365  
  * Revision 1.42  2002/02/19 22:28:46  mpowers
 1366  
  * DisplayGroupNodes immediately unregister themselves as editors.
 1367  
  *
 1368  
  * Revision 1.41  2002/02/13 16:27:38  mpowers
 1369  
  * Exposing setTarget.
 1370  
  *
 1371  
  * Revision 1.40  2001/11/02 20:55:46  mpowers
 1372  
  * Now using fixed index to send node removed events.  This preserves the
 1373  
  * expanded state of the nodes in the corresponding jtree.
 1374  
  *
 1375  
  * Revision 1.39  2001/09/21 21:09:25  mpowers
 1376  
  * Exposed more fields as protected.
 1377  
  *
 1378  
  * Revision 1.38  2001/09/19 15:36:08  mpowers
 1379  
  * Refined behavior for isFetched after notification handling.
 1380  
  *
 1381  
  * Revision 1.37  2001/09/13 14:51:18  mpowers
 1382  
  * DisplayGroupNodes now dispose themselves and mark their parent for update
 1383  
  * when they receive notification that their target has been deleted.
 1384  
  *
 1385  
  * Revision 1.36  2001/09/10 14:10:24  mpowers
 1386  
  * Fix for notification handling.
 1387  
  *
 1388  
  * Revision 1.35  2001/07/30 16:17:01  mpowers
 1389  
  * Minor code cleanup.
 1390  
  *
 1391  
  * Revision 1.34  2001/07/18 22:13:39  mpowers
 1392  
  * getLoadedDescendants now works as advertised.
 1393  
  * Now correctly handling invalidateAllObjects notification.
 1394  
  *
 1395  
  * Revision 1.33  2001/07/18 13:03:32  mpowers
 1396  
  * TreeNodes now refetch only on demand.  Previously, once a node had
 1397  
  * been fetched, it was always refetched after an invalidate, even if
 1398  
  * the node was not being displayed.
 1399  
  *
 1400  
  * Revision 1.32  2001/06/18 14:10:28  mpowers
 1401  
  * Cleaned up event firing: no longer firing insert or remove events twice.
 1402  
  *
 1403  
  * Revision 1.31  2001/06/09 16:15:39  mpowers
 1404  
  * Revised the targetChanged scheme because oldObjects and newObjects were
 1405  
  * identical after the target object is invalidated.
 1406  
  *
 1407  
  * Revision 1.30  2001/05/21 22:17:19  mpowers
 1408  
  * Fix for tree out-of-synch problems when nodes are inserted.
 1409  
  *
 1410  
  * Revision 1.29  2001/05/18 21:07:46  mpowers
 1411  
  * Playing with refresh options.
 1412  
  *
 1413  
  * Revision 1.28  2001/05/14 15:25:43  mpowers
 1414  
  * DisplayGroupNodes now only respond to InvalidateAllObjectsInStore
 1415  
  * if they are already fetched.
 1416  
  *
 1417  
  * Revision 1.27  2001/05/08 19:55:58  mpowers
 1418  
  * Fix for node children not refreshing after sibling was inserted.
 1419  
  *
 1420  
  * Revision 1.26  2001/05/08 18:47:34  mpowers
 1421  
  * Minor fixes for d3.
 1422  
  *
 1423  
  * Revision 1.25  2001/05/06 22:22:55  mpowers
 1424  
  * Debugging.
 1425  
  *
 1426  
  * Revision 1.24  2001/05/04 14:42:58  mpowers
 1427  
  * Now getting stored values in KeyValueCoding.
 1428  
  * MasterDetail now marks dirty based on whether it's an attribute
 1429  
  * or relation.
 1430  
  * Implemented editing context marker.
 1431  
  *
 1432  
  * Revision 1.23  2001/05/02 18:00:43  mpowers
 1433  
  * Removed debug code.
 1434  
  *
 1435  
  * Revision 1.22  2001/05/02 17:31:20  mpowers
 1436  
  * DisplayGroupNode now does a better job determining when to mark its
 1437  
  * parent dirty.
 1438  
  *
 1439  
  * Revision 1.21  2001/05/01 00:52:32  mpowers
 1440  
  * Implemented breadth-first traversal of tree for node.
 1441  
  *
 1442  
  * Revision 1.20  2001/04/26 01:15:19  mpowers
 1443  
  * Major clean-up of DisplayGroupNode: fitter, happier, more productive.
 1444  
  *
 1445  
  * Revision 1.19  2001/04/22 23:13:35  mpowers
 1446  
  * Minor bug.
 1447  
  *
 1448  
  * Revision 1.18  2001/04/22 23:05:33  mpowers
 1449  
  * Totally revised DisplayGroupNode so each object gets its own node
 1450  
  * (so the nodes are no longer fixed by index).
 1451  
  *
 1452  
  * Revision 1.17  2001/04/21 23:05:12  mpowers
 1453  
  * A fairly major revisiting.  I've decided to scrap the pass-thru approach
 1454  
  * where every node simply represents an index and not an object.
 1455  
  * The next update will have each node correspond to a specific object.
 1456  
  *
 1457  
  * Revision 1.16  2001/04/13 16:37:37  mpowers
 1458  
  * Handling bounds checking.
 1459  
  *
 1460  
  * Revision 1.15  2001/04/03 20:36:01  mpowers
 1461  
  * Fixed refaulting/reverting/invalidating to be self-consistent.
 1462  
  *
 1463  
  * Revision 1.14  2001/03/27 17:45:51  mpowers
 1464  
  * More index bounds checking.
 1465  
  *
 1466  
  * Revision 1.13  2001/03/22 21:25:42  mpowers
 1467  
  * Fixed some nasty issues with jtree's internal state and array bounds.
 1468  
  *
 1469  
  * Revision 1.12  2001/03/19 22:18:58  mpowers
 1470  
  * Root node now mirrors contents of titles display group.
 1471  
  *
 1472  
  * Revision 1.11  2001/03/19 21:38:36  mpowers
 1473  
  * Improved redisplay after edit.  Editing nodes off root now works.
 1474  
  *
 1475  
  * Revision 1.10  2001/03/09 22:08:38  mpowers
 1476  
  * Removed unused line.
 1477  
  *
 1478  
  * Revision 1.9  2001/03/07 16:41:04  mpowers
 1479  
  * Now checking size of parent displayed objects array so that we don't
 1480  
  * get array out of bounds execeptions from isLeaf() or object() when
 1481  
  * those messages are called after the TreeAssociation fires a
 1482  
  * nodesDeleted event.  I believe that JTree is mistakenly rendering
 1483  
  * those nodes one last time before erasing them.
 1484  
  *
 1485  
  * Revision 1.8  2001/03/06 23:21:27  mpowers
 1486  
  * Now only notifying parent if the object is not registered in the
 1487  
  * editing context, if any.
 1488  
  *
 1489  
  * Revision 1.7  2001/02/20 16:38:55  mpowers
 1490  
  * MasterDetailAssociations now observe their controlled display group's
 1491  
  * objects for changes to that the parent object will be marked as updated.
 1492  
  * Before, only inserts and deletes to an object's items are registered.
 1493  
  * Also, moved ObservableArray to package access.
 1494  
  *
 1495  
  * Revision 1.6  2001/02/17 16:52:05  mpowers
 1496  
  * Changes in imports to support building with jdk1.1 collections.
 1497  
  *
 1498  
  * Revision 1.5  2001/01/31 17:59:52  mpowers
 1499  
  * Fixed isLeaf aspect of TreeAssociation.
 1500  
  *
 1501  
  * Revision 1.4  2001/01/25 02:16:25  mpowers
 1502  
  * TreeAssociation now returns DisplayGroupNode.getUserObject.
 1503  
  *
 1504  
  * Revision 1.3  2001/01/24 18:14:40  mpowers
 1505  
  * Fixed problem with leaving children aspect unspecified.
 1506  
  *
 1507  
  * Revision 1.2  2001/01/24 16:35:37  mpowers
 1508  
  * Improved documentation on TreeAssociation.
 1509  
  * SortOrderings are now inherited from parent nodes.
 1510  
  * Updates after sorting are still lost on TreeController.
 1511  
  *
 1512  
  * Revision 1.1  2001/01/24 14:17:12  mpowers
 1513  
  * Major revision to TreeAssociation.  Can now add and remove nodes.
 1514  
  * DisplayGroupNode is now it's own class.
 1515  
  *
 1516  
  *
 1517  
  */
 1518