]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WODisplayGroup.m
changed #includes into #imports - does compile against MulleEOF out of the box now
[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) ? [NSNumber numberWithUnsignedInt:idx] : nil;
382
383   if (idxNumber != nil)
384     [self setSelectionIndexes:[NSArray arrayWithObjects:&idxNumber count:1]];
385   else
386     [self setSelectionIndexes:nil];
387 }
388 - (id)selectedObject {
389   unsigned int i, sCount;
390   
391   if ((sCount = [self->selectionIndexes count]) == 0)
392     return nil;
393   
394   i = [[self->selectionIndexes objectAtIndex:0] unsignedIntValue];
395   if (i >= [self->objects count])
396     return nil;
397   
398   // TODO: need to ensure selection is in displayedObjects?
399   return [self->objects objectAtIndex:i];
400 }
401
402 - (void)setSelectedObjects:(NSArray *)_objs {
403   [self selectObjectsIdenticalTo:_objs];
404   //  [self warnWithFormat:@"%s not implemented.", __PRETTY_FUNCTION__];
405 }
406 - (NSArray *)selectedObjects {
407   NSMutableArray *result;
408   unsigned int i, sCount, oCount;
409
410   sCount = [self->selectionIndexes count];
411   oCount = [self->objects count];
412   result = [NSMutableArray arrayWithCapacity:sCount];
413   
414   for (i = 0; i < sCount; i++) {
415     unsigned int idx;
416
417     idx = [[self->selectionIndexes objectAtIndex:i] unsignedIntValue];
418     if (idx < oCount)
419       [result addObject:[self->objects objectAtIndex:idx]];
420   }
421   return result;
422 }
423
424 - (BOOL)selectObject:(id)_obj {
425   /* returns YES if displayedObjects contains _obj otherwise NO */
426   NSNumber *idxNumber;
427   unsigned idx;
428   
429   if (![self->displayObjects containsObject:_obj])
430     return NO;
431   
432   idx = [self->objects indexOfObject:_obj];
433   idxNumber = (idx != NSNotFound) ? [NSNumber numberWithUnsignedInt:idx] : nil;
434
435   // TODO: should we just exchange the first item and/or call
436   //       -setSelectedObject: ?
437   
438 #if 0 /* this was wrong? */
439   if ([self->selectionIndexes containsObject:idxNumber])
440     /* already selected => could be many => move to top? */
441     return YES;
442   
443   tmp = [NSMutableArray arrayWithObjects:self->selectionIndexes];
444   [tmp addObject:idxNumber];
445   [self setSelectionIndexes:tmp];
446 #else
447   if (idxNumber != nil)
448     [self setSelectionIndexes:[NSArray arrayWithObjects:&idxNumber count:1]];
449   else
450     [self setSelectionIndexes:nil];
451 #endif
452   return YES;
453 }
454
455
456 /* returns YES if at least one obj matches otherwise NO */
457 - (BOOL)selectObjectsIdenticalTo:(NSArray *)_objs {
458   NSMutableArray *newIndexes;
459   unsigned       i, cnt;
460   BOOL           ok = NO;
461
462   cnt = [_objs count];
463   
464   if (cnt == 0)
465     return NO;
466
467   newIndexes = [NSMutableArray arrayWithCapacity:cnt];
468   
469   for (i=0; i<cnt; i++) {
470     NSNumber *idxNumber;
471     unsigned idx;
472     id       obj;
473
474     obj = [_objs objectAtIndex:i];
475     if (![self->objects containsObject:obj])
476       continue;
477
478     ok = YES;
479     idx = [self->objects indexOfObject:obj];
480     idxNumber = [NSNumber numberWithUnsignedInt:idx];
481     
482     if ([self->selectionIndexes containsObject:idxNumber])
483       continue;
484
485     [newIndexes addObject:idxNumber];
486   }
487   if (!ok)
488     return NO;
489
490   [newIndexes addObjectsFromArray:self->selectionIndexes];
491   [self setSelectionIndexes:newIndexes];
492   
493   return YES;
494 }
495
496 - (BOOL)selectObjectsIdenticalTo:(NSArray *)_objs
497   selectFirstOnNoMatch:(BOOL)_flag
498 {
499   if ([self selectObjectsIdenticalTo:_objs])
500     return YES;
501   
502   if (_flag)
503     return [self selectObject:[self->displayObjects objectAtIndex:0]];
504   else
505     return NO;
506 }
507
508 /* objects */
509
510 - (void)setObjectArray:(NSArray *)_objects {
511   ASSIGN(self->objects, _objects);
512   
513   /* should try to restore selection */
514   [self clearSelection];
515   if ([_objects isNotEmpty] && [self selectsFirstObjectAfterFetch])
516     [self setSelectionIndexes:uint0Array];
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 isNotEmpty])
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 isNotEmpty])
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   NSDictionary *bindings;
813
814   if ((ds = [self dataSource]) == nil)
815     [self warnWithFormat:@"no datasource set: %@", NSStringFromSelector(_cmd)];
816
817   /* build qualifier */
818   
819   if ((q = [self qualifierFromQueryValues]) != nil)
820     [self setQualifier:q];
821   
822   /* set qualifier in datasource */
823   
824   if ([ds respondsToSelector:@selector(setAuxiliaryQualifier:)]) {
825     [ds setAuxiliaryQualifier:[self qualifier]];
826     //[self logWithFormat:@"set aux qualifier in %@: %@", ds,[self qualifier]];
827   }
828   else if ([ds respondsToSelector:@selector(setQualifier:)])
829     [ds setQualifier:[self qualifier]];
830   else {
831     /* could not qualify ds */
832     [self warnWithFormat:@"could not qualify datasource: %@", ds];
833   }
834   
835   /* set bindings in datasource */
836
837   if ([(bindings = [self queryBindings]) isNotEmpty]) {
838     if ([ds respondsToSelector:@selector(setQualifierBindings:)])
839       [ds setQualifierBindings:bindings];
840     else {
841       [self warnWithFormat:@"could not set bindings in datasource %@: %@", 
842               ds, bindings];
843     }
844   }
845   
846   /* perform fetch */
847   
848   /* action method, returns 'nil' to stay on page */
849   [self fetch];
850   
851   if ([self inQueryMode])
852     [self setInQueryMode:NO];
853 }
854
855 - (id)qualifyDataSourceAndReturnDisplayCount {
856   /* 
857      This is a 'hack' created because we can't bind (and therefore 'call')
858      'void' methods in .wod files.
859   */
860   [self qualifyDataSource];
861   return [NSNumber numberWithUnsignedInt:[[self displayedObjects] count]];
862 }
863
864 /* object creation */
865
866 - (id)insert {
867   unsigned idx;
868
869   idx = [self->selectionIndexes isNotEmpty]
870     ? ([[self->selectionIndexes objectAtIndex:0] unsignedIntValue] + 1)
871     : [self->objects count];
872   
873   return [self insertObjectAtIndex:idx]; /* returns 'nil' */
874 }
875
876 - (id)insertObjectAtIndex:(unsigned)_idx {
877   id newObject;
878   
879   if ((newObject = [[self dataSource] createObject]) == nil) {
880     [self errorWithFormat:@"Failed to create new object in datasource: %@",
881             [self dataSource]];
882     
883     if ([self->delegate respondsToSelector:
884                @selector(displayGroup:createObjectFailedForDataSource:)]) {
885       [self->delegate displayGroup:self 
886                       createObjectFailedForDataSource:[self dataSource]];
887     }
888     return nil /* refresh page */;
889   }
890
891   /* apply default values */
892   
893   [newObject takeValuesFromDictionary:[self insertedObjectDefaultValues]];
894   
895   /* insert */
896
897   [self insertObject:newObject atIndex:_idx];
898   
899   return nil /* refresh page */;
900 }
901
902 - (void)insertObject:(id)_o atIndex:(unsigned)_idx {
903   NSMutableArray *ma;
904   
905   /* ask delegate whether we should insert */
906   if ([self->delegate respondsToSelector:
907              @selector(displayGroup:shouldInsertObject:atIndex:)]) {
908     if (![self->delegate displayGroup:self shouldInsertObject:_o atIndex:_idx])
909       return;
910   }
911   
912   /* insert in datasource */
913   
914   [[self dataSource] insertObject:_o];
915   
916   /* update object-array (ignores qualifier for new objects!) */
917   
918   ma = [self->objects mutableCopy];
919   if (_idx <= [ma count])
920     [ma insertObject:_o atIndex:_idx];
921   else
922     [ma addObject:_o];
923   
924   [self setObjectArray:ma];
925   [ma release]; ma = nil;
926   [self updateDisplayedObjects];
927
928   /* select object */
929   
930   [self selectObject:_o]; // TODO: or use setSelectedObject:?
931   
932   /* let delegate know */
933   if ([self->delegate respondsToSelector:
934              @selector(displayGroup:didInsertObject:)])
935     [self->delegate displayGroup:self didInsertObject:_o];
936 }
937
938
939 /* object deletion */
940
941 - (id)delete {
942   [self deleteSelection];
943   return nil;
944 }
945
946 - (BOOL)deleteSelection {
947   NSArray  *objsToDelete;
948   unsigned i, count;
949   
950   objsToDelete = [[[self selectedObjects] shallowCopy] autorelease];
951
952   for (i = 0, count = [objsToDelete count]; i < count; i++) {
953     unsigned idx;
954     
955     idx = [self->objects indexOfObject:[objsToDelete objectAtIndex:i]];
956     if (idx == NSNotFound) {
957       [self errorWithFormat:@"Did not find object in selection: %@",
958               objsToDelete];
959       return NO;
960     }
961     
962     if (![self deleteObjectAtIndex:idx])
963       return NO;
964   }
965   return YES;
966 }
967
968 - (BOOL)deleteObjectAtIndex:(unsigned)_idx {
969   NSMutableArray *ma;
970   id   object;
971   BOOL ok;
972
973   /* find object */
974   
975   object = (_idx < [self->objects count])
976     ? [[[self->objects objectAtIndex:_idx] retain] autorelease]
977     : nil;
978   // TODO: check for nil?
979   
980   /* ask delegate */
981   
982   if ([self->delegate respondsToSelector:
983              @selector(displayGroup:shouldDeleteObject:)]) {
984     if (![self->delegate displayGroup:self shouldDeleteObject:object])
985       return NO;
986   }
987   
988   /* delete in datasource */
989   
990   ok = YES;
991   NS_DURING
992     [[self dataSource] deleteObject:object];
993   NS_HANDLER
994     *(&ok) = NO;
995   NS_ENDHANDLER;
996
997   if (!ok)
998     return NO;
999   
1000   /* update array */
1001   
1002   ma = [self->objects mutableCopy];
1003   [ma removeObject:object];
1004   [self setObjectArray:ma];
1005   [ma release]; ma = nil;
1006   [self updateDisplayedObjects];
1007   
1008   /* notify delegate */
1009
1010   if ([self->delegate respondsToSelector:
1011              @selector(displayGroup:didDeleteObject:)])
1012     [self->delegate displayGroup:self didDeleteObject:object];
1013   return YES;
1014 }
1015
1016
1017 /* master / detail */
1018
1019 - (BOOL)hasDetailDataSource {
1020   return [[self dataSource] isKindOfClass:[EODetailDataSource class]];
1021 }
1022
1023 - (void)setDetailKey:(NSString *)_key {
1024   // TODO: fix me, probably we want to store the key for later
1025 #if 0
1026   EODataSource *ds;
1027   
1028   if ([(ds = [self dataSource]) respondsToSelector:_cmd])
1029     [(EODetailDataSource *)ds setDetailKey:_key];
1030 #endif
1031 }
1032 - (NSString *)detailKey {
1033   EODataSource *ds;
1034   
1035   return ([(ds = [self dataSource]) respondsToSelector:_cmd])
1036     ? [(EODetailDataSource *)ds detailKey] : nil;
1037 }
1038
1039 - (void)setMasterObject:(id)_object {
1040   [[self dataSource] qualifyWithRelationshipKey:[self detailKey]
1041                      ofObject:_object];
1042   
1043   if ([self fetchesOnLoad])
1044     [self fetch];
1045 }
1046 - (id)masterObject {
1047   EODataSource *ds;
1048   
1049   return ([(ds = [self dataSource]) respondsToSelector:_cmd])
1050     ? [(EODetailDataSource *)ds masterObject] : nil;
1051 }
1052
1053
1054 /* KVC */
1055
1056 - (void)takeValue:(id)_value forKeyPath:(NSString *)_keyPath {
1057   if([_keyPath hasPrefix:@"queryMatch."]) {
1058     [[self queryMatch] takeValue:_value 
1059                        forKey:[_keyPath substringFromIndex:11]];
1060   }
1061   else if([_keyPath hasPrefix:@"queryMax."])
1062     [[self queryMax] takeValue:_value forKey:[_keyPath substringFromIndex:9]];
1063   else if([_keyPath hasPrefix:@"queryMin."])
1064     [[self queryMin] takeValue:_value forKey:[_keyPath substringFromIndex:9]];
1065   else if([_keyPath hasPrefix:@"queryOperator."]) {
1066     [[self queryOperator] takeValue:_value 
1067                           forKey:[_keyPath substringFromIndex:14]];
1068   }
1069   else
1070     [super takeValue:_value forKeyPath:_keyPath];
1071 }
1072 - (id)valueForKeyPath:(NSString *)_keyPath {
1073   if ([_keyPath hasPrefix:@"queryMatch."])
1074     return [[self queryMatch] valueForKey:[_keyPath substringFromIndex:11]];
1075   if ([_keyPath hasPrefix:@"queryMax."])
1076     return [[self queryMax] valueForKey:[_keyPath substringFromIndex:9]];
1077   if ([_keyPath hasPrefix:@"queryMin."])
1078     return [[self queryMin] valueForKey:[_keyPath substringFromIndex:9]];
1079   if ([_keyPath hasPrefix:@"queryOperator."])
1080     return [[self queryOperator] valueForKey:[_keyPath substringFromIndex:14]];
1081
1082   return [super valueForKeyPath:_keyPath];
1083 }
1084
1085 /* NSCoding */
1086
1087 - (id)initWithCoder:(NSCoder *)_coder {
1088   self->dataSource                 = [[_coder decodeObject] retain];
1089   self->delegate                   = [_coder decodeObject];
1090   self->sortOrderings              = [[_coder decodeObject] copy];
1091   self->insertedObjectDefaults     = [[_coder decodeObject] copy];
1092   self->qualifier                  = [[_coder decodeObject] copy];
1093   self->defaultStringMatchFormat   = [[_coder decodeObject] copy];
1094   self->defaultStringMatchOperator = [[_coder decodeObject] copy];
1095   self->_queryBindings             = [[_coder decodeObject] copy];
1096   self->_queryMatch                = [[_coder decodeObject] copy];
1097   self->_queryMin                  = [[_coder decodeObject] copy];
1098   self->_queryMax                  = [[_coder decodeObject] copy];
1099   self->_queryOperator             = [[_coder decodeObject] copy];
1100   
1101   return self;
1102 }
1103
1104 - (void)encodeWithCoder:(NSCoder *)_coder {
1105   [_coder encodeObject:self->dataSource];
1106   [_coder encodeObject:self->delegate];
1107   [_coder encodeObject:self->sortOrderings];
1108   [_coder encodeObject:self->insertedObjectDefaults];
1109   [_coder encodeObject:self->qualifier];
1110   [_coder encodeObject:self->defaultStringMatchFormat];
1111   [_coder encodeObject:self->defaultStringMatchOperator];
1112   [_coder encodeObject:self->_queryBindings];
1113   [_coder encodeObject:self->_queryMatch];
1114   [_coder encodeObject:self->_queryMin];
1115   [_coder encodeObject:self->_queryMax];
1116   [_coder encodeObject:self->_queryOperator];
1117   
1118   [self notImplemented:_cmd];
1119 }
1120
1121 /* KVCArchiving */
1122
1123 - (id)initWithKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
1124   if ((self = [self init]) != nil) {
1125     id tmp;
1126     
1127     if ((tmp = [_unarchiver decodeObjectForKey:@"formatForLikeQualifier"]))
1128       [self setDefaultStringMatchFormat:tmp];
1129     
1130     if ((tmp = [_unarchiver decodeObjectForKey:@"dataSource"]))
1131       [self setDataSource:tmp];
1132
1133     if ((tmp = [_unarchiver decodeObjectForKey:@"numberOfObjectsPerBatch"]))
1134       [self setNumberOfObjectsPerBatch:[tmp intValue]];
1135     
1136     [self setFetchesOnLoad:[_unarchiver decodeBoolForKey:@"fetchesOnLoad"]];
1137     [self setSelectsFirstObjectAfterFetch:
1138           [_unarchiver decodeBoolForKey:@"selectsFirstObjectAfterFetch"]];
1139   }
1140   return self;
1141 }
1142
1143 - (void)encodeWithKeyValueArchiver:(EOKeyValueArchiver *)_archiver {
1144   [_archiver encodeObject:[self defaultStringMatchFormat]
1145              forKey:@"formatForLikeQualifier"];
1146   [_archiver encodeObject:[self dataSource]
1147              forKey:@"dataSource"];
1148   [_archiver encodeObject:
1149                [NSNumber numberWithUnsignedInt:[self numberOfObjectsPerBatch]]
1150              forKey:@"numberOfObjectsPerBatch"];
1151   [_archiver encodeBool:[self fetchesOnLoad]
1152              forKey:@"fetchesOnLoad"];
1153   [_archiver encodeBool:[self selectsFirstObjectAfterFetch]
1154              forKey:@"selectFirstAfterFetch"];
1155 }
1156
1157 - (void)awakeFromKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
1158   if ([self fetchesOnLoad])
1159     [self fetch];
1160 }
1161
1162 /* EOEditorsImpl */
1163
1164 - (void)editingContextWillSaveChanges:(id)_ec {
1165 }
1166 - (BOOL)editorHasChangesForEditingContext:(id)_ec {
1167   return NO;
1168 }
1169
1170 /* EOMessageHandlersImpl */
1171
1172 - (void)editingContext:(id)_ec
1173   presentErrorMessage:(NSString *)_msg
1174 {
1175 }
1176
1177 - (BOOL)editingContext:(id)_ec
1178   shouldContinueFetchingWithCurrentObjectCount:(unsigned)_oc
1179   originalLimit:(unsigned)_olimit
1180   objectStore:(id)_store
1181 {
1182   return NO;
1183 }
1184
1185 /* description */
1186
1187 - (NSString *)description {
1188   return [NSString stringWithFormat:@"<0x%08X %@: ds=%@>",
1189                      self, NSStringFromClass([self class]),
1190                      [self dataSource]];
1191 }
1192
1193 @end /* WODisplayGroup */