2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
23 #include <NGObjWeb/WODynamicElement.h>
24 #include <NGObjWeb/WOContext.h>
29 TODO: describe what it does
32 @interface WEPageView : WODynamicElement
34 WOAssociation *selection;
37 WOAssociation *titleColor;
38 WOAssociation *contentColor;
40 WOAssociation *fontColor;
41 WOAssociation *fontFace;
42 WOAssociation *fontSize;
44 WOAssociation *firstIcon;
45 WOAssociation *firstBlind; // firstBlindIcon
46 WOAssociation *previousIcon;
47 WOAssociation *previousBlind; // previousBlindIcon
48 WOAssociation *nextIcon;
49 WOAssociation *nextBlind; // nextBlindIcon
50 WOAssociation *lastIcon;
51 WOAssociation *lastBlind; // lastBlindIcon
53 WOAssociation *firstLabel;
54 WOAssociation *previousLabel;
55 WOAssociation *nextLabel;
56 WOAssociation *lastLabel;
63 @interface WEPageItem : WODynamicElement
67 WOAssociation *action;
74 @interface WEPageItemInfo : NSObject
84 #include "WEClientCapabilities.h"
85 #import <NGObjWeb/NGObjWeb.h>
86 #import <NGExtensions/NGExtensions.h>
87 #import <EOControl/EOControl.h>
89 // #define DEBUG_TAKEVALUES 1
92 static NSString *WEPageView_HEAD = @"WEPageView_head";
93 static NSString *WEPageView_BODY = @"WEPageView_body";
94 static NSString *WEPageView_KEYS = @"WEPageView_keys";
95 static NSString *WEPageView_ACTIVEKEY = @"WEPageView_activekey";
96 static NSString *WEPageView_COLLECT = @"~tv~";
99 static NSString *WEPageView_first = @"WEPageView_first";
100 static NSString *WEPageView_first_blind = @"WEPageView_first_blind";
101 static NSString *WEPageView_previous = @"WEPageView_previous";
102 static NSString *WEPageView_previous_blind = @"WEPageView_previous_blind";
103 static NSString *WEPageView_next = @"WEPageView_next";
104 static NSString *WEPageView_next_blind = @"WEPageView_next_blind";
105 static NSString *WEPageView_last = @"WEPageView_last";
106 static NSString *WEPageView_last_blind = @"WEPageView_last_blind";
109 static NSString *WEPageView_firstLabel = @"WEPageView_firstLabel";
110 static NSString *WEPageView_previousLabel = @"WEPageView_previousLabel";
111 static NSString *WEPageView_nextLabel = @"WEPageView_nextLabel";
112 static NSString *WEPageView_lastLabel = @"WEPageView_lastLabel";
114 static NSString *WEPageView_ = @"WEPageView_";
116 @implementation WEPageView
118 static NSNumber *YesNumber = nil;
121 if (YesNumber == nil) YesNumber = [[NSNumber numberWithBool:YES] retain];
125 return [super version] + 0;
128 - (id)initWithName:(NSString *)_name
129 associations:(NSDictionary *)_config
130 template:(WOElement *)_subs
132 if ((self = [super initWithName:_name associations:_config template:_subs])) {
133 self->selection = WOExtGetProperty(_config, @"selection");
135 self->contentColor = WOExtGetProperty(_config, @"contentColor");
136 self->titleColor = WOExtGetProperty(_config, @"titleColor");
138 self->firstIcon = WOExtGetProperty(_config, @"firstIcon");
139 self->firstBlind = WOExtGetProperty(_config, @"firstBlindIcon");
140 self->previousIcon = WOExtGetProperty(_config, @"previousIcon");
141 self->previousBlind = WOExtGetProperty(_config, @"previousBlindIcon");
142 self->nextIcon = WOExtGetProperty(_config, @"nextIcon");
143 self->nextBlind = WOExtGetProperty(_config, @"nextBlindIcon");
144 self->lastIcon = WOExtGetProperty(_config, @"lastIcon");
145 self->lastBlind = WOExtGetProperty(_config, @"lastBlindIcon");
147 self->firstLabel = WOExtGetProperty(_config, @"firstLabel");
148 self->previousLabel = WOExtGetProperty(_config, @"previousLabel");
149 self->nextLabel = WOExtGetProperty(_config, @"nextLabel");
150 self->lastLabel = WOExtGetProperty(_config, @"lastLabel");
152 self->fontColor = WOExtGetProperty(_config, @"fontColor");
153 self->fontFace = WOExtGetProperty(_config, @"fontFace");
154 self->fontSize = WOExtGetProperty(_config, @"fontSize");
156 #define SetAssociationValue(_a_, _value_) \
158 _a_ = [[WOAssociation associationWithValue:_value_] retain];
160 SetAssociationValue(self->firstLabel, @"<<");
161 SetAssociationValue(self->previousLabel, @"<");
162 SetAssociationValue(self->nextLabel, @">");
163 SetAssociationValue(self->lastLabel, @">>");
165 #undef SetAssociationValue
167 self->template = [_subs retain];
173 RELEASE(self->selection);
175 RELEASE(self->contentColor);
176 RELEASE(self->titleColor);
178 RELEASE(self->firstIcon);
179 RELEASE(self->firstBlind);
180 RELEASE(self->previousIcon);
181 RELEASE(self->previousBlind);
182 RELEASE(self->nextIcon);
183 RELEASE(self->nextBlind);
184 RELEASE(self->lastIcon);
185 RELEASE(self->lastBlind);
187 RELEASE(self->firstLabel);
188 RELEASE(self->previousLabel);
189 RELEASE(self->nextLabel);
190 RELEASE(self->lastLabel);
192 RELEASE(self->fontColor);
193 RELEASE(self->fontFace);
194 RELEASE(self->fontSize);
196 RELEASE(self->template);
201 - (void)updateConfigInContext:(WOContext *)_ctx {
205 cmp = [_ctx component];
207 #define SetConfigInContext(_a_, _key_) \
208 if (_a_ && (tmp = [_a_ valueInComponent:cmp])) \
209 [_ctx setObject:tmp forKey:_key_]; \
211 SetConfigInContext(self->firstIcon, WEPageView_first);
212 SetConfigInContext(self->firstBlind, WEPageView_first_blind);
213 SetConfigInContext(self->previousIcon, WEPageView_previous);
214 SetConfigInContext(self->previousBlind, WEPageView_previous_blind);
215 SetConfigInContext(self->nextIcon, WEPageView_next);
216 SetConfigInContext(self->nextBlind, WEPageView_next_blind);
217 SetConfigInContext(self->lastIcon, WEPageView_last);
218 SetConfigInContext(self->lastBlind, WEPageView_last_blind);
220 SetConfigInContext(self->firstLabel, WEPageView_firstLabel);
221 SetConfigInContext(self->previousLabel, WEPageView_previousLabel);
222 SetConfigInContext(self->nextLabel, WEPageView_nextLabel);
223 SetConfigInContext(self->lastLabel, WEPageView_lastLabel);
225 #undef SetConfigInContext
228 - (void)removeConfigInContext:(WOContext *)_ctx {
229 [_ctx removeObjectForKey:WEPageView_first];
230 [_ctx removeObjectForKey:WEPageView_first_blind];
231 [_ctx removeObjectForKey:WEPageView_previous];
232 [_ctx removeObjectForKey:WEPageView_previous_blind];
233 [_ctx removeObjectForKey:WEPageView_next];
234 [_ctx removeObjectForKey:WEPageView_next_blind];
235 [_ctx removeObjectForKey:WEPageView_last];
236 [_ctx removeObjectForKey:WEPageView_last_blind];
238 [_ctx removeObjectForKey:WEPageView_firstLabel];
239 [_ctx removeObjectForKey:WEPageView_previousLabel];
240 [_ctx removeObjectForKey:WEPageView_nextLabel];
241 [_ctx removeObjectForKey:WEPageView_lastLabel];
244 static inline NSString *WEPageLabelForKey(NSString *_key, WOContext *_ctx) {
247 key = [NSString stringWithFormat:@"WEPageView_%@Label", _key];
248 return [_ctx objectForKey:key];
253 - (id)saveNestedStateInContext:(WOContext *)_ctx {
256 - (void)restoreNestedState:(id)_state inContext:(WOContext *)_ctx {
257 if (_state == nil) return;
260 - (NSArray *)collectKeysInContext:(WOContext *)_ctx {
261 /* collect mode, collects all keys */
262 [_ctx setObject:WEPageView_COLLECT forKey:WEPageView_HEAD];
264 [self->template appendToResponse:nil inContext:_ctx];
266 [_ctx removeObjectForKey:WEPageView_HEAD];
267 return [_ctx objectForKey:WEPageView_KEYS];
272 - (void)takeValuesFromRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
275 nestedState = [self saveNestedStateInContext:_ctx];
277 [_ctx appendElementIDComponent:@"h"];
278 [self->template takeValuesFromRequest:_request inContext:_ctx];
279 [_ctx deleteLastElementIDComponent];
281 [_ctx appendElementIDComponent:@"b"];
282 [_ctx setObject:self->selection forKey:WEPageView_BODY];
285 [[_ctx component] debugWithFormat:@"WEPageView: body takes values, eid='%@'",
289 [self->template takeValuesFromRequest:_request inContext:_ctx];
291 [_ctx removeObjectForKey:WEPageView_BODY];
292 [_ctx deleteLastElementIDComponent]; /* 'b' */
293 [self restoreNestedState:nestedState inContext:_ctx];
296 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
301 if ((key = [_ctx currentElementID])) {
304 nestedState = [self saveNestedStateInContext:_ctx];
306 if ([key isEqualToString:@"h"]) {
309 [_ctx consumeElementID];
310 [_ctx appendElementIDComponent:key];
312 [_ctx setObject:self->selection forKey:WEPageView_HEAD];
313 result = [self->template invokeActionForRequest:_request inContext:_ctx];
314 [_ctx removeObjectForKey:WEPageView_HEAD];
316 [_ctx deleteLastElementIDComponent];
318 else if ([key isEqualToString:@"b"]) {
321 [_ctx consumeElementID];
322 [_ctx appendElementIDComponent:key];
324 [_ctx setObject:self->selection forKey:WEPageView_BODY];
326 result = [self->template invokeActionForRequest:_request inContext:_ctx];
328 [_ctx removeObjectForKey:WEPageView_BODY];
330 [_ctx deleteLastElementIDComponent];
334 debugWithFormat:@"unknown page container key '%@'", key];
337 [self restoreNestedState:nestedState inContext:_ctx];
344 - (void)_appendNav:(NSString *)_nav isBlind:(BOOL)_isBlind
345 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
346 info:(WEPageItemInfo *)_info
352 doForm = [_ctx isInForm];
353 img = [WEPageView_ stringByAppendingString:_nav];
354 img = [img stringByAppendingString:(_isBlind) ? @"_blind" : @""];
355 img = [_ctx objectForKey:img];
356 img = WEUriOfResource(img,_ctx);
358 label = WEPageLabelForKey(_nav, _ctx);
360 // append as submit button
361 if (doForm && !_isBlind && img && _info) {
364 uri = [[_info->uri componentsSeparatedByString:@"/"] lastObject];
366 [_ctx appendElementIDComponent:_nav];
367 [_response appendContentString:@"<input type=\"image\" border=\"0\""];
368 [_response appendContentString:@" name=\""];
369 [_response appendContentString:uri];
370 [_response appendContentString:@"\" src=\""];
371 [_response appendContentString:img];
372 [_response appendContentString:@"\" alt=\""];
373 [_response appendContentString:label];
374 [_response appendContentString:@"\" />"];
375 [_ctx deleteLastElementIDComponent];
380 if (!_isBlind && _info) {
381 [_ctx appendElementIDComponent:_nav];
382 [_response appendContentString:@"<a href=\""];
383 [_response appendContentString:_info->uri];
384 [_response appendContentString:@"\">"];
387 [_response appendContentCharacter:'['];
388 [_response appendContentString:label];
389 [_response appendContentCharacter:']'];
392 [_response appendContentString:@"<img border=\"0\" src=\""];
393 [_response appendContentString:img];
394 [_response appendContentString:@"\" alt=\""];
395 [_response appendContentString:label];
396 [_response appendContentString:@"\" />"];
399 if (!_isBlind && _info) {
400 [_response appendContentString:@"</a>"];
401 [_ctx deleteLastElementIDComponent];
405 - (void)appendNavToResponse:(WOResponse *)_response
406 inContext:(WOContext *)_ctx
408 activeKey:(WEPageItemInfo *)_activeKey
409 keys:(NSArray *)_keys
411 WEPageItemInfo *info;
414 idx = [_keys indexOfObject:_activeKey];
418 NSLog(@"Warning! WEPageView: idx is out of range!");
423 info = (cnt > 0) ? [_keys objectAtIndex:0] : nil;
424 [self _appendNav:@"first" isBlind:(idx < 1) ? YES : NO
425 toResponse:_response inContext:_ctx info:info];
427 info = (cnt > 0 && idx > 0) ? [_keys objectAtIndex:idx-1] : nil;
428 [self _appendNav:@"previous" isBlind:(idx < 1) ? YES : NO
429 toResponse:_response inContext:_ctx info:info];
432 info = (cnt > idx+1) ? [_keys objectAtIndex:idx+1] : nil;
433 [self _appendNav:@"next" isBlind:(cnt <= idx+1) ? YES : NO
434 toResponse:_response inContext:_ctx info:info];
436 info = (cnt > 0) ? [_keys objectAtIndex:cnt-1] : nil;
437 [self _appendNav:@"last" isBlind:(cnt <= idx+1) ? YES : NO
438 toResponse:_response inContext:_ctx info:info];
442 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
449 WEPageItemInfo *activeInfo = nil;
453 [self updateConfigInContext:_ctx];
455 doForm = NO; /* generate form controls ? */
456 indentContent = YES; /* put additional padding table into content */
457 cmp = [_ctx component];
459 /* check for browser */
462 WEClientCapabilities *ccaps;
464 ccaps = [[_ctx request] clientCapabilities];
465 if ([ccaps isFastTableBrowser] && ![ccaps isTextModeBrowser])
471 nestedState = [self saveNestedStateInContext:_ctx];
473 /* collect & process keys (= available tabs) */
475 [_ctx appendElementIDComponent:@"h"];
477 activeKey = [self->selection stringValueInComponent:cmp];
478 keys = [self collectKeysInContext:_ctx];
480 if (![[keys valueForKey:@"key"] containsObject:activeKey])
481 /* selection is not available in keys */
484 if ((activeKey == nil) && ([keys count] > 0)) {
485 /* no or invalid selection, use first key */
486 activeKey = [[keys objectAtIndex:0] key];
487 if ([self->selection isValueSettable])
488 [self->selection setValue:activeKey inComponent:[_ctx component]];
491 for (i = 0, count = [keys count]; i < count; i++) {
492 WEPageItemInfo *info;
494 info = [keys objectAtIndex:i];
496 if ([info->key isEqualToString:activeKey]) {
502 /* start appending */
504 [_response appendContentString:
505 @"<table border=\"0\" width=\"100%\""
506 @" cellpadding=\"0\" cellspacing=\"0\">"];
508 /* generate header row */
510 bgcolor = [self->titleColor stringValueInComponent:cmp];
512 [_response appendContentString:@"<tr>"];
514 /* left navigation */
515 WEAppendTD(_response, @"left", nil, bgcolor);
516 [self appendNavToResponse:_response
521 [_response appendContentString:@"</td>"];
525 WEAppendTD(_response, @"center", nil, bgcolor);
527 unsigned char buf[256];
528 NSString *tC, *tF, *tS; // text font attrtibutes
531 cmp = [_ctx component];
533 tC = [self->fontColor stringValueInComponent:cmp];
534 tF = [self->fontFace stringValueInComponent:cmp];
535 tS = [self->fontSize stringValueInComponent:cmp];
537 hasFont = (tC || tF || tS) ? YES : NO;
540 WEAppendFont(_response, tC, tF, tS);
542 for (i = 0, count = [keys count]; i < count; i++) {
543 WEPageItemInfo *info;
545 info = [keys objectAtIndex:i];
547 if ([info->key isEqualToString:activeKey]) {
548 [_response appendContentString:@"<b>"];
549 [_response appendContentString:info->title];
550 [_response appendContentString:@"</b>"];
554 sprintf(buf, " <small>(%d/%d)</small>", (i + 1), count);
555 [_response appendContentCString:buf];
558 [_response appendContentString:@"</font>"];
560 [_response appendContentString:@"</td>"];
562 /* right navigation */
564 WEAppendTD(_response, @"right", nil, bgcolor);
565 [self appendNavToResponse:_response
570 [_response appendContentString:@"</td>"];
572 [_response appendContentString:@"</tr>"];
573 [_ctx deleteLastElementIDComponent]; // delete "h"
574 [_ctx removeObjectForKey:WEPageView_HEAD];
578 bgcolor = [self->contentColor stringValueInComponent:cmp];
581 [_response appendContentString:@"<tr><td colspan=\"3\""];
583 [_response appendContentString:@" bgcolor=\""];
584 [_response appendContentHTMLAttributeValue:bgcolor];
585 [_response appendContentString:@"\""];
587 [_response appendContentString:@">"];
590 /* start padding table */
591 [_response appendContentString:
592 @"<table border=\"0\" width=\"100%\""
593 @" cellpadding=\"10\" cellspacing=\"0\">"];
594 [_response appendContentString:@"<tr><td>"];
597 [_ctx appendElementIDComponent:@"b"];
599 /* generate currently active page */
602 [_ctx setObject:activeKey forKey:WEPageView_BODY];
603 [self->template appendToResponse:_response inContext:_ctx];
604 [_ctx removeObjectForKey:WEPageView_BODY];
607 [_ctx deleteLastElementIDComponent];
610 /* close padding table */
611 [_response appendContentString:@"</td></tr></table>"];
613 [_response appendContentString:@"</td></tr>"];
615 [_response appendContentString:@"</table>"];
617 [_ctx removeObjectForKey:WEPageView_ACTIVEKEY];
618 [_ctx removeObjectForKey:WEPageView_KEYS];
619 [self restoreNestedState:nestedState inContext:_ctx];
621 [self removeConfigInContext:_ctx];
624 @end /* WEPageView */
626 @implementation WEPageItem
629 return [super version] + 0;
632 - (id)initWithName:(NSString *)_name
633 associations:(NSDictionary *)_config
634 template:(WOElement *)_subs
636 if ((self = [super initWithName:_name associations:_config template:_subs])) {
637 self->key = WOExtGetProperty(_config, @"key");
638 self->title = WOExtGetProperty(_config, @"title");
639 self->action = WOExtGetProperty(_config, @"action");
641 self->template = RETAIN(_subs);
647 [self->action release];
648 [self->title release];
650 [self->template release];
656 - (void)takeValuesFromRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
659 if ((tmp = [_ctx objectForKey:WEPageView_BODY])) {
660 NSString *activeTabKey;
663 activeTabKey = [tmp stringValueInComponent:[_ctx component]];
664 myTabKey = [self->key stringValueInComponent:[_ctx component]];
666 if ([activeTabKey isEqualToString:myTabKey]) {
667 [_ctx appendElementIDComponent:activeTabKey];
670 [[_ctx component] debugWithFormat:
671 @"WEPageItem: body takes values, eid='%@'",
675 [self->template takeValuesFromRequest:_request inContext:_ctx];
676 [_ctx deleteLastElementIDComponent];
680 [[_ctx component] debugWithFormat:
681 @"WEPageItem: body takes no values, eid='%@'",
690 k = [self->key stringValueInComponent:[_ctx component]];
692 eid = [[_ctx elementID] stringByAppendingString:@"."];
693 eid = [eid stringByAppendingString:k];
695 if (k && [_request formValueForKey:[eid stringByAppendingString:@".x"]]) {
696 [_ctx addActiveFormElement:self];
697 [_ctx setRequestSenderID:eid];
702 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
707 tabkey = [_ctx currentElementID];
708 [_ctx consumeElementID];
709 [_ctx appendElementIDComponent:tabkey];
711 if ((tmp = [_ctx objectForKey:WEPageView_HEAD])) {
712 /* click on tab icon */
713 if ([tmp isValueSettable])
714 [tmp setValue:tabkey inComponent:[_ctx component]];
716 result = [self->action valueInComponent:[_ctx component]];
718 else if ((tmp = [_ctx objectForKey:WEPageView_BODY])) {
719 /* clicked somewhere in the body */
720 if ([tmp isValueSettable])
721 [tmp setValue:tabkey inComponent:[_ctx component]];
723 result = [self->template invokeActionForRequest:_rq inContext:_ctx];
726 [[_ctx component] debugWithFormat:@"WEPageItem: invalid invoke state"];
727 result = [self->template invokeActionForRequest:_rq inContext:_ctx];
730 [_ctx deleteLastElementIDComponent];
734 - (void)appendHead:(NSString *)tmp
735 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
737 NSMutableArray *keys;
738 WEPageItemInfo *info;
742 if (![tmp isEqual:WEPageView_COLLECT])
747 cmp = [_ctx component];
748 keys = [_ctx objectForKey:WEPageView_KEYS];
750 keys = [NSMutableArray arrayWithCapacity:8];
751 [_ctx setObject:keys forKey:WEPageView_KEYS];
754 if ((k = [self->key stringValueInComponent:[_ctx component]]) == nil) {
755 /* auto-assign a key */
756 unsigned char kb[16];
757 sprintf(kb, "%d", [keys count]);
758 k = [NSString stringWithCString:kb];
760 [_ctx appendElementIDComponent:k];
762 info = [[WEPageItemInfo alloc] init];
763 info->key = [k copy];
764 info->title = [[self->title stringValueInComponent:cmp] copy];
765 info->uri = [[_ctx componentActionURL] copy];
767 [keys addObject:info];
770 [_ctx deleteLastElementIDComponent];
773 - (void)appendBody:(NSString *)tmp
774 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
778 k = [self->key stringValueInComponent:[_ctx component]];
779 if (![tmp isEqualToString:k])
782 /* content is active or used as layer*/
783 [_ctx appendElementIDComponent:k];
785 [self debugWithFormat:@"PAGE: %@", k];
787 [self->template appendToResponse:_response inContext:_ctx];
788 [_ctx deleteLastElementIDComponent];
791 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
794 if ((tmp = [_ctx objectForKey:WEPageView_HEAD]))
795 [self appendHead:tmp toResponse:_response inContext:_ctx];
796 else if ((tmp = [_ctx objectForKey:WEPageView_BODY]))
797 [self appendBody:tmp toResponse:_response inContext:_ctx];
799 [_response appendContentString:@"[invalid pageview state]"];
802 @end /* WEPageItem */
804 @implementation WEPageItemInfo
808 [self->title release];
816 - (NSString *)title {
823 @end /* WEPageItemInfo */