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