]> err.no Git - sope/blob - sope-appserver/WEExtensions/WEMonthOverview.m
fixed gcc 4.0 warnings
[sope] / sope-appserver / WEExtensions / WEMonthOverview.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/WODynamicElement.h>
23
24 @class NSMutableArray, NSCalendarDate;
25
26 #define MatrixSize 42
27
28 @interface WEMonthOverview : WODynamicElement
29 {
30   WOAssociation *list;       // list of appointments
31   WOAssociation *item;       // current item in list
32   WOAssociation *index;      // index of current element
33   WOAssociation *identifier; // unique identifier for current item
34
35   WOAssociation *currentDay;  // current day, e.g. 31.Aug, 1.Sep, 2.Sep, ...
36   
37   WOAssociation *year;        // year
38   WOAssociation *month;       // month
39   WOAssociation *timeZone;    // timeZone
40   
41   WOAssociation *firstDay;    // 0 - Sunday .. 6 - Saturday (default:1)
42   WOAssociation *tableTags;   // make table tags
43
44   WOAssociation *startDateKey;
45   WOAssociation *endDateKey;
46
47   WOAssociation *labelStyle;  // style sheet classes
48   WOAssociation *contentStyle;
49
50   WOAssociation *labelColor;
51   WOAssociation *contentColor;
52
53 @private
54   NSMutableArray *matrix[MatrixSize];
55   
56   struct {
57     int firstDisplayedDay; // first day to be displayed Sun 0 .. Sat 6
58     int weeks;             // number of weeks to display
59     NSCalendarDate *start; // reference date in matrix
60   } matrixInfo;
61   
62   WOElement     *template;
63   // extra attributes forwarded to table data
64 }
65
66 @end /* WEMonthOverview */
67
68 @interface WEMonthLabel : WODynamicElement
69 {
70   WOAssociation *orientation;
71   // left/top | top | right/top | right | right/bottom | bottom | left/bottom
72   // left | header
73   WOAssociation *dayOfWeek;
74   // set if orientation is top or bottom
75   WOAssociation *weekOfYear;
76   // set if orientation is left or right
77   WOAssociation *colspan;
78   // set if orientation is header
79   WOElement     *template;
80 }
81 @end /* WEMonthLabel */
82
83 @interface WEMonthTitle : WODynamicElement
84 {
85   WOElement *template;
86 }
87 @end /* WEMonthTitle */
88
89
90 #include "WEContextConditional.h"
91 #include <math.h> /* needed for floor() */
92 #include "common.h"
93
94 static NSString *WEMonthOverview_InfoMode    = @"WEMonthOverview_InfoMode";
95 static NSString *WEMonthOverview_ContentMode = @"WEMonthOverview_ContentMode";
96
97 #define SecondsPerDay (24 * 60 * 60)
98
99 @interface WOContext(WEMonthOverview)
100
101 - (void)setupMonthOverviewContextWithValue:(id)_value forKey:(NSString *)_key;
102 - (void)setupMonthOverviewContextForQueryMode;
103 - (void)setupMonthOverviewContextWithOrientation:(NSString *)_orient;
104
105 - (void)tearDownMonthOverviewContext;
106 - (NSDictionary *)monthOverviewContext;
107 - (NSMutableArray *)monthOverviewQueryObjects;
108
109 - (void)enableMonthOverviewInfoMode;
110 - (void)disableMonthOverviewInfoMode;
111 - (void)enableMonthOverviewContentMode;
112 - (void)disableMonthOverviewContentMode;
113
114 @end
115
116 @implementation WOContext(WEMonthOverview)
117
118 - (void)setupMonthOverviewContextWithValue:(id)_value forKey:(NSString *)_key {
119   NSDictionary *d;
120   
121   d = [[NSDictionary alloc] initWithObjects:&_value forKeys:&_key count:1];
122   [self setObject:d forKey:@"WEMonthOverview"];
123   [d release];
124 }
125 - (void)setupMonthOverviewContextForQueryMode {
126   NSDictionary *d;
127
128   d = [[NSDictionary alloc] initWithObjectsAndKeys:
129                               [NSMutableArray arrayWithCapacity:4],
130                               @"query",nil];
131   [self setObject:d forKey:@"WEMonthOverview"];
132   [d release];
133 }
134 - (void)setupMonthOverviewContextWithOrientation:(NSString *)_orient {
135   NSDictionary *d;
136   
137   d = [[NSDictionary alloc] initWithObjectsAndKeys:@"--", _orient, nil];
138   [self setObject:d forKey:@"WEMonthOverview"];
139   [d release];
140 }
141
142 - (void)tearDownMonthOverviewContext {
143   [self removeObjectForKey:@"WEMonthOverview"];
144 }
145
146 - (NSDictionary *)monthOverviewContext {
147   return [self objectForKey:@"WEMonthOverview"];
148 }
149 - (NSMutableArray *)monthOverviewQueryObjects {
150   return [[self monthOverviewContext] valueForKey:@"query"];
151 }
152
153 - (void)enableMonthOverviewInfoMode {
154   [self setObject:@"YES" forKey:WEMonthOverview_InfoMode];
155 }
156 - (void)disableMonthOverviewInfoMode {
157   [self removeObjectForKey:WEMonthOverview_InfoMode];
158 }
159
160 - (void)enableMonthOverviewContentMode {
161   [self setObject:@"YES" forKey:WEMonthOverview_ContentMode];
162 }
163 - (void)disableMonthOverviewContentMode {
164   [self removeObjectForKey:WEMonthOverview_ContentMode];
165 }
166
167 @end /* WOContext(WEMonthOverview) */
168
169 @implementation WEMonthOverview
170
171 static Class StrClass = nil;
172
173 + (void)initialize {
174   if (StrClass == Nil) StrClass = [NSString class];
175 }
176
177 static NSString *retStrForInt(int i) {
178   switch(i) {
179   case 0:  return @"0";
180   case 1:  return @"1";
181   case 2:  return @"2";
182   case 3:  return @"3";
183   case 4:  return @"4";
184   case 5:  return @"5";
185   case 6:  return @"6";
186   case 7:  return @"7";
187   case 8:  return @"8";
188   case 9:  return @"9";
189   case 10: return @"10";
190     // TODO: find useful count!
191   default: {
192     char buf[32];
193     sprintf(buf, "%i", i);
194     return [[StrClass alloc] initWithCString:buf];
195   }
196   }
197 }
198
199 - (id)initWithName:(NSString *)_name
200   associations:(NSDictionary*)_config
201   template:(WOElement *)_t
202 {
203   if ((self = [super initWithName:_name associations:_config template:_t])) {
204     self->list         = WOExtGetProperty(_config, @"list");
205     self->item         = WOExtGetProperty(_config, @"item");
206     self->index        = WOExtGetProperty(_config, @"index");
207     self->identifier   = WOExtGetProperty(_config, @"identifier");
208
209     self->currentDay   = WOExtGetProperty(_config, @"currentDay");
210     
211     self->year         = WOExtGetProperty(_config, @"year");
212     self->month        = WOExtGetProperty(_config, @"month");
213     self->timeZone     = WOExtGetProperty(_config, @"timeZone");
214     self->firstDay     = WOExtGetProperty(_config, @"firstDay");
215     self->tableTags    = WOExtGetProperty(_config, @"tableTags");
216     
217     self->startDateKey = WOExtGetProperty(_config, @"startDateKey");
218     self->endDateKey   = WOExtGetProperty(_config, @"endDateKey");
219
220     self->labelStyle   = WOExtGetProperty(_config, @"labelStyle");
221     self->contentStyle = WOExtGetProperty(_config, @"contentStyle");
222
223     self->labelColor   = WOExtGetProperty(_config, @"labelColor");
224     self->contentColor = WOExtGetProperty(_config, @"contentColor");
225
226     if (self->startDateKey == nil) {
227       self->startDateKey = 
228         [[WOAssociation associationWithValue:@"startDate"] retain];
229     }
230     if (self->endDateKey == nil) {
231       self->endDateKey = 
232         [[WOAssociation associationWithValue:@"endDate"] retain];
233     }     
234     self->template = [_t retain];
235   }
236   return self;
237 }
238
239 - (void)resetMatrix {
240   int i;
241   
242   for (i=0; i < MatrixSize; i++) {
243     [self->matrix[i] release];
244     self->matrix[i] = nil;
245   }
246   [self->matrixInfo.start release]; self->matrixInfo.start = nil;
247 }
248
249 - (void)dealloc {
250   [self->list       release];
251   [self->item       release];
252   [self->index      release];
253   [self->identifier release];
254   [self->currentDay release];
255   [self->year       release];
256   [self->month      release];
257   [self->timeZone   release];
258   [self->firstDay   release];
259   [self->tableTags  release];
260   
261   [self->startDateKey release];
262   [self->endDateKey   release];
263   [self->labelStyle   release];
264   [self->contentStyle release];
265   [self->labelColor   release];
266   [self->contentColor release];
267
268   [self resetMatrix];
269   
270   [self->template release];
271
272   [super dealloc];
273 }
274
275 /* OWResponder */
276
277 static inline void
278 _applyIdentifier(WEMonthOverview *self, WOComponent *comp, NSString *_idx)
279 {
280   NSArray  *array;
281   unsigned count;
282   unsigned cnt;
283   
284   array = [self->list valueInComponent:comp];
285   count = [array count];
286
287   if (count <= 0)
288     return;
289     
290   /* find subelement for unique id */
291     
292   for (cnt = 0; cnt < count; cnt++) {
293     NSString *ident;
294       
295     if (self->index)
296       [self->index setUnsignedIntValue:cnt inComponent:comp];
297
298     if (self->item)
299       [self->item setValue:[array objectAtIndex:cnt] inComponent:comp];
300     
301     ident = [self->identifier stringValueInComponent:comp];
302     
303     if ([ident isEqualToString:_idx]) {
304       /* found subelement with unique id */
305       return;
306     }
307   }
308     
309   [comp logWithFormat:
310           @"WEMonthOverview: array did change, "
311           @"unique-id isn't contained."];
312   [self->item  setValue:nil          inComponent:comp];
313   [self->index setUnsignedIntValue:0 inComponent:comp];
314 }
315
316 static inline void
317 _applyIndex(WEMonthOverview *self, WOComponent *comp, unsigned _idx)
318 {
319   NSArray *array;
320   unsigned count;
321
322   array = [self->list valueInComponent:comp];
323   
324   if (self->index)
325     [self->index setUnsignedIntValue:_idx inComponent:comp];
326
327   if (self->item == nil)
328     return;
329   
330   count = [array count];
331     
332   if (_idx < count) {
333     [self->item setValue:[array objectAtIndex:_idx] inComponent:comp];
334     return;
335   }
336
337   [comp logWithFormat:
338           @"WEMonthOverview: array did change, index is invalid."];
339   [self->item  setValue:nil          inComponent:comp];
340   [self->index setUnsignedIntValue:0 inComponent:comp];
341 }
342
343
344 static inline void
345 _generateCell(WEMonthOverview *self, WOResponse *response,
346               WOContext *ctx, NSString *key, id value,
347               NSCalendarDate *dateId)
348 {
349   [ctx setupMonthOverviewContextWithValue:value forKey:key];
350   
351   [ctx appendElementIDComponent:key];
352
353   if (dateId) {
354     NSString *s;
355     
356     s = retStrForInt([dateId timeIntervalSince1970]);
357     [ctx appendElementIDComponent:s];
358     [s release];
359   }
360   
361   [self->template appendToResponse:response inContext:ctx];
362   
363   if (dateId) [ctx deleteLastElementIDComponent];
364   [ctx deleteLastElementIDComponent];
365   [ctx tearDownMonthOverviewContext];
366 }
367
368 static inline void
369 _takeValuesInCell(WEMonthOverview *self, WORequest *request,
370                   WOContext *ctx, NSString *key, id value)
371 {
372   [ctx setupMonthOverviewContextWithValue:value forKey:key];
373   
374   [ctx appendElementIDComponent:key];
375   [self->template takeValuesFromRequest:request inContext:ctx];
376   [ctx deleteLastElementIDComponent];
377   // TODO: no teardown of context?
378 }
379
380 - (void)_calcMatrixInContext:(WOContext *)_ctx {
381   WOComponent    *comp;
382   NSArray        *array;
383   NSString       *startKey;
384   NSString       *endKey;
385   int            m, y; // month, year
386   int            i, cnt;
387
388   [self resetMatrix];
389   
390   comp       = [_ctx component];
391   array      = [self->list valueInComponent:comp];
392   startKey   = [self->startDateKey stringValueInComponent:comp];
393   endKey     = [self->endDateKey   stringValueInComponent:comp];
394
395   y = (self->year == nil)
396     ? [[NSCalendarDate calendarDate] yearOfCommonEra]
397     : [self->year intValueInComponent:comp];
398   
399   m = (self->month == nil)
400     ? [[NSCalendarDate calendarDate] monthOfYear]
401     : [self->month intValueInComponent:comp];
402   
403
404   {
405     NSCalendarDate *monthStart = nil;
406     NSCalendarDate *d  = nil;
407     NSTimeZone     *tz = nil;
408     int            firstDisplayedDay, firstIdx;
409     int            i  = 27;
410
411     tz = [self->timeZone valueInComponent:comp];
412     
413     monthStart = [NSCalendarDate dateWithYear:y month:m day:1 hour:0 minute:0
414                                        second:0 timeZone:tz];
415     
416     d = [monthStart dateByAddingYears:0 months:0 days:i];
417
418     while ([d monthOfYear] == m) {
419       i++;
420       d = [monthStart dateByAddingYears:0 months:0 days:i];
421     }
422     
423     firstDisplayedDay = (self->firstDay) 
424       ? ([self->firstDay intValueInComponent:comp] % 7)
425       : 1; // Monday
426     
427     firstIdx = (([monthStart dayOfWeek]-firstDisplayedDay)+7) % 7;
428     
429     self->matrixInfo.weeks = ceil((float)(firstIdx + i) / 7);
430     self->matrixInfo.firstDisplayedDay = firstDisplayedDay;
431
432     // keep the timezone in the date
433     self->matrixInfo.start =
434       [[monthStart dateByAddingYears:0 months:0 days:-firstIdx] retain];
435   }
436   
437   for (i = 0, cnt = [array count]; i < cnt; i++) {
438     id             app;
439     NSCalendarDate *sd, *ed;
440     NSTimeInterval diff;
441     int            idx, idx2;
442
443     app = [array objectAtIndex:i];
444     sd  = [app valueForKey:startKey]; // startDate
445     ed  = [app valueForKey:endKey];   // endDate
446
447     if (sd == nil && ed == nil) continue;
448
449     diff = [sd timeIntervalSinceDate:self->matrixInfo.start];
450     
451     idx = floor(diff / SecondsPerDay);
452
453     if (0 <= idx  && idx < MatrixSize) {
454       if (self->matrix[idx] == nil)
455         self->matrix[idx] = [[NSMutableArray alloc] initWithCapacity:4];
456
457       [self->matrix[idx] addObject:[NSNumber numberWithInt:i]];
458     }
459     idx = (idx < 0) ? 0 : idx + 1;
460
461     diff = [ed timeIntervalSinceDate:self->matrixInfo.start];
462     idx2 = floor(diff / SecondsPerDay);
463     idx2 = (idx2 > MatrixSize) ? MatrixSize : idx2;
464     
465     while (idx < idx2) {
466       if (self->matrix[idx] == nil)
467         self->matrix[idx] = [[NSMutableArray alloc] initWithCapacity:4];
468
469       [self->matrix[idx] addObject:[NSNumber numberWithInt:i]];
470       idx++;
471     }
472   }
473   
474   for (i = 0; i < MatrixSize; i++) {
475     if (self->matrix[i] == nil)
476       self->matrix[i] = [[NSArray alloc] init];
477   }
478 }
479
480 - (void)appendContentToResponse:(WOResponse *)_response
481   inContext:(WOContext *)_ctx
482   index:(int)_idx
483 {
484   WOComponent  *comp;
485   NSArray      *array;
486   id           app;
487   int          i, cnt, idx, count;
488
489   comp  = [_ctx component];
490   array = [self->list valueInComponent:comp];
491   count = [array count];
492
493   // *** append day info
494   [_ctx enableMonthOverviewInfoMode];
495   [_ctx appendElementIDComponent:@"i"];
496   [self->template appendToResponse:_response inContext:_ctx];
497   [_ctx deleteLastElementIDComponent];
498   [_ctx disableMonthOverviewInfoMode];
499   
500   // *** append day content
501   [_ctx enableMonthOverviewContentMode];
502   [_ctx appendElementIDComponent:@"c"]; // append content mode
503   for (i = 0, cnt = [self->matrix[_idx] count]; i < cnt; i++) {
504     NSString *s;
505     
506     idx = [[self->matrix[_idx] objectAtIndex:i] intValue];
507
508     if (idx >= count) {
509       NSLog(@"Warning! WEMonthOverview: index out of range");
510       continue;
511     }
512     app = [array objectAtIndex:idx];
513     
514     if ([self->item isValueSettable])
515       [self->item  setValue:app inComponent:comp];
516     if ([self->index isValueSettable])
517       [self->index setIntValue:idx inComponent:comp];
518
519     if (self->identifier == nil) {
520       s = retStrForInt(idx);
521       [_ctx appendElementIDComponent:s];
522       [s release];
523     }
524     else {
525       s = [self->identifier stringValueInComponent:comp];
526       [_ctx appendElementIDComponent:s];
527     }
528     
529     [self->template appendToResponse:_response inContext:_ctx];
530     [_ctx deleteLastElementIDComponent];
531   }
532   [_ctx deleteLastElementIDComponent]; // delete content mode
533   [_ctx disableMonthOverviewContentMode];
534 }
535
536 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
537   WOComponent    *comp;
538   NSString       *style;
539   NSString       *bgcolor;
540   BOOL           useTableTags;
541
542   BOOL           renderDefaults = NO;
543   BOOL           hasTitle       = NO;
544   BOOL           hasHeader      = NO;
545   BOOL           hasLeftTop     = NO;
546   BOOL           hasLeft        = NO;
547   BOOL           hasTop         = NO;
548   BOOL           hasRightTop    = NO;
549   BOOL           hasRight       = NO;
550   BOOL           hasLeftBottom  = NO;
551   BOOL           hasBottom      = NO;
552   BOOL           hasRightBottom = NO;
553   BOOL           hasCell        = NO;
554
555   [self _calcMatrixInContext:_ctx];
556       
557   comp = [_ctx component];
558
559   useTableTags = (self->tableTags) 
560     ? [self->tableTags boolValueInComponent:comp]
561     : YES;
562
563   { // query mode ... testing orientations
564     NSEnumerator *queryE;
565     NSString     *orient;
566     
567     // only query mode .. no value setting
568     [_ctx setupMonthOverviewContextForQueryMode];
569     
570     /* this walks over all subelements and collects 'query' info */
571     
572     [self->template appendToResponse:_response inContext:_ctx];
573     
574     /* now process the results */
575     
576     queryE = [[_ctx monthOverviewQueryObjects] objectEnumerator];
577     
578     while ((orient = [queryE nextObject])) {
579       if ((!hasHeader) && ([orient isEqualToString:@"header"]))
580         hasHeader = YES;
581       if ((!hasCell) && ([orient isEqualToString:@"cell"]))
582         hasCell = YES;
583       if ((!hasTitle) && ([orient isEqualToString:@"title"]))
584         hasTitle = YES;
585       if ((!hasLeftTop) && ([orient isEqualToString:@"left/top"]))
586         hasLeftTop = YES;
587       if ((!hasLeftBottom) && ([orient isEqualToString:@"left/bottom"]))
588         hasLeftBottom = YES;
589       if ((!hasLeft) && ([orient isEqualToString:@"left"]))
590         hasLeft = YES;
591       if ((!hasTop) && ([orient isEqualToString:@"top"]))
592         hasTop = YES;
593       if ((!hasRightTop) && ([orient isEqualToString:@"right/top"]))
594         hasRightTop = YES;
595       if ((!hasRight) && ([orient isEqualToString:@"right"]))
596         hasRight = YES;
597       if ((!hasRightBottom) && ([orient isEqualToString:@"right/bottom"]))
598         hasRightBottom = YES;
599       if ((!hasBottom) && ([orient isEqualToString:@"bottom"]))
600         hasBottom = YES;
601     }
602
603     if (!(hasLeft || hasRight || hasTop || hasBottom))
604       renderDefaults = YES;
605
606     [_ctx tearDownMonthOverviewContext];
607   }
608   
609   /* open table */
610   if (useTableTags) {
611     [_response appendContentString:@"<table"];
612     [self appendExtraAttributesToResponse:_response inContext:_ctx];
613     [_response appendContentString:@">"];
614   }
615
616   /* generating head */
617   if (hasHeader) {
618     NSString *s;
619     int width = 7;
620     
621     if ((hasLeft) || (hasLeftTop) || (hasLeftBottom))
622       width++;
623     if ((hasRight) || (hasRightTop) || (hasRightBottom))
624       width++;
625
626     [_response appendContentString:@"<tr>"];
627     
628     s = retStrForInt(width);
629     _generateCell(self, _response, _ctx, @"header", s, nil);
630     [s release];
631
632     [_response appendContentString:@"</tr>"];
633   }
634
635   // generating top
636   if ((hasTop) || (hasLeftTop) || (hasRightTop) || (renderDefaults)) {
637     [_response appendContentString:@"<tr>"];
638
639     if (hasLeftTop)
640       _generateCell(self, _response, _ctx, @"left/top", @"--", nil);
641     else if (hasLeft || hasLeftBottom || renderDefaults)
642       [_response appendContentString:@"<td>&nbsp;</td>"];
643
644     if (hasTop) {
645       int i, dow = 0;
646
647       dow = self->matrixInfo.firstDisplayedDay;
648       for (i = 0; i < 7; i++) {
649         _generateCell(self, _response, _ctx, @"top",
650                       [[NSNumber numberWithInt:dow] stringValue], nil);
651         dow = (dow == 6) ? 0 : dow+1;
652       }
653     }
654     else if (renderDefaults) {
655       NSCalendarDate *day;
656       int i;
657       
658       day = self->matrixInfo.start;
659       for (i = 0; i < 7; i++) {
660         NSString *s;
661         
662         [_response appendContentString:@"<td align=\"center\""];
663         if ((style = [self->labelStyle stringValueInComponent:comp])) {
664             [_response appendContentString:@" class=\""];
665             [_response appendContentHTMLAttributeValue:style];
666             [_response appendContentCharacter:'"'];
667         }
668         if ((bgcolor = [self->labelColor stringValueInComponent:comp])) {
669           [_response appendContentString:@" bgcolor=\""];
670           [_response appendContentString:bgcolor];
671           [_response appendContentCharacter:'"'];
672         }
673         [_response appendContentString:@"><b>"];
674         /* TODO: replace with manual string */
675         s = [day descriptionWithCalendarFormat:@"%A"];
676         [_response appendContentString:s];
677         [_response appendContentString:@"</b></td>"];
678         day = [day tomorrow];
679       }
680     }
681     else if (hasRightTop || hasLeftTop) {
682       [_response appendContentString:
683                  @"<td></td><td></td><td></td><td></td>"
684                  @"<td></td><td></td><td></td>"];
685     }
686     
687     if (hasRightTop)
688       _generateCell(self, _response, _ctx, @"right/top", @"--", nil);
689     else if (hasRightBottom || hasRight)
690       [_response appendContentString:@"<td>&nbsp;</td>"];
691
692     [_response appendContentString:@"</tr>"];
693   }
694
695   /* generating content */
696   {
697     NSCalendarDate *day;
698     NSString       *week;
699     int            i, j, maxNumberOfWeeks;
700
701     day = self->matrixInfo.start;
702     maxNumberOfWeeks = [day numberOfWeeksInYear];
703  
704     week = 
705       retStrForInt([[day dateByAddingYears:0 months:0 days:3] weekOfYear]);
706  
707     for (i = 0; i < self->matrixInfo.weeks; i++) {
708       [_response appendContentString:@"<tr>"];
709
710       if (hasLeft) {
711         _generateCell(self, _response, _ctx, @"left", week, nil);
712       }
713       else if (renderDefaults) {
714         [_response appendContentString:@"<td width=\"2%\" align=\"center\""];
715           if ((style = [self->labelStyle stringValueInComponent:comp])) {
716               [_response appendContentString:@" class=\""];
717               [_response appendContentHTMLAttributeValue:style];
718               [_response appendContentCharacter:'"'];
719           }
720           if ((bgcolor = [self->labelColor stringValueInComponent:comp])) {
721           [_response appendContentString:@" bgcolor=\""];
722           [_response appendContentString:bgcolor];
723           [_response appendContentCharacter:'"'];
724         }
725         [_response appendContentCharacter:'>'];
726         [_response appendContentString:week];
727         [_response appendContentString:@"</td>"];
728       }
729       else if (hasLeftTop || hasLeftBottom)
730         [_response appendContentString:@"<td>&nbsp;</td>"];
731
732       /* append days of week */
733       for (j = 0; j < 7; j++) {
734         NSString *s;
735         
736         if ([self->currentDay isValueSettable])
737           [self->currentDay setValue:day inComponent:comp];
738         
739         [_response appendContentString:@"<td"];
740         if ((style = [self->contentStyle stringValueInComponent:comp])) {
741             [_response appendContentString:@" class=\""];
742             [_response appendContentHTMLAttributeValue:style];
743             [_response appendContentCharacter:'"'];
744         }
745         if ((bgcolor = [self->contentColor stringValueInComponent:comp])) {
746           [_response appendContentString:@" bgcolor=\""];
747           [_response appendContentString:bgcolor];
748           [_response appendContentCharacter:'"'];
749         }
750         [_response appendContentCharacter:'>'];
751
752         
753         [_response appendContentString:
754                    @"<table border='0' height='100%' cellspacing='0'"
755                    @" cellpadding='2' width='100%'><tr>"];
756         
757         if (hasTitle)
758           _generateCell(self, _response, _ctx, @"title", @"--", day);
759         else {
760           [_response appendContentString:
761                        @"<td valign=\"top\">"
762                        @"<font size=\"4\" color=\"black\">"
763                        @"<u>"];
764           s = retStrForInt([day dayOfMonth]);
765           [_response appendContentString:s];
766           [s release];
767           [_response appendContentString:@"</u></font></td>"];
768         }
769
770
771         /*** appending content ***/
772         [_ctx appendElementIDComponent:@"cell"];
773         s = retStrForInt([day timeIntervalSince1970]);
774         [_ctx appendElementIDComponent:s];
775         [s release];
776         [_response appendContentString:@"<td valign=\"top\">"];
777         [self appendContentToResponse:_response inContext:_ctx index:(i*7+j)];
778         [_response appendContentString:@"</td>"];
779         [_ctx deleteLastElementIDComponent]; // delete date
780         [_ctx deleteLastElementIDComponent]; // delete "cell"
781
782         [_response appendContentString:@"</tr></table></td>"];
783         day  = [day tomorrow];
784       }
785
786       if (hasRight)
787         _generateCell(self, _response, _ctx, @"right", week, nil);
788       else if (hasRightTop || hasRightBottom)
789         [_response appendContentString:@"<td>&nbsp;</td>"];
790       
791       [_response appendContentString:@"</tr>"];
792       
793       {
794          int nextWeek;
795          nextWeek = ([week intValue] % maxNumberOfWeeks) + 1;
796          [week release]; week = nil;
797          week = retStrForInt(nextWeek);
798       }
799     }
800     [week release]; week = nil;
801   }
802   
803   /* generating footer */
804   if ((hasBottom) || (hasLeftBottom) || (hasRightBottom)) {
805
806     [_response appendContentString:@"<tr>"];
807
808     if (hasLeftBottom)
809       _generateCell(self, _response, _ctx, @"left/bottom", @"--", nil);
810     else if (hasLeft || hasLeftTop)
811       [_response appendContentString:@"<td>&nbsp;</td>"];
812
813     if (hasBottom) {
814       int i, dow = 0; // dayOfWeek
815
816       dow = self->matrixInfo.firstDisplayedDay;
817       
818       for (i = 0; i < 7; i++) {
819         NSString *s;
820
821         s = retStrForInt(dow);
822         _generateCell(self, _response, _ctx, @"bottom", s, nil);
823         [s release];
824         dow = (dow == 6) ? 0 : dow + 1;
825       }
826     }
827     else {
828       [_response appendContentString:
829                  @"<td></td><td></td><td></td><td></td>"
830                  @"<td></td><td></td><td></td>"];
831     }
832
833     if (hasRightBottom)
834       _generateCell(self, _response, _ctx, @"right/bottom", @"--", nil);
835     else if (hasRightTop || hasRight)
836       [_response appendContentString:@"<td>&nbsp;</td>"]; 
837
838     [_response appendContentString:@"</tr>"];
839   }
840
841   // close table
842   if (useTableTags)
843     [_response appendContentString:@"</table>"];
844
845   [self resetMatrix];
846 }
847
848
849 - (void)takeContentValues:(WORequest *)_req inContext:(WOContext *)_ctx
850   index:(int)_idx
851 {
852   WOComponent *comp;
853   NSString    *s;
854   NSArray     *array;
855   int         i, cnt, count;
856
857   comp  = [_ctx component];
858   array = [self->list valueInComponent:comp];
859   count = [array count];
860   
861   [_ctx appendElementIDComponent:@"c"]; // append content mode
862   
863   for (i = 0, cnt = [self->matrix[_idx] count]; i < cnt; i++) {
864     int idx;
865     
866     idx = [[self->matrix[_idx] objectAtIndex:i] intValue];
867
868     if (self->index != nil)
869       [self->index setUnsignedIntValue:idx inComponent:comp];
870     if (self->item != nil)
871       [self->item setValue:[array objectAtIndex:idx] inComponent:comp];
872     
873     s = (self->identifier != nil)
874       ? [[self->identifier stringValueInComponent:comp] retain]
875       : retStrForInt(idx);
876     
877     [_ctx appendElementIDComponent:s]; // append index-id
878     [s release];
879       
880     [self->template takeValuesFromRequest:_req inContext:_ctx];
881     [_ctx deleteLastElementIDComponent]; // delte index-id
882   }
883 }
884
885 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
886   WOComponent    *sComponent;
887   NSCalendarDate *day;
888   NSString       *week;
889   int            i, j;
890   unsigned int   weekOfYear;
891   char           buf[32];
892   
893   [self _calcMatrixInContext:_ctx];
894   
895   sComponent = [_ctx component];
896   
897   day = self->matrixInfo.start;
898   
899   weekOfYear = [[day dateByAddingYears:0 months:0 days:3] weekOfYear];
900   sprintf(buf, "%d", weekOfYear);
901   week = [StrClass stringWithCString:buf];
902   
903   // TODO: weird use of NSString for week?
904   for (i = 0; i < self->matrixInfo.weeks; i++) {
905     for (j = 0; j < 7; j++) {
906       NSString *eid;
907       
908       if ([self->currentDay isValueSettable])
909         [self->currentDay setValue:day inComponent:sComponent];
910       
911       sprintf(buf, "%d", (unsigned)[day timeIntervalSince1970]);
912       eid = [[StrClass alloc] initWithCString:buf];
913       [_ctx appendElementIDComponent:eid];
914       [eid release];
915       
916       _takeValuesInCell(self, _req, _ctx, @"title", @"--");
917       [self takeContentValues:_req inContext:_ctx index:(i * 7 + j)];
918       [_ctx deleteLastElementIDComponent];
919       
920       day  = [day tomorrow];
921     }
922     sprintf(buf, "%d", ([week intValue] + 1));
923     week = [StrClass stringWithCString:buf];
924   }
925
926   [self resetMatrix];
927 }
928
929 - (id)invokeContentAction:(WORequest *)_request inContext:(WOContext *)_ctx{
930   id       result = nil;
931   NSString *idxId = nil;
932
933   if ((idxId = [_ctx currentElementID]) == 0) // no content nor info mode
934     return nil;
935     
936   [_ctx consumeElementID];                // consume mode
937   [_ctx appendElementIDComponent:idxId];  // append mode ("c" or "i")
938
939   if ([idxId isEqualToString:@"i"])
940     // info mode
941     result = [self->template invokeActionForRequest:_request inContext:_ctx];
942   else if ((idxId = [_ctx currentElementID])) {
943     // content mode
944     [_ctx consumeElementID];               // consume index-id
945     [_ctx appendElementIDComponent:idxId];
946
947     if (self->identifier)
948       _applyIdentifier(self, [_ctx component], idxId);
949     else
950       _applyIndex(self, [_ctx component], [idxId intValue]);
951
952     result = [self->template invokeActionForRequest:_request inContext:_ctx];
953
954     [_ctx deleteLastElementIDComponent]; // delete index-id
955   }
956   [_ctx deleteLastElementIDComponent]; // delete mode
957     
958   return result;
959 }
960
961 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
962   WOComponent     *sComponent;
963   id              result = nil;
964   NSString        *ident;
965   NSString        *orient;
966
967   sComponent = [_ctx component];
968   
969   if ((orient = [_ctx currentElementID]) == nil) {
970     [[_ctx session]
971            logWithFormat:@"%@: MISSING ORIENTATION ID in URL !", self];
972     return nil;
973   }
974
975   [_ctx consumeElementID];
976   [_ctx appendElementIDComponent:orient];
977   
978   [_ctx setupMonthOverviewContextWithOrientation:orient];
979   
980   if ([orient isEqualToString:@"cell"] || [orient isEqualToString:@"title"]){
981     /* content or 'title' */
982     if ((ident = [_ctx currentElementID]) != nil) {
983         NSCalendarDate *day;
984         int ti;
985     
986         [_ctx consumeElementID]; // consume date-id
987         [_ctx appendElementIDComponent:ident];
988
989         ti = [ident intValue];
990       
991         day = [NSCalendarDate dateWithTimeIntervalSince1970:ti];
992         [day setTimeZone:[self->timeZone valueInComponent:sComponent]];
993
994         if ([self->currentDay isValueSettable])
995           [self->currentDay setValue:day inComponent:sComponent];
996
997         if ([orient isEqualToString:@"title"])
998           result = [self->template invokeActionForRequest:_req inContext:_ctx];
999         else
1000           result = [self invokeContentAction:_req inContext:_ctx];
1001         
1002         [_ctx deleteLastElementIDComponent]; // delete 'cell' or 'title'
1003     }
1004     else
1005       [[_ctx session]
1006              logWithFormat:@"%@: MISSING DATE ID in '%@' URL !", self, orient];
1007   }
1008   else {
1009     /* neither 'cell' nor 'title' (some label) */
1010     result = [self->template invokeActionForRequest:_req inContext:_ctx];
1011   }
1012   [_ctx deleteLastElementIDComponent]; /* delete orient */
1013
1014   // TODO: no teardown of month-overview context?
1015     
1016   return result;
1017 }
1018
1019 @end /* WEMonthOverview */
1020
1021
1022 @implementation WEMonthLabel
1023
1024 - (id)initWithName:(NSString *)_name
1025   associations:(NSDictionary*)_config
1026   template:(WOElement *)_t
1027 {
1028   if ((self = [super initWithName:_name associations:_config template:_t])) {
1029     self->orientation  = WOExtGetProperty(_config, @"orientation");
1030     self->dayOfWeek    = WOExtGetProperty(_config, @"dayOfWeek");
1031     self->weekOfYear   = WOExtGetProperty(_config, @"weekOfYear");
1032     self->colspan      = WOExtGetProperty(_config, @"colspan");
1033
1034     self->template = [_t retain];
1035   }
1036   return self;
1037 }
1038
1039 - (void)dealloc {
1040   [self->orientation release];
1041   [self->dayOfWeek   release];
1042   [self->weekOfYear  release];
1043   [self->colspan     release];
1044   
1045   [self->template release];
1046   [super dealloc];
1047 }
1048
1049 /* handle requests */
1050
1051 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1052   NSDictionary *monthViewContextDict;
1053   NSString     *orient;
1054   BOOL         isEdge;
1055   id tmp;
1056   
1057   orient = [self->orientation valueInComponent:[_ctx component]];
1058   isEdge = ([orient rangeOfString:@"/"].length > 0);
1059   
1060   monthViewContextDict  = [_ctx monthOverviewContext];
1061   if ((tmp = [monthViewContextDict objectForKey:orient]) == nil)
1062     return;
1063
1064   if (!isEdge) {
1065     [_ctx appendElementIDComponent:orient];
1066     [self->template takeValuesFromRequest:_req inContext:_ctx];
1067     [_ctx deleteLastElementIDComponent];
1068   }
1069   else
1070     [self->template takeValuesFromRequest:_req inContext:_ctx];
1071 }
1072
1073 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1074   NSDictionary *monthViewContextDict;
1075   NSString     *orient;
1076   BOOL         isEdge;
1077   id           result;
1078   id tmp;
1079   
1080   orient = [self->orientation valueInComponent:[_ctx component]];
1081   isEdge = ([orient rangeOfString:@"/"].length > 0);
1082   
1083   monthViewContextDict  = [_ctx monthOverviewContext];
1084   if ((tmp = [monthViewContextDict objectForKey:orient]) == nil)
1085     return nil;
1086
1087   if (isEdge)
1088     return [self->template invokeActionForRequest:_req inContext:_ctx];
1089
1090   tmp = [_ctx currentElementID];
1091   [_ctx consumeElementID];
1092   [_ctx appendElementIDComponent:tmp];
1093       
1094   if ([orient isEqualToString:@"top"] ||
1095       [orient isEqualToString:@"bottom"]) {
1096     [self->dayOfWeek setIntValue:[tmp intValue]
1097                          inComponent:[_ctx component]];
1098   }
1099   else if ([orient isEqualToString:@"left"] ||
1100            [orient isEqualToString:@"right"]) {
1101     [self->weekOfYear setIntValue:[tmp intValue]
1102                           inComponent:[_ctx component]];
1103   }
1104   else if ([orient isEqualToString:@"header"]) {
1105     [self->colspan setIntValue:[tmp intValue]
1106                        inComponent:[_ctx component]];
1107   }
1108       
1109   result = [self->template invokeActionForRequest:_req inContext:_ctx];
1110
1111   [_ctx deleteLastElementIDComponent];
1112   return result;
1113 }
1114
1115 /* generate response */
1116
1117 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
1118   NSDictionary   *monthViewContextDict;
1119   NSMutableArray *queryContext;
1120   id       tmp;
1121   NSString *orient;
1122   BOOL     isEdge;
1123   int      cols;
1124
1125   orient = [self->orientation stringValueInComponent:[_ctx component]];
1126   isEdge = ([orient rangeOfString:@"/"].length > 0);
1127   
1128   if (orient == nil) return;
1129   
1130   if ((queryContext = [_ctx monthOverviewQueryObjects]) != nil) {
1131     [queryContext addObject:orient];
1132     return;
1133   }
1134   
1135   monthViewContextDict = [_ctx monthOverviewContext];
1136   if ((tmp = [monthViewContextDict objectForKey:orient]) == nil)
1137     return;
1138   
1139   cols = -1;
1140   if (!isEdge) {
1141     int orientIntValue;
1142     
1143     orientIntValue = [tmp intValue];
1144     if ([orient isEqualToString:@"top"] ||
1145         [orient isEqualToString:@"bottom"]) {
1146         [self->dayOfWeek setIntValue:orientIntValue 
1147                          inComponent:[_ctx component]];
1148       }
1149       else if ([orient isEqualToString:@"left"] ||
1150                [orient isEqualToString:@"right"]) {
1151         [self->weekOfYear setIntValue:orientIntValue
1152                           inComponent:[_ctx component]];
1153       } 
1154       else if ([orient isEqualToString:@"header"]) {
1155         [self->colspan setIntValue:orientIntValue
1156                        inComponent:[_ctx component]];
1157         cols = [tmp intValue];
1158       }
1159   }
1160     
1161   [_response appendContentString:@"<td"];
1162
1163   if (cols != -1) {
1164     NSString *colStr;
1165     
1166     colStr = retStrForInt(cols);
1167     [_response appendContentString:@" colspan=\""];
1168     [_response appendContentString:colStr];
1169     [_response appendContentString:@"\""];
1170     [colStr release];
1171   }
1172     
1173   [self appendExtraAttributesToResponse:_response inContext:_ctx];
1174   [_response appendContentString:@">"];
1175       
1176   if (!isEdge)
1177     [_ctx appendElementIDComponent:[tmp stringValue]];
1178     
1179   [self->template appendToResponse:_response inContext:_ctx];
1180     
1181   if (!isEdge)
1182     [_ctx deleteLastElementIDComponent];
1183
1184   // close table data tag
1185   [_response appendContentString:@"</td>"];
1186 }
1187
1188 @end /* WEMonthLabel */
1189
1190
1191 @implementation WEMonthTitle
1192
1193 - (id)initWithName:(NSString *)_name
1194   associations:(NSDictionary*)_config
1195   template:(WOElement *)_t
1196 {
1197   if ((self = [super initWithName:_name associations:_config template:_t])) {
1198     self->template = [_t retain];
1199   }
1200   return self;
1201 }
1202
1203 - (void)dealloc {
1204   [self->template release];
1205   [super dealloc];
1206 }
1207
1208 /* handling requests */
1209
1210 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1211   NSDictionary *monthViewContextDict;
1212   id tmp;
1213   
1214   monthViewContextDict  = [_ctx monthOverviewContext];
1215   if ((tmp = [monthViewContextDict objectForKey:@"title"]) != nil)
1216     [self->template takeValuesFromRequest:_req inContext:_ctx];
1217 }
1218
1219 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1220   NSDictionary *monthViewContextDict;
1221   id tmp;
1222   
1223   monthViewContextDict  = [_ctx monthOverviewContext];
1224   if ((tmp = [monthViewContextDict objectForKey:@"title"]) != nil)
1225     return [self->template invokeActionForRequest:_req inContext:_ctx];
1226   
1227   return nil;
1228 }
1229
1230 /* generating response */
1231
1232 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
1233   NSDictionary *monthViewContextDict;
1234   id tmp;
1235   
1236   if ((tmp = [_ctx monthOverviewQueryObjects]) != nil) {
1237     [(NSMutableArray *)tmp addObject:@"title"];
1238     return;
1239   }
1240   
1241   monthViewContextDict = [_ctx monthOverviewContext];
1242   if ((tmp = [monthViewContextDict objectForKey:@"title"]) != nil) {
1243     // append table date, forwarding extra attributes
1244     [_response appendContentString:@"<td"];
1245     [self appendExtraAttributesToResponse:_response inContext:_ctx];
1246     [_response appendContentString:@">"];
1247     // append child
1248     [self->template appendToResponse:_response inContext:_ctx];
1249     // close table data tag
1250     [_response appendContentString:@"</td>"];
1251   }
1252 }
1253
1254 @end /* WEMonthTitle */
1255
1256
1257 @interface WEMonthOverviewInfoMode : WEContextConditional
1258 @end
1259
1260 @implementation WEMonthOverviewInfoMode
1261
1262 - (NSString *)_contextKey {
1263   return WEMonthOverview_InfoMode;
1264 }
1265
1266 @end /* WEMonthOverviewInfoMode */
1267
1268
1269 @interface WEMonthOverviewContentMode : WEContextConditional
1270 @end
1271
1272 @implementation WEMonthOverviewContentMode
1273
1274 - (NSString *)_contextKey {
1275   return WEMonthOverview_ContentMode;
1276 }
1277
1278 @end /* WEMonthOverviewContentMode */