1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 public class TreeModelAssociation extends EOAssociation
169 implements TreeModel, TreeSelectionListener
170 {
171 static final NSArray aspects =
172 new NSArray( new Object[] {
173 TitlesAspect, ChildrenAspect, IsLeafAspect
174 } );
175 static final NSArray aspectSignatures =
176 new NSArray( new Object[] {
177 AttributeToOneAspectSignature
178 } );
179 static final NSArray objectKeysTaken =
180 new NSArray( new Object[] {
181 "model"
182 } );
183
184 private final static NSSelector getSelectionModel =
185 new NSSelector( "getSelectionModel" );
186 private final static NSSelector setModel =
187 new NSSelector( "setModel",
188 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 super( anObject );
219
220 titlesDisplayGroup = null;
221 titlesKey = null;
222 childrenDisplayGroup = null;
223 childrenKey = null;
224 leafDisplayGroup = null;
225 leafKey = null;
226 listeners = new Vector();
227
228 selectionPaintedImmediately = false;
229
230
231 recentChangesObserver = new EOObserverProxy(
232 this, new NSSelector( "processRecentChanges" ),
233 EODelayedObserver.ObserverPrioritySixth );
234 EOObserverCenter.addObserver( recentChangesObserver, this );
235
236 insertingChild = true;
237 insertingAfter = true;
238
239 pleaseSelectRootNode = false;
240
241 rootLabel = "Root";
242 rootNode = createNode( null, null );
243 }
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 this( anObject );
252 rootLabel = aRootLabel;
253 rootNode.setUserObject( aRootLabel );
254 }
255
256 /***
257 * Gets the current root label.
258 */
259 public Object rootLabel()
260 {
261 return rootLabel;
262 }
263
264 /***
265 * Gets the current root label.
266 */
267 public Object getRootLabel()
268 {
269 return rootLabel();
270 }
271
272 /***
273 * Sets the root label.
274 */
275 public void setRootLabel( Object aLabel )
276 {
277 rootLabel = aLabel;
278 }
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 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 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 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 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 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 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 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 if ( TitlesAspect.equals( anAspect ) )
370 {
371 titlesDisplayGroup = aDisplayGroup;
372 titlesKey = aKey;
373 }
374 if ( ChildrenAspect.equals( anAspect ) )
375 {
376 childrenDisplayGroup = aDisplayGroup;
377 childrenKey = aKey;
378 }
379 if ( IsLeafAspect.equals( anAspect ) )
380 {
381 leafDisplayGroup = aDisplayGroup;
382 leafKey = aKey;
383 }
384 if ( childrenDisplayGroup == null )
385 {
386 childrenDisplayGroup = titlesDisplayGroup;
387 }
388 super.bindAspect( anAspect, aDisplayGroup, aKey );
389 }
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 if ( titlesDisplayGroup == null )
399 {
400 throw new WotonomyException(
401 "TreeModelAssociation: Titles aspect must be bound" );
402 }
403
404
405 rootNode = createNode( titlesDisplayGroup, null );
406 rootNode.setObjectArray( titlesDisplayGroup.displayedObjects() );
407 rootNode.setSortOrderings( titlesDisplayGroup.sortOrderings() );
408
409 EODataSource dataSource = childrenDisplayGroup.dataSource();
410 if ( dataSource == null ) dataSource = titlesDisplayGroup.dataSource();
411 while ( dataSource instanceof DelegatingTreeDataSource )
412 {
413 dataSource = ((DelegatingTreeDataSource)dataSource).delegateDataSource;
414 }
415
416 childrenDisplayGroup.setDataSource(
417 new DelegatingTreeDataSource( this, dataSource ) );
418
419
420 childrenDisplayGroup.setSortOrderings( new NSArray() );
421
422
423 if ( object() instanceof TreeSelectionModel )
424 {
425 selectionModel = (TreeSelectionModel) object();
426 }
427 else
428 {
429 try
430 {
431 setModel.invoke( object(), new Object[] { this } );
432 selectionModel = (TreeSelectionModel)
433 getSelectionModel.invoke( object(), new Object[] {} );
434 }
435 catch ( Exception exc )
436 {
437 throw new WotonomyException( exc );
438 }
439 }
440
441 addAsListener();
442 super.establishConnection();
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457 }
458
459 protected void fireRootStructureChanged()
460 {
461 int count = rootNode.displayedObjects().count();
462 int[] childIndices = new int[ count ];
463 Object[] children = new Object[ count ];
464 for ( int i = 0; i < count; i++ )
465 {
466 childIndices[i] = i;
467 children[i] = rootNode.getChildNodeAt( i );
468 }
469
470
471
472 fireTreeStructureChanged( this, new Object[] { rootNode },
473 childIndices, children );
474 }
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 if ( childrenDisplayGroup != null )
484 {
485 if ( childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource )
486 {
487 if ( titlesDisplayGroup == childrenDisplayGroup )
488 {
489 titlesDisplayGroup.setDataSource( ((DelegatingTreeDataSource)
490 childrenDisplayGroup.dataSource()).delegateDataSource );
491 }
492 else
493 {
494 childrenDisplayGroup.setDataSource( null );
495 }
496 }
497 }
498
499 removeAsListener();
500 super.breakConnection();
501 }
502
503 protected void addAsListener()
504 {
505 isListening = true;
506 selectionModel.addTreeSelectionListener( this );
507 }
508
509 protected void removeAsListener()
510 {
511 isListening = false;
512 selectionModel.removeTreeSelectionListener( this );
513 }
514
515 protected boolean isListening = false;
516 private boolean pleaseIgnore = false;
517 protected boolean titlesGroupChanged = false;
518 protected boolean childrenGroupChanged = false;
519
520 /***
521 * Overridden to better discriminate what is changed.
522 */
523 public void objectWillChange( Object anObject )
524 {
525 if ( ! isListening ) return;
526
527 if ( anObject == titlesDisplayGroup )
528 {
529 titlesGroupChanged = true;
530 }
531 if ( anObject == childrenDisplayGroup )
532 {
533 childrenGroupChanged = true;
534 if ( childrenDisplayGroup.qualifier() != null )
535 {
536 if ( ( rootNode.qualifier() == null ) ||
537 ! childrenDisplayGroup.qualifier().equals( rootNode.qualifier() ) )
538 {
539
540 rootNode.setQualifier( childrenDisplayGroup.qualifier() );
541 childrenDisplayGroup.setQualifier( null );
542 rootNode.updateDisplayedObjects();
543 }
544 }
545 }
546 super.objectWillChange( anObject );
547 }
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
556 if ( titlesGroupChanged )
557 {
558 if ( titlesDisplayGroup.contentsChanged() )
559 {
560 NSArray displayedObjects = titlesDisplayGroup.displayedObjects();
561 NSArray childrenObjects;
562 if ( titlesDisplayGroup != childrenDisplayGroup
563 || displayedObjects.count()
564 != (childrenObjects = objectsFetchedIntoChildrenGroup()).count()
565 || ! displayedObjects.containsAll( childrenObjects ) )
566 {
567 populateFromDisplayGroup( displayedObjects );
568 }
569 }
570 }
571
572 if ( childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged() )
573 {
574 selectFromDisplayGroup( childrenDisplayGroup );
575 }
576
577 titlesGroupChanged = false;
578 childrenGroupChanged = false;
579 }
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
587 willChange();
588
589
590 int previousCount = rootNode.previouslyDisplayedObjects.length;
591
592
593 rootNode.setObjectArray( displayedObjects );
594
595
596
597 if ( previousCount == 0 )
598 {
599 fireRootStructureChanged();
600 }
601 }
602
603 /***
604 * Package access so DisplayGroupNode can replace the selection after an update.
605 */
606 void selectFromDisplayGroup( EODisplayGroup aDisplayGroup )
607 {
608
609 removeAsListener();
610
611 TreePath[] paths = selectionModel.getSelectionPaths();
612 NSArray selectedObjects = aDisplayGroup.selectedObjects();
613
614
615 List treeSelection = new LinkedList();
616 if ( paths != null )
617 {
618 for ( int i = 0; i < paths.length; i ++ )
619 {
620 treeSelection.add(
621 ((DisplayGroupNode)paths[i].getLastPathComponent()).getUserObject() );
622 }
623 }
624
625 if ( ! ( selectedObjects.size() == treeSelection.size()
626 && treeSelection.containsAll( selectedObjects ) ) )
627 {
628 selectionModel.clearSelection();
629
630
631 if ( pleaseSelectRootNode )
632 {
633 selectionModel.addSelectionPath( new TreePath( this.getRoot() ) );
634 pleaseSelectRootNode = false;
635 }
636
637
638 for ( int i = 0; i < selectedObjects.count(); i++ )
639 {
640
641
642
643
644 selectionModel.addSelectionPath(
645 getPathForObject(
646 selectedObjects.objectAtIndex( i ) ) );
647 }
648 }
649
650 addAsListener();
651 }
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 TreePath result = getPathForObject( anObject );
661 if ( result != null )
662 {
663 return result.getLastPathComponent();
664 }
665 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 if ( aNode instanceof DisplayGroupNode )
675 {
676 return ((DisplayGroupNode)aNode).getUserObject();
677 }
678
679
680 throw new WotonomyException(
681 "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 if ( aNode instanceof DisplayGroupNode )
691 {
692 return ((DisplayGroupNode)aNode).treePath();
693 }
694
695
696 throw new WotonomyException(
697 "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 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 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 LinkedList queue = new LinkedList();
734
735
736 queue.addLast( aPath );
737
738 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 LinkedList queue = new LinkedList();
750
751
752 queue.addLast( aPath );
753
754 List result = new LinkedList();
755 processQueue( anObject, queue, result );
756 TreePath[] paths = new TreePath[ result.size() ];
757 for ( int i = 0; i < paths.length; i++ )
758 {
759 paths[i] = (TreePath) result.get(i);
760 }
761 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 while ( ! aQueue.isEmpty() )
772 {
773 path = (TreePath) aQueue.removeFirst();
774 path = checkNode( anObject, path, aQueue );
775 if ( path != null )
776 {
777 if ( aResult != null )
778 {
779 aResult.add( path );
780 }
781 else
782 {
783 return path;
784 }
785 }
786 }
787 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 TreePath result = null;
800 Object child;
801 Object parent = aPath.getLastPathComponent();
802 int count = getChildCount( parent );
803
804 for ( int i = 0; i < count; i++ )
805 {
806 child = getChild( parent, i );
807
808
809 if ( ((DisplayGroupNode)child).isFetched )
810 {
811 aQueue.addLast( aPath.pathByAddingChild( child ) );
812 }
813
814
815 if ( ((DisplayGroupNode)child).object() == anObject )
816 {
817
818 result = aPath.pathByAddingChild( child );
819
820 }
821
822
823
824
825 }
826 return result;
827 }
828
829
830
831 public void valueChanged(TreeSelectionEvent e)
832 {
833 if ( ! isListening ) return;
834 selectFromSelectionModel();
835 }
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 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 selectionPaintedImmediately = isImmediate;
864 }
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 {
872 removeAsListener();
873 DisplayGroupNode node;
874 TreePath parentPath;
875 TreePath[] selectedPaths = selectionModel.getSelectionPaths();
876 NSMutableArray selectionList = new NSMutableArray();
877 if ( selectedPaths != null )
878 {
879 for ( int i = 0; i < selectedPaths.length; i++ )
880 {
881
882 if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) )
883 {
884
885 pleaseSelectRootNode = true;
886 }
887 else
888 {
889 node = (DisplayGroupNode)
890 selectedPaths[i].getLastPathComponent();
891 Object o = node.object();
892
893 if ( selectionList.indexOfIdenticalObject(o) == NSArray.NotFound )
894 {
895 selectionList.addObject( o );
896 }
897 }
898 }
899 }
900 childrenDisplayGroup.fetch();
901 if ( ! childrenDisplayGroup.selectObjectsIdenticalTo( selectionList ) )
902 {
903 addAsListener();
904 selectFromDisplayGroup( childrenDisplayGroup );
905 removeAsListener();
906 }
907 addAsListener();
908 return childrenDisplayGroup;
909 }
910
911
912
913 public Object getRoot()
914 {
915 return rootNode;
916 }
917
918 public Object getChild(Object parent, int index)
919 {
920
921
922
923 Object result = ((DisplayGroupNode)parent).getChildNodeAt( index );
924
925 return result;
926
927 }
928
929 public int getChildCount(Object parent)
930 {
931 int result = ((DisplayGroupNode)parent).getChildCount();
932
933 return result;
934
935 }
936
937 public boolean isLeaf(Object node)
938 {
939 boolean result = ((DisplayGroupNode)node).isLeaf();
940
941 return result;
942
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 return true;
957 }
958
959 public void valueForPathChanged(TreePath path, Object newValue)
960 {
961 ((DisplayGroupNode)path.getLastPathComponent()).setUserObject( newValue );
962 }
963
964 public int getIndexOfChild(Object parent, Object child)
965 {
966 int result = ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child );
967
968 return result;
969
970 }
971
972 public void addTreeModelListener(TreeModelListener aListener)
973 {
974 listeners.add( aListener );
975 }
976 public void removeTreeModelListener(TreeModelListener aListener)
977 {
978 listeners.remove( aListener );
979 }
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 willChange();
993 TreeModelEvent event = new TreeModelEvent(
994 source, path, childIndices, children );
995
996 Enumeration it = listeners.elements();
997 while ( it.hasMoreElements() )
998 {
999 try
1000 {
1001 ((TreeModelListener)it.nextElement()).treeNodesChanged( event );
1002 }
1003 catch ( Exception exc )
1004 {
1005 System.out.println( "TreeModelAssociation.fireTreeNodesChanged: caught: " + exc );
1006 System.out.println( "Source:" + source );
1007 System.out.println( "Path:" );
1008 for ( int i = 0; i < path.length; i++ )
1009 {
1010 System.out.print( path[i] + "-" );
1011 }
1012 System.out.println();
1013 System.out.println( "Indices:" );
1014 for ( int i = 0; i < childIndices.length; i++ )
1015 {
1016 System.out.print( childIndices[i] + "-" );
1017 }
1018 System.out.println();
1019 System.out.println( "Children:" );
1020 for ( int i = 0; i < children.length; i++ )
1021 {
1022 System.out.print( children[i] + "-" );
1023 }
1024 System.out.println();
1025 exc.printStackTrace();
1026 }
1027 }
1028 }
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 willChange();
1042 TreeModelEvent event = new TreeModelEvent(
1043 source, path, childIndices, children );
1044
1045 Enumeration it = listeners.elements();
1046 while ( it.hasMoreElements() )
1047 {
1048 try
1049 {
1050 ((TreeModelListener)it.nextElement()).treeNodesInserted( event );
1051 }
1052 catch ( Exception exc )
1053 {
1054 System.out.println( "TreeModelAssociation.fireTreeNodesInserted: caught: " + exc );
1055 }
1056 }
1057 }
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 willChange();
1071 TreeModelEvent event = new TreeModelEvent(
1072 source, path, childIndices, children );
1073
1074 Enumeration it = listeners.elements();
1075 while ( it.hasMoreElements() )
1076 {
1077 try
1078 {
1079
1080
1081 boolean wasListening = isListening;
1082 if ( wasListening ) isListening = false;
1083 ((TreeModelListener)it.nextElement()).treeNodesRemoved( event );
1084 if ( wasListening ) isListening = true;
1085 }
1086 catch ( Exception exc )
1087 {
1088 System.out.println( "TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc );
1089 }
1090 }
1091 }
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 willChange();
1105 TreeModelEvent event = new TreeModelEvent(
1106 source, path, childIndices, children );
1107
1108 Enumeration it = listeners.elements();
1109 while ( it.hasMoreElements() )
1110 {
1111 ((TreeModelListener)it.nextElement()).treeStructureChanged( event );
1112 }
1113 }
1114
1115 /***
1116 * Creates and returns a new display group node.
1117 */
1118 public DisplayGroupNode createNode( EODisplayGroup aParentGroup, Object anObject )
1119 {
1120 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 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 insertingChild = asChild;
1143 }
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 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 insertingAfter = after;
1175 }
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
1194 DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot();
1195 if ( selectionModel != null )
1196 {
1197
1198 TreePath path = selectionModel.getSelectionPath();
1199
1200
1201 if ( path != null )
1202 {
1203 selectedNode = (DisplayGroupNode) path.getLastPathComponent();
1204 }
1205 }
1206
1207 int index = 0;
1208 if ( ( isInsertingChild() ) || ( selectedNode == getRoot() ) )
1209 {
1210 if ( isInsertingAfter() )
1211 {
1212 index = selectedNode.getChildCount();
1213 }
1214 }
1215 else
1216 {
1217 DisplayGroupNode parentNode = selectedNode.getParentGroup();
1218 index = parentNode.getIndex( selectedNode );
1219 if ( isInsertingAfter() )
1220 {
1221 index++;
1222 }
1223 selectedNode = parentNode;
1224 }
1225
1226
1227 selectedNode.insertObjectAtIndex( anObject, index );
1228 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 TreePath[] paths = getPathsForObject( anObject );
1245 if ( paths != null )
1246 {
1247 for ( int i = 0; i < paths.length; i++ )
1248 {
1249 ((DisplayGroupNode)paths[i].getLastPathComponent()).removeFromParent();
1250 }
1251 }
1252 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 TreePath[] selectedPaths = selectionModel.getSelectionPaths();
1266 NSMutableArray objectList = new NSMutableArray();
1267 if ( selectedPaths != null )
1268 {
1269 for ( int i = 0; i < selectedPaths.length; i++ )
1270 {
1271
1272 if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) )
1273 {
1274
1275 pleaseSelectRootNode = true;
1276 }
1277 else
1278 {
1279 node = (DisplayGroupNode)
1280 selectedPaths[i].getLastPathComponent();
1281 Object o = node.object();
1282
1283
1284 if ( node.parentGroup != null )
1285 {
1286 Enumeration e =
1287 node.parentGroup.displayedObjects().objectEnumerator();
1288 while ( e.hasMoreElements() )
1289 {
1290
1291 o = e.nextElement();
1292 if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound )
1293 {
1294 objectList.addObject( o );
1295 }
1296 }
1297 }
1298 else
1299 {
1300
1301 if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound )
1302 {
1303 objectList.addObject( o );
1304 }
1305 }
1306 }
1307 }
1308 }
1309
1310
1311 if ( objectList.size() == 0 )
1312 {
1313
1314 objectList.addAll( rootNode.displayedObjects() );
1315 }
1316 return objectList;
1317 }
1318
1319 /***
1320 * Queues processRecentChanges to be run in the event queue.
1321 */
1322 private void willChange()
1323 {
1324 EOObserverCenter.notifyObserversObjectWillChange( this );
1325 }
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 Runnable update = new Runnable()
1336 {
1337 public void run()
1338 {
1339 removeAsListener();
1340 childrenDisplayGroup.fetch();
1341 addAsListener();
1342 selectFromDisplayGroup( childrenDisplayGroup );
1343 }
1344 };
1345 if ( isListening )
1346 {
1347 if ( selectionPaintedImmediately )
1348 {
1349
1350
1351 SwingUtilities.invokeLater( update );
1352 }
1353 else
1354 {
1355
1356 update.run();
1357 }
1358 }
1359 }
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 public DelegatingTreeDataSource(
1373 TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource )
1374 {
1375 parentAssociation = aTreeModelAssociation;
1376 delegateDataSource = aDataSource;
1377 }
1378
1379 /***
1380 * Calls to delegateDataSource if it exists, otherwise
1381 * calls to super.
1382 */
1383 public Object createObject()
1384 {
1385 if ( delegateDataSource != null )
1386 {
1387 return delegateDataSource.createObject();
1388 }
1389 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 anObject =
1400 parentAssociation.objectInsertedIntoChildrenGroup(
1401 anObject );
1402 if ( anObject != null )
1403 {
1404 if ( delegateDataSource != null )
1405 {
1406 if ( delegateDataSource instanceof OrderedDataSource )
1407 {
1408 ((OrderedDataSource)delegateDataSource).insertObjectAtIndex( anObject, anIndex );
1409 }
1410 else
1411 {
1412 delegateDataSource.insertObject( anObject );
1413 }
1414 }
1415 else
1416 {
1417 super.insertObjectAtIndex( anObject, anIndex );
1418 }
1419 }
1420 }
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 anObject =
1429 parentAssociation.objectDeletedFromChildrenGroup(
1430 anObject );
1431 if ( anObject != null )
1432 {
1433 if ( delegateDataSource != null )
1434 {
1435 delegateDataSource.deleteObject( anObject );
1436 }
1437 super.deleteObject( anObject );
1438 }
1439 }
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 EOEditingContext result = null;
1449 if ( delegateDataSource != null )
1450 {
1451 result = delegateDataSource.editingContext();
1452 }
1453 if ( result == null )
1454 {
1455 EODataSource parentDataSource =
1456 parentAssociation.titlesDisplayGroup.dataSource();
1457 if ( parentDataSource != this && parentDataSource != null )
1458 {
1459 result = parentAssociation.titlesDisplayGroup.
1460 dataSource().editingContext();
1461 }
1462 }
1463 if ( result == null )
1464 {
1465 result = super.editingContext();
1466 }
1467 return result;
1468 }
1469
1470 /***
1471 * Returns a List containing the objects in this
1472 * data source.
1473 */
1474 public NSArray fetchObjects ()
1475 {
1476
1477 if ( parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup )
1478 {
1479
1480 if ( parentAssociation.isListening )
1481 {
1482
1483 if ( delegateDataSource != null )
1484 {
1485
1486 NSArray result = delegateDataSource.fetchObjects();
1487 NSArray rootObjects = parentAssociation.rootNode.displayedObjects();
1488
1489 if ( rootObjects.count() != result.count()
1490 || ! rootObjects.containsAll( result ) )
1491 {
1492
1493
1494 return result;
1495 }
1496 }
1497 }
1498 }
1499
1500
1501 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 if ( delegateDataSource != null )
1515 {
1516 return delegateDataSource.dataSourceQualifiedByKey( aKey );
1517 }
1518 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 if ( delegateDataSource != null )
1531 {
1532 delegateDataSource.qualifyWithRelationshipKey( aKey, anObject );
1533 }
1534 }
1535
1536 /***
1537 * Returns the value from the delegateDataSource, if it exists.
1538 * Otherwise calls super.
1539 */
1540 public EOClassDescription classDescriptionForObjects()
1541 {
1542 if ( delegateDataSource != null )
1543 {
1544 return delegateDataSource.classDescriptionForObjects();
1545 }
1546 return super.classDescriptionForObjects();
1547 }
1548
1549 }
1550
1551 }
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751