Coverage Report - net.wotonomy.ui.swing.TreeModelAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
TreeModelAssociation
0% 
0% 
2.529
 
 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.util.Enumeration;
 22  
 import java.util.LinkedList;
 23  
 import java.util.List;
 24  
 import java.util.Vector;
 25  
 
 26  
 import javax.swing.SwingUtilities;
 27  
 import javax.swing.event.TreeModelEvent;
 28  
 import javax.swing.event.TreeModelListener;
 29  
 import javax.swing.event.TreeSelectionEvent;
 30  
 import javax.swing.event.TreeSelectionListener;
 31  
 import javax.swing.tree.TreeModel;
 32  
 import javax.swing.tree.TreePath;
 33  
 import javax.swing.tree.TreeSelectionModel;
 34  
 
 35  
 import net.wotonomy.control.EOClassDescription;
 36  
 import net.wotonomy.control.EODataSource;
 37  
 import net.wotonomy.control.EODelayedObserver;
 38  
 import net.wotonomy.control.EOEditingContext;
 39  
 import net.wotonomy.control.EOObserverCenter;
 40  
 import net.wotonomy.control.EOObserverProxy;
 41  
 import net.wotonomy.control.OrderedDataSource;
 42  
 import net.wotonomy.control.PropertyDataSource;
 43  
 import net.wotonomy.foundation.NSArray;
 44  
 import net.wotonomy.foundation.NSMutableArray;
 45  
 import net.wotonomy.foundation.NSSelector;
 46  
 import net.wotonomy.foundation.internal.WotonomyException;
 47  
 import net.wotonomy.ui.EOAssociation;
 48  
 import net.wotonomy.ui.EODisplayGroup;
 49  
 
 50  
 /**
 51  
 * TreeModelAssociation binds a JTree or similar component
 52  
 * that uses a TreeModel to a display group's
 53  
 * list of displayable objects, each of which may have
 54  
 * a list of child objects managed by another display
 55  
 * group, and so on.  TreeModelAssociation works exactly
 56  
 * like a ListAssociation, with the additional capability
 57  
 * to specify a "Children" aspect, that will allow child
 58  
 * objects to be retrieved from a parent display group.
 59  
 *
 60  
 * <ul>
 61  
 *
 62  
 * <li>titles: a property convertable to a string for
 63  
 * display in the nodes of the tree.  The objects in
 64  
 * the bound display group will be used to populate the
 65  
 * initial root nodes of the tree (more accurately,
 66  
 * children of the offscreen root node in the tree).</li>
 67  
 *
 68  
 * <li>children: a property of a node value that returns
 69  
 * zero, one or many objects, each of which will correspond
 70  
 * to a child node for the corresponding node in the tree.
 71  
 * The data source of the bound display group is replaced 
 72  
 * a data source that populates the display group with
 73  
 * the selection in the tree component as determined by 
 74  
 * calling fetchObjectsIntoChildrenGroup.
 75  
 * If this aspect is not bound, the tree behaves like a list.
 76  
 * <br><br>
 77  
 * Binding this aspect with a null display group is the same
 78  
 * as binding it with the titles display group.  
 79  
 * In this configuration the contents of the titles 
 80  
 * display group will be replaced with the selection in the
 81  
 * tree component, as specified above, replacing the existing 
 82  
 * data source.  
 83  
 * <br><br>
 84  
 * In that case, the display groups for the nodes in
 85  
 * the tree will still use the original data source
 86  
 * for resolving their children key, and programmatically
 87  
 * setting the contents of the display group will still 
 88  
 * repopulate the root nodes of the tree.
 89  
 * </li>
 90  
 *
 91  
 * <li>isLeaf: a property of a node value that returns
 92  
 * a value convertable to a boolean value (aside from
 93  
 * an actual boolean value, zeroes evaluate to true,
 94  
 * as does any String containing "yes" or "true" or that
 95  
 * is convertable to a number equal to zero; other values
 96  
 * evaluate to false).  
 97  
 * <br><br>
 98  
 * If the isLeaf aspect is not bound,
 99  
 * the tree must force nodes to load their children to
 100  
 * determine whether they are leaf nodes (in effect
 101  
 * loading the grandchildren for any expanded node).
 102  
 * If bound, child loading is deferred until the node
 103  
 * is actually expanded.  
 104  
 * <br><br>
 105  
 * For example, binding this value to a null
 106  
 * display group and the key "false" will result in a
 107  
 * deferred-loading tree that works much like Windows
 108  
 * Explorer's network volume browser - all nodes appear
 109  
 * with "pluses" until they are expanded.
 110  
 * <br><br>
 111  
 * Note that the display group is ignored: the property
 112  
 * will be applied directly to the object corresponding
 113  
 * to the node.</li>
 114  
 *
 115  
 * </ul>
 116  
 *
 117  
 * This class acts as the TreeModel for the controlled
 118  
 * component: calling yourcomponent.getModel() will
 119  
 * return this association.  The tree model methods on
 120  
 * this class are public and may be used to affect changes
 121  
 * on the controlled components.<br><br>
 122  
 *
 123  
 * The titles display group's contents are inserted
 124  
 * into a new display group that acts as the root node.
 125  
 * After that point, changes in the titles display group
 126  
 * will cause the tree model to reset itself, creating
 127  
 * a new display group for the root node.
 128  
 * <br><br>
 129  
 *
 130  
 * If a separate display group is bound to the children 
 131  
 * aspect, it will
 132  
 * be used to hold the selected objects and their siblings
 133  
 * and selection will be maintained there, and the titles
 134  
 * display group selection will not be updated.
 135  
 * Any editing or detail associations should in that case
 136  
 * be attached to the children display group, not the titles
 137  
 * group. <br><br>
 138  
 *
 139  
 * Each node in the tree is an EODisplayGroup that
 140  
 * contains the child objects of the object it represents
 141  
 * in the tree.  These objects can be programmatically
 142  
 * inserted, updated, or removed using DisplayGroup
 143  
 * methods.  Each node's takes its parent group's
 144  
 * sortOrderings until a sort ordering is explicitly
 145  
 * specified - setting a sort ordering to null will resume
 146  
 * using the parent group's sort ordering.<br><br>
 147  
 *
 148  
 * Each node in the tree also implements MutableTreeNode.
 149  
 * The value that a node represents is the titles property
 150  
 * value of the object in the parent's displayed objects
 151  
 * list at the index corresponding to the index of the node.
 152  
 * Calling toString on a node returns the string representation
 153  
 * of the titles property value, and setUserObject will update
 154  
 * that value directly in the corresponding object.
 155  
 * Moving a node from one parent to another will remove the
 156  
 * actual object in the parent display group and insert it
 157  
 * into the destination display group.<br><br>
 158  
 *
 159  
 * In short, any nodes obtained from this class'
 160  
 * implementation of TreeModel may be cast as either
 161  
 * EODisplayGroup or MutableTreeNode and maybe be
 162  
 * programmatically manipulated in either manner.
 163  
 *
 164  
 * @author michael@mpowers.net
 165  
 * @author $Author: cgruber $
 166  
 * @version $Revision: 904 $
 167  
 */
 168  0
 public class TreeModelAssociation extends EOAssociation
 169  
     implements TreeModel, TreeSelectionListener
 170  
 {
 171  0
     static final NSArray aspects =
 172  0
         new NSArray( new Object[] {
 173  0
             TitlesAspect, ChildrenAspect, IsLeafAspect
 174  
         } );
 175  0
     static final NSArray aspectSignatures =
 176  0
         new NSArray( new Object[] {
 177  0
             AttributeToOneAspectSignature
 178  
         } );
 179  0
     static final NSArray objectKeysTaken =
 180  0
         new NSArray( new Object[] {
 181  0
             "model"
 182  
         } );
 183  
 
 184  0
     private final static NSSelector getSelectionModel =
 185  0
         new NSSelector( "getSelectionModel" );
 186  0
     private final static NSSelector setModel =
 187  0
         new NSSelector( "setModel",
 188  0
         new Class[] { TreeModel.class } );
 189  
 
 190  
     EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup;
 191  
     String titlesKey, childrenKey, leafKey;
 192  
     DisplayGroupNode rootNode;
 193  
     Vector listeners;
 194  
     Object rootLabel;
 195  
 
 196  
     TreeSelectionModel selectionModel;
 197  
     boolean selectionPaintedImmediately;
 198  
     
 199  
     boolean insertingChild;
 200  
     boolean insertingAfter;
 201  
     
 202  
     EOObserverProxy recentChangesObserver;
 203  
 
 204  
     private boolean pleaseSelectRootNode;
 205  
     
 206  
     /**
 207  
     * Constructor expecting a JTree or any other object
 208  
     * that has void setModel(TreeModel) and TreeModel getSelectionModel()
 209  
     * methods.  This tree association will be used for the TreeModel.
 210  
     * The root node will be labeled "Root".  <br><br>
 211  
     *
 212  
     * As an alternate way to use a TreeModelAssociation, you may pass a
 213  
     * TreeSelectionModel to the constructor and then manually set your
 214  
     * component to use this class as its TreeModel.
 215  
     */
 216  
     public TreeModelAssociation ( Object anObject )
 217  
     {
 218  0
         super( anObject );
 219  
 
 220  0
         titlesDisplayGroup = null;
 221  0
         titlesKey = null;
 222  0
         childrenDisplayGroup = null;
 223  0
         childrenKey = null;
 224  0
         leafDisplayGroup = null;
 225  0
         leafKey = null;
 226  0
         listeners = new Vector();
 227  
 
 228  0
         selectionPaintedImmediately = false;
 229  
         
 230  
         // after display group nodes process recent changes
 231  0
         recentChangesObserver = new EOObserverProxy(
 232  0
             this, new NSSelector( "processRecentChanges" ), 
 233  0
             EODelayedObserver.ObserverPrioritySixth );
 234  0
         EOObserverCenter.addObserver( recentChangesObserver, this );
 235  
         
 236  0
         insertingChild = true;
 237  0
         insertingAfter = true;
 238  
 
 239  0
         pleaseSelectRootNode = false;
 240  
 
 241  0
         rootLabel = "Root";
 242  0
         rootNode = createNode( null, null );
 243  0
     }
 244  
 
 245  
     /**
 246  
     * Constructor expecting a JTree or similar component
 247  
     * and specifying a label for the root node.
 248  
     */
 249  
     public TreeModelAssociation( Object anObject, Object aRootLabel )
 250  
     {
 251  0
         this( anObject );
 252  0
         rootLabel = aRootLabel;
 253  0
         rootNode.setUserObject( aRootLabel );
 254  0
     }
 255  
 
 256  
     /**
 257  
     * Gets the current root label.
 258  
     */
 259  
     public Object rootLabel()
 260  
     {
 261  0
         return rootLabel;
 262  
     }
 263  
 
 264  
     /**
 265  
     * Gets the current root label.
 266  
     */
 267  
     public Object getRootLabel()
 268  
     {
 269  0
         return rootLabel();
 270  
     }
 271  
 
 272  
     /**
 273  
     * Sets the root label.
 274  
     */
 275  
     public void setRootLabel( Object aLabel )
 276  
     {
 277  0
         rootLabel = aLabel;
 278  0
     }
 279  
 
 280  
     /**
 281  
     * Returns a List of aspect signatures whose contents
 282  
     * correspond with the aspects list.  Each element is
 283  
     * a string whose characters represent a capability of
 284  
     * the corresponding aspect. <ul>
 285  
     * <li>"A" attribute: the aspect can be bound to
 286  
     * an attribute.</li>
 287  
     * <li>"1" to-one: the aspect can be bound to a
 288  
     * property that returns a single object.</li>
 289  
     * <li>"M" to-one: the aspect can be bound to a
 290  
     * property that returns multiple objects.</li>
 291  
     * </ul>
 292  
     * An empty signature "" means that the aspect can
 293  
     * bind without needing a key.
 294  
     * This implementation returns "A1M" for each
 295  
     * element in the aspects array.
 296  
     */
 297  
     public static NSArray aspectSignatures ()
 298  
     {
 299  0
         return aspectSignatures;
 300  
     }
 301  
 
 302  
     /**
 303  
     * Returns a List that describes the aspects supported
 304  
     * by this class.  Each element in the list is the string
 305  
     * name of the aspect.  This implementation returns an
 306  
     * empty list.
 307  
     */
 308  
     public static NSArray aspects ()
 309  
     {
 310  0
         return aspects;
 311  
     }
 312  
 
 313  
     /**
 314  
     * Returns a List of EOAssociation subclasses that,
 315  
     * for the objects that are usable for this association,
 316  
     * are less suitable than this association.
 317  
     */
 318  
     public static NSArray associationClassesSuperseded ()
 319  
     {
 320  0
         return new NSArray();
 321  
     }
 322  
 
 323  
     /**
 324  
     * Returns whether this class can control the specified
 325  
     * object.
 326  
     */
 327  
     public static boolean isUsableWithObject ( Object anObject )
 328  
     {
 329  0
         return setModel.implementedByObject( anObject );
 330  
     }
 331  
 
 332  
     /**
 333  
     * Returns a List of properties of the controlled object
 334  
     * that are controlled by this class.  For example,
 335  
     * "stringValue", or "selected".
 336  
     */
 337  
     public static NSArray objectKeysTaken ()
 338  
     {
 339  0
         return objectKeysTaken;
 340  
     }
 341  
 
 342  
     /**
 343  
     * Returns the aspect that is considered primary
 344  
     * or default.  This is typically "value" or somesuch.
 345  
     */
 346  
     public static String primaryAspect ()
 347  
     {
 348  0
         return TitlesAspect;
 349  
     }
 350  
 
 351  
     /**
 352  
     * Returns whether this association can bind to the
 353  
     * specified display group on the specified key for
 354  
     * the specified aspect.
 355  
     */
 356  
     public boolean canBindAspect (
 357  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey )
 358  
     {
 359  0
         return ( aspects.containsObject( anAspect ) );
 360  
     }
 361  
 
 362  
     /**
 363  
     * Binds the specified aspect of this association to the
 364  
     * specified key on the specified display group.
 365  
     */
 366  
     public void bindAspect (
 367  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey )
 368  
     {
 369  0
         if ( TitlesAspect.equals( anAspect ) )
 370  
         {
 371  0
             titlesDisplayGroup = aDisplayGroup;
 372  0
             titlesKey = aKey;
 373  
         }
 374  0
         if ( ChildrenAspect.equals( anAspect ) )
 375  
         {
 376  0
             childrenDisplayGroup = aDisplayGroup;
 377  0
             childrenKey = aKey;
 378  
         }
 379  0
         if ( IsLeafAspect.equals( anAspect ) )
 380  
         {
 381  0
             leafDisplayGroup = aDisplayGroup;
 382  0
             leafKey = aKey;
 383  
         }
 384  0
         if ( childrenDisplayGroup == null )
 385  
         {
 386  0
             childrenDisplayGroup = titlesDisplayGroup;
 387  
         }
 388  0
         super.bindAspect( anAspect, aDisplayGroup, aKey );
 389  0
     }
 390  
 
 391  
     /**
 392  
     * Establishes a connection between this association
 393  
     * and the controlled object.  Subclasses should begin
 394  
     * listening for events from their controlled object here.
 395  
     */
 396  
     public void establishConnection ()
 397  
     {
 398  0
         if ( titlesDisplayGroup == null )
 399  
         {
 400  0
             throw new WotonomyException( 
 401  0
                 "TreeModelAssociation: Titles aspect must be bound" );
 402  
         }
 403  
 
 404  
         // populate the root node
 405  0
         rootNode = createNode( titlesDisplayGroup, null );
 406  0
         rootNode.setObjectArray( titlesDisplayGroup.displayedObjects() );
 407  0
         rootNode.setSortOrderings( titlesDisplayGroup.sortOrderings() );
 408  
 
 409  0
         EODataSource dataSource = childrenDisplayGroup.dataSource();
 410  0
         if ( dataSource == null ) dataSource = titlesDisplayGroup.dataSource();
 411  0
         while ( dataSource instanceof DelegatingTreeDataSource )
 412  
         {   // unwrap any existing delegating data sources
 413  0
             dataSource = ((DelegatingTreeDataSource)dataSource).delegateDataSource;
 414  0
         }
 415  
         // create a new delegating data source
 416  0
         childrenDisplayGroup.setDataSource( 
 417  0
             new DelegatingTreeDataSource( this, dataSource ) );
 418  
 
 419  
         //TODO: find out why omitting this line causes weird selection behavior
 420  0
         childrenDisplayGroup.setSortOrderings( new NSArray() );
 421  
             
 422  
         // check for alternate usage
 423  0
         if ( object() instanceof TreeSelectionModel )
 424  
         {
 425  0
             selectionModel = (TreeSelectionModel) object();
 426  0
         }
 427  
         else // use specified object
 428  
         {
 429  
             try
 430  
             {
 431  0
                 setModel.invoke( object(), new Object[] { this } );
 432  0
                 selectionModel = (TreeSelectionModel)
 433  0
                     getSelectionModel.invoke( object(), new Object[] {} );
 434  
             }
 435  0
             catch ( Exception exc )
 436  
             {
 437  0
                 throw new WotonomyException( exc );
 438  0
             }
 439  
         }
 440  
 
 441  0
         addAsListener();
 442  0
         super.establishConnection();
 443  
 /*
 444  
         fireRootStructureChanged();
 445  
 
 446  
 //        titlesGroupChanged = true;
 447  
 //        subjectChanged();
 448  
         
 449  
         // update the children group
 450  
         removeAsListener();
 451  
         childrenDisplayGroup.fetch();
 452  
         addAsListener();
 453  
         
 454  
         // update selection
 455  
         selectFromDisplayGroup( titlesDisplayGroup );
 456  
 */
 457  0
     }
 458  
 
 459  
     protected void fireRootStructureChanged()
 460  
     {
 461  0
         int count = rootNode.displayedObjects().count();
 462  0
         int[] childIndices = new int[ count ];
 463  0
         Object[] children = new Object[ count ];
 464  0
         for ( int i = 0; i < count; i++ )
 465  
         {
 466  0
             childIndices[i] = i;
 467  0
             children[i] = rootNode.getChildNodeAt( i );
 468  
         }
 469  
 
 470  
         // must fire a tree structure changed with children,
 471  
         //   otherwise the tree gets weird selection behavior
 472  0
         fireTreeStructureChanged( this, new Object[] { rootNode },
 473  0
             childIndices, children );
 474  0
     }
 475  
 
 476  
     /**
 477  
     * Breaks the connection between this association and
 478  
     * its object.  Override to stop listening for events
 479  
     * from the object.
 480  
     */
 481  
     public void breakConnection ()
 482  
     {
 483  0
         if ( childrenDisplayGroup != null )
 484  
         {
 485  0
             if ( childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource )
 486  
             {
 487  0
                 if ( titlesDisplayGroup == childrenDisplayGroup )
 488  
                 {
 489  0
                     titlesDisplayGroup.setDataSource( ((DelegatingTreeDataSource) 
 490  0
                         childrenDisplayGroup.dataSource()).delegateDataSource );
 491  0
                 }
 492  
                 else
 493  
                 {
 494  0
                     childrenDisplayGroup.setDataSource( null );
 495  
                 }
 496  
             }
 497  
         }
 498  
 
 499  0
         removeAsListener();
 500  0
         super.breakConnection();
 501  0
     }
 502  
 
 503  
     protected void addAsListener()
 504  
     {
 505  0
         isListening = true;
 506  0
         selectionModel.addTreeSelectionListener( this );
 507  0
     }
 508  
 
 509  
     protected void removeAsListener()
 510  
     {
 511  0
         isListening = false;
 512  0
         selectionModel.removeTreeSelectionListener( this );
 513  0
     }
 514  
 
 515  0
     protected boolean isListening = false;
 516  0
     private boolean pleaseIgnore = false;
 517  0
     protected boolean titlesGroupChanged = false;
 518  0
     protected boolean childrenGroupChanged = false;
 519  
     
 520  
     /**
 521  
     * Overridden to better discriminate what is changed.
 522  
     */
 523  
     public void objectWillChange( Object anObject )
 524  
     {
 525  0
         if ( ! isListening ) return;
 526  
         
 527  0
         if ( anObject == titlesDisplayGroup )
 528  
         {
 529  0
             titlesGroupChanged = true;
 530  
         }
 531  0
         if ( anObject == childrenDisplayGroup )
 532  
         {
 533  0
             childrenGroupChanged = true;
 534  0
             if ( childrenDisplayGroup.qualifier() != null )
 535  
             {
 536  0
                 if ( ( rootNode.qualifier() == null ) ||
 537  0
                      ! childrenDisplayGroup.qualifier().equals( rootNode.qualifier() ) )
 538  
                 {
 539  
                     // quietly move qualifier from children group to root node
 540  0
                     rootNode.setQualifier( childrenDisplayGroup.qualifier() );
 541  0
                     childrenDisplayGroup.setQualifier( null );
 542  0
                     rootNode.updateDisplayedObjects();
 543  
                 }
 544  
             }
 545  
         }
 546  0
         super.objectWillChange( anObject );
 547  0
     }
 548  
 
 549  
     /**
 550  
     * Called when either the selection or the contents
 551  
     * of an associated display group have changed.
 552  
     */
 553  
     public void subjectChanged ()
 554  
     {
 555  
         // titles aspect
 556  0
         if ( titlesGroupChanged )
 557  
         {
 558  0
             if ( titlesDisplayGroup.contentsChanged() )
 559  
             {
 560  0
                 NSArray displayedObjects = titlesDisplayGroup.displayedObjects();
 561  
                 NSArray childrenObjects;
 562  0
                 if ( titlesDisplayGroup != childrenDisplayGroup 
 563  0
                 ||   displayedObjects.count() 
 564  0
                         != (childrenObjects = objectsFetchedIntoChildrenGroup()).count() 
 565  0
                 || ! displayedObjects.containsAll( childrenObjects ) )
 566  
                 {
 567  0
                     populateFromDisplayGroup( displayedObjects );
 568  
                 }
 569  
             }
 570  
         }
 571  
         
 572  0
         if ( childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged() )
 573  
         {
 574  0
             selectFromDisplayGroup( childrenDisplayGroup );
 575  
         }
 576  
         
 577  0
         titlesGroupChanged = false;
 578  0
         childrenGroupChanged = false;
 579  0
     }
 580  
 
 581  
     /**
 582  
     * Called by subjectChanged() in response to an external change in the titles display group.
 583  
     */    
 584  
     void populateFromDisplayGroup( List displayedObjects )
 585  
     {
 586  
         // trigger processRecentChanges
 587  0
         willChange();
 588  
 
 589  
         // workaround: see below
 590  0
         int previousCount = rootNode.previouslyDisplayedObjects.length;        
 591  
         
 592  
         // update the root node
 593  0
         rootNode.setObjectArray( displayedObjects );
 594  
         
 595  
         //FIXME: workaround for what appears to be a bug in JTree:
 596  
         // if root node is not visible and has no children, insert events are ignored
 597  0
         if ( previousCount == 0 )
 598  
         {
 599  0
             fireRootStructureChanged(); 
 600  
         }
 601  0
     }
 602  
 
 603  
     /**
 604  
     * Package access so DisplayGroupNode can replace the selection after an update.
 605  
     */
 606  
     void selectFromDisplayGroup( EODisplayGroup aDisplayGroup )
 607  
     {  // System.out.println( "selectFromDisplayGroup: " + aDisplayGroup.selectedObjects() );
 608  
 
 609  0
         removeAsListener();
 610  
 
 611  0
         TreePath[] paths = selectionModel.getSelectionPaths();
 612  0
         NSArray selectedObjects = aDisplayGroup.selectedObjects();
 613  
         
 614  
         // assemble current selection list
 615  0
         List treeSelection = new LinkedList();
 616  0
         if ( paths != null )
 617  
         {
 618  0
             for ( int i = 0; i < paths.length; i ++ )
 619  
             {
 620  0
                 treeSelection.add( 
 621  0
                     ((DisplayGroupNode)paths[i].getLastPathComponent()).getUserObject() );
 622  
             }
 623  
         }
 624  
 
 625  0
         if ( ! ( selectedObjects.size() == treeSelection.size()
 626  0
               && treeSelection.containsAll( selectedObjects ) ) )
 627  
         {
 628  0
             selectionModel.clearSelection();
 629  
     
 630  
             // workaround to select root node from valueChanged()
 631  0
             if ( pleaseSelectRootNode )
 632  
             {
 633  0
                 selectionModel.addSelectionPath( new TreePath( this.getRoot() ) );
 634  0
                 pleaseSelectRootNode = false;
 635  
             }
 636  
     
 637  
             //FIXME: display group is assumed to have only one instance of each object
 638  0
             for ( int i = 0; i < selectedObjects.count(); i++ )
 639  
             {
 640  
                 //FIXME: selects only the first instance for now
 641  
                 //selectionModel.addSelectionPaths(
 642  
                 //    getPathsForObject(
 643  
                 //        selectedObjects.objectAtIndex( i ) ) );
 644  0
                 selectionModel.addSelectionPath(
 645  0
                     getPathForObject(
 646  0
                         selectedObjects.objectAtIndex( i ) ) );
 647  
             }
 648  
         }
 649  
 
 650  0
         addAsListener();
 651  0
     }
 652  
 
 653  
     /**
 654  
     * Returns the first node found that represents the
 655  
     * specified object, or null if not found.
 656  
     * This implementation simply calls getPathForObject.
 657  
     */
 658  
     public Object getNodeForObject( Object anObject )
 659  
     {
 660  0
         TreePath result = getPathForObject( anObject );
 661  0
         if ( result != null )
 662  
         {
 663  0
             return result.getLastPathComponent();
 664  
         }
 665  0
         return null;
 666  
     }
 667  
 
 668  
     /**
 669  
     * Returns the object represented by the specified node
 670  
     * which must be a display group node from this tree.
 671  
     */
 672  
     public Object getObjectForNode( Object aNode )
 673  
     {
 674  0
         if ( aNode instanceof DisplayGroupNode )
 675  
         {
 676  0
             return ((DisplayGroupNode)aNode).getUserObject();
 677  
         }
 678  
 
 679  
         // not a display group node
 680  0
         throw new WotonomyException(
 681  0
             "Not a display group node: " + aNode );
 682  
     }
 683  
 
 684  
     /**
 685  
     * Returns the tree path for the specified node,
 686  
     * which must be a display group node from this tree.
 687  
     */
 688  
     public TreePath getPathForNode( Object aNode )
 689  
     {
 690  0
         if ( aNode instanceof DisplayGroupNode )
 691  
         {
 692  0
             return ((DisplayGroupNode)aNode).treePath();
 693  
         }
 694  
 
 695  
         // not a display group node
 696  0
         throw new WotonomyException(
 697  0
             "Not a display group node: " + aNode );
 698  
     }
 699  
 
 700  
     /**
 701  
     * Returns the first tree path for the node that represents
 702  
     * the specified object, or null if the object does not exist in this tree.
 703  
     * This implementation does a breadth-first search of the tree
 704  
     * for the object, looking only at nodes that have been loaded.
 705  
     * This means that if the object does not exist in the tree,
 706  
     * the entire tree must be traversed.
 707  
     */
 708  
     public TreePath getPathForObject( Object anObject )
 709  
     {
 710  0
         return getPathForObjectInPath( anObject, new TreePath( this.getRoot() ) );
 711  
     }
 712  
 
 713  
     /**
 714  
     * Returns the tree path for the node that represents
 715  
     * the specified object,
 716  
     * or null if the object does not exist in this tree.
 717  
     * This implementation does a breadth-first search of the tree
 718  
     * for the object, looking only at nodes that have been loaded.
 719  
     * This means that the entire tree is traversed.
 720  
     */
 721  
     public TreePath[] getPathsForObject( Object anObject )
 722  
     {
 723  0
         return getPathsForObjectInPath( anObject, new TreePath( this.getRoot() ) );
 724  
     }
 725  
 
 726  
     /**
 727  
     * A breadth-first search of the tree starting
 728  
     * at the specified tree path, comparing by reference.
 729  
     * Returns immediately with the first match.
 730  
     */
 731  
     private TreePath getPathForObjectInPath( Object anObject, TreePath aPath )
 732  
     {
 733  0
         LinkedList queue = new LinkedList();
 734  
 
 735  
         // add the specified path
 736  0
         queue.addLast( aPath );
 737  
 
 738  0
         return processQueue( anObject, queue, null );
 739  
     }
 740  
 
 741  
     /**
 742  
     * A breadth-first search of the tree starting
 743  
     * at the specified tree path, comparing by reference.
 744  
     * The entire branch is searched before returning
 745  
     * an array of all matches.
 746  
     */
 747  
     private TreePath[] getPathsForObjectInPath( Object anObject, TreePath aPath )
 748  
     {
 749  0
         LinkedList queue = new LinkedList();
 750  
 
 751  
         // add the specified path
 752  0
         queue.addLast( aPath );
 753  
 
 754  0
         List result = new LinkedList();
 755  0
         processQueue( anObject, queue, result );
 756  0
         TreePath[] paths = new TreePath[ result.size() ];
 757  0
         for ( int i = 0; i < paths.length; i++ )
 758  
         {
 759  0
             paths[i] = (TreePath) result.get(i);
 760  
         }
 761  0
         return paths;
 762  
     }
 763  
 
 764  
     /**
 765  
     * Processes the specified queue, appending results to aResult if it exists,
 766  
     * or returning immediately with a TreePath is aResult is null.
 767  
     */
 768  
     private TreePath processQueue( Object anObject, LinkedList aQueue, List aResult )
 769  
     {
 770  
         TreePath path;
 771  0
         while ( ! aQueue.isEmpty() )
 772  
         {
 773  0
             path = (TreePath) aQueue.removeFirst();
 774  0
             path = checkNode( anObject, path, aQueue );
 775  0
             if ( path != null )
 776  
             {
 777  0
                 if ( aResult != null )
 778  
                 {
 779  0
                     aResult.add( path );
 780  0
                 }
 781  
                 else
 782  
                 {
 783  0
                     return path;
 784  
                 }
 785  
             }
 786  
         }
 787  0
         return null;
 788  
     }
 789  
 
 790  
     /**
 791  
     * Compares the specified object by reference each of the children of
 792  
     * the node at the end of the specified tree path, adding nodes that
 793  
     * do not match but have fetched object to the end of the specified queue.
 794  
     * Returns the path of the first child node that matches the specified object,
 795  
     * or null if no match was found.
 796  
     */
 797  
     private TreePath checkNode( Object anObject, TreePath aPath, LinkedList aQueue )
 798  
     {
 799  0
         TreePath result = null;
 800  
         Object child;
 801  0
         Object parent = aPath.getLastPathComponent();
 802  0
         int count = getChildCount( parent );
 803  
 
 804  0
         for ( int i = 0; i < count; i++ )
 805  
         {
 806  0
             child = getChild( parent, i );
 807  
 
 808  
             // add to queue if node has fetched children
 809  0
             if ( ((DisplayGroupNode)child).isFetched )
 810  
             {
 811  0
                 aQueue.addLast( aPath.pathByAddingChild( child ) );
 812  
             }
 813  
 
 814  
             // compares by reference
 815  0
             if ( ((DisplayGroupNode)child).object() == anObject )
 816  
             {
 817  
                 // assumes same object cannot be in display group twice
 818  0
                 result = aPath.pathByAddingChild( child );
 819  
 //System.out.println( "TRUE: " + ((DisplayGroupNode)child).object() + " == " + anObject );
 820  
             }
 821  
 //            else
 822  
 //            {
 823  
 //System.out.println( ((DisplayGroupNode)child).object() + " != " + anObject );
 824  
 //            }
 825  
         }
 826  0
         return result;
 827  
     }
 828  
 
 829  
     // interface TreeSelectionListener
 830  
 
 831  
     public void valueChanged(TreeSelectionEvent e)
 832  
     {
 833  0
         if ( ! isListening ) return;
 834  0
         selectFromSelectionModel();
 835  0
     }
 836  
 
 837  
     /**
 838  
     * Determines whether the selection should be painted
 839  
     * immediately after the user clicks and therefore
 840  
     * before the children display group is updated.
 841  
     * When the children group is bound to many associations
 842  
     * or is bound to a master-detail association, updating
 843  
     * the display group can take a perceptibly long time.
 844  
     * This property defaults to false.
 845  
     * @see #setSelectionPaintedImmediately
 846  
     */
 847  
     public boolean isSelectionPaintedImmediately()
 848  
     {
 849  0
         return selectionPaintedImmediately;
 850  
     }
 851  
 
 852  
     /**
 853  
     * Sets whether the selection should be painted immediately.
 854  
     * Setting this property to true will let the tree paint
 855  
     * first before the display group is updated.
 856  
     * This means that any tree selection listers will
 857  
     * also be notified before the display group is updated
 858  
     * and will have to invokeLater if they want to see the
 859  
     * updated display group.
 860  
     */
 861  
     public void setSelectionPaintedImmediately( boolean isImmediate )
 862  
     {
 863  0
         selectionPaintedImmediately = isImmediate;
 864  0
     }
 865  
 
 866  
     /**
 867  
     * Package access so DisplayGroupNode can replace the selection.
 868  
     * Returns the display group containing the current selection: titles or children.
 869  
     */
 870  
     EODisplayGroup selectFromSelectionModel()
 871  
     {  // System.out.print( "selectFromSelectionModel: " );
 872  0
         removeAsListener();
 873  
         DisplayGroupNode node;
 874  
         TreePath parentPath;
 875  0
         TreePath[] selectedPaths = selectionModel.getSelectionPaths();
 876  0
         NSMutableArray selectionList = new NSMutableArray();
 877  0
         if ( selectedPaths != null )
 878  
         {
 879  0
             for ( int i = 0; i < selectedPaths.length; i++ )
 880  
             {
 881  
                 // root node is zero - ignore root node
 882  0
                 if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) )
 883  
                 {
 884  
                     // select root in selectFromDisplayGroup()
 885  0
                     pleaseSelectRootNode = true;
 886  0
                 }
 887  
                 else
 888  
                 {
 889  0
                     node = (DisplayGroupNode)
 890  0
                         selectedPaths[i].getLastPathComponent();
 891  0
                     Object o = node.object();
 892  
 
 893  0
                     if ( selectionList.indexOfIdenticalObject(o) == NSArray.NotFound )
 894  
                     {
 895  0
                         selectionList.addObject( o );
 896  
                     }
 897  
                 }
 898  
             }
 899  
         }
 900  0
         childrenDisplayGroup.fetch(); //note that we're not currently listening for changes
 901  0
         if ( ! childrenDisplayGroup.selectObjectsIdenticalTo( selectionList ) )
 902  
         {
 903  0
             addAsListener(); // because we don't have a listener stack 
 904  0
             selectFromDisplayGroup( childrenDisplayGroup );
 905  0
             removeAsListener();
 906  
         }
 907  0
         addAsListener();
 908  0
         return childrenDisplayGroup; // titles is now children if children not explicitly set
 909  
     }
 910  
 
 911  
     // interface TreeModel
 912  
 
 913  
     public Object getRoot()
 914  
     {
 915  0
         return rootNode;
 916  
     }
 917  
 
 918  
     public Object getChild(Object parent, int index)
 919  
     {
 920  
         // interestingly, this gets called by
 921  
         // BasicTreeUI.paintVerticalPartOfLeg for
 922  
         // the last child of each expanded tree node.
 923  0
         Object result = ((DisplayGroupNode)parent).getChildNodeAt( index );
 924  
 //((DisplayGroupNode)parent).suppressRecentChangeProcessing();
 925  0
 return result;
 926  
 //        return ((DisplayGroupNode)parent).getChildNodeAt( index );
 927  
     }
 928  
 
 929  
     public int getChildCount(Object parent)
 930  
     {
 931  0
         int result = ((DisplayGroupNode)parent).getChildCount();
 932  
 //((DisplayGroupNode)parent).suppressRecentChangeProcessing();
 933  0
 return result;
 934  
 //        return ((DisplayGroupNode)parent).getChildCount();
 935  
     }
 936  
 
 937  
     public boolean isLeaf(Object node)
 938  
     {
 939  0
         boolean result = ((DisplayGroupNode)node).isLeaf();
 940  
 //((DisplayGroupNode)node).suppressRecentChangeProcessing();
 941  0
 return result;
 942  
 //        return ((DisplayGroupNode)node).isLeaf();
 943  
     }
 944  
 
 945  
     /**
 946  
     * Returns whether this node is visible in the UI.
 947  
     * This implementation returns true.  
 948  
     * <br><br> 
 949  
     * Subclasses should return false if they can 
 950  
     * determine that the node is not displayed or 
 951  
     * expanded or otherwise visible.  Non-visible 
 952  
     * nodes will fetch only when they are shown.
 953  
     */
 954  
     public boolean isVisible(Object node)
 955  
     {
 956  0
         return true;
 957  
     }
 958  
 
 959  
     public void valueForPathChanged(TreePath path, Object newValue)
 960  
     {
 961  0
         ((DisplayGroupNode)path.getLastPathComponent()).setUserObject( newValue );
 962  0
     }
 963  
 
 964  
     public int getIndexOfChild(Object parent, Object child)
 965  
     {
 966  0
         int result = ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child );
 967  
 //((DisplayGroupNode)parent).suppressRecentChangeProcessing();
 968  0
 return result;
 969  
 //        return ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child );
 970  
     }
 971  
 
 972  
     public void addTreeModelListener(TreeModelListener aListener)
 973  
     {
 974  0
         listeners.add( aListener );
 975  0
     }
 976  
     public void removeTreeModelListener(TreeModelListener aListener)
 977  
     {
 978  0
         listeners.remove( aListener );
 979  0
     }
 980  
 
 981  
     /**
 982  
     * Fires a tree nodes changed event to all listeners.
 983  
     * Provided as a convenience if you need to make manual
 984  
     * changes to the tree model.
 985  
     */
 986  
     public void fireTreeNodesChanged(Object source,
 987  
                                     Object[] path,
 988  
                                     int[] childIndices,
 989  
                                     Object[] children)
 990  
     {
 991  
         
 992  0
         willChange(); // queue processRecentChanges
 993  0
         TreeModelEvent event = new TreeModelEvent(
 994  0
             source, path, childIndices, children );
 995  
 //System.out.println( "fireTreeNodesChanged: " + event );
 996  0
         Enumeration it = listeners.elements();
 997  0
         while ( it.hasMoreElements() )
 998  
         {
 999  
             try
 1000  
             {
 1001  0
                 ((TreeModelListener)it.nextElement()).treeNodesChanged( event );
 1002  
             }
 1003  0
             catch ( Exception exc )
 1004  
             {
 1005  0
                 System.out.println( "TreeModelAssociation.fireTreeNodesChanged: caught: " + exc );
 1006  0
                 System.out.println( "Source:" + source );
 1007  0
                 System.out.println( "Path:" );
 1008  0
                 for ( int i = 0; i < path.length; i++ )
 1009  
                 {
 1010  0
                      System.out.print( path[i] + "-" );
 1011  
                 }
 1012  0
                 System.out.println();
 1013  0
                 System.out.println( "Indices:" );
 1014  0
                 for ( int i = 0; i < childIndices.length; i++ )
 1015  
                 {
 1016  0
                      System.out.print( childIndices[i] + "-" );
 1017  
                 }
 1018  0
                 System.out.println();
 1019  0
                 System.out.println( "Children:" );
 1020  0
                 for ( int i = 0; i < children.length; i++ )
 1021  
                 {
 1022  0
                      System.out.print( children[i] + "-" );
 1023  
                 }
 1024  0
                 System.out.println();
 1025  0
                 exc.printStackTrace();
 1026  0
             }
 1027  0
         }
 1028  0
     }
 1029  
 
 1030  
     /**
 1031  
     * Fires a tree nodes inserted event to all listeners.
 1032  
     * Provided as a convenience if you need to make manual
 1033  
     * changes to the tree model.
 1034  
     */
 1035  
     public void fireTreeNodesInserted(Object source,
 1036  
                                      Object[] path,
 1037  
                                      int[] childIndices,
 1038  
                                      Object[] children)
 1039  
     {
 1040  
         
 1041  0
         willChange(); // queue processRecentChanges
 1042  0
         TreeModelEvent event = new TreeModelEvent(
 1043  0
             source, path, childIndices, children );
 1044  
 //System.out.println( "fireTreeNodesInserted: " + event );
 1045  0
         Enumeration it = listeners.elements();
 1046  0
         while ( it.hasMoreElements() )
 1047  
         {
 1048  
             try
 1049  
             {
 1050  0
                 ((TreeModelListener)it.nextElement()).treeNodesInserted( event );
 1051  
             }
 1052  0
             catch ( Exception exc )
 1053  
             {
 1054  0
                 System.out.println( "TreeModelAssociation.fireTreeNodesInserted: caught: " + exc );
 1055  0
             }
 1056  0
         }
 1057  0
     }
 1058  
 
 1059  
     /**
 1060  
     * Fires a tree nodes removed event to all listeners.
 1061  
     * Provided as a convenience if you need to make manual
 1062  
     * changes to the tree model.
 1063  
     */
 1064  
     public void fireTreeNodesRemoved(Object source,
 1065  
                                     Object[] path,
 1066  
                                     int[] childIndices,
 1067  
                                     Object[] children)
 1068  
     {
 1069  
         
 1070  0
         willChange(); // queue processRecentChanges
 1071  0
         TreeModelEvent event = new TreeModelEvent(
 1072  0
             source, path, childIndices, children );
 1073  
 //System.out.println( "fireTreeNodesRemoved: " + event );
 1074  0
         Enumeration it = listeners.elements();
 1075  0
         while ( it.hasMoreElements() )
 1076  
         {
 1077  
             try
 1078  
             {
 1079  
                 //NOTE: removing nodes causes tree to fire selection change event
 1080  
                 // which confuses us if we're rearranging nodes (when sorting, for example).
 1081  0
                 boolean wasListening = isListening;                
 1082  0
                 if ( wasListening ) isListening = false;
 1083  0
                 ((TreeModelListener)it.nextElement()).treeNodesRemoved( event );
 1084  0
                 if ( wasListening ) isListening = true;
 1085  
             }
 1086  0
             catch ( Exception exc )
 1087  
             {
 1088  0
                 System.out.println( "TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc );
 1089  0
             }
 1090  0
         }
 1091  0
     }
 1092  
 
 1093  
     /**
 1094  
     * Fires a tree structure changed event to all listeners.
 1095  
     * Provided as a convenience if you need to make manual
 1096  
     * changes to the tree model.
 1097  
     */
 1098  
     public void fireTreeStructureChanged(Object source,
 1099  
                                     Object[] path,
 1100  
                                     int[] childIndices,
 1101  
                                     Object[] children)
 1102  
     {
 1103  
         
 1104  0
         willChange(); // queue processRecentChanges
 1105  0
         TreeModelEvent event = new TreeModelEvent(
 1106  0
             source, path, childIndices, children );
 1107  
 //System.out.println( "fireStructureChanged: " + event );
 1108  0
         Enumeration it = listeners.elements();
 1109  0
         while ( it.hasMoreElements() )
 1110  
         {
 1111  0
             ((TreeModelListener)it.nextElement()).treeStructureChanged( event );
 1112  0
         }
 1113  0
     }
 1114  
 
 1115  
     /**
 1116  
     * Creates and returns a new display group node.
 1117  
     */
 1118  
     public DisplayGroupNode createNode( EODisplayGroup aParentGroup, Object anObject )
 1119  
     {
 1120  0
         return new MutableDisplayGroupNode( this, aParentGroup, anObject );
 1121  
     }
 1122  
     
 1123  
     /**
 1124  
     * Gets whether new objects programmatically inserted into the children
 1125  
     * display group should be inserted as a child of the first selected node.
 1126  
     * If false, new objects are inserted as siblings of the first selected node.
 1127  
     * Default value is true.
 1128  
     */
 1129  
     public boolean isInsertingChild()
 1130  
     {
 1131  0
         return insertingChild;
 1132  
     }
 1133  
     
 1134  
     /**
 1135  
     * Sets whether new objects programmatically inserted into the children
 1136  
     * display group should be inserted as a child of the first selected node.
 1137  
     * If false, new objects are inserted as siblings of the first selected node.
 1138  
     * Default value is true.
 1139  
     */
 1140  
     public void setInsertingChild( boolean asChild )
 1141  
     {
 1142  0
         insertingChild = asChild;
 1143  0
     }
 1144  
     
 1145  
     /**
 1146  
     * Determines where new objects programmatically inserted into the children
 1147  
     * display group should be inserted, based on the value of insertingChild.
 1148  
     * If insertingChild, isInsertingAfter causes objects to be inserted at 
 1149  
     * the end of the selected node's child list; otherwise, objects are inserted
 1150  
     * at the beginning of the list.  
 1151  
     * If inserting as a sibling, isInsertingAfter causes objects to be inserted
 1152  
     * before the selected node in the selected node's parent's child list; 
 1153  
     * otherwise, objects are inserted after the selected node in the child list.
 1154  
     * Default value is true.
 1155  
     */
 1156  
     public boolean isInsertingAfter()
 1157  
     {
 1158  0
         return insertingAfter;
 1159  
     }
 1160  
     
 1161  
     /**
 1162  
     * Determines where new objects programmatically inserted into the children
 1163  
     * display group should be inserted, based on the value of insertingChild.
 1164  
     * If insertingChild, isInsertingAfter causes objects to be inserted at 
 1165  
     * the end of the selected node's child list; otherwise, objects are inserted
 1166  
     * at the beginning of the list.  
 1167  
     * If inserting as a sibling, isInsertingAfter causes objects to be inserted
 1168  
     * before the selected node in the selected node's parent's child list; 
 1169  
     * otherwise, objects are inserted after the selected node in the child list.
 1170  
     * Default value is true.
 1171  
     */
 1172  
     public void setInsertingAfter( boolean after )
 1173  
     {
 1174  0
         insertingAfter = after;
 1175  0
     }
 1176  
     
 1177  
     /**
 1178  
     * Called to by the children group's data source when it receives
 1179  
     * an insertObject message, usually after an object has been inserted
 1180  
     * into the children display group.  
 1181  
     * Return the object that should be passed to the titles display 
 1182  
     * group's data source's implementation of insertObject, or return 
 1183  
     * null to prevent that method from being called. <br><br>
 1184  
     * This implementation inserts the specified object into the tree
 1185  
     * as determined by calling isInsertingChild and isInsertingAfter,
 1186  
     * then returns the unmodified object.  If there's no selection, or
 1187  
     * no selection model, the root node is assumed to be selected.  
 1188  
     * And if the root node is selected, the new node will obviously be 
 1189  
     * inserted as a child.  Override to customize.
 1190  
     */
 1191  
     protected Object objectInsertedIntoChildrenGroup( Object anObject )
 1192  
     {
 1193  
         // determine selection
 1194  0
         DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot();
 1195  0
         if ( selectionModel != null )
 1196  
         {
 1197  
             // get selected path
 1198  0
             TreePath path = selectionModel.getSelectionPath();
 1199  
         
 1200  
             // get selected node
 1201  0
             if ( path != null )
 1202  
             {
 1203  0
                 selectedNode = (DisplayGroupNode) path.getLastPathComponent();
 1204  
             }
 1205  
         }
 1206  
         // determine location of insertion
 1207  0
         int index = 0;
 1208  0
         if ( ( isInsertingChild() ) || ( selectedNode == getRoot() ) )
 1209  
         {
 1210  0
             if ( isInsertingAfter() )
 1211  
             {
 1212  0
                 index = selectedNode.getChildCount();
 1213  0
             }
 1214  
         }
 1215  
         else // inserting as sibling
 1216  
         {
 1217  0
             DisplayGroupNode parentNode = selectedNode.getParentGroup();
 1218  0
             index = parentNode.getIndex( selectedNode );
 1219  0
             if ( isInsertingAfter() )
 1220  
             {
 1221  0
                 index++;
 1222  
             }
 1223  0
             selectedNode = parentNode;
 1224  
         }
 1225  
         
 1226  
         // insert and return
 1227  0
         selectedNode.insertObjectAtIndex( anObject, index );
 1228  0
         return anObject;
 1229  
     }
 1230  
 
 1231  
     /**
 1232  
     * Called to by the children group's data source when it receives
 1233  
     * a deleteObject message, usually after an object has been deleted
 1234  
     * from the children display group.  
 1235  
     * Return the object that should be passed to the titles display 
 1236  
     * group's data source's implementation of deleteObject, or return 
 1237  
     * null to prevent that method from being called. <br><br>
 1238  
     * This implementation deletes all instances of the selected object
 1239  
     * from the tree nodes that are currently loaded, and returns the 
 1240  
     * unmodified object.  Override to customize.
 1241  
     */
 1242  
     protected Object objectDeletedFromChildrenGroup( Object anObject )
 1243  
     {
 1244  0
         TreePath[] paths = getPathsForObject( anObject );
 1245  0
         if ( paths != null )
 1246  
         {
 1247  0
             for ( int i = 0; i < paths.length; i++ )
 1248  
             {
 1249  0
                 ((DisplayGroupNode)paths[i].getLastPathComponent()).removeFromParent();
 1250  
             }
 1251  
         }
 1252  0
         return anObject;
 1253  
     }
 1254  
 
 1255  
     /**
 1256  
     * Called to by the children group's data source to populate it
 1257  
     * with all selected nodes and their siblings.  To customize,
 1258  
     * override this method, or specify a different data source for
 1259  
     * the children display group.
 1260  
     */
 1261  
     protected NSArray objectsFetchedIntoChildrenGroup()
 1262  
     {
 1263  
         DisplayGroupNode node;
 1264  
         TreePath parentPath;
 1265  0
         TreePath[] selectedPaths = selectionModel.getSelectionPaths();
 1266  0
         NSMutableArray objectList = new NSMutableArray();
 1267  0
         if ( selectedPaths != null )
 1268  
         {
 1269  0
             for ( int i = 0; i < selectedPaths.length; i++ )
 1270  
             {
 1271  
                 // root node is zero - ignore root node
 1272  0
                 if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) )
 1273  
                 {
 1274  
                         // select root in selectFromDisplayGroup()
 1275  0
                         pleaseSelectRootNode = true;
 1276  0
                 }
 1277  
                 else
 1278  
                 {
 1279  0
                     node = (DisplayGroupNode)
 1280  0
                         selectedPaths[i].getLastPathComponent();
 1281  0
                     Object o = node.object();
 1282  
 
 1283  
                     // add all children of parent to object list - includes self
 1284  0
                     if ( node.parentGroup != null )
 1285  
                     {
 1286  0
                         Enumeration e =
 1287  0
                             node.parentGroup.displayedObjects().objectEnumerator();
 1288  0
                         while ( e.hasMoreElements() )
 1289  
                         {
 1290  
                             // add only if not already in list
 1291  0
                             o = e.nextElement();
 1292  0
                             if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound )
 1293  
                             {
 1294  0
                                 objectList.addObject( o );
 1295  0
                             }
 1296  
                         }
 1297  0
                     }
 1298  
                     else // no parent node - add the node by itself
 1299  
                     {
 1300  
                         // add only if not already in list
 1301  0
                         if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound )
 1302  
                         {
 1303  0
                             objectList.addObject( o );
 1304  
                         }
 1305  
                     }
 1306  
                 }
 1307  
             }
 1308  
         }
 1309  
         
 1310  
         // if no selection
 1311  0
         if ( objectList.size() == 0 )
 1312  
         {
 1313  
             // populate with children of root
 1314  0
             objectList.addAll( rootNode.displayedObjects() );
 1315  
         }
 1316  0
         return objectList;
 1317  
     }
 1318  
 
 1319  
     /**
 1320  
     * Queues processRecentChanges to be run in the event queue.
 1321  
     */
 1322  
     private void willChange()
 1323  
     {
 1324  0
         EOObserverCenter.notifyObserversObjectWillChange( this );
 1325  0
     }
 1326  
     
 1327  
     /**
 1328  
     * Tells the children display group to refetch, so that it reflects
 1329  
     * any changes that were made in the node tree,
 1330  
     * and then updates the selection in the selection model.
 1331  
     * Triggered in response to willChange().
 1332  
     */
 1333  
     public void processRecentChanges()
 1334  
     {
 1335  0
         Runnable update = new Runnable()
 1336  
         {
 1337  0
             public void run()
 1338  
             {
 1339  0
                 removeAsListener(); // prevent data source refetch: see fetchObjects()
 1340  0
                 childrenDisplayGroup.fetch();
 1341  0
                 addAsListener();
 1342  0
                 selectFromDisplayGroup( childrenDisplayGroup );
 1343  0
             }
 1344  
         };
 1345  0
         if ( isListening )
 1346  
         {
 1347  0
             if ( selectionPaintedImmediately )
 1348  
             {
 1349  
                 // if painting selection immediately, run even later
 1350  
                 //   so that AWT's repaint event fires before we do.
 1351  0
                 SwingUtilities.invokeLater( update );
 1352  0
             }
 1353  
             else
 1354  
             {
 1355  
                 // otherwise run now
 1356  0
                 update.run();
 1357  
             }
 1358  
         }
 1359  0
     }
 1360  
 
 1361  
     /**
 1362  
     * Delegates most behaviors to the specified data source,
 1363  
     * except fetchObjects, which calls fetchObjectsIntoChildrenGroup
 1364  
     * on the tree model association.  If delegate is null,
 1365  
     * calls are passed to the superclass which is a PropertyDataSource.
 1366  
     */
 1367  
     static class DelegatingTreeDataSource extends PropertyDataSource
 1368  
     {
 1369  
         TreeModelAssociation parentAssociation;
 1370  
         EODataSource delegateDataSource;
 1371  
         
 1372  0
         public DelegatingTreeDataSource( 
 1373  
             TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource )
 1374  0
         {
 1375  0
             parentAssociation = aTreeModelAssociation;
 1376  0
             delegateDataSource = aDataSource;
 1377  0
         }
 1378  
         
 1379  
         /**
 1380  
         * Calls to delegateDataSource if it exists, otherwise
 1381  
         * calls to super.
 1382  
         */
 1383  
         public Object createObject()
 1384  
         { 
 1385  0
             if ( delegateDataSource != null ) 
 1386  
             {
 1387  0
                 return delegateDataSource.createObject();
 1388  
             }
 1389  0
             return super.createObject();
 1390  
         }
 1391  
     
 1392  
         /**
 1393  
         * Calls objectInsertedIntoChildrenGroup, and if not null
 1394  
         * calls to delegateDataSource.insertObject if it exists,
 1395  
         * and super.insertObjectAtIndex if not.
 1396  
         */
 1397  
         public void insertObjectAtIndex( Object anObject, int anIndex )
 1398  
         {
 1399  0
             anObject = 
 1400  0
                 parentAssociation.objectInsertedIntoChildrenGroup( 
 1401  0
                     anObject );
 1402  0
             if ( anObject != null )
 1403  
             {
 1404  0
                 if ( delegateDataSource != null ) 
 1405  
                 {
 1406  0
                     if ( delegateDataSource instanceof OrderedDataSource )
 1407  
                     {
 1408  0
                         ((OrderedDataSource)delegateDataSource).insertObjectAtIndex( anObject, anIndex );
 1409  0
                     }
 1410  
                     else
 1411  
                     {
 1412  0
                         delegateDataSource.insertObject( anObject );
 1413  
                     }
 1414  0
                 }
 1415  
                 else
 1416  
                 {
 1417  0
                     super.insertObjectAtIndex( anObject, anIndex );
 1418  
                 }
 1419  
             }
 1420  0
         }
 1421  
 
 1422  
         /**
 1423  
         * Calls objectDeletedIntoChildrenGroup, and if not null
 1424  
         * calls to delegateDataSource if it exists.
 1425  
         */
 1426  
         public void deleteObject( Object anObject )
 1427  
         {
 1428  0
             anObject = 
 1429  0
                 parentAssociation.objectDeletedFromChildrenGroup( 
 1430  0
                     anObject );
 1431  0
             if ( anObject != null )
 1432  
             {
 1433  0
                 if ( delegateDataSource != null ) 
 1434  
                 {
 1435  0
                     delegateDataSource.deleteObject( anObject );
 1436  
                 }
 1437  0
                 super.deleteObject( anObject );
 1438  
             }
 1439  0
         }
 1440  
 
 1441  
         /**
 1442  
         * Overridden to return the delegate's editing context,
 1443  
         * the titles display group's editing context, 
 1444  
         * and failing that calling to super.
 1445  
         */
 1446  
         public EOEditingContext editingContext ()
 1447  
         {
 1448  0
             EOEditingContext result = null;
 1449  0
             if ( delegateDataSource != null ) 
 1450  
             {
 1451  0
                 result = delegateDataSource.editingContext();
 1452  
             }
 1453  0
             if ( result == null )
 1454  
             {
 1455  0
                 EODataSource parentDataSource = 
 1456  0
                     parentAssociation.titlesDisplayGroup.dataSource();
 1457  0
                 if ( parentDataSource != this && parentDataSource != null )
 1458  
                 {
 1459  0
                     result = parentAssociation.titlesDisplayGroup.
 1460  0
                         dataSource().editingContext();
 1461  
                 }
 1462  
             }
 1463  0
             if ( result == null )
 1464  
             {
 1465  0
                 result = super.editingContext();
 1466  
             }
 1467  0
             return result;
 1468  
         }
 1469  
 
 1470  
         /**
 1471  
         * Returns a List containing the objects in this
 1472  
         * data source.
 1473  
         */
 1474  
         public NSArray fetchObjects ()
 1475  
         {
 1476  
             // if titles group is doing double-duty as children group
 1477  0
             if ( parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup )
 1478  
             {
 1479  
                 // if we're not initiating this fetch
 1480  0
                 if ( parentAssociation.isListening )
 1481  
                 {
 1482  
                     // need to call to delegate to see if we should update values
 1483  0
                     if ( delegateDataSource != null )
 1484  
                     {
 1485  
 // System.out.println( "fetching from delegate (slow!)" );            
 1486  0
                         NSArray result = delegateDataSource.fetchObjects();
 1487  0
                         NSArray rootObjects = parentAssociation.rootNode.displayedObjects();
 1488  
                         // if titles data source has different objects, return them
 1489  0
                         if ( rootObjects.count() != result.count() 
 1490  0
                         || ! rootObjects.containsAll( result ) )
 1491  
                         {
 1492  
                             // this will force the root node to repopulate in subjectChanged()
 1493  
 //System.out.println( "fetchObjects: data source" );            
 1494  0
                             return result;
 1495  
                         }
 1496  
                     }
 1497  
                 }
 1498  
             }
 1499  
             // otherwise: just repopulate the titles group
 1500  
 //System.out.println( "fetchObjects: objectsFetchedIntoChildrenGroup" );            
 1501  0
             return parentAssociation.objectsFetchedIntoChildrenGroup();
 1502  
         }
 1503  
         
 1504  
         /**
 1505  
         * Returns a data source that is capable of
 1506  
         * manipulating objects of the type returned by
 1507  
         * applying the specified key to objects
 1508  
         * vended by this data source.
 1509  
         * @see #qualifyWithRelationshipKey
 1510  
         */
 1511  
         public EODataSource
 1512  
             dataSourceQualifiedByKey ( String aKey )
 1513  
         {
 1514  0
             if ( delegateDataSource != null )
 1515  
             {
 1516  0
                 return delegateDataSource.dataSourceQualifiedByKey( aKey );
 1517  
             }
 1518  0
             return null;
 1519  
         }
 1520  
 
 1521  
         /**
 1522  
         * Restricts this data source to vend those
 1523  
         * objects that are associated with the specified
 1524  
         * key on the specified object.
 1525  
         */
 1526  
         public void
 1527  
             qualifyWithRelationshipKey (
 1528  
             String aKey, Object anObject )
 1529  
         {
 1530  0
             if ( delegateDataSource != null )
 1531  
             {
 1532  0
                 delegateDataSource.qualifyWithRelationshipKey( aKey, anObject );
 1533  
             }
 1534  0
         }
 1535  
         
 1536  
         /**
 1537  
         * Returns the value from the delegateDataSource, if it exists.
 1538  
         * Otherwise calls super.
 1539  
         */
 1540  
         public EOClassDescription classDescriptionForObjects()
 1541  
         {
 1542  0
             if ( delegateDataSource != null ) 
 1543  
             {
 1544  0
                 return delegateDataSource.classDescriptionForObjects();
 1545  
             }
 1546  0
             return super.classDescriptionForObjects();
 1547  
         }
 1548  
 
 1549  
     }
 1550  
     
 1551  
 }
 1552  
 
 1553  
 /*
 1554  
  * $Log$
 1555  
  * Revision 1.2  2006/02/18 23:19:05  cgruber
 1556  
  * Update imports and maven dependencies.
 1557  
  *
 1558  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 1559  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 1560  
  *
 1561  
  * Revision 1.20  2003/08/06 23:07:52  chochos
 1562  
  * general code cleanup (mostly, removing unused imports)
 1563  
  *
 1564  
  * Revision 1.19  2002/05/03 21:41:18  mpowers
 1565  
  * No longer clearing the selection model when updating from display group:
 1566  
  * we now only modify if a change needs to be made.
 1567  
  * No longer listening for selection change during firing of delete events:
 1568  
  * delete events cause JTree's to update their selection model.
 1569  
  * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges()
 1570  
  * must happen after the screen is painted, or the selection is not displayed.
 1571  
  *
 1572  
  * Revision 1.18  2002/04/23 19:12:28  mpowers
 1573  
  * Reimplemented fireEventsForChanges.  Fitter and happier.
 1574  
  *
 1575  
  * Revision 1.17  2002/04/19 21:18:46  mpowers
 1576  
  * Removed tree event coalescing, which was causing way too many problems.
 1577  
  * The fireChangeEvent algorithm is way faster than before, so we should
 1578  
  * still be better off than before.  At least now, we don't have to track
 1579  
  * whether the view component has encountered a particular node.
 1580  
  *
 1581  
  * Revision 1.16  2002/04/18 20:36:11  mpowers
 1582  
  * TreeModelAssociation now populates children group before selected objects.
 1583  
  * Got rid of the forceOnSync workaround for cancelled selection change.
 1584  
  *
 1585  
  * Revision 1.15  2002/04/15 21:52:50  mpowers
 1586  
  * Tightening up TreeModelAssociation and DisplayGroupNode.
 1587  
  * Now only firing root structure changed once.
 1588  
  * Now disposing of root's children.
 1589  
  * Better event coalescing.
 1590  
  *
 1591  
  * Revision 1.14  2002/04/12 21:05:58  mpowers
 1592  
  * Now distinguishing changes in titles group even better.
 1593  
  *
 1594  
  * Revision 1.11  2002/04/10 21:20:04  mpowers
 1595  
  * Better handling for tree nodes when working with editing contexts.
 1596  
  * Better handling for invalidation.  No longer broadcasting events
 1597  
  * when nodes have not been "registered" in the tree.
 1598  
  *
 1599  
  * Revision 1.10  2002/04/03 20:01:24  mpowers
 1600  
  * Removed printlns.
 1601  
  *
 1602  
  * Revision 1.8  2002/03/11 03:16:28  mpowers
 1603  
  * Better handling of change events; coalescing changes to children group.
 1604  
  *
 1605  
  * Revision 1.7  2002/03/08 23:19:57  mpowers
 1606  
  * Refactoring of DelegatingTreeDataSource to facilitate binding of titles
 1607  
  * and children aspects to the same display group.
 1608  
  *
 1609  
  * Revision 1.6  2002/03/07 23:04:36  mpowers
 1610  
  * Refining TreeColumnAssociation.
 1611  
  *
 1612  
  * Revision 1.5  2002/03/06 13:04:16  mpowers
 1613  
  * Implemented cascading qualifiers in tree nodes.
 1614  
  *
 1615  
  * Revision 1.4  2002/03/04 22:47:48  mpowers
 1616  
  * Fixed sort ordering for titles group.  Optimization for delegate selection.
 1617  
  *
 1618  
  * Revision 1.3  2002/03/04 12:28:47  mpowers
 1619  
  * Revised case where children and titles are bound to same display group.
 1620  
  *
 1621  
  * Revision 1.2  2002/03/01 23:42:09  mpowers
 1622  
  * Implemented TreeColumnAssociation, and updated documentation.
 1623  
  *
 1624  
  * Revision 1.1  2002/02/27 23:19:17  mpowers
 1625  
  * Refactoring of TreeAssociation to create TreeModelAssociation parent.
 1626  
  *
 1627  
  * Revision 1.38  2002/02/18 03:46:08  mpowers
 1628  
  * Implemented TreeTableCellRenderer.
 1629  
  *
 1630  
  * Revision 1.37  2002/02/13 21:20:15  mpowers
 1631  
  * Updated comments.
 1632  
  *
 1633  
  * Revision 1.36  2001/11/21 15:13:25  mpowers
 1634  
  * Better repainting for selectionPaintedImmediately.
 1635  
  * Better handling for selection with multiple instances of the same
 1636  
  * object in the tree (from yjcheung).
 1637  
  *
 1638  
  * Revision 1.35  2001/11/20 19:13:51  mpowers
 1639  
  * Finished implementation of children group's specialized data source.
 1640  
  *
 1641  
  * Revision 1.34  2001/11/19 16:30:37  mpowers
 1642  
  * Tree repaint strategy is now a preference: selectionPaintedImmediately.
 1643  
  *
 1644  
  * Revision 1.33  2001/11/15 17:56:41  mpowers
 1645  
  * Initial implementation of data source for the children display group.
 1646  
  *
 1647  
  * Revision 1.32  2001/11/14 00:05:54  mpowers
 1648  
  * Eliminated the run later in favor of repainting the component immediately.
 1649  
  * This makes things more predictable for users of the association that
 1650  
  * want to listen to mouse or selection events on the tree.
 1651  
  *
 1652  
  * Revision 1.31  2001/11/02 20:43:15  mpowers
 1653  
  * Fixes for delegate's shouldChangeSelection veto (from yjcheung).
 1654  
  *
 1655  
  * Revision 1.30  2001/10/29 20:42:56  mpowers
 1656  
  * On selection change, repainting tree before notifying display group;
 1657  
  * using NSRunLoop instead of SwingUtilities.
 1658  
  *
 1659  
  * Revision 1.29  2001/10/12 20:12:53  mpowers
 1660  
  * Better handling of selection change vetoing when changing selection
 1661  
  * to a node that is not the sibling of the originally selected node.
 1662  
  *
 1663  
  * Revision 1.28  2001/09/14 13:40:26  mpowers
 1664  
  * User-initiated selection changes are now handled on the next event loop
 1665  
  * so that the component repaints the new selection before any potentially
 1666  
  * lengthy logic is triggered by the selection change.
 1667  
  *
 1668  
  * Revision 1.27  2001/09/10 14:10:03  mpowers
 1669  
  * Tree now handles multiple instances of the same object.
 1670  
  *
 1671  
  * Revision 1.26  2001/07/18 13:03:32  mpowers
 1672  
  * TreeNodes now refetch only on demand.  Previously, once a node had
 1673  
  * been fetched, it was always refetched after an invalidate, even if
 1674  
  * the node was not being displayed.
 1675  
  *
 1676  
  * Revision 1.25  2001/05/14 15:25:35  mpowers
 1677  
  * No longer copying titles group's data source to children group.
 1678  
  *
 1679  
  * Revision 1.24  2001/05/08 18:47:34  mpowers
 1680  
  * Minor fixes for d3.
 1681  
  *
 1682  
  * Revision 1.23  2001/05/01 00:52:32  mpowers
 1683  
  * Implemented breadth-first traversal of tree for node.
 1684  
  *
 1685  
  * Revision 1.22  2001/04/26 01:15:19  mpowers
 1686  
  * Major clean-up of DisplayGroupNode: fitter, happier, more productive.
 1687  
  *
 1688  
  * Revision 1.21  2001/04/22 23:13:35  mpowers
 1689  
  * Minor bug.
 1690  
  *
 1691  
  * Revision 1.20  2001/04/22 23:05:33  mpowers
 1692  
  * Totally revised DisplayGroupNode so each object gets its own node
 1693  
  * (so the nodes are no longer fixed by index).
 1694  
  *
 1695  
  * Revision 1.19  2001/04/21 23:06:33  mpowers
 1696  
  * A major revisiting to support the revising of DisplayGroupNode.
 1697  
  *
 1698  
  * Revision 1.18  2001/04/03 20:36:01  mpowers
 1699  
  * Fixed refaulting/reverting/invalidating to be self-consistent.
 1700  
  *
 1701  
  * Revision 1.17  2001/03/29 21:35:08  mpowers
 1702  
  * Now handling circular references in the graph.
 1703  
  *
 1704  
  * Revision 1.16  2001/03/22 21:25:42  mpowers
 1705  
  * Fixed some nasty issues with jtree's internal state and array bounds.
 1706  
  *
 1707  
  * Revision 1.15  2001/03/19 21:37:58  mpowers
 1708  
  * Improved refresh of titles display group.
 1709  
  * Fixed dangling selection problem after refresh.
 1710  
  *
 1711  
  * Revision 1.14  2001/03/09 22:08:57  mpowers
 1712  
  * Trying to handle the dangling reference problem after an update.
 1713  
  *
 1714  
  * Revision 1.13  2001/02/17 17:23:49  mpowers
 1715  
  * More changes to support compiling with jdk1.1 collections.
 1716  
  *
 1717  
  * Revision 1.12  2001/01/25 02:16:25  mpowers
 1718  
  * TreeModelAssociation now returns DisplayGroupNode.getUserObject.
 1719  
  *
 1720  
  * Revision 1.11  2001/01/24 18:14:40  mpowers
 1721  
  * Fixed problem with leaving children aspect unspecified.
 1722  
  *
 1723  
  * Revision 1.10  2001/01/24 17:49:15  mpowers
 1724  
  * Added getObjectForNode and getNodeForObject convenience methods.
 1725  
  *
 1726  
  * Revision 1.9  2001/01/24 17:44:11  mpowers
 1727  
  * Renamed getPathForNode to getPathForObject to be more precise.
 1728  
  * And created a new getPathForNode method.
 1729  
  *
 1730  
  * Revision 1.8  2001/01/24 17:20:29  mpowers
 1731  
  * Children display group now holds siblings of selected objects
 1732  
  * in addition to the selected objects.
 1733  
  *
 1734  
  * Revision 1.5  2001/01/19 23:21:15  mpowers
 1735  
  * Fine tuning events broadcast from TreeModelAssociation.
 1736  
  *
 1737  
  * Revision 1.4  2001/01/18 21:27:29  mpowers
 1738  
  * Major rework of TreeModelAssociation.
 1739  
  *
 1740  
  * Revision 1.2  2001/01/11 20:29:19  mpowers
 1741  
  * Expanded access to tree event firing methods.
 1742  
  *
 1743  
  * Revision 1.1.1.1  2000/12/21 15:49:18  mpowers
 1744  
  * Contributing wotonomy.
 1745  
  *
 1746  
  * Revision 1.20  2000/12/20 16:25:42  michael
 1747  
  * Added log to all files.
 1748  
  *
 1749  
  *
 1750  
  */
 1751