]> err.no Git - sope/blob - sope-appserver/WEExtensions/WEMonthOverview.m
fixed copyrights for 2005
[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 - (NSArray *)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     unsigned 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
850                 inContext:(WOContext *)_ctx
851                     index:(int)_idx
852 {
853   WOComponent *comp;
854   NSString    *s;
855   NSArray     *array;
856   int         i, idx, cnt, count;
857
858   comp  = [_ctx component];
859   array = [self->list valueInComponent:comp];
860   count = [array count];
861   
862   [_ctx appendElementIDComponent:@"c"]; // append content mode
863
864   cnt = [self->matrix[_idx] count];
865   for (i=0; i<cnt; i++) {
866     idx = [[self->matrix[_idx] objectAtIndex:i] intValue];
867
868     if (self->index)
869       [self->index setUnsignedIntValue:idx inComponent:comp];
870     if (self->item)
871       [self->item setValue:[array objectAtIndex:idx] inComponent:comp];
872     
873     s = (self->identifier)
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   unsigned 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 @implementation WEMonthLabel
1022
1023 - (id)initWithName:(NSString *)_name
1024   associations:(NSDictionary*)_config
1025   template:(WOElement *)_t
1026 {
1027   if ((self = [super initWithName:_name associations:_config template:_t])) {
1028     self->orientation  = WOExtGetProperty(_config, @"orientation");
1029     self->dayOfWeek    = WOExtGetProperty(_config, @"dayOfWeek");
1030     self->weekOfYear   = WOExtGetProperty(_config, @"weekOfYear");
1031     self->colspan      = WOExtGetProperty(_config, @"colspan");
1032
1033     self->template = [_t retain];
1034   }
1035   return self;
1036 }
1037
1038 - (void)dealloc {
1039   [self->orientation release];
1040   [self->dayOfWeek   release];
1041   [self->weekOfYear  release];
1042   [self->colspan     release];
1043   
1044   [self->template release];
1045   [super dealloc];
1046 }
1047
1048 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1049   NSDictionary *monthViewContextDict;
1050   NSString     *orient;
1051   BOOL         isEdge;
1052   id tmp;
1053   
1054   orient = [self->orientation valueInComponent:[_ctx component]];
1055   isEdge = ([orient rangeOfString:@"/"].length > 0);
1056   
1057   monthViewContextDict  = [_ctx monthOverviewContext];
1058   if ((tmp = [monthViewContextDict objectForKey:orient]) != nil) {
1059     if (!isEdge) {
1060       [_ctx appendElementIDComponent:orient];
1061       [self->template takeValuesFromRequest:_req inContext:_ctx];
1062       [_ctx deleteLastElementIDComponent];
1063     }
1064     else {
1065       [self->template takeValuesFromRequest:_req inContext:_ctx];
1066     }
1067   }
1068 }
1069
1070 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1071   NSDictionary *monthViewContextDict;
1072   NSString     *orient;
1073   BOOL         isEdge;
1074   id           result;
1075   id tmp;
1076   
1077   orient = [self->orientation valueInComponent:[_ctx component]];
1078   isEdge = ([orient rangeOfString:@"/"].length > 0);
1079   
1080   monthViewContextDict  = [_ctx monthOverviewContext];
1081   if ((tmp = [monthViewContextDict objectForKey:orient]) == nil)
1082     return nil;
1083
1084   if (isEdge)
1085     return [self->template invokeActionForRequest:_req inContext:_ctx];
1086
1087   tmp = [_ctx currentElementID];
1088   [_ctx consumeElementID];
1089   [_ctx appendElementIDComponent:tmp];
1090       
1091   if ([orient isEqualToString:@"top"] ||
1092       [orient isEqualToString:@"bottom"]) {
1093     [self->dayOfWeek setIntValue:[tmp intValue]
1094                          inComponent:[_ctx component]];
1095   }
1096   else if ([orient isEqualToString:@"left"] ||
1097            [orient isEqualToString:@"right"]) {
1098     [self->weekOfYear setIntValue:[tmp intValue]
1099                           inComponent:[_ctx component]];
1100   }
1101   else if ([orient isEqualToString:@"header"]) {
1102     [self->colspan setIntValue:[tmp intValue]
1103                        inComponent:[_ctx component]];
1104   }
1105       
1106   result = [self->template invokeActionForRequest:_req inContext:_ctx];
1107
1108   [_ctx deleteLastElementIDComponent];
1109   return result;
1110 }
1111
1112 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
1113   NSDictionary   *monthViewContextDict;
1114   NSMutableArray *queryContext;
1115   id       tmp;
1116   NSString *orient;
1117   BOOL     isEdge;
1118   int      cols;
1119
1120   orient = [self->orientation stringValueInComponent:[_ctx component]];
1121   isEdge = ([orient rangeOfString:@"/"].length > 0);
1122   
1123   if (orient == nil) return;
1124   
1125   if ((queryContext = [_ctx monthOverviewQueryObjects]) != nil) {
1126     [queryContext addObject:orient];
1127     return;
1128   }
1129   
1130   monthViewContextDict = [_ctx monthOverviewContext];
1131   if ((tmp = [monthViewContextDict objectForKey:orient]) == nil)
1132     return;
1133   
1134   cols = -1;
1135   if (!isEdge) {
1136     int orientIntValue;
1137     
1138     orientIntValue = [tmp intValue];
1139     if ([orient isEqualToString:@"top"] ||
1140         [orient isEqualToString:@"bottom"]) {
1141         [self->dayOfWeek setIntValue:orientIntValue 
1142                          inComponent:[_ctx component]];
1143       }
1144       else if ([orient isEqualToString:@"left"] ||
1145                [orient isEqualToString:@"right"]) {
1146         [self->weekOfYear setIntValue:orientIntValue
1147                           inComponent:[_ctx component]];
1148       } 
1149       else if ([orient isEqualToString:@"header"]) {
1150         [self->colspan setIntValue:orientIntValue
1151                        inComponent:[_ctx component]];
1152         cols = [tmp intValue];
1153       }
1154   }
1155     
1156   [_response appendContentString:@"<td"];
1157
1158   if (cols != -1) {
1159     NSString *colStr;
1160     
1161     colStr = retStrForInt(cols);
1162     [_response appendContentString:@" colspan=\""];
1163     [_response appendContentString:colStr];
1164     [_response appendContentString:@"\""];
1165     [colStr release];
1166   }
1167     
1168   [self appendExtraAttributesToResponse:_response inContext:_ctx];
1169   [_response appendContentString:@">"];
1170       
1171   if (!isEdge)
1172     [_ctx appendElementIDComponent:[tmp stringValue]];
1173     
1174   [self->template appendToResponse:_response inContext:_ctx];
1175     
1176   if (!isEdge)
1177     [_ctx deleteLastElementIDComponent];
1178
1179   // close table data tag
1180   [_response appendContentString:@"</td>"];
1181 }
1182
1183 @end /* WEMonthLabel */
1184
1185 @implementation WEMonthTitle
1186
1187 - (id)initWithName:(NSString *)_name
1188   associations:(NSDictionary*)_config
1189   template:(WOElement *)_t
1190 {
1191   if ((self = [super initWithName:_name associations:_config template:_t])) {
1192     self->template = [_t retain];
1193   }
1194   return self;
1195 }
1196
1197 - (void)dealloc {
1198   [self->template release];
1199   [super dealloc];
1200 }
1201
1202 /* handling requests */
1203
1204 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1205   NSDictionary *monthViewContextDict;
1206   id tmp;
1207   
1208   monthViewContextDict  = [_ctx monthOverviewContext];
1209   if ((tmp = [monthViewContextDict objectForKey:@"title"]) != nil)
1210     [self->template takeValuesFromRequest:_req inContext:_ctx];
1211 }
1212
1213 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
1214   NSDictionary *monthViewContextDict;
1215   id tmp;
1216   
1217   monthViewContextDict  = [_ctx monthOverviewContext];
1218   if ((tmp = [monthViewContextDict objectForKey:@"title"]) != nil)
1219     return [self->template invokeActionForRequest:_req inContext:_ctx];
1220   
1221   return nil;
1222 }
1223
1224 /* generating response */
1225
1226 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
1227   NSDictionary *monthViewContextDict;
1228   id tmp;
1229   
1230   if ((tmp = [_ctx monthOverviewQueryObjects]) != nil) {
1231     [(NSMutableArray *)tmp addObject:@"title"];
1232     return;
1233   }
1234   
1235   monthViewContextDict = [_ctx monthOverviewContext];
1236   if ((tmp = [monthViewContextDict objectForKey:@"title"]) != nil) {
1237     // append table date, forwarding extra attributes
1238     [_response appendContentString:@"<td"];
1239     [self appendExtraAttributesToResponse:_response inContext:_ctx];
1240     [_response appendContentString:@">"];
1241     // append child
1242     [self->template appendToResponse:_response inContext:_ctx];
1243     // close table data tag
1244     [_response appendContentString:@"</td>"];
1245   }
1246 }
1247
1248 @end /* WEMonthTitle */
1249
1250 @interface WEMonthOverviewInfoMode : WEContextConditional
1251 @end
1252
1253 @implementation WEMonthOverviewInfoMode
1254
1255 - (NSString *)_contextKey {
1256   return WEMonthOverview_InfoMode;
1257 }
1258
1259 @end /* WEMonthOverviewInfoMode */
1260
1261 @interface WEMonthOverviewContentMode : WEContextConditional
1262 @end
1263
1264 @implementation WEMonthOverviewContentMode
1265
1266 - (NSString *)_contextKey {
1267   return WEMonthOverview_ContentMode;
1268 }
1269
1270 @end /* WEMonthOverviewContentMode */