1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.web;
20
21 import java.util.Collection;
22 import java.util.Enumeration;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Observable;
28
29 import net.wotonomy.control.EODataSource;
30 import net.wotonomy.control.EOEditingContext;
31 import net.wotonomy.control.EOKeyValueCoding;
32 import net.wotonomy.control.EOKeyValueCodingSupport;
33 import net.wotonomy.control.EOObjectStore;
34 import net.wotonomy.control.EOObserverCenter;
35 import net.wotonomy.control.EOObserving;
36 import net.wotonomy.control.EOQualifier;
37 import net.wotonomy.control.EOSortOrdering;
38 import net.wotonomy.control.OrderedDataSource;
39 import net.wotonomy.control.PropertyDataSource;
40 import net.wotonomy.foundation.NSArray;
41 import net.wotonomy.foundation.NSDictionary;
42 import net.wotonomy.foundation.NSMutableArray;
43 import net.wotonomy.foundation.NSMutableDictionary;
44 import net.wotonomy.foundation.NSNotification;
45 import net.wotonomy.foundation.NSNotificationCenter;
46 import net.wotonomy.foundation.NSRange;
47 import net.wotonomy.foundation.NSSelector;
48 import net.wotonomy.foundation.internal.Duplicator;
49 import net.wotonomy.foundation.internal.WotonomyException;
50
51 /***
52 * WODisplayGroup manages a set of objects,
53 * allowing them to be sorted, batched, and filtered.
54 * WODisplay also acts as a bridge to the wotonomy's
55 * control package, including WODisplayGroup and
56 * EOEditingContext.
57 *
58 * @author michael@mpowers.net
59 * @author $Author: cgruber $
60 * @version $Revision: 905 $
61 */
62 public class WODisplayGroup extends Observable
63 implements EOObserving, EOEditingContext.Editor,
64 java.io.Serializable
65 {
66 /***
67 * Notification sent when the display group is about to fetch.
68 */
69 public static final String DisplayGroupWillFetchNotification
70 = "DisplayGroupWillFetchNotification";
71
72 private static boolean
73 globalDefaultForValidatesChangesImmediately = true;
74 private static String
75 globalDefaultStringMatchFormat = "caseInsensitiveLike";
76 private static String
77 globalDefaultStringMatchOperator = "%@*";
78
79 protected NSMutableArray allObjects;
80 protected NSArray allObjectsProxy;
81 protected NSMutableArray displayedObjects;
82 protected NSArray displayedObjectsProxy;
83 protected NSMutableArray selectedObjects;
84 protected NSArray selectedObjectsProxy;
85 protected NSMutableArray selectedIndexes;
86
87 private String defaultStringMatchOperator;
88 private String defaultStringMatchFormat;
89
90 private boolean validatesChangesImmediately;
91 private Object delegate;
92 private EODataSource dataSource;
93 private EOQualifier qualifier;
94 private NSMutableArray sortOrderings;
95 private NSArray sortOrderingsProxy;
96
97 private NSArray localKeys;
98 private NSDictionary insertedObjectDefaultValues;
99 private boolean fetchesOnLoad;
100 private boolean selectsFirstObjectAfterFetch;
101 private boolean usesOptimisticRefresh;
102 private boolean inQueryMode;
103
104
105 boolean selectionChanged;
106 int updatedObjectIndex;
107
108
109 private int batchIndex;
110 private int batchSize;
111
112
113 private boolean compareByReference = false;
114
115 private EOObserving lastGroupObserver;
116
117 /***
118 * Creates a new display group.
119 */
120 public WODisplayGroup()
121 {
122 validatesChangesImmediately =
123 globalDefaultForValidatesChangesImmediately();
124 defaultStringMatchOperator =
125 globalDefaultStringMatchFormat();
126 defaultStringMatchFormat =
127 globalDefaultStringMatchOperator();
128
129 allObjects = new ObservableArray( this );
130 allObjectsProxy = NSArray.arrayBackedByList( allObjects );
131 displayedObjects = new NSMutableArray();
132 displayedObjectsProxy = NSArray.arrayBackedByList( displayedObjects );
133 selectedObjects = new NSMutableArray();
134 selectedObjectsProxy = NSArray.arrayBackedByList( selectedObjects );
135 sortOrderings = new NSMutableArray();
136 sortOrderingsProxy = NSArray.arrayBackedByList( sortOrderings );
137 selectedIndexes = new NSMutableArray();
138
139 delegate = null;
140 dataSource = null;
141 qualifier = null;
142
143 localKeys = new NSArray();
144 insertedObjectDefaultValues = new NSDictionary();
145 fetchesOnLoad = false;
146 selectsFirstObjectAfterFetch = false;
147 usesOptimisticRefresh = false;
148 inQueryMode = false;
149
150 selectionChanged = false;
151 updatedObjectIndex = -1;
152
153 batchIndex = 0;
154 batchSize = 0;
155 }
156
157
158
159
160
161 /***
162 * Sets the data source that will be used by
163 * this display group.
164 */
165 public void setDataSource ( EODataSource aDataSource )
166 {
167 if ( ( dataSource != null )
168 && ( dataSource.editingContext() != null ) )
169 {
170
171 NSNotificationCenter.defaultCenter().removeObserver(
172 this, null, dataSource.editingContext() );
173 dataSource.editingContext().removeEditor( this );
174 if ( dataSource.editingContext().messageHandler() == this )
175 {
176 dataSource.editingContext().setMessageHandler( null );
177 }
178
179 }
180
181 dataSource = aDataSource;
182
183 if ( ( dataSource != null )
184 && ( dataSource.editingContext() != null ) )
185 {
186
187 NSNotificationCenter.defaultCenter().addObserver(
188 this, new NSSelector( "objectsInvalidatedInEditingContext",
189 new Class[] { NSNotification.class } ),
190 null, dataSource.editingContext() );
191
192
193 dataSource.editingContext().addEditor( this );
194
195
196 if ( dataSource.editingContext().messageHandler() == null )
197 {
198 dataSource.editingContext().setMessageHandler( this );
199 }
200 }
201 }
202
203 /***
204 * Returns the current data source backing this display group,
205 * or null if no dataSource is currently used.
206 */
207 public EODataSource dataSource()
208 {
209 return dataSource;
210 }
211
212 /***
213 * Returns the key by which this display group is bound a master
214 * display group, or null if this is not a detail display group.
215 */
216 public String detailKey()
217 {
218 if ( dataSource instanceof PropertyDataSource )
219 {
220 return ((PropertyDataSource)dataSource).key();
221 }
222 return null;
223 }
224
225 /***
226 * Sets the key by which this display group is bound a master
227 * display group. Does nothing if this is not a detail display group.
228 */
229 public void setDetailKey( String aKey )
230 {
231 if ( dataSource instanceof PropertyDataSource )
232 {
233 ((PropertyDataSource)dataSource).setKey( aKey );
234 }
235 }
236
237 /***
238 * Returns whether the data source is a detail data source,
239 * suggesting that this is a detail display group.
240 */
241 public boolean hasDetailDataSource()
242 {
243 return ( dataSource instanceof PropertyDataSource );
244 }
245
246 /***
247 * Returns the selected object in the master display group,
248 * or null if this is not a detail display group.
249 */
250 public Object masterObject()
251 {
252 if ( dataSource instanceof PropertyDataSource )
253 {
254 return ((PropertyDataSource)dataSource).source();
255 }
256 return null;
257 }
258
259 /***
260 * Sets the master object in the detail data source.
261 * Does nothing if there is no detail data source.
262 */
263 public void setMasterObject( Object anObject )
264 {
265 if ( dataSource instanceof PropertyDataSource )
266 {
267 ((PropertyDataSource)dataSource).setSource( anObject );
268 }
269 }
270
271
272
273 /***
274 * Sets the display group delegate that
275 * will be used by this display group.
276 */
277 public void setDelegate ( Object aDelegate )
278 {
279 delegate = aDelegate;
280 }
281
282 /***
283 * Returns the current delegate for this display group,
284 * or null if no delegate is currently set.
285 */
286 public Object delegate()
287 {
288 return delegate;
289 }
290
291
292
293
294
295 /***
296 * Returns the current string matching format.
297 * If not set, defaults to "%@*".
298 */
299 public String defaultStringMatchFormat()
300 {
301 return defaultStringMatchFormat;
302 }
303
304 /***
305 * Returns the current string matching operator.
306 * If not set, defaults to "caseInsensitiveLike".
307 */
308 public String defaultStringMatchOperator()
309 {
310 return defaultStringMatchOperator;
311 }
312
313 /***
314 * Sets the display group and associations to edit a
315 * "query by example" query object. This method is
316 * used for target/action connections.
317 */
318 public void enterQueryMode ( Object aSender )
319 {
320 throw new RuntimeException( "Not implemented yet." );
321 }
322
323 /***
324 * Returns whether this display group should immediate
325 * fetch when loaded.
326 */
327 public boolean fetchesOnLoad()
328 {
329 return fetchesOnLoad;
330 }
331
332 /***
333 * Returns whether this display group is in "query by
334 * example" mode.
335 */
336 public boolean inQueryMode()
337 {
338 return inQueryMode;
339 }
340
341 /***
342 * Returns a Map of default values that are applied
343 * to new objects that are inserted into the list.
344 */
345 public NSDictionary insertedObjectDefaultValues()
346 {
347 return insertedObjectDefaultValues;
348 }
349
350 /***
351 * Returns the keys that were declared when read from
352 * an external resource file.
353 */
354 public NSArray localKeys()
355 {
356 return localKeys;
357 }
358
359 /***
360 * Sets whether this display group will select the
361 * first object in the list after a fetch.
362 */
363 public boolean selectsFirstObjectAfterFetch()
364 {
365 return selectsFirstObjectAfterFetch;
366 }
367
368 /***
369 * Sets the default string matching format that
370 * will be used by this display group.
371 */
372 public void setDefaultStringMatchFormat ( String aFormat )
373 {
374 throw new RuntimeException( "Not implemented yet." );
375 }
376
377 /***
378 * Sets the default string matching operator that
379 * will be used by this display group.
380 */
381 public void setDefaultStringMatchOperator ( String anOperator )
382 {
383 throw new RuntimeException( "Not implemented yet." );
384 }
385
386 /***
387 * Sets whether this display group will fetch objects
388 * from its data source on load.
389 */
390 public void setFetchesOnLoad ( boolean willFetch )
391 {
392 fetchesOnLoad = willFetch;
393 }
394
395 /***
396 * Sets whether this display group is in "query by example"
397 * mode. If true, all associations will bind to a special
398 * "example" object.
399 */
400 public void setInQueryMode ( boolean isInQueryMode )
401 {
402 inQueryMode = isInQueryMode;
403 }
404
405 /***
406 * Sets the mapping that contains the values that will
407 * be applied to new objects inserted into the display group.
408 */
409 public void setInsertedObjectDefaultValues ( Map aMap )
410 {
411 insertedObjectDefaultValues = new NSDictionary( aMap );
412 }
413
414 /***
415 * Sets the keys that are declared when instantiated from
416 * an external resource file.
417 */
418 public void setLocalKeys ( List aKeyList )
419 {
420 localKeys = new NSArray( (Collection) aKeyList );
421 }
422
423 /***
424 * Sets whether the first object in the list will be
425 * selected after a fetch.
426 */
427 public void setSelectsFirstObjectAfterFetch (
428 boolean selectsFirst )
429 {
430 selectsFirstObjectAfterFetch = selectsFirst;
431 }
432
433 /***
434 * Sets the order of the keys by which this display group
435 * will be ordered after a fetch or after a call to
436 * updateDisplayedObjects(). The elements in the display
437 * group will be sorted first by the first key, within
438 * the first key, by the second key, and so on.
439 */
440 public void setSortOrderings ( List aList )
441 {
442 sortOrderings.removeAllObjects();
443
444 Object o;
445 Iterator it = aList.iterator();
446 while ( it.hasNext() )
447 {
448 o = it.next();
449
450 if ( ! ( o instanceof EOSortOrdering ) )
451 {
452 o = new EOSortOrdering(
453 o.toString(), EOSortOrdering.CompareAscending );
454 }
455 sortOrderings.add( o );
456 }
457 }
458
459 /***
460 * Sets whether only changed objects are refreshed (optimistic),
461 * or whether all objects are refreshed (pessimistic, default).
462 * By default, when the display group receives notification that
463 * one of its objects has changed, updateDisplayedObjects is called.
464 */
465 public void setUsesOptimisticRefresh ( boolean isOptimistic )
466 {
467 usesOptimisticRefresh = isOptimistic;
468 }
469
470 /***
471 * Sets whether changes made by associations are validated
472 * immediately, or when changes are saved.
473 */
474 public void setValidatesChangesImmediately (
475 boolean validatesImmediately )
476 {
477 validatesChangesImmediately = validatesImmediately;
478 }
479
480 /***
481 * Returns a read-only List of sort orderings for this display group.
482 */
483 public NSArray sortOrderings()
484 {
485 return sortOrderingsProxy;
486 }
487
488 /***
489 * Returns whether this display group refreshes only
490 * the changed objects or all objects on refresh.
491 */
492 public boolean usesOptimisticRefresh()
493 {
494 return usesOptimisticRefresh;
495 }
496
497 /***
498 * Returns whether this display group validates changes
499 * immediately. Otherwise, validation should occur when
500 * changes are saved. Default is the global default,
501 * which is initially true.
502 */
503 public boolean validatesChangesImmediately()
504 {
505 return validatesChangesImmediately;
506 }
507
508
509
510
511 /***
512 * Returns a qualifier that will be applied all the objects
513 * in this display group to determine which objects will
514 * be displayed.
515 */
516 public EOQualifier qualifier()
517 {
518 return qualifier;
519 }
520
521 /***
522 * Returns a new qualifier built from the three query
523 * value maps: greater than, equal to, and less than.
524 */
525 public EOQualifier qualifierFromQueryValues()
526 {
527
528
529 return new EOQualifier()
530 {
531
532 public EOQualifier qualifierWithBindings(
533 Map aMap,
534 boolean requireAll )
535 {
536 return null;
537 }
538 public Throwable
539 validateKeysWithRootClassDescription( Class aClass )
540 {
541 return null;
542 }
543 public boolean evaluateWithObject(Object o)
544 {
545 return false;
546 }
547 };
548 }
549
550 /***
551 * Calls qualifierFromQueryValues(), applies the result
552 * to the data source, and calls fetch().
553 */
554 public void qualifyDataSource()
555 {
556 throw new RuntimeException( "Not implemented yet." );
557 }
558
559 /***
560 * Calls qualifierFromQueryValues(), sets the qualifier
561 * with setQualifier(), and calls updateDisplayedObjects().
562 */
563 public void qualifyDisplayGroup()
564 {
565 setQualifier( qualifierFromQueryValues() );
566 updateDisplayedObjects();
567 }
568
569 /***
570 * Returns a Map containing the mappings of keys
571 * to binding query values.
572 */
573 public NSDictionary queryBindingValues()
574 {
575 throw new RuntimeException( "Not implemented yet." );
576 }
577
578 /***
579 * Returns a Map containing the mappings of keys
580 * to binding query values.
581 */
582 public NSMutableDictionary queryBindings()
583 {
584 throw new RuntimeException( "Not implemented yet." );
585 }
586
587 /***
588 * Returns a Map containing the mappings of keys
589 * to binding query values for a matching query.
590 */
591 public NSMutableDictionary queryMatch()
592 {
593 throw new RuntimeException( "Not implemented yet." );
594 }
595
596 /***
597 * Returns a Map containing the mappings of keys
598 * to binding query values for a minimum value query.
599 */
600 public NSMutableDictionary queryMin()
601 {
602 throw new RuntimeException( "Not implemented yet." );
603 }
604
605 /***
606 * Returns a Map containing the mappings of keys
607 * to binding query values for a maximum value query.
608 */
609 public NSMutableDictionary queryMax()
610 {
611 throw new RuntimeException( "Not implemented yet." );
612 }
613
614 /***
615 * Returns a Map containing the mappings of keys
616 * to operator values.
617 */
618 public NSMutableDictionary queryOperator()
619 {
620 throw new RuntimeException( "Not implemented yet." );
621 }
622
623 /***
624 * Returns a list containing all supported qualifier operators.
625 */
626 public NSArray allQualifierOperators()
627 {
628 throw new RuntimeException( "Not implemented yet." );
629 }
630
631 /***
632 * Returns a Map containing the mappings of keys
633 * to operator values.
634 */
635 public NSDictionary queryOperatorValues()
636 {
637 throw new RuntimeException( "Not implemented yet." );
638 }
639
640 /***
641 * Sets the qualifier that will be used by
642 * updateDisplayedObjects() to filter displayed objects.
643 */
644 public void setQualifier ( EOQualifier aQualifier )
645 {
646 qualifier = aQualifier;
647 }
648
649 /***
650 * Sets the mapping that contains the mappings of keys
651 * to binding values.
652 */
653 public void setQueryBindingValues ( Map aMap )
654 {
655 throw new RuntimeException( "Not implemented yet." );
656 }
657
658 /***
659 * Sets the mapping that contains the mappings of keys
660 * to operator values.
661 */
662 public void setQueryOperatorValues ( Map aMap )
663 {
664 throw new RuntimeException( "Not implemented yet." );
665 }
666
667
668
669
670
671 /***
672 * Returns a Map containing the mappings of keys
673 * to query values that will be used to test for equality.
674 */
675 public NSDictionary equalToQueryValues()
676 {
677 throw new RuntimeException( "Not implemented yet." );
678 }
679
680 /***
681 * Returns a Map containing the mappings of keys
682 * to query values that will be used to test for greater value.
683 */
684 public NSDictionary greaterThanQueryValues()
685 {
686 throw new RuntimeException( "Not implemented yet." );
687 }
688
689 /***
690 * Returns a Map containing the mappings of keys
691 * to query values that will be used to test for lesser value.
692 */
693 public NSDictionary lessThanQueryValues()
694 {
695 throw new RuntimeException( "Not implemented yet." );
696 }
697
698 /***
699 * Sets the Map that contains the mappings of keys
700 * to query values that will be used to test for equality.
701 */
702 public void setEqualToQueryValues ( Map aMap )
703 {
704 throw new RuntimeException( "Not implemented yet." );
705 }
706
707 /***
708 * Sets the mapping that contains the mappings of keys
709 * to query values that will be used to test for greater value.
710 */
711 public void setGreaterThanQueryValues ( Map aMap )
712 {
713 throw new RuntimeException( "Not implemented yet." );
714 }
715
716 /***
717 * Sets the mapping that contains the mappings of keys
718 * to query values that will be used to test for lesser value.
719 */
720 public void setLessThanQueryValues ( Map aMap )
721 {
722 throw new RuntimeException( "Not implemented yet." );
723 }
724
725 /***
726 * Deprecated: returns true.
727 */
728 public boolean endEditing()
729 {
730 return true;
731 }
732
733
734
735
736 /***
737 * Returns a read-only List containing all objects managed by the display group.
738 * This includes those objects not visible due to disqualification.
739 */
740 public NSArray allObjects()
741 {
742 return allObjectsProxy;
743 }
744
745 /***
746 * Returns the total number of batches held by this display group.
747 */
748 public int batchCount()
749 {
750 if ( batchSize < 1 ) return 1;
751 int count = displayedObjects.count();
752 if ( count == 0 ) return 1;
753 return ((count-1) / batchSize) + 1;
754 }
755
756 /***
757 * Returns the index of the currently displayed batch.
758 */
759 public int currentBatchIndex()
760 {
761 return batchIndex;
762 }
763
764 /***
765 * Sets the index of the currently displayed batch.
766 */
767 public void setCurrentBatchIndex( int aBatchIndex )
768 {
769 batchIndex = aBatchIndex;
770 updateDisplayedObjects();
771 }
772
773 /***
774 * Sets the displayed objects to the batch containing
775 * the first selected object, and updates the current
776 * batch index, and returns null to force a page reload.
777 * Displays the first batch is there is no selection.
778 */
779 public Object displayBatchContainingSelectedObject()
780 {
781 if ( batchSize < 1 ) return null;
782 NSArray indexes = selectionIndexes();
783 if ( indexes.count() > 0 )
784 {
785 batchIndex =
786 ((Number)indexes.objectAtIndex( 0 )).intValue() / batchSize;
787 }
788 else
789 {
790 batchIndex = 0;
791 }
792 updateDisplayedObjects();
793 return null;
794 }
795
796 /***
797 * Sets the displayed objects to the next batch
798 * and updates the current batch index, and returns null
799 * to force a page reload. If there is no next batch
800 * the first batch is displayed.
801 */
802 public Object displayNextBatch()
803 {
804 batchIndex = (batchIndex + 1) % batchCount();
805 updateDisplayedObjects();
806 return null;
807 }
808
809 /***
810 * Sets the displayed objects to the next batch
811 * and updates the current batch index, and returns null
812 * to force a page reload. If there is no previous
813 * batch, the last batch is displayed.
814 */
815 public Object displayPreviousBatch()
816 {
817 batchIndex--;
818 if ( batchIndex < 0 ) batchIndex = batchCount() - 1;
819 updateDisplayedObjects();
820 return null;
821 }
822
823 /***
824 * Returns whether the displayed objects have been batched.
825 */
826 public boolean hasMultipleBatches()
827 {
828 return batchCount() > 1;
829 }
830
831 /***
832 * Returns the one-based index within the displayed objects list
833 * of the first displayed object in the current batch.
834 */
835 public int indexOfFirstDisplayedObject()
836 {
837 if ( batchSize < 1 ) return 1;
838 return batchIndex * batchSize + 1;
839 }
840
841 /***
842 * Returns the one-based index within the displayed objects list
843 * of the first last object in the current batch.
844 */
845 public int indexOfLastDisplayedObject()
846 {
847 if ( batchSize < 1 ) return displayedObjects.count();
848 return Math.min(
849 ((batchIndex+1) * batchSize),
850 displayedObjects.count() );
851 }
852
853 /***
854 * Returns the number of objects per batch.
855 */
856 public int numberOfObjectsPerBatch()
857 {
858 return batchSize;
859 }
860
861 /***
862 * Returns the number of objects per batch.
863 */
864 public void setNumberOfObjectsPerBatch( int aSize )
865 {
866 batchSize = aSize;
867 updateDisplayedObjects();
868 }
869
870 /***
871 * Clears the current selection.
872 * @return True is the selection was cleared,
873 * False if the selection could not be cleared
874 * @see #setSelectionIndexes
875 */
876 public boolean clearSelection()
877 {
878 Object result = notifyDelegate(
879 "displayGroupShouldChangeSelection",
880 new Class[] { WODisplayGroup.class, List.class },
881 new Object[] { this, new NSArray( selectedObjects ) } );
882 if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
883 {
884 return false;
885 }
886
887 selectionChanged = true;
888 willChange();
889
890 selectedObjects.removeAllObjects();
891 selectedIndexes.removeAllObjects();
892
893 notifyDelegate(
894 "displayGroupDidChangeSelection",
895 new Class[] { WODisplayGroup.class },
896 new Object[] { this } );
897 notifyDelegate(
898 "displayGroupDidChangeSelectedObjects",
899 new Class[] { WODisplayGroup.class },
900 new Object[] { this } );
901
902 return true;
903 }
904
905 /***
906 * Convenience for binding to a component action:
907 * calls deleteSelection() and then displayBatchContainingSelectedObject()
908 * and returns null, which is a suitable result from a component action.
909 */
910 public Object delete()
911 {
912 deleteSelection();
913 displayBatchContainingSelectedObject();
914 return null;
915 }
916
917 /***
918 * Deletes the object at the specified index,
919 * notifying the delegate before and after the operation,
920 * and then updating the selection if needed.
921 * @return True if delete was successful, false if the
922 * object was not deleted.
923 */
924 public boolean deleteObjectAtIndex ( int anIndex )
925 {
926 Object target = displayedObjects.objectAtIndex( anIndex );
927
928 Object result = notifyDelegate(
929 "displayGroupShouldDeleteObject",
930 new Class[] { WODisplayGroup.class, Object.class },
931 new Object[] { this, target } );
932 if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
933 {
934 return false;
935 }
936
937 deleteObjectAtIndexNoNotify( anIndex );
938
939 if ( dataSource != null )
940 {
941 dataSource.deleteObject( target );
942 }
943
944 notifyDelegate(
945 "displayGroupDidDeleteObject",
946 new Class[] { WODisplayGroup.class, Object.class },
947 new Object[] { this, target } );
948
949 return true;
950 }
951
952 private void deleteObjectAtIndexNoNotify ( int anIndex )
953 {
954 Object target = displayedObjects.objectAtIndex( anIndex );
955
956 int i;
957
958
959 i = indexOf( selectedObjects, target );
960 if ( i != NSArray.NotFound )
961 {
962 selectionChanged = true;
963 willChange();
964 selectedObjects.removeObjectAtIndex( i );
965 selectedIndexes.remove( new Integer( i ) );
966 }
967 else
968 {
969 willChange();
970 }
971
972
973 i = indexOf( allObjects, target );
974 if ( i != NSArray.NotFound )
975 {
976 allObjects.removeObjectAtIndex( i );
977 }
978 else
979 {
980
981
982 }
983
984
985 displayedObjects.removeObjectAtIndex( anIndex );
986 }
987
988 /***
989 * Deletes the currently selected objects.
990 * This implementation calls deleteObjectAtIndex() for
991 * each index in the selection list, immediately returning
992 * false if any delete operation fails.
993 * @return True if all selected objects were deleted,
994 * false if any deletion failed.
995 */
996 public boolean deleteSelection()
997 {
998 int i;
999 boolean result = true;
1000
1001 Enumeration e = new NSArray( selectedObjects ).objectEnumerator();
1002 while ( e.hasMoreElements() )
1003 {
1004 i = indexOf( displayedObjects, e.nextElement() );
1005 if ( i == NSArray.NotFound )
1006 {
1007
1008 throw new WotonomyException(
1009 "Selected object not found in displayedObjects" );
1010 }
1011 result = result && deleteObjectAtIndex( i );
1012 }
1013
1014 return result;
1015 }
1016
1017 /***
1018 * Returns a read-only List of all objects in the display group
1019 * that are currently displayed by the associations.
1020 */
1021 public NSArray displayedObjects()
1022 {
1023 if ( batchSize < 1 ) return displayedObjectsProxy;
1024 return displayedObjectsProxy.subarrayWithRange(
1025 new NSRange( batchIndex * batchSize, batchSize ) );
1026 }
1027
1028 /***
1029 * Requests a list of objects from the DataSource
1030 * and calls setObjectArray to populate the list.
1031 * More specifically, calls endEditing(), asks the
1032 * delegate, fetches the objects, notifies the delegate,
1033 * and populates the list. Returns null to force a
1034 * page reload.
1035 */
1036 public Object fetch()
1037 {
1038 endEditing();
1039
1040 if ( dataSource == null )
1041 {
1042 return null;
1043 }
1044
1045 Object result = notifyDelegate(
1046 "displayGroupShouldFetch",
1047 new Class[] { WODisplayGroup.class },
1048 new Object[] { this } );
1049 if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
1050 {
1051 return null;
1052 }
1053
1054 NSNotificationCenter.defaultCenter().postNotification(
1055 DisplayGroupWillFetchNotification, this, new NSDictionary() );
1056
1057 NSArray objectList = dataSource.fetchObjects();
1058
1059 notifyDelegate(
1060 "displayGroupDidFetchObjects",
1061 new Class[] { WODisplayGroup.class, List.class },
1062 new Object[] { this, objectList } );
1063
1064 setObjectArray( objectList );
1065
1066 if ( ( selectsFirstObjectAfterFetch ) && ( displayedObjects.size() > 0 ) )
1067 {
1068 setSelectionIndexes( new NSArray( new Integer( 0 ) ) );
1069 }
1070
1071 return null;
1072 }
1073
1074 /***
1075 * Convenience to call insertNewObjectAtIndex with the current selection plus one,
1076 * or at the end of the list if there is no selection.
1077 * Returns null to force a page reload.
1078 */
1079 public Object insert()
1080 {
1081 NSArray indexes = selectionIndexes();
1082 int size = indexes.count();
1083 if ( size == 0 )
1084 {
1085 insertNewObjectAtIndex( displayedObjects.count() );
1086 }
1087 else
1088 {
1089 insertNewObjectAtIndex(
1090 ((Number)selectedIndexes.objectAtIndex( size-1 )).intValue()+1 );
1091 }
1092 return null;
1093 }
1094
1095 /***
1096 * Creates a new object at the specified index.
1097 * Calls insertObjectAtIndex() with the result
1098 * from sending createObject() to the data source.
1099 * @return the newly created object.
1100 */
1101 public Object insertNewObjectAtIndex ( int anIndex )
1102 {
1103 Object result = null;
1104 if ( dataSource != null )
1105 {
1106 result = dataSource.createObject();
1107 }
1108 if ( result != null )
1109 {
1110 if ( insertedObjectDefaultValues != null )
1111 {
1112 Duplicator.writePropertiesForObject(
1113 insertedObjectDefaultValues, result );
1114 }
1115 insertObjectAtIndex( result, anIndex );
1116 }
1117 else
1118 {
1119 if ( delegate() != null )
1120 {
1121 NSSelector selector = new NSSelector(
1122 "displayGroupCreateObjectFailed",
1123 new Class[] { WODisplayGroup.class, EODataSource.class } );
1124 if ( selector.implementedByObject( delegate() ) )
1125 {
1126 try
1127 {
1128 selector.invoke( delegate(), new Object[] { this, dataSource } );
1129 return result;
1130 }
1131 catch ( Exception exc )
1132 {
1133 System.err.println( "Error notifying delegate: displayGroupCreateObjectFailed" );
1134 exc.printStackTrace();
1135 }
1136 }
1137 }
1138 }
1139 return result;
1140 }
1141
1142 /***
1143 * Inserts the specified object into the list at
1144 * the specified index.
1145 */
1146 public void insertObjectAtIndex ( Object anObject, int anIndex )
1147 {
1148 Object result = notifyDelegate(
1149 "displayGroupShouldInsertObject",
1150 new Class[] { WODisplayGroup.class, Object.class, int.class },
1151 new Object[] { this, anObject, new Integer(anIndex) } );
1152 if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
1153 {
1154 return;
1155 }
1156
1157 updatedObjectIndex = anIndex;
1158 willChange();
1159
1160 int i;
1161
1162
1163 if ( anIndex == displayedObjects.size() )
1164 {
1165 allObjects.addObject( anObject );
1166 }
1167 else
1168 {
1169 Object target = displayedObjects.objectAtIndex( anIndex );
1170 int targetIndex = indexOf( allObjects, target );
1171 if ( targetIndex != NSArray.NotFound )
1172 {
1173 allObjects.insertObjectAtIndex( anObject, targetIndex );
1174 }
1175 else
1176 {
1177 throw new WotonomyException(
1178 "Could not find displayed object in all objects list: "
1179 + target );
1180 }
1181 }
1182
1183
1184 displayedObjects.insertObjectAtIndex( anObject, anIndex );
1185
1186 if ( dataSource != null )
1187 {
1188 if ( dataSource instanceof OrderedDataSource )
1189 {
1190 ((OrderedDataSource)dataSource).insertObjectAtIndex(
1191 anObject, anIndex );
1192 }
1193 else
1194 {
1195 dataSource.insertObject( anObject );
1196 }
1197 }
1198
1199 notifyDelegate(
1200 "displayGroupDidInsertObject",
1201 new Class[] { WODisplayGroup.class, Object.class },
1202 new Object[] { this, anObject } );
1203 }
1204
1205 /***
1206 * Sets contentsChanged to true and notifies all observers.
1207 */
1208 public void redisplay()
1209 {
1210 willChange();
1211 }
1212
1213 /***
1214 * Sets the selection to the next displayed object after the current
1215 * selection. If the last object is selected, or if no object
1216 * is selected, then the first object becomes selected.
1217 * If multiple items are selected, the first selected item is
1218 * considered the selected item for the purposes of this method.
1219 * Does not call redisplay().
1220 * @return null to force a page reload.
1221 */
1222 public Object selectNext()
1223 {
1224 int count = displayedObjects.count();
1225 if ( count == 0 ) return null;
1226 if ( count == 1 )
1227 {
1228 selectObject( displayedObjects.objectAtIndex( 0 ) );
1229 return null;
1230 }
1231
1232 int i = -1;
1233 Object selectedObject = selectedObject();
1234 if ( selectedObject != null )
1235 {
1236 i = indexOf( displayedObjects, selectedObject );
1237 }
1238 if ( i == NSArray.NotFound ) i = -1;
1239
1240
1241 i++;
1242 if ( i != displayedObjects.count() )
1243 {
1244
1245 selectedObject = displayedObjects.objectAtIndex( i );
1246 }
1247 else
1248 {
1249
1250 selectedObject = displayedObjects.objectAtIndex( 0 );
1251 }
1252
1253 selectObject( selectedObject );
1254 return null;
1255 }
1256
1257 /***
1258 * Sets the selection to the specified object.
1259 * If the specified object is null or does not exist
1260 * in the list of displayed objects, the selection
1261 * will be cleared.
1262 * @return true if the object was selected.
1263 */
1264 public boolean selectObject ( Object anObject )
1265 {
1266 if ( ( anObject == null ) ||
1267 ( indexOf( displayedObjects, anObject )
1268 == NSArray.NotFound ) )
1269 {
1270 clearSelection();
1271 return false;
1272 }
1273
1274 selectObjectsIdenticalTo( new NSArray( new Object[] { anObject } ) );
1275 return true;
1276 }
1277
1278 /***
1279 * Sets the selection to the specified objects.
1280 * If the specified list is null or if none of the objects
1281 * in the list exist in the list of displayed objects, the
1282 * selection will be cleared.
1283 * @return true if all specified objects were selected.
1284 */
1285 public boolean selectObjectsIdenticalTo ( List anObjectList )
1286 {
1287
1288 if ( ( anObjectList != null ) && ( selectedObjects.size() == anObjectList.size() ) )
1289 {
1290 boolean identical = true;
1291 int size = selectedObjects.size();
1292 for ( int i = 0; ( i < size ) && identical; i++ )
1293 {
1294
1295 if ( anObjectList.get( i ) != selectedObjects.get( i ) )
1296 {
1297 identical = false;
1298 }
1299 else if ( displayedObjects.indexOfIdenticalObject(
1300 anObjectList.get( i ) ) == NSArray.NotFound )
1301 {
1302 identical = false;
1303 }
1304 }
1305 if ( identical )
1306 {
1307 return true;
1308 }
1309 }
1310
1311 Object result = notifyDelegate(
1312 "displayGroupShouldChangeSelection",
1313 new Class[] { WODisplayGroup.class, List.class },
1314 new Object[] { this, anObjectList } );
1315 if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
1316 {
1317
1318
1319 selectionChanged = true;
1320 willChange();
1321 return false;
1322 }
1323
1324 int i;
1325 selectionChanged = true;
1326 willChange();
1327 Object o;
1328 selectedObjects.removeAllObjects();
1329 selectedIndexes.removeAllObjects();
1330 Iterator it = anObjectList.iterator();
1331 while ( it.hasNext() )
1332 {
1333 o = it.next();
1334 if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
1335 != NSArray.NotFound )
1336 {
1337 selectedObjects.addObject( o );
1338 selectedIndexes.addObject( new Integer( i ) );
1339 }
1340 }
1341
1342 notifyDelegate(
1343 "displayGroupDidChangeSelection",
1344 new Class[] { WODisplayGroup.class },
1345 new Object[] { this } );
1346 notifyDelegate(
1347 "displayGroupDidChangeSelectedObjects",
1348 new Class[] { WODisplayGroup.class },
1349 new Object[] { this } );
1350
1351 return true;
1352 }
1353
1354 /***
1355 * Calls selectObjectsIdenticalTo and if false is returned
1356 * and selectFirstIfNoMatch is true, selects the first object.
1357 */
1358 public boolean selectObjectsIdenticalToSelectFirstOnNoMatch(
1359 List anObjectList, boolean selectFirstIfNoMatch )
1360 {
1361 if ( selectObjectsIdenticalTo( anObjectList ) )
1362 {
1363 return true;
1364 }
1365 if ( selectFirstIfNoMatch )
1366 {
1367 clearSelection();
1368 selectNext();
1369 return true;
1370 }
1371 return false;
1372 }
1373
1374 /***
1375 * Sets the selection to the previous displayed object before the current
1376 * selection. If the first object is selected, or if no object
1377 * is selected, then the last object becomes selected.
1378 * If multiple items are selected, the first selected item is
1379 * considered the selected item for the purposes of this method.
1380 * Does not call redisplay().
1381 * @return null to force a page reload.
1382 */
1383 public Object selectPrevious()
1384 {
1385 int i = displayedObjects.count();
1386 if ( i == 0 ) return null;
1387 if ( i == 1 )
1388 {
1389 selectObject( displayedObjects.objectAtIndex( 0 ) );
1390 return null;
1391 }
1392
1393 Object selectedObject = selectedObject();
1394 if ( selectedObject != null )
1395 {
1396 i = indexOf( displayedObjects, selectedObject );
1397 }
1398 if ( i == NSArray.NotFound ) i = displayedObjects.count();
1399
1400
1401 i--;
1402 if ( i < 0 )
1403 {
1404
1405 i = displayedObjects.count() - 1;
1406 }
1407
1408 selectObject( displayedObjects.objectAtIndex( i ) );
1409 return null;
1410 }
1411
1412 /***
1413 * Returns the currently selected object, or null if
1414 * there is no selection.
1415 */
1416 public Object selectedObject()
1417 {
1418 if ( selectedObjects.count() == 0 )
1419 {
1420 return null;
1421 }
1422 return selectedObjects.objectAtIndex( 0 );
1423 }
1424
1425 /***
1426 * Returns a read-only List containing all selected objects, if any.
1427 * Returns an empty list if no objects are selected.
1428 */
1429 public NSArray selectedObjects()
1430 {
1431 return selectedObjectsProxy;
1432 }
1433
1434 /***
1435 * Returns a read-only List containing the indexes of all selected
1436 * objects, if any. The list contains instances of
1437 * java.lang.Number; call intValue() to retrieve the index.
1438 */
1439 public NSArray selectionIndexes()
1440 {
1441
1442 int i;
1443 NSMutableArray result = new NSMutableArray();
1444 Enumeration e = selectedObjects.objectEnumerator();
1445 while ( e.hasMoreElements() )
1446 {
1447 i = indexOf( displayedObjects, e.nextElement() );
1448 if ( i != NSArray.NotFound )
1449 {
1450 result.addObject( new Integer( i ) );
1451 }
1452 else
1453 {
1454 System.err.println(
1455 "Should never happen: selected objects not in displayed objects" );
1456 new RuntimeException().printStackTrace( System.err );
1457 }
1458 }
1459 return result;
1460 }
1461
1462 /***
1463 * Sets the objects managed by this display group.
1464 * updateDisplayedObjects() is called to filter the
1465 * display objects. The previous selection will be
1466 * maintained if possible. The data source is not
1467 * notified.
1468 */
1469 public void setObjectArray ( List anObjectList )
1470 {
1471 if ( anObjectList == null ) anObjectList = new NSArray();
1472
1473 Object result = notifyDelegate(
1474 "displayGroupDisplayArrayForObjects",
1475 new Class[] { WODisplayGroup.class, List.class },
1476 new Object[] { this, anObjectList } );
1477 if ( result != null )
1478 {
1479 anObjectList = (List) result;
1480 }
1481
1482 willChange();
1483
1484 NSArray oldSelectedObjects = new NSArray( selectedObjects );
1485
1486
1487 allObjects.removeAllObjects();
1488 allObjects.addObjectsFromArray( anObjectList );
1489
1490
1491 updateDisplayedObjects();
1492
1493
1494 selectObjectsIdenticalTo( oldSelectedObjects );
1495
1496 batchIndex = 0;
1497 displayBatchContainingSelectedObject();
1498 }
1499
1500 /***
1501 * Sets the currently selected object, or clears the
1502 * selection if the object is not found or is null.
1503 * Note: it's not clear how this differs from
1504 * selectObject in the spec. It is recommended that
1505 * you call selectObject for now.
1506 */
1507 public void setSelectedObject ( Object anObject )
1508 {
1509 selectObject( anObject );
1510 }
1511
1512 /***
1513 * Sets the current selection to the specified objects.
1514 * The previous selection is cleared, and any objects
1515 * in the display group that are in the specified list
1516 * are then selected. If no items in the specified list
1517 * are found in the display group, then the selection is
1518 * effectively cleared.
1519 * Note: it's not clear how this differs from
1520 * selectObjectsIdenticalTo in the spec.
1521 * It is recommended that you call that method for now.
1522 */
1523 public void setSelectedObjects ( List aList )
1524 {
1525 selectObjectsIdenticalTo( aList );
1526 }
1527
1528 /***
1529 * Sets the current selection to the objects at the
1530 * specified indexes. Items in the list are assumed
1531 * to be instances of java.lang.Number.
1532 * The previous selection is cleared, and any objects
1533 * in the display group that are in the specified list
1534 * are then selected. If no items in the specified list
1535 * are found in the display group, then the selection is
1536 * effectively cleared.
1537 */
1538 public boolean setSelectionIndexes ( List aList )
1539 {
1540 Object o;
1541 int index;
1542 NSMutableArray objects = new NSMutableArray();
1543 Iterator it = aList.iterator();
1544 while ( it.hasNext() )
1545 {
1546 index = ((Number)it.next()).intValue();
1547 if ( index < displayedObjects.count() )
1548 {
1549 o = displayedObjects.objectAtIndex( index );
1550 if ( o != null )
1551 {
1552 objects.add( o );
1553 }
1554 }
1555 }
1556 return selectObjectsIdenticalTo( objects );
1557 }
1558
1559 /***
1560 * Applies the qualifier to all objects and sorts
1561 * the results to update the list of displayed objects.
1562 * Observing associations are notified to reflect the changes.
1563 */
1564 public void updateDisplayedObjects()
1565 {
1566 updatedObjectIndex = -1;
1567 willChange();
1568
1569 displayedObjects.removeAllObjects();
1570
1571 displayedObjects.addObjectsFromArray( allObjects );
1572
1573
1574 if ( qualifier != null )
1575 {
1576 EOQualifier.filterArrayWithQualifier(
1577 displayedObjects, qualifier );
1578 }
1579
1580
1581 NSArray orderings = sortOrderings();
1582 if ( orderings != null )
1583 {
1584 if ( orderings.count() > 0 )
1585 {
1586 selectionChanged = true;
1587 willChange();
1588 EOSortOrdering.sortArrayUsingKeyOrderArray(
1589 displayedObjects, orderings );
1590 }
1591 }
1592
1593
1594 int i;
1595 Object o;
1596 Iterator it = new LinkedList( selectedObjects ).iterator();
1597 boolean removeflag = false;
1598 selectedIndexes.removeAllObjects();
1599 while ( it.hasNext() )
1600 {
1601 o = it.next();
1602 if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
1603 == NSArray.NotFound )
1604 {
1605 selectedObjects.removeIdenticalObject( o );
1606 removeflag = true;
1607 }
1608 else
1609 {
1610 selectedIndexes.addObject( new Integer( i ) );
1611 }
1612 }
1613
1614
1615
1616 if (removeflag)
1617 {
1618 selectionChanged = true;
1619 willChange();
1620
1621 notifyDelegate(
1622 "displayGroupDidChangeSelection",
1623 new Class[] { WODisplayGroup.class },
1624 new Object[] { this } );
1625 notifyDelegate(
1626 "displayGroupDidChangeSelectedObjects",
1627 new Class[] { WODisplayGroup.class },
1628 new Object[] { this } );
1629 }
1630 }
1631
1632 /***
1633 * Returns the index of the changed object. If more than
1634 * one object has changed, -1 is returned.
1635 */
1636 public int updatedObjectIndex()
1637 {
1638 return updatedObjectIndex;
1639 }
1640
1641
1642
1643 /***
1644 * Returns a value on the selected object for the specified key.
1645 */
1646 public Object selectedObjectValueForKey ( String aKey )
1647 {
1648 Object selectedObject = selectedObject();
1649 if ( selectedObject == null ) return null;
1650 return valueForObject( selectedObject, aKey );
1651 }
1652
1653 /***
1654 * Sets the specified value for the specified key on
1655 * all selected objects.
1656 */
1657 public boolean setSelectedObjectValue (
1658 Object aValue, String aKey )
1659 {
1660 Object selectedObject = selectedObject();
1661 if ( selectedObject == null ) return false;
1662 return setValueForObject( aValue, selectedObject, aKey );
1663 }
1664
1665 /***
1666 * Sets the specified value for the specified key on
1667 * the specified object. Validations may be triggered,
1668 * and error dialogs may appear to the user.
1669 * @return True if the value was set successfully,
1670 * false if the value could not be set and the update
1671 * operation should not continue.
1672 */
1673 public boolean setValueForObject (
1674 Object aValue, Object anObject, String aKey )
1675 {
1676
1677
1678 EOObserverCenter.notifyObserversObjectWillChange( anObject );
1679
1680
1681
1682
1683 try
1684 {
1685 if ( anObject instanceof EOKeyValueCoding )
1686 {
1687 ((EOKeyValueCoding)anObject).takeValueForKey( aValue, aKey );
1688 }
1689 else
1690 {
1691 EOKeyValueCodingSupport.takeValueForKey( anObject, aValue, aKey );
1692 }
1693 }
1694 catch ( RuntimeException exc )
1695 {
1696 Object result = notifyDelegate(
1697 "displayGroupShouldDisplayAlert",
1698 new Class[] { WODisplayGroup.class, String.class, String.class },
1699 new Object[] { this, "Error", exc.getMessage() } );
1700 if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1701 {
1702 throw exc;
1703 }
1704 return false;
1705 }
1706
1707 notifyDelegate(
1708 "displayGroupDidSetValueForObject",
1709 new Class[] { WODisplayGroup.class, Object.class, Object.class, String.class },
1710 new Object[] { this, aValue, anObject, aKey } );
1711
1712 return true;
1713 }
1714
1715 /***
1716 * Calls setValueForObject() for the object at
1717 * the specified index.
1718 */
1719 public boolean setValueForObjectAtIndex (
1720 Object aValue, int anIndex, String aKey )
1721 {
1722 return setValueForObject(
1723 aValue, displayedObjects.objectAtIndex( anIndex ), aKey );
1724 }
1725
1726 /***
1727 * Returns the value for the specified key on the specified object.
1728 */
1729 public Object valueForObject ( Object anObject, String aKey )
1730 {
1731
1732 if ( aKey == null ) return anObject;
1733 if ( aKey.equals( "" ) ) return anObject;
1734
1735 try
1736 {
1737 if ( anObject instanceof EOKeyValueCoding )
1738 {
1739 return ((EOKeyValueCoding)anObject).valueForKey( aKey );
1740 }
1741 else
1742 {
1743 return EOKeyValueCodingSupport.valueForKey( anObject, aKey );
1744 }
1745 }
1746 catch ( RuntimeException exc )
1747 {
1748 Object result = notifyDelegate(
1749 "displayGroupShouldDisplayAlert",
1750 new Class[] { WODisplayGroup.class, String.class, String.class },
1751 new Object[] { this, "Error", exc.getMessage() } );
1752 if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1753 {
1754 throw exc;
1755 }
1756 return null;
1757 }
1758 }
1759
1760 /***
1761 * Calls valueForObject() for the object at the specified index.
1762 * Returns null if out of bounds.
1763 */
1764 public Object valueForObjectAtIndex ( int anIndex, String aKey )
1765 {
1766 if ( displayedObjects.count() <= anIndex ) return null;
1767 Object o = displayedObjects.objectAtIndex( anIndex );
1768 return valueForObject( o, aKey );
1769 }
1770
1771 /***
1772 * Prints out the list of displayed objects.
1773 */
1774 public String toString()
1775 {
1776 return displayedObjects.toString();
1777 }
1778
1779
1780 /***
1781 * Handles notifications from the data source's editing context,
1782 * looking for InvalidatedAllObjectsInStoreNotification and
1783 * ObjectsChangedInEditingContextNotification, refetching in
1784 * the former case and updating displayed objects in the latter.
1785 * Note: This method is not in the public specification.
1786 */
1787 public void objectsInvalidatedInEditingContext( NSNotification aNotification )
1788 {
1789 if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification
1790 .equals( aNotification.name() ) )
1791 {
1792 Object result = notifyDelegate(
1793 "displayGroupShouldRefetch",
1794 new Class[] { WODisplayGroup.class, NSNotification.class },
1795 new Object[] { this, aNotification } );
1796 if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1797 {
1798 fetch();
1799 }
1800 }
1801 else
1802 if ( EOEditingContext.ObjectsChangedInEditingContextNotification
1803 .equals( aNotification.name() ) )
1804 {
1805 Object result = notifyDelegate(
1806 "displayGroupShouldRedisplay",
1807 new Class[] { WODisplayGroup.class, NSNotification.class },
1808 new Object[] { this, aNotification } );
1809 if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1810 {
1811 int index;
1812 Enumeration e;
1813 boolean didChange = false;
1814 NSDictionary userInfo = aNotification.userInfo();
1815
1816
1817
1818
1819 NSArray updates = (NSArray) userInfo.objectForKey(
1820 EOObjectStore.UpdatedKey );
1821 e = updates.objectEnumerator();
1822 while ( e.hasMoreElements() )
1823 {
1824 index = indexOf( displayedObjects, e.nextElement() );
1825 if ( index != NSArray.NotFound )
1826 {
1827
1828 if ( ! didChange )
1829 {
1830 didChange = true;
1831 willChange();
1832 updatedObjectIndex = index;
1833 }
1834 else
1835 {
1836 updatedObjectIndex = -1;
1837 }
1838 }
1839 }
1840
1841
1842 NSArray invalidates = (NSArray) userInfo.objectForKey(
1843 EOObjectStore.InvalidatedKey );
1844 e = invalidates.objectEnumerator();
1845 while ( e.hasMoreElements() )
1846 {
1847 index = indexOf( displayedObjects, e.nextElement() );
1848 if ( index != NSArray.NotFound )
1849 {
1850
1851 if ( ! didChange )
1852 {
1853 didChange = true;
1854 willChange();
1855 updatedObjectIndex = index;
1856 }
1857 else
1858 {
1859 updatedObjectIndex = -1;
1860 }
1861 }
1862 }
1863
1864
1865 NSArray deletes = (NSArray) userInfo.objectForKey(
1866 EOObjectStore.DeletedKey );
1867 e = deletes.objectEnumerator();
1868 Object o;
1869 while ( e.hasMoreElements() )
1870 {
1871 o = e.nextElement();
1872 index = indexOf( displayedObjects, o );
1873 if ( index != NSArray.NotFound )
1874 {
1875
1876 deleteObjectAtIndexNoNotify( index );
1877 }
1878 }
1879
1880 if ( !usesOptimisticRefresh() )
1881 {
1882 updateDisplayedObjects();
1883 }
1884 }
1885 }
1886
1887 }
1888
1889
1890
1891 /***
1892 * Specifies the default behavior for whether changes
1893 * should be validated immediately for all display groups.
1894 */
1895 public static boolean
1896 globalDefaultForValidatesChangesImmediately()
1897 {
1898 return globalDefaultForValidatesChangesImmediately;
1899 }
1900
1901 /***
1902 * Specifies the default string matching format for all
1903 * display groups.
1904 */
1905 public static String globalDefaultStringMatchFormat()
1906 {
1907 return globalDefaultStringMatchFormat;
1908 }
1909
1910 /***
1911 * Specifies the default string matching operator for all
1912 * display groups.
1913 */
1914 public static String globalDefaultStringMatchOperator()
1915 {
1916 return globalDefaultStringMatchOperator;
1917 }
1918
1919 /***
1920 * Sets the default behavior for validating changes
1921 * for all display groups.
1922 */
1923 public static void
1924 setGlobalDefaultForValidatesChangesImmediately (
1925 boolean validatesImmediately )
1926 {
1927 globalDefaultForValidatesChangesImmediately =
1928 validatesImmediately;
1929 }
1930
1931 /***
1932 * Sets the default string matching format that
1933 * will be used by all display groups.
1934 */
1935 public static void
1936 setGlobalDefaultStringMatchFormat ( String aFormat )
1937 {
1938 globalDefaultStringMatchFormat = aFormat;
1939 }
1940
1941 /***
1942 * Sets the default string matching operator that
1943 * will be used by all display groups.
1944 */
1945 public static void
1946 setGlobalDefaultStringMatchOperator ( String anOperator )
1947 {
1948 globalDefaultStringMatchOperator = anOperator;
1949 }
1950
1951 /***
1952 * Needed because we don't inherit from NSObject.
1953 * Calls EOObserverCenter.notifyObserversObjectWillChange.
1954 */
1955 protected void willChange()
1956 {
1957 EOObserverCenter.notifyObserversObjectWillChange( this );
1958 }
1959
1960 /***
1961 * Returns the index of the specified object in the
1962 * specified NSArray, comparing by value or by reference
1963 * as determined by the private instance variable
1964 * compareByReference. If not found, returns NSArray.NotFound.
1965 */
1966 private int indexOf( NSArray anArray, Object anObject )
1967 {
1968 if ( compareByReference )
1969 {
1970 return anArray.indexOfIdenticalObject( anObject );
1971 }
1972 else
1973 {
1974 return anArray.indexOf( anObject );
1975 }
1976 }
1977
1978
1979
1980 /***
1981 * Receives notifications of changes from objects that
1982 * are managed by this display group. This implementation
1983 * sets updatedObjectIndex as appropriate.
1984 */
1985 public void objectWillChange(Object anObject)
1986 {
1987 int index = indexOf( displayedObjects, anObject );
1988 if ( index != NSArray.NotFound )
1989 {
1990 updatedObjectIndex = index;
1991 willChange();
1992 }
1993 }
1994
1995
1996
1997 /***
1998 * Called before the editing context begins to save changes.
1999 * This implementation calls endEditing().
2000 */
2001 public void editingContextWillSaveChanges(
2002 EOEditingContext anEditingContext )
2003 {
2004 endEditing();
2005 }
2006
2007 /***
2008 * Called to determine whether this editor has changes
2009 * that have not been committed to the object in the context.
2010 * This implementation returns false.
2011 */
2012 public boolean editorHasChangesForEditingContext(
2013 EOEditingContext anEditingContext )
2014 {
2015 return false;
2016 }
2017
2018
2019
2020 /***
2021 * Called to display a message for an error that occurred
2022 * in the specified editing context. If the delegate allows,
2023 * this implementation writes a message to the standard output.
2024 * Override to customize.
2025 */
2026 public void editingContextPresentErrorMessage(
2027 EOEditingContext anEditingContext,
2028 String aMessage )
2029 {
2030 Object result = notifyDelegate(
2031 "displayGroupShouldDisplayAlert",
2032 new Class[] { WODisplayGroup.class, String.class, String.class },
2033 new Object[] { this, "Error", aMessage } );
2034 if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
2035 {
2036 System.out.println( aMessage );
2037 }
2038 }
2039
2040 /***
2041 * Called by the specified object store to determine whether
2042 * fetching should continue, where count is the current count
2043 * and limit is the limit as specified by the fetch specification.
2044 * This implementation returns true. Override to customize.
2045 */
2046 public boolean editingContextShouldContinueFetching(
2047 EOEditingContext anEditingContext,
2048 int count,
2049 int limit,
2050 EOObjectStore anObjectStore )
2051 {
2052 return true;
2053 }
2054
2055 /***
2056 * Sends the specified message to the delegate.
2057 * Returns the return value of the method,
2058 * or null if no return value or no delegate
2059 * or no implementation.
2060 */
2061 private Object notifyDelegate(
2062 String aMethodName, Class[] types, Object[] params )
2063 {
2064 try
2065 {
2066 Object delegate = delegate();
2067 if ( delegate == null ) return null;
2068 return NSSelector.invoke(
2069 aMethodName, types, delegate, params );
2070 }
2071 catch ( NoSuchMethodException e )
2072 {
2073
2074 }
2075 catch ( Exception exc )
2076 {
2077
2078 System.err.println(
2079 "Error while messaging delegate: " +
2080 delegate + " : " + aMethodName );
2081 exc.printStackTrace();
2082 }
2083
2084 return null;
2085 }
2086
2087 /***
2088 * DisplayGroups can delegate important decisions to a Delegate.
2089 * Note that DisplayGroup doesn't require its delegates to implement
2090 * this interface: rather, this interface defines the methods that
2091 * DisplayGroup will attempt to invoke dynamically on its delegate.
2092 * The delegate may choose to implement only a subset of the methods
2093 * on the interface.
2094 */
2095 public interface Delegate
2096 {
2097 /***
2098 * Called when the specified data source fails
2099 * to create an object for the specified display group.
2100 */
2101 void displayGroupCreateObjectFailed (
2102 WODisplayGroup aDisplayGroup,
2103 EODataSource aDataSource );
2104
2105 /***
2106 * Called after the specified display group's
2107 * data source is changed.
2108 */
2109 void displayGroupDidChangeDataSource (
2110 WODisplayGroup aDisplayGroup );
2111
2112 /***
2113 * Called after a change occurs in the specified
2114 * display group's selected objects.
2115 */
2116 void displayGroupDidChangeSelectedObjects (
2117 WODisplayGroup aDisplayGroup );
2118
2119 /***
2120 * Called after the specified display group's
2121 * selection has changed.
2122 */
2123 void displayGroupDidChangeSelection (
2124 WODisplayGroup aDisplayGroup );
2125
2126 /***
2127 * Called after the specified display group has
2128 * deleted the specified object.
2129 */
2130 void displayGroupDidDeleteObject (
2131 WODisplayGroup aDisplayGroup,
2132 Object anObject );
2133
2134 /***
2135 * Called after the specified display group
2136 * has fetched the specified object list.
2137 */
2138 void displayGroupDidFetchObjects (
2139 WODisplayGroup aDisplayGroup,
2140 List anObjectList );
2141
2142 /***
2143 * Called after the specified display group
2144 * has inserted the specified object into
2145 * its internal object list.
2146 */
2147 void displayGroupDidInsertObject (
2148 WODisplayGroup aDisplayGroup,
2149 Object anObject );
2150
2151 /***
2152 * Called after the specified display group
2153 * has set the specified value for the specified
2154 * object and key.
2155 */
2156 void displayGroupDidSetValueForObject (
2157 WODisplayGroup aDisplayGroup,
2158 Object aValue,
2159 Object anObject,
2160 String aKey );
2161
2162 /***
2163 * Called by the specified display group to
2164 * determine what objects should be displayed
2165 * for the objects in the specified list.
2166 * @return An NSArray containing the objects
2167 * to be displayed for the objects in the
2168 * specified list.
2169 */
2170 NSArray displayGroupDisplayArrayForObjects (
2171 WODisplayGroup aDisplayGroup,
2172 List aList );
2173
2174 /***
2175 * Called by the specified display group before
2176 * it attempts to change the selection.
2177 * @return True to allow the selection to change,
2178 * false otherwise.
2179 */
2180 boolean displayGroupShouldChangeSelection (
2181 WODisplayGroup aDisplayGroup,
2182 List aSelectionList );
2183
2184 /***
2185 * Called by the specified display group before
2186 * it attempts to delete the specified object.
2187 * @return True to allow the object to be deleted
2188 * false to prevent the deletion.
2189 */
2190 boolean displayGroupShouldDeleteObject (
2191 WODisplayGroup aDisplayGroup,
2192 Object anObject );
2193
2194 /***
2195 * Called by the specified display group before
2196 * it attempts display the specified alert to
2197 * the user.
2198 * @return True to allow the message to be
2199 * displayed, false if you want to handle the
2200 * alert yourself and suppress the display group's
2201 * notification.
2202 */
2203 boolean displayGroupShouldDisplayAlert (
2204 WODisplayGroup aDisplayGroup,
2205 String aTitle,
2206 String aMessage );
2207
2208 /***
2209 * Called by the specified display group before
2210 * it attempts fetch objects.
2211 * @return True to allow the fetch to take place,
2212 * false to prevent the fetch.
2213 */
2214 boolean displayGroupShouldFetch (
2215 WODisplayGroup aDisplayGroup );
2216
2217 /***
2218 * Called by the specified display group before
2219 * it attempts to insert the specified object.
2220 * @return True to allow the object to be inserted
2221 * false to prevent the insertion.
2222 */
2223 boolean displayGroupShouldInsertObject (
2224 WODisplayGroup aDisplayGroup,
2225 Object anObject,
2226 int anIndex );
2227
2228 /***
2229 * Called by the specified display group when
2230 * it receives the specified
2231 * ObjectsChangedInEditingContextNotification.
2232 * @return True to allow the display group to
2233 * update the display (recommended), false
2234 * to prevent the update.
2235 */
2236 boolean displayGroupShouldRedisplay (
2237 WODisplayGroup aDisplayGroup,
2238 NSNotification aNotification );
2239
2240 /***
2241 * Called by the specified display group when
2242 * it receives the specified
2243 * InvalidatedAllObjectsInStoreNotification.
2244 * @return True to allow the display group to
2245 * refetch (recommended), false to prevent the
2246 * refetch.
2247 */
2248 boolean displayGroupShouldRefetch (
2249 WODisplayGroup aDisplayGroup,
2250 NSNotification aNotification );
2251
2252 }
2253
2254 }
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455