2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include <NGObjWeb/WODynamicElement.h>
23 #include <NGObjWeb/WOContext.h>
28 TODO: describe what it does
31 @interface WEPageView : WODynamicElement
33 WOAssociation *selection;
36 WOAssociation *titleColor;
37 WOAssociation *contentColor;
39 WOAssociation *fontColor;
40 WOAssociation *fontFace;
41 WOAssociation *fontSize;
43 WOAssociation *firstIcon;
44 WOAssociation *firstBlind; // firstBlindIcon
45 WOAssociation *previousIcon;
46 WOAssociation *previousBlind; // previousBlindIcon
47 WOAssociation *nextIcon;
48 WOAssociation *nextBlind; // nextBlindIcon
49 WOAssociation *lastIcon;
50 WOAssociation *lastBlind; // lastBlindIcon
52 WOAssociation *firstLabel;
53 WOAssociation *previousLabel;
54 WOAssociation *nextLabel;
55 WOAssociation *lastLabel;
62 @interface WEPageItem : WODynamicElement
66 WOAssociation *action;
73 @interface WEPageItemInfo : NSObject
82 #include <NGObjWeb/WEClientCapabilities.h>
85 // #define DEBUG_TAKEVALUES 1
88 static NSString *WEPageView_HEAD = @"WEPageView_head";
89 static NSString *WEPageView_BODY = @"WEPageView_body";
90 static NSString *WEPageView_KEYS = @"WEPageView_keys";
91 static NSString *WEPageView_ACTIVEKEY = @"WEPageView_activekey";
92 static NSString *WEPageView_COLLECT = @"~tv~";
95 static NSString *WEPageView_first = @"WEPageView_first";
96 static NSString *WEPageView_first_blind = @"WEPageView_first_blind";
97 static NSString *WEPageView_previous = @"WEPageView_previous";
98 static NSString *WEPageView_previous_blind = @"WEPageView_previous_blind";
99 static NSString *WEPageView_next = @"WEPageView_next";
100 static NSString *WEPageView_next_blind = @"WEPageView_next_blind";
101 static NSString *WEPageView_last = @"WEPageView_last";
102 static NSString *WEPageView_last_blind = @"WEPageView_last_blind";
105 static NSString *WEPageView_firstLabel = @"WEPageView_firstLabel";
106 static NSString *WEPageView_previousLabel = @"WEPageView_previousLabel";
107 static NSString *WEPageView_nextLabel = @"WEPageView_nextLabel";
108 static NSString *WEPageView_lastLabel = @"WEPageView_lastLabel";
110 static NSString *WEPageView_ = @"WEPageView_";
112 @implementation WEPageView
114 static NSNumber *YesNumber = nil;
117 if (YesNumber == nil) YesNumber = [[NSNumber numberWithBool:YES] retain];
121 return [super version] + 0;
124 - (id)initWithName:(NSString *)_name
125 associations:(NSDictionary *)_config
126 template:(WOElement *)_subs
128 if ((self = [super initWithName:_name associations:_config template:_subs])) {
129 self->selection = WOExtGetProperty(_config, @"selection");
131 self->contentColor = WOExtGetProperty(_config, @"contentColor");
132 self->titleColor = WOExtGetProperty(_config, @"titleColor");
134 self->firstIcon = WOExtGetProperty(_config, @"firstIcon");
135 self->firstBlind = WOExtGetProperty(_config, @"firstBlindIcon");
136 self->previousIcon = WOExtGetProperty(_config, @"previousIcon");
137 self->previousBlind = WOExtGetProperty(_config, @"previousBlindIcon");
138 self->nextIcon = WOExtGetProperty(_config, @"nextIcon");
139 self->nextBlind = WOExtGetProperty(_config, @"nextBlindIcon");
140 self->lastIcon = WOExtGetProperty(_config, @"lastIcon");
141 self->lastBlind = WOExtGetProperty(_config, @"lastBlindIcon");
143 self->firstLabel = WOExtGetProperty(_config, @"firstLabel");
144 self->previousLabel = WOExtGetProperty(_config, @"previousLabel");
145 self->nextLabel = WOExtGetProperty(_config, @"nextLabel");
146 self->lastLabel = WOExtGetProperty(_config, @"lastLabel");
148 self->fontColor = WOExtGetProperty(_config, @"fontColor");
149 self->fontFace = WOExtGetProperty(_config, @"fontFace");
150 self->fontSize = WOExtGetProperty(_config, @"fontSize");
152 #define SetAssociationValue(_a_, _value_) \
154 _a_ = [[WOAssociation associationWithValue:_value_] retain];
156 SetAssociationValue(self->firstLabel, @"<<");
157 SetAssociationValue(self->previousLabel, @"<");
158 SetAssociationValue(self->nextLabel, @">");
159 SetAssociationValue(self->lastLabel, @">>");
161 #undef SetAssociationValue
163 self->template = [_subs retain];
169 RELEASE(self->selection);
171 RELEASE(self->contentColor);
172 RELEASE(self->titleColor);
174 RELEASE(self->firstIcon);
175 RELEASE(self->firstBlind);
176 RELEASE(self->previousIcon);
177 RELEASE(self->previousBlind);
178 RELEASE(self->nextIcon);
179 RELEASE(self->nextBlind);
180 RELEASE(self->lastIcon);
181 RELEASE(self->lastBlind);
183 RELEASE(self->firstLabel);
184 RELEASE(self->previousLabel);
185 RELEASE(self->nextLabel);
186 RELEASE(self->lastLabel);
188 RELEASE(self->fontColor);
189 RELEASE(self->fontFace);
190 RELEASE(self->fontSize);
192 RELEASE(self->template);
197 - (void)updateConfigInContext:(WOContext *)_ctx {
201 cmp = [_ctx component];
203 #define SetConfigInContext(_a_, _key_) \
204 if (_a_ && (tmp = [_a_ valueInComponent:cmp])) \
205 [_ctx setObject:tmp forKey:_key_]; \
207 SetConfigInContext(self->firstIcon, WEPageView_first);
208 SetConfigInContext(self->firstBlind, WEPageView_first_blind);
209 SetConfigInContext(self->previousIcon, WEPageView_previous);
210 SetConfigInContext(self->previousBlind, WEPageView_previous_blind);
211 SetConfigInContext(self->nextIcon, WEPageView_next);
212 SetConfigInContext(self->nextBlind, WEPageView_next_blind);
213 SetConfigInContext(self->lastIcon, WEPageView_last);
214 SetConfigInContext(self->lastBlind, WEPageView_last_blind);
216 SetConfigInContext(self->firstLabel, WEPageView_firstLabel);
217 SetConfigInContext(self->previousLabel, WEPageView_previousLabel);
218 SetConfigInContext(self->nextLabel, WEPageView_nextLabel);
219 SetConfigInContext(self->lastLabel, WEPageView_lastLabel);
221 #undef SetConfigInContext
224 - (void)removeConfigInContext:(WOContext *)_ctx {
225 [_ctx removeObjectForKey:WEPageView_first];
226 [_ctx removeObjectForKey:WEPageView_first_blind];
227 [_ctx removeObjectForKey:WEPageView_previous];
228 [_ctx removeObjectForKey:WEPageView_previous_blind];
229 [_ctx removeObjectForKey:WEPageView_next];
230 [_ctx removeObjectForKey:WEPageView_next_blind];
231 [_ctx removeObjectForKey:WEPageView_last];
232 [_ctx removeObjectForKey:WEPageView_last_blind];
234 [_ctx removeObjectForKey:WEPageView_firstLabel];
235 [_ctx removeObjectForKey:WEPageView_previousLabel];
236 [_ctx removeObjectForKey:WEPageView_nextLabel];
237 [_ctx removeObjectForKey:WEPageView_lastLabel];
240 static inline NSString *WEPageLabelForKey(NSString *_key, WOContext *_ctx) {
243 key = [NSString stringWithFormat:@"WEPageView_%@Label", _key];
244 return [_ctx objectForKey:key];
249 - (id)saveNestedStateInContext:(WOContext *)_ctx {
252 - (void)restoreNestedState:(id)_state inContext:(WOContext *)_ctx {
253 if (_state == nil) return;
256 - (NSArray *)collectKeysInContext:(WOContext *)_ctx {
257 /* collect mode, collects all keys */
258 [_ctx setObject:WEPageView_COLLECT forKey:WEPageView_HEAD];
260 [self->template appendToResponse:nil inContext:_ctx];
262 [_ctx removeObjectForKey:WEPageView_HEAD];
263 return [_ctx objectForKey:WEPageView_KEYS];
268 - (void)takeValuesFromRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
271 nestedState = [self saveNestedStateInContext:_ctx];
273 [_ctx appendElementIDComponent:@"h"];
274 [self->template takeValuesFromRequest:_request inContext:_ctx];
275 [_ctx deleteLastElementIDComponent];
277 [_ctx appendElementIDComponent:@"b"];
278 [_ctx setObject:self->selection forKey:WEPageView_BODY];
281 [[_ctx component] debugWithFormat:@"WEPageView: body takes values, eid='%@'",
285 [self->template takeValuesFromRequest:_request inContext:_ctx];
287 [_ctx removeObjectForKey:WEPageView_BODY];
288 [_ctx deleteLastElementIDComponent]; /* 'b' */
289 [self restoreNestedState:nestedState inContext:_ctx];
292 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
297 if ((key = [_ctx currentElementID])) {
300 nestedState = [self saveNestedStateInContext:_ctx];
302 if ([key isEqualToString:@"h"]) {
305 [_ctx consumeElementID];
306 [_ctx appendElementIDComponent:key];
308 [_ctx setObject:self->selection forKey:WEPageView_HEAD];
309 result = [self->template invokeActionForRequest:_request inContext:_ctx];
310 [_ctx removeObjectForKey:WEPageView_HEAD];
312 [_ctx deleteLastElementIDComponent];
314 else if ([key isEqualToString:@"b"]) {
317 [_ctx consumeElementID];
318 [_ctx appendElementIDComponent:key];
320 [_ctx setObject:self->selection forKey:WEPageView_BODY];
322 result = [self->template invokeActionForRequest:_request inContext:_ctx];
324 [_ctx removeObjectForKey:WEPageView_BODY];
326 [_ctx deleteLastElementIDComponent];
330 debugWithFormat:@"unknown page container key '%@'", key];
333 [self restoreNestedState:nestedState inContext:_ctx];
340 - (void)_appendNav:(NSString *)_nav isBlind:(BOOL)_isBlind
341 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
342 info:(WEPageItemInfo *)_info
348 doForm = [_ctx isInForm];
349 img = [WEPageView_ stringByAppendingString:_nav];
350 img = [img stringByAppendingString:(_isBlind) ? @"_blind" : @""];
351 img = [_ctx objectForKey:img];
352 img = WEUriOfResource(img,_ctx);
354 label = WEPageLabelForKey(_nav, _ctx);
356 // append as submit button
357 if (doForm && !_isBlind && img && _info) {
360 uri = [[_info->uri componentsSeparatedByString:@"/"] lastObject];
362 [_ctx appendElementIDComponent:_nav];
363 [_response appendContentString:@"<input type=\"image\" border=\"0\""];
364 [_response appendContentString:@" name=\""];
365 [_response appendContentString:uri];
366 [_response appendContentString:@"\" src=\""];
367 [_response appendContentString:img];
368 [_response appendContentString:@"\" alt=\""];
369 [_response appendContentString:label];
370 if (_ctx->wcFlags.xmlStyleEmptyElements)
371 [_response appendContentString:@"\" />"];
373 [_response appendContentString:@"\">"];
374 [_ctx deleteLastElementIDComponent];
379 if (!_isBlind && _info) {
380 [_ctx appendElementIDComponent:_nav];
381 [_response appendContentString:@"<a href=\""];
382 [_response appendContentString:_info->uri];
383 [_response appendContentString:@"\">"];
386 [_response appendContentCharacter:'['];
387 [_response appendContentString:label];
388 [_response appendContentCharacter:']'];
391 [_response appendContentString:@"<img border=\"0\" src=\""];
392 [_response appendContentString:img];
393 [_response appendContentString:@"\" alt=\""];
394 [_response appendContentString:label];
395 if (_ctx->wcFlags.xmlStyleEmptyElements)
396 [_response appendContentString:@"\" />"];
398 [_response appendContentString:@"\">"];
401 if (!_isBlind && _info) {
402 [_response appendContentString:@"</a>"];
403 [_ctx deleteLastElementIDComponent];
407 - (void)appendNavToResponse:(WOResponse *)_response
408 inContext:(WOContext *)_ctx
410 activeKey:(WEPageItemInfo *)_activeKey
411 keys:(NSArray *)_keys
413 WEPageItemInfo *info;
416 idx = [_keys indexOfObject:_activeKey];
420 NSLog(@"Warning! WEPageView: idx is out of range!");
425 info = (cnt > 0) ? [_keys objectAtIndex:0] : nil;
426 [self _appendNav:@"first" isBlind:(idx < 1) ? YES : NO
427 toResponse:_response inContext:_ctx info:info];
429 info = (cnt > 0 && idx > 0) ? [_keys objectAtIndex:idx-1] : nil;
430 [self _appendNav:@"previous" isBlind:(idx < 1) ? YES : NO
431 toResponse:_response inContext:_ctx info:info];
434 info = (cnt > idx+1) ? [_keys objectAtIndex:idx+1] : nil;
435 [self _appendNav:@"next" isBlind:(cnt <= idx+1) ? YES : NO
436 toResponse:_response inContext:_ctx info:info];
438 info = (cnt > 0) ? [_keys objectAtIndex:cnt-1] : nil;
439 [self _appendNav:@"last" isBlind:(cnt <= idx+1) ? YES : NO
440 toResponse:_response inContext:_ctx info:info];
444 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
451 WEPageItemInfo *activeInfo = nil;
455 [self updateConfigInContext:_ctx];
457 doForm = NO; /* generate form controls ? */
458 indentContent = YES; /* put additional padding table into content */
459 cmp = [_ctx component];
461 /* check for browser */
464 WEClientCapabilities *ccaps;
466 ccaps = [[_ctx request] clientCapabilities];
467 if ([ccaps isFastTableBrowser] && ![ccaps isTextModeBrowser])
473 nestedState = [self saveNestedStateInContext:_ctx];
475 /* collect & process keys (= available tabs) */
477 [_ctx appendElementIDComponent:@"h"];
479 activeKey = [self->selection stringValueInComponent:cmp];
480 keys = [self collectKeysInContext:_ctx];
482 if (![[keys valueForKey:@"key"] containsObject:activeKey])
483 /* selection is not available in keys */
486 if ((activeKey == nil) && ([keys count] > 0)) {
487 /* no or invalid selection, use first key */
488 activeKey = [[keys objectAtIndex:0] key];
489 if ([self->selection isValueSettable])
490 [self->selection setValue:activeKey inComponent:[_ctx component]];
493 for (i = 0, count = [keys count]; i < count; i++) {
494 WEPageItemInfo *info;
496 info = [keys objectAtIndex:i];
498 if ([info->key isEqualToString:activeKey]) {
504 /* start appending */
506 [_response appendContentString:
507 @"<table border=\"0\" width=\"100%\""
508 @" cellpadding=\"0\" cellspacing=\"0\">"];
510 /* generate header row */
512 bgcolor = [self->titleColor stringValueInComponent:cmp];
514 [_response appendContentString:@"<tr>"];
516 /* left navigation */
517 WEAppendTD(_response, @"left", nil, bgcolor);
518 [self appendNavToResponse:_response
523 [_response appendContentString:@"</td>"];
527 WEAppendTD(_response, @"center", nil, bgcolor);
529 unsigned char buf[256];
530 NSString *tC, *tF, *tS; // text font attrtibutes
533 cmp = [_ctx component];
536 tC = [self->fontColor stringValueInComponent:cmp];
537 tF = [self->fontFace stringValueInComponent:cmp];
538 tS = [self->fontSize stringValueInComponent:cmp];
540 hasFont = (tC || tF || tS) ? YES : NO;
543 WEAppendFont(_response, tC, tF, tS);
545 for (i = 0, count = [keys count]; i < count; i++) {
546 WEPageItemInfo *info;
548 info = [keys objectAtIndex:i];
551 if ([info->key isEqualToString:activeKey]) {
552 [_response appendContentString:@"<b>"];
553 [_response appendContentString:info->title];
554 [_response appendContentString:@"</b>"];
559 sprintf((char *)buf, " <small>(%d/%d)</small>", (i + 1), count);
560 [_response appendContentCString:buf];
563 [_response appendContentString:@"</font>"];
565 [_response appendContentString:@"</td>"];
567 /* right navigation */
569 WEAppendTD(_response, @"right", nil, bgcolor);
570 [self appendNavToResponse:_response
575 [_response appendContentString:@"</td>"];
577 [_response appendContentString:@"</tr>"];
578 [_ctx deleteLastElementIDComponent]; // delete "h"
579 [_ctx removeObjectForKey:WEPageView_HEAD];
583 bgcolor = [self->contentColor stringValueInComponent:cmp];
586 [_response appendContentString:@"<tr><td colspan=\"3\""];
588 [_response appendContentString:@" bgcolor=\""];
589 [_response appendContentHTMLAttributeValue:bgcolor];
590 [_response appendContentString:@"\""];
592 [_response appendContentString:@">"];
595 /* start padding table */
596 [_response appendContentString:
597 @"<table border=\"0\" width=\"100%\""
598 @" cellpadding=\"10\" cellspacing=\"0\">"];
599 [_response appendContentString:@"<tr><td>"];
602 [_ctx appendElementIDComponent:@"b"];
604 /* generate currently active page */
607 [_ctx setObject:activeKey forKey:WEPageView_BODY];
608 [self->template appendToResponse:_response inContext:_ctx];
609 [_ctx removeObjectForKey:WEPageView_BODY];
612 [_ctx deleteLastElementIDComponent];
615 /* close padding table */
616 [_response appendContentString:@"</td></tr></table>"];
618 [_response appendContentString:@"</td></tr>"];
620 [_response appendContentString:@"</table>"];
622 [_ctx removeObjectForKey:WEPageView_ACTIVEKEY];
623 [_ctx removeObjectForKey:WEPageView_KEYS];
624 [self restoreNestedState:nestedState inContext:_ctx];
626 [self removeConfigInContext:_ctx];
629 @end /* WEPageView */
631 @implementation WEPageItem
634 return [super version] + 0;
637 - (id)initWithName:(NSString *)_name
638 associations:(NSDictionary *)_config
639 template:(WOElement *)_subs
641 if ((self = [super initWithName:_name associations:_config template:_subs])) {
642 self->key = WOExtGetProperty(_config, @"key");
643 self->title = WOExtGetProperty(_config, @"title");
644 self->action = WOExtGetProperty(_config, @"action");
646 self->template = RETAIN(_subs);
652 [self->action release];
653 [self->title release];
655 [self->template release];
661 - (void)takeValuesFromRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
664 if ((tmp = [_ctx objectForKey:WEPageView_BODY])) {
665 NSString *activeTabKey;
668 activeTabKey = [tmp stringValueInComponent:[_ctx component]];
669 myTabKey = [self->key stringValueInComponent:[_ctx component]];
671 if ([activeTabKey isEqualToString:myTabKey]) {
672 [_ctx appendElementIDComponent:activeTabKey];
675 [[_ctx component] debugWithFormat:
676 @"WEPageItem: body takes values, eid='%@'",
680 [self->template takeValuesFromRequest:_request inContext:_ctx];
681 [_ctx deleteLastElementIDComponent];
685 [[_ctx component] debugWithFormat:
686 @"WEPageItem: body takes no values, eid='%@'",
695 k = [self->key stringValueInComponent:[_ctx component]];
697 eid = [[_ctx elementID] stringByAppendingString:@"."];
698 eid = [eid stringByAppendingString:k];
700 if (k && [_request formValueForKey:[eid stringByAppendingString:@".x"]]) {
701 [_ctx addActiveFormElement:self];
702 [_ctx setRequestSenderID:eid];
707 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
712 tabkey = [_ctx currentElementID];
713 [_ctx consumeElementID];
714 [_ctx appendElementIDComponent:tabkey];
716 if ((tmp = [_ctx objectForKey:WEPageView_HEAD])) {
717 /* click on tab icon */
718 if ([tmp isValueSettable])
719 [tmp setValue:tabkey inComponent:[_ctx component]];
721 result = [self->action valueInComponent:[_ctx component]];
723 else if ((tmp = [_ctx objectForKey:WEPageView_BODY])) {
724 /* clicked somewhere in the body */
725 if ([tmp isValueSettable])
726 [tmp setValue:tabkey inComponent:[_ctx component]];
728 result = [self->template invokeActionForRequest:_rq inContext:_ctx];
731 [[_ctx component] debugWithFormat:@"WEPageItem: invalid invoke state"];
732 result = [self->template invokeActionForRequest:_rq inContext:_ctx];
735 [_ctx deleteLastElementIDComponent];
739 - (void)appendHead:(NSString *)tmp
740 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
742 NSMutableArray *keys;
743 WEPageItemInfo *info;
747 if (![tmp isEqual:WEPageView_COLLECT])
752 cmp = [_ctx component];
753 keys = [_ctx objectForKey:WEPageView_KEYS];
755 keys = [NSMutableArray arrayWithCapacity:8];
756 [_ctx setObject:keys forKey:WEPageView_KEYS];
759 if ((k = [self->key stringValueInComponent:[_ctx component]]) == nil) {
760 /* auto-assign a key */
762 sprintf(kb, "%d", [keys count]);
763 k = [NSString stringWithCString:kb];
765 [_ctx appendElementIDComponent:k];
767 info = [[WEPageItemInfo alloc] init];
768 info->key = [k copy];
769 info->title = [[self->title stringValueInComponent:cmp] copy];
770 info->uri = [[_ctx componentActionURL] copy];
772 [keys addObject:info];
775 [_ctx deleteLastElementIDComponent];
778 - (void)appendBody:(NSString *)tmp
779 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
783 k = [self->key stringValueInComponent:[_ctx component]];
784 if (![tmp isEqualToString:k])
787 /* content is active or used as layer*/
788 [_ctx appendElementIDComponent:k];
790 [self debugWithFormat:@"PAGE: %@", k];
792 [self->template appendToResponse:_response inContext:_ctx];
793 [_ctx deleteLastElementIDComponent];
796 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
799 if ((tmp = [_ctx objectForKey:WEPageView_HEAD]))
800 [self appendHead:tmp toResponse:_response inContext:_ctx];
801 else if ((tmp = [_ctx objectForKey:WEPageView_BODY]))
802 [self appendBody:tmp toResponse:_response inContext:_ctx];
804 [_response appendContentString:@"[invalid pageview state]"];
807 @end /* WEPageItem */
809 @implementation WEPageItemInfo
813 [self->title release];
821 - (NSString *)title {
828 @end /* WEPageItemInfo */