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