]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WODisplayGroup.m
added new WOCopyValue element
[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 count] == 0)
328     return nil;
329   
330   if ([self->selectionIndexes count] == 0) {
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 count] == 0)
352     return nil;
353   
354   if ([self->selectionIndexes count] == 0) {
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 count] > 0 && [self selectsFirstObjectAfterFetch]) {
515     [self setSelectionIndexes:uint0Array];
516   }
517 }
518  
519 - (NSArray *)allObjects {
520   return self->objects;
521 }
522
523 - (NSArray *)displayedObjects {
524   return self->displayObjects;
525 }
526
527 - (id)fetch {
528   NSArray *objs;
529   
530   if ([self->delegate respondsToSelector:@selector(displayGroupShouldFetch:)]){
531     if (![self->delegate displayGroupShouldFetch:self])
532       /* delegate rejected fetch-request */
533       return nil;
534   }
535
536   objs = [[self dataSource] fetchObjects];
537
538   [self setObjectArray:objs];
539
540   if ([self->delegate respondsToSelector:
541            @selector(displayGroup:didFetchObjects:)]) {
542     [self->delegate displayGroup:self didFetchObjects:objs];
543   }
544
545   [self updateDisplayedObjects];
546
547   if ([self selectsFirstObjectAfterFetch]) {
548     [self clearSelection];
549     
550     if ([objs count] > 0)
551       [self setSelectedObject:[objs objectAtIndex:0]];
552   }
553   
554   return nil /* stay on page */;
555 }
556
557 - (void)updateDisplayedObjects {
558   NSArray *darray; // display  objects
559   NSArray *sarray; // selected objects
560
561   sarray = [self selectedObjects];
562   
563   if ([self->delegate respondsToSelector:
564            @selector(displayGroup:displayArrayForObjects:)]) {
565     darray = [self->delegate displayGroup:self
566                              displayArrayForObjects:[self allObjects]];
567
568     ASSIGNCOPY(self->displayObjects, darray);
569     return;
570   }
571   
572   {
573 //    EOQualifier *q;
574     NSArray     *so, *ao;
575     
576     ao = [self allObjects];
577
578     /* apply qualifier */
579 #if 0
580     if ((q = [self qualifier]))
581       ao = [ao filteredArrayUsingQualifier:q];
582 #endif // should be done in qualifyDisplayGroup
583
584     /* apply sort orderings */
585     if ((so = [self sortOrderings]))
586       ao = [ao sortedArrayUsingKeyOrderArray:so];
587
588     if (ao != self->objects)
589       [self setObjectArray:ao];
590
591     darray = ao;
592
593     /* apply batch */
594     if ([self batchCount] > 1) {
595       unsigned first = [self indexOfFirstDisplayedObject];
596       unsigned last  = [self indexOfLastDisplayedObject];
597
598       darray = [darray subarrayWithRange:NSMakeRange(first, last-first+1)];
599     }
600   }
601   
602   darray = [darray copy];
603   RELEASE(self->displayObjects);
604   self->displayObjects = darray;
605
606   [self selectObjectsIdenticalTo:sarray];
607 }
608
609 /* query */
610
611 - (void)setInQueryMode:(BOOL)_flag {
612   self->flags.inQueryMode = _flag ? 1 : 0;
613 }
614 - (BOOL)inQueryMode {
615   return self->flags.inQueryMode ? YES : NO;
616 }
617
618 - (EOQualifier *)qualifierFromQueryValues {
619   NSMutableDictionary *qm, *qmin, *qmax, *qop;
620   NSMutableArray *quals;
621   NSEnumerator   *keys;
622   NSString       *key;
623   
624   qm   = [self queryMatch];
625   qmin = [self queryMin];
626   qmax = [self queryMax];
627   qop  = [self queryOperator];
628   
629   quals = [NSMutableArray arrayWithCapacity:[qm count]];
630   
631   /* construct qualifier for all query-match entries */
632   
633   keys = [qm keyEnumerator];
634   while ((key = [keys nextObject]) != nil) {
635     NSString *op;
636     SEL      ops;
637     id       value;
638     EOQualifier *q;
639     
640     value = [qm objectForKey:key];
641     
642     if ((op = [qop objectForKey:key]) == nil) {
643       /* default operator is equality */
644       op  = @"=";
645       ops = EOQualifierOperatorEqual;
646     }
647     else if ([value isKindOfClass:[NSString class]]) {
648       /* strings are treated in a special way */
649       NSString *fmt;
650
651       fmt = [self defaultStringMatchFormat];
652       op  = [self defaultStringMatchOperator];
653       ops = [EOQualifier operatorSelectorForString:op];
654       
655       value = [NSString stringWithFormat:fmt, value];
656     }
657     else {
658       ops = [EOQualifier operatorSelectorForString:op];
659     }
660
661     q = [[EOKeyValueQualifier alloc]
662                               initWithKey:key
663                               operatorSelector:ops
664                               value:value];
665     [quals addObject:q];
666     [q release]; q = nil;
667   }
668   
669   /* construct min qualifiers */
670
671   keys = [qmin keyEnumerator];
672   while ((key = [keys nextObject]) != nil) {
673     EOQualifier *q;
674     id value;
675     
676     value = [qmin objectForKey:key];
677
678     q = [[EOKeyValueQualifier alloc]
679                               initWithKey:key
680                               operatorSelector:EOQualifierOperatorGreaterThan
681                               value:value];
682     [quals addObject:q];
683     [q release];
684   }
685
686   /* construct max qualifiers */
687   
688   keys = [qmax keyEnumerator];
689   while ((key = [keys nextObject]) != nil) {
690     EOQualifier *q;
691     id value;
692     
693     value = [qmax objectForKey:key];
694
695     q = [[EOKeyValueQualifier alloc]
696                               initWithKey:key
697                               operatorSelector:EOQualifierOperatorLessThan
698                               value:value];
699     [quals addObject:q];
700     [q release];
701   }
702
703   if ([quals count] == 0)
704     return nil;
705   if ([quals count] == 1)
706     return [quals objectAtIndex:0];
707   
708   return [[[EOAndQualifier alloc] initWithQualifierArray:quals] autorelease];
709 }
710
711 - (NSMutableDictionary *)queryBindings {
712   if (self->_queryBindings == nil)
713     self->_queryBindings = [[NSMutableDictionary alloc] initWithCapacity:8];
714   return self->_queryBindings;
715 }
716 - (NSMutableDictionary *)queryMatch {
717   if (self->_queryMatch == nil)
718     self->_queryMatch = [[NSMutableDictionary alloc] initWithCapacity:8];
719   return self->_queryMatch;
720 }
721 - (NSMutableDictionary *)queryMin {
722   if (self->_queryMin == nil)
723     self->_queryMin = [[NSMutableDictionary alloc] initWithCapacity:8];
724   return self->_queryMin;
725 }
726 - (NSMutableDictionary *)queryMax {
727   if (self->_queryMax == nil)
728     self->_queryMax = [[NSMutableDictionary alloc] initWithCapacity:8];
729   return self->_queryMax;
730 }
731 - (NSMutableDictionary *)queryOperator {
732   if (self->_queryOperator == nil)
733     self->_queryOperator = [[NSMutableDictionary alloc] initWithCapacity:8];
734   return self->_queryOperator;
735 }
736
737 - (void)setDefaultStringMatchFormat:(NSString *)_tmp {
738   ASSIGNCOPY(self->defaultStringMatchFormat, _tmp);
739 }
740 - (NSString *)defaultStringMatchFormat {
741   return self->defaultStringMatchFormat;
742 }
743 - (void)setDefaultStringMatchOperator:(NSString *)_tmp {
744   ASSIGNCOPY(self->defaultStringMatchOperator, _tmp);
745 }
746 - (NSString *)defaultStringMatchOperator {
747   return self->defaultStringMatchOperator;
748 }
749 + (NSString *)globalDefaultStringMatchFormat {
750   return @"%@*";
751 }
752 + (NSString *)globalDefaultStringMatchOperator {
753   return @"caseInsensitiveLike";
754 }
755
756
757 /* qualfiers */
758
759 - (void)setQualifier:(EOQualifier *)_q {
760   ASSIGN(self->qualifier, _q);
761 }
762 - (EOQualifier *)qualifier {
763   return self->qualifier;
764 }
765
766 - (NSArray *)allQualifierOperators {
767   static NSArray *quals = nil;
768   if (quals == nil) {
769     quals = [[NSArray alloc] initWithObjects:
770                                @"=", @"!=", @"<", @"<=", @">", @">=",
771                                @"like", @"caseInsensitiveLike", nil];
772   }
773   return quals;
774 }
775 - (NSArray *)stringQualifierOperators {
776   static NSArray *quals = nil;
777   if (quals == nil) {
778     quals = [[NSArray alloc] initWithObjects:
779                                @"starts with",
780                                @"contains",
781                                @"ends with",
782                                @"is",
783                                @"like",
784                                nil];
785   }
786   return quals;
787 }
788 - (NSArray *)relationalQualifierOperators {
789   static NSArray *quals = nil;
790   if (quals == nil) {
791     quals = [[NSArray alloc] initWithObjects:
792                                @"=", @"!=", @"<", @"<=", @">", @">=", nil];
793   }
794   return quals;
795 }
796
797 - (void)qualifyDisplayGroup {
798   EOQualifier *q;
799
800   if ((q = [self qualifierFromQueryValues]) != nil)
801     [self setQualifier:q];
802   
803   [self updateDisplayedObjects];
804   
805   if ([self inQueryMode])
806     [self setInQueryMode:NO];
807 }
808
809 - (void)qualifyDataSource {
810   EODataSource *ds;
811   EOQualifier  *q;
812
813   ds = [self dataSource];
814   
815   if ((q = [self qualifierFromQueryValues]) != nil)
816     [self setQualifier:q];
817   
818   if ([ds respondsToSelector:@selector(setAuxiliaryQualifier:)])
819     [ds setAuxiliaryQualifier:[self qualifier]];
820   else if ([ds respondsToSelector:@selector(setQualifier:)])
821     [ds setQualifier:[self qualifier]];
822   else {
823     /* could not qualify ds */
824   }
825   
826   if ([ds respondsToSelector:@selector(setQualifierBindings:)])
827     [ds setQualifierBindings:[self queryBindings]];
828   
829   [self fetch];
830   
831   if ([self inQueryMode])
832     [self setInQueryMode:NO];
833 }
834
835
836 /* object creation */
837
838 - (id)insert {
839   unsigned idx;
840
841   idx = [self->selectionIndexes count] > 0
842     ? ([[self->selectionIndexes objectAtIndex:0] unsignedIntValue] + 1)
843     : [self->objects count];
844   
845   return [self insertObjectAtIndex:idx]; /* returns 'nil' */
846 }
847
848 - (id)insertObjectAtIndex:(unsigned)_idx {
849   id newObject;
850   
851   if ((newObject = [[self dataSource] createObject]) == nil) {
852     [self errorWithFormat:@"Failed to create new object in datasource: %@",
853             [self dataSource]];
854     
855     if ([self->delegate respondsToSelector:
856                @selector(displayGroup:createObjectFailedForDataSource:)]) {
857       [self->delegate displayGroup:self 
858                       createObjectFailedForDataSource:[self dataSource]];
859     }
860     return nil /* refresh page */;
861   }
862
863   /* apply default values */
864   
865   [newObject takeValuesFromDictionary:[self insertedObjectDefaultValues]];
866   
867   /* insert */
868
869   [self insertObject:newObject atIndex:_idx];
870   
871   return nil /* refresh page */;
872 }
873
874 - (void)insertObject:(id)_o atIndex:(unsigned)_idx {
875   NSMutableArray *ma;
876   
877   /* ask delegate whether we should insert */
878   if ([self->delegate respondsToSelector:
879              @selector(displayGroup:shouldInsertObject:atIndex:)]) {
880     if (![self->delegate displayGroup:self shouldInsertObject:_o atIndex:_idx])
881       return;
882   }
883   
884   /* insert in datasource */
885   
886   [[self dataSource] insertObject:_o];
887   
888   /* update object-array (ignores qualifier for new objects!) */
889   
890   ma = [self->objects mutableCopy];
891   if (_idx <= [ma count])
892     [ma insertObject:_o atIndex:_idx];
893   else
894     [ma addObject:_o];
895   
896   [self setObjectArray:ma];
897   [ma release]; ma = nil;
898   [self updateDisplayedObjects];
899
900   /* select object */
901   
902   [self selectObject:_o]; // TODO: or use setSelectedObject:?
903   
904   /* let delegate know */
905   if ([self->delegate respondsToSelector:
906              @selector(displayGroup:didInsertObject:)])
907     [self->delegate displayGroup:self didInsertObject:_o];
908 }
909
910
911 /* object deletion */
912
913 - (id)delete {
914   [self deleteSelection];
915   return nil;
916 }
917
918 - (BOOL)deleteSelection {
919   NSArray  *objsToDelete;
920   unsigned i, count;
921   
922   objsToDelete = [[[self selectedObjects] shallowCopy] autorelease];
923
924   for (i = 0, count = [objsToDelete count]; i < count; i++) {
925     unsigned idx;
926     
927     idx = [self->objects indexOfObject:[objsToDelete objectAtIndex:i]];
928     if (idx == NSNotFound) {
929       [self errorWithFormat:@"Did not find object in selection: %@",
930               objsToDelete];
931       return NO;
932     }
933     
934     if (![self deleteObjectAtIndex:idx])
935       return NO;
936   }
937   return YES;
938 }
939
940 - (BOOL)deleteObjectAtIndex:(unsigned)_idx {
941   NSMutableArray *ma;
942   id   object;
943   BOOL ok;
944
945   /* find object */
946   
947   object = (_idx < [self->objects count])
948     ? [[[self->objects objectAtIndex:_idx] retain] autorelease]
949     : nil;
950   // TODO: check for nil?
951   
952   /* ask delegate */
953   
954   if ([self->delegate respondsToSelector:
955              @selector(displayGroup:shouldDeleteObject:)]) {
956     if (![self->delegate displayGroup:self shouldDeleteObject:object])
957       return NO;
958   }
959   
960   /* delete in datasource */
961   
962   ok = YES;
963   NS_DURING
964     [[self dataSource] deleteObject:object];
965   NS_HANDLER
966     *(&ok) = NO;
967   NS_ENDHANDLER;
968
969   if (!ok)
970     return NO;
971   
972   /* update array */
973   
974   ma = [self->objects mutableCopy];
975   [ma removeObject:object];
976   [self setObjectArray:ma];
977   [ma release]; ma = nil;
978   [self updateDisplayedObjects];
979   
980   /* notify delegate */
981
982   if ([self->delegate respondsToSelector:
983              @selector(displayGroup:didDeleteObject:)])
984     [self->delegate displayGroup:self didDeleteObject:object];
985   return YES;
986 }
987
988
989 /* master / detail */
990
991 - (BOOL)hasDetailDataSource {
992   return [[self dataSource] isKindOfClass:[EODetailDataSource class]];
993 }
994
995 - (void)setDetailKey:(NSString *)_key {
996   // TODO: fix me, probably we want to store the key for later
997 #if 0
998   EODataSource *ds;
999   
1000   if ([(ds = [self dataSource]) respondsToSelector:_cmd])
1001     [(EODetailDataSource *)ds setDetailKey:_key];
1002 #endif
1003 }
1004 - (NSString *)detailKey {
1005   EODataSource *ds;
1006   
1007   return ([(ds = [self dataSource]) respondsToSelector:_cmd])
1008     ? [(EODetailDataSource *)ds detailKey] : nil;
1009 }
1010
1011 - (void)setMasterObject:(id)_object {
1012   [[self dataSource] qualifyWithRelationshipKey:[self detailKey]
1013                      ofObject:_object];
1014   
1015   if ([self fetchesOnLoad])
1016     [self fetch];
1017 }
1018 - (id)masterObject {
1019   EODataSource *ds;
1020   
1021   return ([(ds = [self dataSource]) respondsToSelector:_cmd])
1022     ? [(EODetailDataSource *)ds masterObject] : nil;
1023 }
1024
1025
1026 /* KVC */
1027
1028 - (void)takeValue:(id)_value forKeyPath:(NSString *)_keyPath {
1029   if([_keyPath hasPrefix:@"queryMatch."]) {
1030     [[self queryMatch] takeValue:_value 
1031                        forKey:[_keyPath substringFromIndex:11]];
1032   }
1033   else if([_keyPath hasPrefix:@"queryMax."])
1034     [[self queryMax] takeValue:_value forKey:[_keyPath substringFromIndex:9]];
1035   else if([_keyPath hasPrefix:@"queryMin."])
1036     [[self queryMin] takeValue:_value forKey:[_keyPath substringFromIndex:9]];
1037   else if([_keyPath hasPrefix:@"queryOperator."]) {
1038     [[self queryOperator] takeValue:_value 
1039                           forKey:[_keyPath substringFromIndex:14]];
1040   }
1041   else
1042     [super takeValue:_value forKeyPath:_keyPath];
1043 }
1044 - (id)valueForKeyPath:(NSString *)_keyPath {
1045   if ([_keyPath hasPrefix:@"queryMatch."])
1046     return [[self queryMatch] valueForKey:[_keyPath substringFromIndex:11]];
1047   if ([_keyPath hasPrefix:@"queryMax."])
1048     return [[self queryMax] valueForKey:[_keyPath substringFromIndex:9]];
1049   if ([_keyPath hasPrefix:@"queryMin."])
1050     return [[self queryMin] valueForKey:[_keyPath substringFromIndex:9]];
1051   if ([_keyPath hasPrefix:@"queryOperator."])
1052     return [[self queryOperator] valueForKey:[_keyPath substringFromIndex:14]];
1053
1054   return [super valueForKeyPath:_keyPath];
1055 }
1056
1057 /* NSCoding */
1058
1059 - (id)initWithCoder:(NSCoder *)_coder {
1060   self->dataSource                 = [[_coder decodeObject] retain];
1061   self->delegate                   = [_coder decodeObject];
1062   self->sortOrderings              = [[_coder decodeObject] copy];
1063   self->insertedObjectDefaults     = [[_coder decodeObject] copy];
1064   self->qualifier                  = [[_coder decodeObject] copy];
1065   self->defaultStringMatchFormat   = [[_coder decodeObject] copy];
1066   self->defaultStringMatchOperator = [[_coder decodeObject] copy];
1067   self->_queryBindings             = [[_coder decodeObject] copy];
1068   self->_queryMatch                = [[_coder decodeObject] copy];
1069   self->_queryMin                  = [[_coder decodeObject] copy];
1070   self->_queryMax                  = [[_coder decodeObject] copy];
1071   self->_queryOperator             = [[_coder decodeObject] copy];
1072   
1073   return self;
1074 }
1075
1076 - (void)encodeWithCoder:(NSCoder *)_coder {
1077   [_coder encodeObject:self->dataSource];
1078   [_coder encodeObject:self->delegate];
1079   [_coder encodeObject:self->sortOrderings];
1080   [_coder encodeObject:self->insertedObjectDefaults];
1081   [_coder encodeObject:self->qualifier];
1082   [_coder encodeObject:self->defaultStringMatchFormat];
1083   [_coder encodeObject:self->defaultStringMatchOperator];
1084   [_coder encodeObject:self->_queryBindings];
1085   [_coder encodeObject:self->_queryMatch];
1086   [_coder encodeObject:self->_queryMin];
1087   [_coder encodeObject:self->_queryMax];
1088   [_coder encodeObject:self->_queryOperator];
1089   
1090   [self notImplemented:_cmd];
1091 }
1092
1093 /* KVCArchiving */
1094
1095 - (id)initWithKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
1096   if ((self = [self init]) != nil) {
1097     id tmp;
1098     
1099     if ((tmp = [_unarchiver decodeObjectForKey:@"formatForLikeQualifier"]))
1100       [self setDefaultStringMatchFormat:tmp];
1101     
1102     if ((tmp = [_unarchiver decodeObjectForKey:@"dataSource"]))
1103       [self setDataSource:tmp];
1104
1105     if ((tmp = [_unarchiver decodeObjectForKey:@"numberOfObjectsPerBatch"]))
1106       [self setNumberOfObjectsPerBatch:[tmp intValue]];
1107     
1108     [self setFetchesOnLoad:[_unarchiver decodeBoolForKey:@"fetchesOnLoad"]];
1109     [self setSelectsFirstObjectAfterFetch:
1110           [_unarchiver decodeBoolForKey:@"selectsFirstObjectAfterFetch"]];
1111   }
1112   return self;
1113 }
1114
1115 - (void)encodeWithKeyValueArchiver:(EOKeyValueArchiver *)_archiver {
1116   [_archiver encodeObject:[self defaultStringMatchFormat]
1117              forKey:@"formatForLikeQualifier"];
1118   [_archiver encodeObject:[self dataSource]
1119              forKey:@"dataSource"];
1120   [_archiver encodeObject:
1121                [NSNumber numberWithUnsignedInt:[self numberOfObjectsPerBatch]]
1122              forKey:@"numberOfObjectsPerBatch"];
1123   [_archiver encodeBool:[self fetchesOnLoad]
1124              forKey:@"fetchesOnLoad"];
1125   [_archiver encodeBool:[self selectsFirstObjectAfterFetch]
1126              forKey:@"selectFirstAfterFetch"];
1127 }
1128
1129 - (void)awakeFromKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
1130   if ([self fetchesOnLoad])
1131     [self fetch];
1132 }
1133
1134 /* EOEditorsImpl */
1135
1136 - (void)editingContextWillSaveChanges:(id)_ec {
1137 }
1138 - (BOOL)editorHasChangesForEditingContext:(id)_ec {
1139   return NO;
1140 }
1141
1142 /* EOMessageHandlersImpl */
1143
1144 - (void)editingContext:(id)_ec
1145   presentErrorMessage:(NSString *)_msg
1146 {
1147 }
1148
1149 - (BOOL)editingContext:(id)_ec
1150   shouldContinueFetchingWithCurrentObjectCount:(unsigned)_oc
1151   originalLimit:(unsigned)_olimit
1152   objectStore:(id)_store
1153 {
1154   return NO;
1155 }
1156
1157 /* description */
1158
1159 - (NSString *)description {
1160   return [NSString stringWithFormat:@"<0x%08X %@: ds=%@>",
1161                      self, NSStringFromClass([self class]),
1162                      [self dataSource]];
1163 }
1164
1165 @end /* WODisplayGroup */