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