]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WODisplayGroup.m
use %p for pointer formats, fixed gcc 4.1 warnings, minor code improvs
[sope] / sope-appserver / NGObjWeb / WODisplayGroup.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include <NGObjWeb/WODisplayGroup.h>
23 #import <EOControl/EOControl.h>
24 #import <EOControl/EOKeyValueArchiver.h>
25 #import <Foundation/Foundation.h>
26 #import <Foundation/NSNotification.h>
27 #include "common.h"
28
29 @interface EODataSource(DGQualifierSetting)
30 - (void)setAuxiliaryQualifier:(EOQualifier *)_q;
31 - (void)setQualifier:(EOQualifier *)_q;
32 - (void)setQualifierBindings:(NSDictionary *)_bindings;
33 @end
34
35 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
36 @interface NSObject(Miss)
37 - (void)notImplemented:(SEL)cmd;
38 @end
39 #endif
40
41
42 @interface NSObject(EditingContext)
43 - (id)editingContext;
44 - (void)addEditor:(id)_editor;
45 - (void)removeEditor:(id)_editor;
46 - (void)setMessageHandler:(id)_handler;
47 - (id)messageHandler;
48 @end
49
50
51 @implementation WODisplayGroup
52
53 static NSNumber *uint0 = nil;
54 static NSArray  *uint0Array = nil;
55
56 + (void)initialize {
57   if (uint0 == nil)
58     uint0 = [[NSNumber alloc] initWithUnsignedInt:0];
59   if (uint0Array == nil)
60     uint0Array = [[NSArray alloc] initWithObjects:&uint0 count:1];
61 }
62
63 - (id)init {
64   if ((self = [super init])) {
65     [self setDefaultStringMatchFormat:
66             [[self class] globalDefaultStringMatchFormat]];
67     [self setDefaultStringMatchOperator:
68             [[self class] globalDefaultStringMatchOperator]];
69     self->currentBatchIndex = 1;
70   }
71   return self;
72 }
73
74 - (void)dealloc {
75   [[NSNotificationCenter defaultCenter] removeObserver:self];
76   [self setDataSource:nil];
77
78   [self->_queryMatch                release];
79   [self->_queryMin                  release];
80   [self->_queryMax                  release];
81   [self->_queryOperator             release];
82   [self->_queryBindings             release];
83   [self->defaultStringMatchFormat   release];
84   [self->defaultStringMatchOperator release];
85   [self->qualifier                  release];
86   [self->objects                    release];
87   [self->displayObjects             release];
88   [self->selectionIndexes           release];
89   [self->sortOrderings              release];
90   [self->insertedObjectDefaults     release];
91   [super dealloc];
92 }
93
94 /* notifications */
95
96 - (void)_objectsChangedInEC:(NSNotification *)_notification {
97   id d;
98   BOOL doRedisplay;
99
100   doRedisplay = YES;
101   if ((d = [self delegate]) != nil) {
102     if ([d respondsToSelector:
103        @selector(displayGroup:shouldRedisplayForChangesInEditingContext:)]) {
104       doRedisplay = [d displayGroup:self
105                        shouldRedisplayForEditingContextChangeNotification:
106                          _notification];
107     }
108   }
109
110   if (doRedisplay)
111     [self redisplay];
112 }
113
114 /* display */
115
116 - (void)redisplay {
117   /* contents changed notification ??? */
118 }
119
120 /* accessors */
121
122 - (void)setDelegate:(id)_delegate {
123   self->delegate = _delegate;
124 }
125 - (id)delegate {
126   return self->delegate;
127 }
128
129 - (void)setDataSource:(EODataSource *)_ds {
130   NSNotificationCenter *nc = nil;
131   id ec;
132   
133   if (_ds == self->dataSource)
134     return;
135   
136   /* unregister with old editing context */
137   if ([self->dataSource respondsToSelector:@selector(editingContext)]) {
138     if ((ec = [self->dataSource editingContext]) != nil) {
139       [ec removeEditor:self];
140       if ([ec messageHandler] == self)
141         [ec setMessageHandler:nil];
142     
143       [[NSNotificationCenter defaultCenter]
144         removeObserver:self
145         name:@"EOObjectsChangedInEditingContext"
146         object:ec];
147     }
148   }
149   
150   ASSIGN(self->dataSource, _ds);
151   
152   /* register with new editing context */
153   if ([self->dataSource respondsToSelector:@selector(editingContext)]) {
154     if ((ec = [self->dataSource editingContext]) != nil) {
155       [ec addEditor:self];
156       if ([ec messageHandler] == nil)
157         [ec setMessageHandler:self];
158       
159       [nc addObserver:self
160           selector:@selector(_objectsChangedInEC:)
161           name:@"EOObjectsChangedInEditingContext"
162           object:ec];
163     }
164   }
165   
166   if ([self->delegate respondsToSelector:
167                @selector(displayGroupDidChangeDataSource:)])
168     [self->delegate displayGroupDidChangeDataSource:self];
169 }
170 - (EODataSource *)dataSource {
171   return self->dataSource;
172 }
173
174 - (void)setSortOrderings:(NSArray *)_orderings {
175   ASSIGNCOPY(self->sortOrderings, _orderings);
176 }
177 - (NSArray *)sortOrderings {
178   return self->sortOrderings;
179 }
180
181 - (void)setFetchesOnLoad:(BOOL)_flag {
182   self->flags.fetchesOnLoad = _flag ? 1 : 0;
183 }
184 - (BOOL)fetchesOnLoad {
185   return self->flags.fetchesOnLoad ? YES : NO;
186 }
187
188 - (void)setInsertedObjectDefaultValues:(NSDictionary *)_values {
189   ASSIGNCOPY(self->insertedObjectDefaults, [_values copy]);
190 }
191 - (NSDictionary *)insertedObjectDefaultValues {
192   return self->insertedObjectDefaults;
193 }
194
195 - (void)setNumberOfObjectsPerBatch:(unsigned)_count {
196   self->numberOfObjectsPerBatch = _count;
197 }
198 - (unsigned)numberOfObjectsPerBatch {
199   return self->numberOfObjectsPerBatch;
200 }
201
202 - (void)setSelectsFirstObjectAfterFetch:(BOOL)_flag {
203   self->flags.selectFirstAfterFetch = _flag ? 1 : 0;
204 }
205 - (BOOL)selectsFirstObjectAfterFetch {
206   return self->flags.selectFirstAfterFetch ? YES : NO;
207 }
208
209 - (void)setValidatesChangesImmediatly:(BOOL)_flag {
210   self->flags.validatesChangesImmediatly = _flag ? 1 : 0;
211 }
212 - (BOOL)validatesChangesImmediatly {
213   return self->flags.validatesChangesImmediatly ? YES : NO;
214 }
215
216 /* batches */
217
218 - (BOOL)hasMultipleBatches {
219   return [self batchCount] > 1 ? YES : NO;
220 }
221 - (unsigned)batchCount {
222   unsigned doc, nob;
223   
224   doc = [[self allObjects] count];
225   nob = [self numberOfObjectsPerBatch];
226   
227   return (nob == 0)
228     ? 1
229     : doc / nob + ((doc % nob) ? 1 : 0) ;
230 }
231
232 - (void)setCurrentBatchIndex:(unsigned)_index {
233   self->currentBatchIndex = (_index <= [self batchCount]) ? _index : 1;
234 }
235 - (unsigned)currentBatchIndex {
236   if (self->currentBatchIndex > [self batchCount])
237     self->currentBatchIndex = 1;
238   return self->currentBatchIndex;
239 }
240
241 - (unsigned)indexOfFirstDisplayedObject {
242   return ([self currentBatchIndex] - 1) * [self numberOfObjectsPerBatch];
243 }
244
245 - (unsigned)indexOfLastDisplayedObject {
246   unsigned nob = [self numberOfObjectsPerBatch];
247   unsigned cnt = [[self allObjects] count];
248
249   if (nob == 0)
250     return cnt-1;
251   else
252     return (([self indexOfFirstDisplayedObject] + nob) < cnt)
253       ? ([self indexOfFirstDisplayedObject] + nob) - 1
254       : cnt-1;
255 }
256
257 - (id)displayNextBatch {
258   [self clearSelection];
259   
260   self->currentBatchIndex++;
261   if (self->currentBatchIndex > [self batchCount])
262     self->currentBatchIndex = 1;
263
264   [self updateDisplayedObjects];
265   
266   return nil;
267 }
268 - (id)displayPreviousBatch {
269   [self clearSelection];
270
271   self->currentBatchIndex--;
272   if ([self currentBatchIndex] <= 0)
273     self->currentBatchIndex = [self batchCount];
274   
275   [self updateDisplayedObjects];
276   
277   return nil;
278 }
279 - (id)displayBatchContainingSelectedObject {
280   [self warnWithFormat:@"%s not implemenented", __PRETTY_FUNCTION__];
281   [self updateDisplayedObjects];
282   return nil;
283 }
284
285 /* selection */
286
287 - (BOOL)setSelectionIndexes:(NSArray *)_selection {
288   BOOL ok;
289   id   d;
290   NSSet *before, *after;
291
292   ok = YES;
293   if ((d = [self delegate])) {
294     if ([d respondsToSelector:
295              @selector(displayGroup:shouldChangeSelectionToIndexes:)]) {
296       ok = [d displayGroup:self shouldChangeSelectionToIndexes:_selection];
297     }
298   }
299   if (!ok)
300     return NO;
301   
302   /* apply selection */
303
304   before = [NSSet setWithArray:self->selectionIndexes];
305   after  = [NSSet setWithArray:_selection];
306   
307   ASSIGN(self->selectionIndexes, _selection);
308   
309   if (![before isEqual:after]) {
310     [d displayGroupDidChangeSelection:self];
311     [d displayGroupDidChangeSelectedObjects:self];
312   }
313   return YES;
314 }
315 - (NSArray *)selectionIndexes {
316   return self->selectionIndexes;
317 }
318
319 - (BOOL)clearSelection {
320   static NSArray *emptyArray = nil;
321   if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
322   return [self setSelectionIndexes:emptyArray];
323 }
324
325 - (id)selectNext {
326   unsigned int idx;
327   
328   if (![self->displayObjects isNotEmpty])
329     return nil;
330   
331   if (![self->selectionIndexes isNotEmpty]) {
332     [self setSelectionIndexes:uint0Array];
333     return nil;
334   }
335   
336   idx = [[self->selectionIndexes lastObject] unsignedIntValue];
337   if (idx >= ([self->displayObjects count] - 1)) {
338     /* last object is already selected, select first one */
339     [self setSelectionIndexes:uint0Array];
340     return nil;
341   }
342   
343   /* select next object .. */
344   [self setSelectionIndexes:
345           [NSArray arrayWithObject:[NSNumber numberWithUnsignedInt:(idx + 1)]]];
346   return nil;
347 }
348
349 - (id)selectPrevious {
350   unsigned int idx;
351   
352   if (![self->displayObjects isNotEmpty])
353     return nil;
354   
355   if (![self->selectionIndexes isNotEmpty]) {
356     [self setSelectionIndexes:uint0Array];
357     return nil;
358   }
359   
360   idx = [[self->selectionIndexes objectAtIndex:0] unsignedIntValue];
361   if (idx == 0) {
362     /* first object is selected, now select last one */
363     NSNumber *sidx;
364     sidx = [NSNumber numberWithUnsignedInt:([self->displayObjects count] - 1)];
365     [self setSelectionIndexes:[NSArray arrayWithObject:sidx]];
366   }
367   
368   /* select previous object .. */
369   [self setSelectionIndexes:
370           [NSArray arrayWithObject:[NSNumber numberWithUnsignedInt:(idx - 1)]]];
371   return nil;
372 }
373
374 - (void)setSelectedObject:(id)_obj {
375   unsigned idx;
376   NSNumber *idxNumber;
377   
378   // TODO: maybe we need to retain the selection array and just swap the first
379   
380   idx = [self->objects indexOfObject:_obj];
381   idxNumber = (idx != NSNotFound)
382     ? [NSNumber numberWithUnsignedInt:idx] : (NSNumber *)nil;
383
384   if (idxNumber != nil) {
385     NSArray *a;
386     
387     a = [[NSArray alloc] initWithObjects:&idxNumber count:1];
388     [self setSelectionIndexes:a];
389     [a release]; a = nil;
390   }
391   else
392     [self setSelectionIndexes:nil];
393 }
394 - (id)selectedObject {
395   unsigned int i, sCount;
396   
397   if ((sCount = [self->selectionIndexes count]) == 0)
398     return nil;
399   
400   i = [[self->selectionIndexes objectAtIndex:0] unsignedIntValue];
401   if (i >= [self->objects count])
402     return nil;
403   
404   // TODO: need to ensure selection is in displayedObjects?
405   return [self->objects objectAtIndex:i];
406 }
407
408 - (void)setSelectedObjects:(NSArray *)_objs {
409   [self selectObjectsIdenticalTo:_objs];
410   //  [self warnWithFormat:@"%s not implemented.", __PRETTY_FUNCTION__];
411 }
412 - (NSArray *)selectedObjects {
413   NSMutableArray *result;
414   unsigned int i, sCount, oCount;
415
416   sCount = [self->selectionIndexes count];
417   oCount = [self->objects count];
418   result = [NSMutableArray arrayWithCapacity:sCount];
419   
420   for (i = 0; i < sCount; i++) {
421     unsigned int idx;
422
423     idx = [[self->selectionIndexes objectAtIndex:i] unsignedIntValue];
424     if (idx < oCount)
425       [result addObject:[self->objects objectAtIndex:idx]];
426   }
427   return result;
428 }
429
430 - (BOOL)selectObject:(id)_obj {
431   /* returns YES if displayedObjects contains _obj otherwise NO */
432   NSNumber *idxNumber;
433   unsigned idx;
434   
435   if (![self->displayObjects containsObject:_obj])
436     return NO;
437   
438   idx = [self->objects indexOfObject:_obj];
439   idxNumber = (idx != NSNotFound) 
440     ? [NSNumber numberWithUnsignedInt:idx] : (NSNumber *)nil;
441
442   // TODO: should we just exchange the first item and/or call
443   //       -setSelectedObject: ?
444   
445 #if 0 /* this was wrong? */
446   if ([self->selectionIndexes containsObject:idxNumber])
447     /* already selected => could be many => move to top? */
448     return YES;
449   
450   tmp = [NSMutableArray arrayWithObjects:self->selectionIndexes];
451   [tmp addObject:idxNumber];
452   [self setSelectionIndexes:tmp];
453 #else
454   if (idxNumber != nil)
455     [self setSelectionIndexes:[NSArray arrayWithObjects:&idxNumber count:1]];
456   else
457     [self setSelectionIndexes:nil];
458 #endif
459   return YES;
460 }
461
462
463 /* returns YES if at least one obj matches otherwise NO */
464 - (BOOL)selectObjectsIdenticalTo:(NSArray *)_objs {
465   NSMutableArray *newIndexes;
466   unsigned       i, cnt;
467   BOOL           ok = NO;
468
469   cnt = [_objs count];
470   
471   if (cnt == 0)
472     return NO;
473
474   newIndexes = [NSMutableArray arrayWithCapacity:cnt];
475   
476   for (i=0; i<cnt; i++) {
477     NSNumber *idxNumber;
478     unsigned idx;
479     id       obj;
480
481     obj = [_objs objectAtIndex:i];
482     if (![self->objects containsObject:obj])
483       continue;
484
485     ok = YES;
486     idx = [self->objects indexOfObject:obj];
487     idxNumber = [NSNumber numberWithUnsignedInt:idx];
488     
489     if ([self->selectionIndexes containsObject:idxNumber])
490       continue;
491
492     [newIndexes addObject:idxNumber];
493   }
494   if (!ok)
495     return NO;
496
497   [newIndexes addObjectsFromArray:self->selectionIndexes];
498   [self setSelectionIndexes:newIndexes];
499   
500   return YES;
501 }
502
503 - (BOOL)selectObjectsIdenticalTo:(NSArray *)_objs
504   selectFirstOnNoMatch:(BOOL)_flag
505 {
506   if ([self selectObjectsIdenticalTo:_objs])
507     return YES;
508   
509   if (_flag)
510     return [self selectObject:[self->displayObjects objectAtIndex:0]];
511   else
512     return NO;
513 }
514
515 /* objects */
516
517 - (void)setObjectArray:(NSArray *)_objects {
518   ASSIGN(self->objects, _objects);
519   
520   /* should try to restore selection */
521   [self clearSelection];
522   if ([_objects isNotEmpty] && [self selectsFirstObjectAfterFetch])
523     [self setSelectionIndexes:uint0Array];
524 }
525  
526 - (NSArray *)allObjects {
527   return self->objects;
528 }
529
530 - (NSArray *)displayedObjects {
531   return self->displayObjects;
532 }
533
534 - (id)fetch {
535   NSArray *objs;
536   
537   if ([self->delegate respondsToSelector:@selector(displayGroupShouldFetch:)]){
538     if (![self->delegate displayGroupShouldFetch:self])
539       /* delegate rejected fetch-request */
540       return nil;
541   }
542
543   objs = [[self dataSource] fetchObjects];
544
545   [self setObjectArray:objs];
546
547   if ([self->delegate respondsToSelector:
548            @selector(displayGroup:didFetchObjects:)]) {
549     [self->delegate displayGroup:self didFetchObjects:objs];
550   }
551
552   [self updateDisplayedObjects];
553   
554   if ([self selectsFirstObjectAfterFetch]) {
555     [self clearSelection];
556     
557     if ([objs isNotEmpty])
558       [self setSelectedObject:[objs objectAtIndex:0]];
559   }
560   
561   return nil /* stay on page */;
562 }
563
564 - (void)updateDisplayedObjects {
565   NSArray *darray; // display  objects
566   NSArray *sarray; // selected objects
567
568   sarray = [self selectedObjects];
569   
570   if ([self->delegate respondsToSelector:
571            @selector(displayGroup:displayArrayForObjects:)]) {
572     darray = [self->delegate displayGroup:self
573                              displayArrayForObjects:[self allObjects]];
574
575     ASSIGNCOPY(self->displayObjects, darray);
576     return;
577   }
578   
579   {
580 //    EOQualifier *q;
581     NSArray     *so, *ao;
582     
583     ao = [self allObjects];
584
585     /* apply qualifier */
586 #if 0
587     if ((q = [self qualifier]))
588       ao = [ao filteredArrayUsingQualifier:q];
589 #endif // should be done in qualifyDisplayGroup
590
591     /* apply sort orderings */
592     if ((so = [self sortOrderings]))
593       ao = [ao sortedArrayUsingKeyOrderArray:so];
594
595     if (ao != self->objects)
596       [self setObjectArray:ao];
597
598     darray = ao;
599
600     /* apply batch */
601     if ([self batchCount] > 1) {
602       unsigned first = [self indexOfFirstDisplayedObject];
603       unsigned last  = [self indexOfLastDisplayedObject];
604
605       darray = [darray subarrayWithRange:NSMakeRange(first, last-first+1)];
606     }
607   }
608   
609   darray = [darray copy];
610   RELEASE(self->displayObjects);
611   self->displayObjects = darray;
612
613   [self selectObjectsIdenticalTo:sarray];
614 }
615
616 /* query */
617
618 - (void)setInQueryMode:(BOOL)_flag {
619   self->flags.inQueryMode = _flag ? 1 : 0;
620 }
621 - (BOOL)inQueryMode {
622   return self->flags.inQueryMode ? YES : NO;
623 }
624
625 - (EOQualifier *)qualifierFromQueryValues {
626   NSMutableDictionary *qm, *qmin, *qmax, *qop;
627   NSMutableArray *quals;
628   NSEnumerator   *keys;
629   NSString       *key;
630   
631   qm   = [self queryMatch];
632   qmin = [self queryMin];
633   qmax = [self queryMax];
634   qop  = [self queryOperator];
635   
636   quals = [NSMutableArray arrayWithCapacity:[qm count]];
637   
638   /* construct qualifier for all query-match entries */
639   
640   keys = [qm keyEnumerator];
641   while ((key = [keys nextObject]) != nil) {
642     NSString *op;
643     SEL      ops;
644     id       value;
645     EOQualifier *q;
646     
647     value = [qm objectForKey:key];
648     
649     if ((op = [qop objectForKey:key]) == nil) {
650       /* default operator is equality */
651       op  = @"=";
652       ops = EOQualifierOperatorEqual;
653     }
654     else if ([value isKindOfClass:[NSString class]]) {
655       /* strings are treated in a special way */
656       NSString *fmt;
657
658       fmt = [self defaultStringMatchFormat];
659       op  = [self defaultStringMatchOperator];
660       ops = [EOQualifier operatorSelectorForString:op];
661       
662       value = [NSString stringWithFormat:fmt, value];
663     }
664     else {
665       ops = [EOQualifier operatorSelectorForString:op];
666     }
667
668     q = [[EOKeyValueQualifier alloc]
669                               initWithKey:key
670                               operatorSelector:ops
671                               value:value];
672     [quals addObject:q];
673     [q release]; q = nil;
674   }
675   
676   /* construct min qualifiers */
677
678   keys = [qmin keyEnumerator];
679   while ((key = [keys nextObject]) != nil) {
680     EOQualifier *q;
681     id value;
682     
683     value = [qmin objectForKey:key];
684
685     q = [[EOKeyValueQualifier alloc]
686                               initWithKey:key
687                               operatorSelector:EOQualifierOperatorGreaterThan
688                               value:value];
689     [quals addObject:q];
690     [q release];
691   }
692
693   /* construct max qualifiers */
694   
695   keys = [qmax keyEnumerator];
696   while ((key = [keys nextObject]) != nil) {
697     EOQualifier *q;
698     id value;
699     
700     value = [qmax objectForKey:key];
701
702     q = [[EOKeyValueQualifier alloc]
703                               initWithKey:key
704                               operatorSelector:EOQualifierOperatorLessThan
705                               value:value];
706     [quals addObject:q];
707     [q release];
708   }
709
710   if (![quals isNotEmpty])
711     return nil;
712   if ([quals count] == 1)
713     return [quals objectAtIndex:0];
714   
715   return [[[EOAndQualifier alloc] initWithQualifierArray:quals] autorelease];
716 }
717
718 - (NSMutableDictionary *)queryBindings {
719   if (self->_queryBindings == nil)
720     self->_queryBindings = [[NSMutableDictionary alloc] initWithCapacity:8];
721   return self->_queryBindings;
722 }
723 - (NSMutableDictionary *)queryMatch {
724   if (self->_queryMatch == nil)
725     self->_queryMatch = [[NSMutableDictionary alloc] initWithCapacity:8];
726   return self->_queryMatch;
727 }
728 - (NSMutableDictionary *)queryMin {
729   if (self->_queryMin == nil)
730     self->_queryMin = [[NSMutableDictionary alloc] initWithCapacity:8];
731   return self->_queryMin;
732 }
733 - (NSMutableDictionary *)queryMax {
734   if (self->_queryMax == nil)
735     self->_queryMax = [[NSMutableDictionary alloc] initWithCapacity:8];
736   return self->_queryMax;
737 }
738 - (NSMutableDictionary *)queryOperator {
739   if (self->_queryOperator == nil)
740     self->_queryOperator = [[NSMutableDictionary alloc] initWithCapacity:8];
741   return self->_queryOperator;
742 }
743
744 - (void)setDefaultStringMatchFormat:(NSString *)_tmp {
745   ASSIGNCOPY(self->defaultStringMatchFormat, _tmp);
746 }
747 - (NSString *)defaultStringMatchFormat {
748   return self->defaultStringMatchFormat;
749 }
750 - (void)setDefaultStringMatchOperator:(NSString *)_tmp {
751   ASSIGNCOPY(self->defaultStringMatchOperator, _tmp);
752 }
753 - (NSString *)defaultStringMatchOperator {
754   return self->defaultStringMatchOperator;
755 }
756 + (NSString *)globalDefaultStringMatchFormat {
757   return @"%@*";
758 }
759 + (NSString *)globalDefaultStringMatchOperator {
760   return @"caseInsensitiveLike";
761 }
762
763
764 /* qualfiers */
765
766 - (void)setQualifier:(EOQualifier *)_q {
767   ASSIGN(self->qualifier, _q);
768 }
769 - (EOQualifier *)qualifier {
770   return self->qualifier;
771 }
772
773 - (NSArray *)allQualifierOperators {
774   static NSArray *quals = nil;
775   if (quals == nil) {
776     quals = [[NSArray alloc] initWithObjects:
777                                @"=", @"!=", @"<", @"<=", @">", @">=",
778                                @"like", @"caseInsensitiveLike", nil];
779   }
780   return quals;
781 }
782 - (NSArray *)stringQualifierOperators {
783   static NSArray *quals = nil;
784   if (quals == nil) {
785     quals = [[NSArray alloc] initWithObjects:
786                                @"starts with",
787                                @"contains",
788                                @"ends with",
789                                @"is",
790                                @"like",
791                                nil];
792   }
793   return quals;
794 }
795 - (NSArray *)relationalQualifierOperators {
796   static NSArray *quals = nil;
797   if (quals == nil) {
798     quals = [[NSArray alloc] initWithObjects:
799                                @"=", @"!=", @"<", @"<=", @">", @">=", nil];
800   }
801   return quals;
802 }
803
804 - (void)qualifyDisplayGroup {
805   EOQualifier *q;
806
807   if ((q = [self qualifierFromQueryValues]) != nil)
808     [self setQualifier:q];
809   
810   [self updateDisplayedObjects];
811   
812   if ([self inQueryMode])
813     [self setInQueryMode:NO];
814 }
815
816 - (void)qualifyDataSource {
817   EODataSource *ds;
818   EOQualifier  *q;
819   NSDictionary *bindings;
820
821   if ((ds = [self dataSource]) == nil)
822     [self warnWithFormat:@"no datasource set: %@", NSStringFromSelector(_cmd)];
823
824   /* build qualifier */
825   
826   if ((q = [self qualifierFromQueryValues]) != nil)
827     [self setQualifier:q];
828   
829   /* set qualifier in datasource */
830   
831   if ([ds respondsToSelector:@selector(setAuxiliaryQualifier:)]) {
832     [ds setAuxiliaryQualifier:[self qualifier]];
833     //[self logWithFormat:@"set aux qualifier in %@: %@", ds,[self qualifier]];
834   }
835   else if ([ds respondsToSelector:@selector(setQualifier:)])
836     [ds setQualifier:[self qualifier]];
837   else {
838     /* could not qualify ds */
839     [self warnWithFormat:@"could not qualify datasource: %@", ds];
840   }
841   
842   /* set bindings in datasource */
843
844   if ([(bindings = [self queryBindings]) isNotEmpty]) {
845     if ([ds respondsToSelector:@selector(setQualifierBindings:)])
846       [ds setQualifierBindings:bindings];
847     else {
848       [self warnWithFormat:@"could not set bindings in datasource %@: %@", 
849               ds, bindings];
850     }
851   }
852   
853   /* perform fetch */
854   
855   /* action method, returns 'nil' to stay on page */
856   [self fetch];
857   
858   if ([self inQueryMode])
859     [self setInQueryMode:NO];
860 }
861
862 - (id)qualifyDataSourceAndReturnDisplayCount {
863   /* 
864      This is a 'hack' created because we can't bind (and therefore 'call')
865      'void' methods in .wod files.
866   */
867   [self qualifyDataSource];
868   return [NSNumber numberWithUnsignedInt:[[self displayedObjects] count]];
869 }
870
871 /* object creation */
872
873 - (id)insert {
874   unsigned idx;
875
876   idx = [self->selectionIndexes isNotEmpty]
877     ? ([[self->selectionIndexes objectAtIndex:0] unsignedIntValue] + 1)
878     : [self->objects count];
879   
880   return [self insertObjectAtIndex:idx]; /* returns 'nil' */
881 }
882
883 - (id)insertObjectAtIndex:(unsigned)_idx {
884   id newObject;
885   
886   if ((newObject = [[self dataSource] createObject]) == nil) {
887     [self errorWithFormat:@"Failed to create new object in datasource: %@",
888             [self dataSource]];
889     
890     if ([self->delegate respondsToSelector:
891                @selector(displayGroup:createObjectFailedForDataSource:)]) {
892       [self->delegate displayGroup:self 
893                       createObjectFailedForDataSource:[self dataSource]];
894     }
895     return nil /* refresh page */;
896   }
897
898   /* apply default values */
899   
900   [newObject takeValuesFromDictionary:[self insertedObjectDefaultValues]];
901   
902   /* insert */
903
904   [self insertObject:newObject atIndex:_idx];
905   
906   return nil /* refresh page */;
907 }
908
909 - (void)insertObject:(id)_o atIndex:(unsigned)_idx {
910   NSMutableArray *ma;
911   
912   /* ask delegate whether we should insert */
913   if ([self->delegate respondsToSelector:
914              @selector(displayGroup:shouldInsertObject:atIndex:)]) {
915     if (![self->delegate displayGroup:self shouldInsertObject:_o atIndex:_idx])
916       return;
917   }
918   
919   /* insert in datasource */
920   
921   [[self dataSource] insertObject:_o];
922   
923   /* update object-array (ignores qualifier for new objects!) */
924   
925   ma = [self->objects mutableCopy];
926   if (_idx <= [ma count])
927     [ma insertObject:_o atIndex:_idx];
928   else
929     [ma addObject:_o];
930   
931   [self setObjectArray:ma];
932   [ma release]; ma = nil;
933   [self updateDisplayedObjects];
934
935   /* select object */
936   
937   [self selectObject:_o]; // TODO: or use setSelectedObject:?
938   
939   /* let delegate know */
940   if ([self->delegate respondsToSelector:
941              @selector(displayGroup:didInsertObject:)])
942     [self->delegate displayGroup:self didInsertObject:_o];
943 }
944
945
946 /* object deletion */
947
948 - (id)delete {
949   [self deleteSelection];
950   return nil;
951 }
952
953 - (BOOL)deleteSelection {
954   NSArray  *objsToDelete;
955   unsigned i, count;
956   
957   objsToDelete = [[[self selectedObjects] shallowCopy] autorelease];
958
959   for (i = 0, count = [objsToDelete count]; i < count; i++) {
960     unsigned idx;
961     
962     idx = [self->objects indexOfObject:[objsToDelete objectAtIndex:i]];
963     if (idx == NSNotFound) {
964       [self errorWithFormat:@"Did not find object in selection: %@",
965               objsToDelete];
966       return NO;
967     }
968     
969     if (![self deleteObjectAtIndex:idx])
970       return NO;
971   }
972   return YES;
973 }
974
975 - (BOOL)deleteObjectAtIndex:(unsigned)_idx {
976   NSMutableArray *ma;
977   id   object;
978   BOOL ok;
979
980   /* find object */
981   
982   object = (_idx < [self->objects count])
983     ? [[[self->objects objectAtIndex:_idx] retain] autorelease]
984     : nil;
985   // TODO: check for nil?
986   
987   /* ask delegate */
988   
989   if ([self->delegate respondsToSelector:
990              @selector(displayGroup:shouldDeleteObject:)]) {
991     if (![self->delegate displayGroup:self shouldDeleteObject:object])
992       return NO;
993   }
994   
995   /* delete in datasource */
996   
997   ok = YES;
998   NS_DURING
999     [[self dataSource] deleteObject:object];
1000   NS_HANDLER
1001     *(&ok) = NO;
1002   NS_ENDHANDLER;
1003
1004   if (!ok)
1005     return NO;
1006   
1007   /* update array */
1008   
1009   ma = [self->objects mutableCopy];
1010   [ma removeObject:object];
1011   [self setObjectArray:ma];
1012   [ma release]; ma = nil;
1013   [self updateDisplayedObjects];
1014   
1015   /* notify delegate */
1016
1017   if ([self->delegate respondsToSelector:
1018              @selector(displayGroup:didDeleteObject:)])
1019     [self->delegate displayGroup:self didDeleteObject:object];
1020   return YES;
1021 }
1022
1023
1024 /* master / detail */
1025
1026 - (BOOL)hasDetailDataSource {
1027   return [[self dataSource] isKindOfClass:[EODetailDataSource class]];
1028 }
1029
1030 - (void)setDetailKey:(NSString *)_key {
1031   // TODO: fix me, probably we want to store the key for later
1032 #if 0
1033   EODataSource *ds;
1034   
1035   if ([(ds = [self dataSource]) respondsToSelector:_cmd])
1036     [(EODetailDataSource *)ds setDetailKey:_key];
1037 #endif
1038 }
1039 - (NSString *)detailKey {
1040   EODataSource *ds;
1041   
1042   return ([(ds = [self dataSource]) respondsToSelector:_cmd])
1043     ? [(EODetailDataSource *)ds detailKey] : (NSString *)nil;
1044 }
1045
1046 - (void)setMasterObject:(id)_object {
1047   [[self dataSource] qualifyWithRelationshipKey:[self detailKey]
1048                      ofObject:_object];
1049   
1050   if ([self fetchesOnLoad])
1051     [self fetch];
1052 }
1053 - (id)masterObject {
1054   EODataSource *ds;
1055   
1056   return ([(ds = [self dataSource]) respondsToSelector:_cmd])
1057     ? [(EODetailDataSource *)ds masterObject] : nil;
1058 }
1059
1060
1061 /* KVC */
1062
1063 - (void)takeValue:(id)_value forKeyPath:(NSString *)_keyPath {
1064   if([_keyPath hasPrefix:@"queryMatch."]) {
1065     [[self queryMatch] takeValue:_value 
1066                        forKey:[_keyPath substringFromIndex:11]];
1067   }
1068   else if([_keyPath hasPrefix:@"queryMax."])
1069     [[self queryMax] takeValue:_value forKey:[_keyPath substringFromIndex:9]];
1070   else if([_keyPath hasPrefix:@"queryMin."])
1071     [[self queryMin] takeValue:_value forKey:[_keyPath substringFromIndex:9]];
1072   else if([_keyPath hasPrefix:@"queryOperator."]) {
1073     [[self queryOperator] takeValue:_value 
1074                           forKey:[_keyPath substringFromIndex:14]];
1075   }
1076   else
1077     [super takeValue:_value forKeyPath:_keyPath];
1078 }
1079 - (id)valueForKeyPath:(NSString *)_keyPath {
1080   if ([_keyPath hasPrefix:@"queryMatch."])
1081     return [[self queryMatch] valueForKey:[_keyPath substringFromIndex:11]];
1082   if ([_keyPath hasPrefix:@"queryMax."])
1083     return [[self queryMax] valueForKey:[_keyPath substringFromIndex:9]];
1084   if ([_keyPath hasPrefix:@"queryMin."])
1085     return [[self queryMin] valueForKey:[_keyPath substringFromIndex:9]];
1086   if ([_keyPath hasPrefix:@"queryOperator."])
1087     return [[self queryOperator] valueForKey:[_keyPath substringFromIndex:14]];
1088
1089   return [super valueForKeyPath:_keyPath];
1090 }
1091
1092 /* NSCoding */
1093
1094 - (id)initWithCoder:(NSCoder *)_coder {
1095   self->dataSource                 = [[_coder decodeObject] retain];
1096   self->delegate                   = [_coder decodeObject];
1097   self->sortOrderings              = [[_coder decodeObject] copy];
1098   self->insertedObjectDefaults     = [[_coder decodeObject] copy];
1099   self->qualifier                  = [[_coder decodeObject] copy];
1100   self->defaultStringMatchFormat   = [[_coder decodeObject] copy];
1101   self->defaultStringMatchOperator = [[_coder decodeObject] copy];
1102   self->_queryBindings             = [[_coder decodeObject] copy];
1103   self->_queryMatch                = [[_coder decodeObject] copy];
1104   self->_queryMin                  = [[_coder decodeObject] copy];
1105   self->_queryMax                  = [[_coder decodeObject] copy];
1106   self->_queryOperator             = [[_coder decodeObject] copy];
1107   
1108   return self;
1109 }
1110
1111 - (void)encodeWithCoder:(NSCoder *)_coder {
1112   [_coder encodeObject:self->dataSource];
1113   [_coder encodeObject:self->delegate];
1114   [_coder encodeObject:self->sortOrderings];
1115   [_coder encodeObject:self->insertedObjectDefaults];
1116   [_coder encodeObject:self->qualifier];
1117   [_coder encodeObject:self->defaultStringMatchFormat];
1118   [_coder encodeObject:self->defaultStringMatchOperator];
1119   [_coder encodeObject:self->_queryBindings];
1120   [_coder encodeObject:self->_queryMatch];
1121   [_coder encodeObject:self->_queryMin];
1122   [_coder encodeObject:self->_queryMax];
1123   [_coder encodeObject:self->_queryOperator];
1124   
1125   [self notImplemented:_cmd];
1126 }
1127
1128 /* KVCArchiving */
1129
1130 - (id)initWithKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
1131   if ((self = [self init]) != nil) {
1132     id tmp;
1133     
1134     if ((tmp = [_unarchiver decodeObjectForKey:@"formatForLikeQualifier"]))
1135       [self setDefaultStringMatchFormat:tmp];
1136     
1137     if ((tmp = [_unarchiver decodeObjectForKey:@"dataSource"]))
1138       [self setDataSource:tmp];
1139
1140     if ((tmp = [_unarchiver decodeObjectForKey:@"numberOfObjectsPerBatch"]))
1141       [self setNumberOfObjectsPerBatch:[tmp intValue]];
1142     
1143     [self setFetchesOnLoad:[_unarchiver decodeBoolForKey:@"fetchesOnLoad"]];
1144     [self setSelectsFirstObjectAfterFetch:
1145           [_unarchiver decodeBoolForKey:@"selectsFirstObjectAfterFetch"]];
1146   }
1147   return self;
1148 }
1149
1150 - (void)encodeWithKeyValueArchiver:(EOKeyValueArchiver *)_archiver {
1151   [_archiver encodeObject:[self defaultStringMatchFormat]
1152              forKey:@"formatForLikeQualifier"];
1153   [_archiver encodeObject:[self dataSource]
1154              forKey:@"dataSource"];
1155   [_archiver encodeObject:
1156                [NSNumber numberWithUnsignedInt:[self numberOfObjectsPerBatch]]
1157              forKey:@"numberOfObjectsPerBatch"];
1158   [_archiver encodeBool:[self fetchesOnLoad]
1159              forKey:@"fetchesOnLoad"];
1160   [_archiver encodeBool:[self selectsFirstObjectAfterFetch]
1161              forKey:@"selectFirstAfterFetch"];
1162 }
1163
1164 - (void)awakeFromKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
1165   if ([self fetchesOnLoad])
1166     [self fetch];
1167 }
1168
1169 /* EOEditorsImpl */
1170
1171 - (void)editingContextWillSaveChanges:(id)_ec {
1172 }
1173 - (BOOL)editorHasChangesForEditingContext:(id)_ec {
1174   return NO;
1175 }
1176
1177 /* EOMessageHandlersImpl */
1178
1179 - (void)editingContext:(id)_ec
1180   presentErrorMessage:(NSString *)_msg
1181 {
1182 }
1183
1184 - (BOOL)editingContext:(id)_ec
1185   shouldContinueFetchingWithCurrentObjectCount:(unsigned)_oc
1186   originalLimit:(unsigned)_olimit
1187   objectStore:(id)_store
1188 {
1189   return NO;
1190 }
1191
1192 /* description */
1193
1194 - (NSString *)description {
1195   return [NSString stringWithFormat:@"<0x%p %@: ds=%@>",
1196                      self, NSStringFromClass([self class]),
1197                      [self dataSource]];
1198 }
1199
1200 @end /* WODisplayGroup */