/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOPE; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
/*
WETreeView
A WETreeView is very similiar to a WETableView (eg it can have arbitary
columns), but can also show/manage a tree of objects.
TODO: we should support a cookie to store the tree hiearchy for stateless
servers like the Zope tree. This probably needs to be implemented in
takeValues (cookie decoding) and appendToResponse (cookie encoding).
Zooming (invokeAction) needs to ignore? the component-id in case the
cookie is set and somehow work based on cookie data.
TODO: we need to support CSS.
WETreeView associations:
list & sublist & (item | index | currentPath)
itemIsLeaf
showItem
zoom
string
noTable
// config:
iconWidth
plusIcon
minusIcon
leafIcon
junctionIcon
cornerIcon
cornerPlusIcon
cornerMinusIcon
leafCornerIcon
lineIcon
WETreeHeader associations:
isTreeElement
icon
cornerIcon
title
string
WETreeData associations:
isTreeElement
icon
cornerIcon
title
string
Example:
TestTree.wod:
--- snip ---
TestTree: WETreeView {
list = rootList;
item = item;
index = index;
sublist = item.sublist;
zoom = treeState.isExpanded; // take a look at LSWTreeState !!!
// if you leave out *zoom*, the tree is rendered full expanded
// and without plus and minus icons
// if no icons are specified, the tree replaces these icons with
// ascii characters (that style is supposed to be ugly :-)
// icon config
iconWidth = "13"; // every icon's width should be equal to "13"
plusIcon = "plus.gif";
minusIcon = "minus.gif";
leafIcon = "leaf.gif";
junctionIcon = "junction.gif";
cornerIcon = "corner.gif";
cornerPlusIcon = "corner_plus.gif";
cornerMinusIcon = "corner_miunus.gif";
leafCornerIcon = "leaf_corner.gif";
lineIcon = "line.gif";
}
TreeDataCell: WETreeData {
isTreeElement = YES; // this is a tree cell (that means, it has plus
// and minus icons and all that stuff)
}
DataCell: WETreeData {
isTreeElement = NO; // this is NOT a tree cell, i.e. it does NOT
// have any plus or minus icons. (This is just a
// ordinary
| !!!)
}
TreeHeaderCell: WETreeHeader {
isTreeElement = YES;
}
HeaderCell: WETreeHeader {
isTreeElement = NO;
}
--- snap ---
TestTree.html:
--- snip ---
<#TestTree>
<#TreeHeaderCell>some title#TreeHeaderCell>
<#HeaderCell">some title#HeaderCell>
<#HeaderCell">some title#HeaderCell>
<#TreeDataCell">some content#TreeDataCell>
<#DataCell">some content#DataCell>
<#DataCell">some content#DataCell>
#TestTree>
--- snap ---
TestTree.wox
---snip---
treecell
first name
---snap---
*/
#include
@class NSMutableArray;
@interface WETreeView : WODynamicElement
{
// WODynamicElement: extraAttributes
// WODynamicElement: otherTagString
@protected
WOAssociation *list; // array of objects to iterate through
WOAssociation *item; // current item in the array
WOAssociation *sublist; // sub list of item
WOAssociation *itemIsLeaf; // hh-optimization
WOAssociation *index; // current index
WOAssociation *zoom; // show sub list of item (BOOL)
WOAssociation *currentPath; //
WOAssociation *showItem; // show current item
WOAssociation *noTable; // render no TABLE (BOOL)
// config:
WOAssociation *plusIcon;
WOAssociation *minusIcon;
WOAssociation *leafIcon;
WOAssociation *junctionIcon;
WOAssociation *cornerIcon;
WOAssociation *cornerPlusIcon;
WOAssociation *cornerMinusIcon;
WOAssociation *leafCornerIcon;
WOAssociation *lineIcon;
WOAssociation *spaceIcon;
WOAssociation *iconWidth;
// private:
NSMutableArray *matrix;
WOElement *template;
}
@end /* WETreeView */
#include "WETreeContextKeys.h"
#include "WETreeMatrixElement.h"
#include "common.h"
#include
NSString *WETreeView_HEADER_MODE = @"WETreeView_HEADER_MODE";
NSString *WETreeView_ZOOM_ACTION_ID = @"_";
NSString *WETreeView_TreeElement = @"WETreeView_TreeElement";
NSString *WETreeView_RenderNoTable = @"WETreeView_RenderNoTable";
NSString *WETreeView_IconWidth = @"WETreeView_IconWidth";
NSString *WETreeView_Plus = @"WETreeView_Plus";
NSString *WETreeView_Minus = @"WETreeView_Minus";
NSString *WETreeView_Leaf = @"WETreeView_Leaf";
NSString *WETreeView_Line = @"WETreeView_Line";
NSString *WETreeView_Junction = @"WETreeView_Junction";
NSString *WETreeView_Corner = @"WETreeView_Corner";
NSString *WETreeView_CornerPlus = @"WETreeView_CornerPlus";
NSString *WETreeView_CornerMinus = @"WETreeView_CornerMinus";
NSString *WETreeView_CornerLeaf = @"WETreeView_CornerLeaf";
NSString *WETreeView_Space = @"WETreeView_Space";
@implementation WETreeView
- (id)initWithName:(NSString *)_name
associations:(NSDictionary *)_config
template:(WOElement *)_t
{
if ((self = [super initWithName:_name associations:_config template:_t])) {
self->list = WOExtGetProperty(_config, @"list");
self->item = WOExtGetProperty(_config, @"item");
self->index = WOExtGetProperty(_config, @"index");
self->sublist = WOExtGetProperty(_config, @"sublist");
self->itemIsLeaf = WOExtGetProperty(_config, @"itemIsLeaf");
self->zoom = WOExtGetProperty(_config, @"zoom");
self->currentPath = WOExtGetProperty(_config, @"currentPath");
self->showItem = WOExtGetProperty(_config, @"showItem");
self->noTable = WOExtGetProperty(_config, @"noTable");
// config
self->plusIcon = WOExtGetProperty(_config, @"plusIcon");
self->minusIcon = WOExtGetProperty(_config, @"minusIcon");
self->leafIcon = WOExtGetProperty(_config, @"leafIcon");
self->junctionIcon = WOExtGetProperty(_config, @"junctionIcon");
self->cornerIcon = WOExtGetProperty(_config, @"cornerIcon");
self->cornerPlusIcon = WOExtGetProperty(_config, @"cornerPlusIcon");
self->cornerMinusIcon = WOExtGetProperty(_config, @"cornerMinusIcon");
self->leafCornerIcon = WOExtGetProperty(_config, @"leafCornerIcon");
self->lineIcon = WOExtGetProperty(_config, @"lineIcon");
self->spaceIcon = WOExtGetProperty(_config, @"spaceIcon");
self->iconWidth = WOExtGetProperty(_config, @"iconWidth");
self->template = [_t retain];
}
return self;
}
- (void)dealloc {
[self->itemIsLeaf release];
[self->sublist release];
[self->list release];
[self->item release];
[self->index release];
[self->zoom release];
[self->currentPath release];
[self->showItem release];
[self->noTable release];
[self->plusIcon release];
[self->minusIcon release];
[self->leafIcon release];
[self->junctionIcon release];
[self->cornerIcon release];
[self->cornerPlusIcon release];
[self->cornerMinusIcon release];
[self->leafCornerIcon release];
[self->lineIcon release];
[self->spaceIcon release];
[self->iconWidth release];
[self->template release];
[self->matrix release];
[super dealloc];
}
- (void)updateConfigInContext:(WOContext *)_ctx {
NSString *tmp;
WOComponent *cmp;
cmp = [_ctx component];
// TODO: replace the macro with methods?
#define SetConfigInContext(_a_, _key_) \
if (_a_ && (tmp = [_a_ valueInComponent:cmp])) \
[_ctx setObject:tmp forKey:_key_]; \
SetConfigInContext(self->plusIcon, WETreeView_Plus);
SetConfigInContext(self->minusIcon, WETreeView_Minus);
SetConfigInContext(self->leafIcon, WETreeView_Leaf);
SetConfigInContext(self->junctionIcon, WETreeView_Junction);
SetConfigInContext(self->cornerIcon, WETreeView_Corner);
SetConfigInContext(self->cornerPlusIcon, WETreeView_CornerPlus);
SetConfigInContext(self->cornerMinusIcon, WETreeView_CornerMinus);
SetConfigInContext(self->leafCornerIcon, WETreeView_CornerLeaf);
SetConfigInContext(self->lineIcon, WETreeView_Line);
SetConfigInContext(self->spaceIcon, WETreeView_Space);
SetConfigInContext(self->iconWidth, WETreeView_IconWidth);
#undef SetConfigInContext
}
- (void)removeConfigInContext:(WOContext *)_ctx {
[_ctx removeObjectForKey:WETreeView_Plus];
[_ctx removeObjectForKey:WETreeView_Minus];
[_ctx removeObjectForKey:WETreeView_Leaf];
[_ctx removeObjectForKey:WETreeView_Junction];
[_ctx removeObjectForKey:WETreeView_Corner];
[_ctx removeObjectForKey:WETreeView_CornerPlus];
[_ctx removeObjectForKey:WETreeView_CornerMinus];
[_ctx removeObjectForKey:WETreeView_CornerLeaf];
[_ctx removeObjectForKey:WETreeView_Line];
[_ctx removeObjectForKey:WETreeView_Space];
[_ctx removeObjectForKey:WETreeView_IconWidth];
}
- (id)_toggleZoomInContext:(WOContext *)_ctx {
WOComponent *component = [_ctx component];
if ([self->zoom isValueSettable]) {
BOOL isZoom;
isZoom = [self->zoom boolValueInComponent:component];
[self->zoom setBoolValue:!isZoom inComponent:component];
}
return nil;
}
/* OWResponder */
- (NSArray *)_sublistInContext:(WOContext *)_ctx {
NSArray *a;
if (self->sublist == nil)
return nil;
if (self->itemIsLeaf) {
if ([self->itemIsLeaf boolValueInComponent:[_ctx component]])
return nil;
}
if ((a = [self->sublist valueInComponent:[_ctx component]]) == nil)
return nil;
return ([a count] > 0) ? a : nil;
}
- (void)_takeValuesFromRequest:(WORequest *)_req
inContext:(WOContext *)_ctx
withArray:(NSArray *)array
depth:(int)_depth
{
WOComponent *cmp;
int i, cnt;
#if DEBUG
#if 1
if (!(_depth <= MAX_TREE_DEPTH-1)) {
NSLog(@"ERROR[%s]: WETreeView takeValuesFromRequest: max."
@"recursion depth is %d",
__PRETTY_FUNCTION__, MAX_TREE_DEPTH-1);
return;
}
#else
NSAssert1((_depth <= MAX_TREE_DEPTH-1),
@"WETreeView takeValuesFromRequest: max. recursion depth is %d",
MAX_TREE_DEPTH-1);
#endif
#endif
cmp = [_ctx component];
cnt = [array count];
[_ctx appendZeroElementIDComponent]; // append index
for (i = 0; i < cnt; i++) {
NSArray *subArray;
if ([self->index isValueSettable])
[self->index setUnsignedIntValue:i inComponent:cmp];
if ([self->item isValueSettable])
[self->item setValue:[array objectAtIndex:i] inComponent:cmp];
if (self->showItem && ![self->showItem boolValueInComponent:cmp])
continue;
if (self->zoom == nil || [self->zoom boolValueInComponent:cmp])
subArray = [self _sublistInContext:_ctx];
else
subArray = nil;
if (subArray)
[self _takeValuesFromRequest:_req
inContext:_ctx
withArray:subArray
depth:_depth+1];
else
[self->template takeValuesFromRequest:_req inContext:_ctx];
[_ctx incrementLastElementIDComponent];
}
[_ctx deleteLastElementIDComponent]; // delete index
}
- (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
NSArray *array;
array = [self->list valueInComponent:[_ctx component]];
[self _takeValuesFromRequest:_req inContext:_ctx withArray:array depth:0];
}
- (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
WOComponent *sComponent;
id result = nil;
id idxId = nil;
id object = nil;
NSMutableArray *stack = nil;
NSArray *array;
unsigned idCount = 0;
sComponent = [_ctx component];
array = [self->list valueInComponent:sComponent];
if ([array count] < 1) return nil;
stack = [NSMutableArray arrayWithCapacity:8];
idxId = [_ctx currentElementID]; // top level index
idCount = 0;
if ([idxId isEqualToString:@"h"]) {
[_ctx setObject:@"YES" forKey:WETreeView_HEADER_MODE];
[_ctx appendElementIDComponent:@"h"];
[_ctx consumeElementID];
result = [self->template invokeActionForRequest:_rq inContext:_ctx];
[_ctx deleteLastElementIDComponent];
[_ctx removeObjectForKey:WETreeView_HEADER_MODE];
return result;
}
while ((![idxId isEqualToString:@"end"]) && (idxId != nil) &&
(array != nil)) {
unsigned idx = [idxId unsignedIntValue];
object = [array objectAtIndex:idx];
[stack addObject:object];
if ([self->index isValueSettable])
[self->index setUnsignedIntValue:idx inComponent:sComponent];
if ([self->item isValueSettable])
[self->item setValue:object inComponent:sComponent];
if ([self->currentPath isValueSettable])
[self->currentPath setValue:stack inComponent:sComponent];
array = [self->sublist valueInComponent:sComponent];
[_ctx appendElementIDComponent:idxId]; idCount++;
idxId = [_ctx consumeElementID]; // sub level index
}
if ([idxId isEqualToString:@"end"]) {
[_ctx appendElementIDComponent:idxId]; idCount++;
idxId = [_ctx consumeElementID];
}
result = ([[_ctx senderID] hasSuffix:WETreeView_ZOOM_ACTION_ID])
? [self _toggleZoomInContext:_ctx]
: [self->template invokeActionForRequest:_rq inContext:_ctx];
/* remove element-ids */
for (; idCount > 0; idCount--)
[_ctx deleteLastElementIDComponent];
return result;
}
- (void)appendList:(NSArray *)_list
treeElement:(_WETreeMatrixElement *)_element
inContext:(WOContext *)_ctx
{
/* TODO: split up this method! */
WOComponent *comp;
unsigned i, cnt;
comp = [_ctx component];
cnt = [_list count];
#if DEBUG
#if 1
if (!([_element depth] <= MAX_TREE_DEPTH-1)) {
NSLog(@"ERROR[%s]: WETreeView takeValuesFromRequest: max."
@"recursion depth is %d",
__PRETTY_FUNCTION__, MAX_TREE_DEPTH-1);
return;
}
#else
NSAssert1(([_element depth] <= MAX_TREE_DEPTH-1),
@"WETreeView appendToResponse: max. recursion depth is %d",
MAX_TREE_DEPTH-1);
#endif
#endif
for (i = 0; i < cnt; i++) {
id object = [_list objectAtIndex:i];
[_element setIndex:i];
[_element setItem:object];
if ([self->index isValueSettable])
[self->index setUnsignedIntValue:i inComponent:comp];
if ([self->item isValueSettable])
[self->item setValue:object inComponent:comp];
if ([self->currentPath isValueSettable])
[self->currentPath setValue:[_element currentPath] inComponent:comp];
if (self->showItem && ![self->showItem boolValueInComponent:comp])
continue;
{
BOOL isLast;
BOOL isLeaf;
BOOL isZoom = YES;
NSArray *sl;
if (self->itemIsLeaf) {
isLeaf = [self->itemIsLeaf boolValueInComponent:comp];
isZoom = (self->zoom)
? [self->zoom boolValueInComponent:comp]
: YES;
sl = (!isLeaf && isZoom)
? [self _sublistInContext:_ctx]
: nil;
}
else {
sl = [self _sublistInContext:_ctx];
isLeaf = !([sl count] > 0);
}
if (self->showItem) {
if (i == (cnt-1))
isLast = YES;
else {
id obj;
unsigned k;
isLast = YES;
for (k = (i + 1); k < cnt; k++) {
obj = [_list objectAtIndex:k];
[_element setIndex:k];
[_element setItem:obj];
if ([self->index isValueSettable])
[self->index setUnsignedIntValue:k inComponent:comp];
if ([self->item isValueSettable])
[self->item setValue:obj inComponent:comp];
if ([self->currentPath isValueSettable])
[self->currentPath setValue:[_element currentPath]
inComponent:comp];
if ([self->showItem boolValueInComponent:comp]) {
isLast = NO;
break;
}
}
[_element setIndex:i];
[_element setItem:object];
if ([self->index isValueSettable])
[self->index setUnsignedIntValue:i inComponent:comp];
if ([self->item isValueSettable])
[self->item setValue:object inComponent:comp];
if ([self->currentPath isValueSettable])
[self->currentPath setValue:[_element currentPath]
inComponent:comp];
}
}
else
isLast = (i == (cnt-1));
if (!isLeaf) { // not a leaf
_WETreeMatrixElement *newElement;
if (self->zoom == nil) {
[_element setElement:(isLast)
? WETreeView_Corner
: WETreeView_Junction];
}
else {
isZoom = [self->zoom boolValueInComponent:comp];
if (isZoom) {
[_element setElement:(isLast)
? ([sl count]) ? WETreeView_CornerMinus : WETreeView_Corner
: ([sl count]) ? WETreeView_Minus : WETreeView_Junction];
}
else
[_element setElement:(isLast)
? WETreeView_CornerPlus
: WETreeView_Plus];
}
[_element setLeaf:(isZoom && [sl count])
? WETreeView_CornerLeaf
: WETreeView_Leaf];
newElement = [[_WETreeMatrixElement alloc] initWithElement:_element];
[self->matrix addObject:newElement];
[newElement release]; newElement = nil;
if (isZoom) {
[_element setElement:(isLast)
? WETreeView_Space
: WETreeView_Line];
newElement = [[_WETreeMatrixElement alloc] initWithElement:_element];
[self appendList:sl treeElement:newElement inContext:_ctx];
[newElement release]; newElement = nil;
}
}
else {
_WETreeMatrixElement *newElement;
[_element setElement: (isLast)
? WETreeView_Corner
: WETreeView_Junction];
newElement = [[_WETreeMatrixElement alloc] initWithElement:_element];
[newElement setLeaf:WETreeView_Leaf];
[self->matrix addObject:newElement];
[newElement release]; newElement = nil;
}
}
}
}
- (void)_calcMatrixInContext:(WOContext *)_ctx depth:(int *)_depth {
_WETreeMatrixElement *treeElm = nil;
NSArray *top = nil;
int i, cnt, d, maxDepth = 0;
top = [self->list valueInComponent:[_ctx component]];
[self->matrix release]; self->matrix = nil;
self->matrix = [[NSMutableArray allocWithZone:[self zone]]
initWithCapacity:64];
treeElm = [[_WETreeMatrixElement alloc] init];
[self appendList:top treeElement:treeElm inContext:_ctx];
[treeElm release]; treeElm = nil;
cnt = [self->matrix count];
/* calc max depth */
for (i = 0; i < cnt; i++) {
d = [[self->matrix objectAtIndex:i] depth];
maxDepth = (maxDepth < d) ? d : maxDepth;
}
/* update depth */
for (i = 0; i < cnt; i++) {
_WETreeMatrixElement *element;
element = [self->matrix objectAtIndex:i];
[element setColspan:maxDepth-[element depth]+1];
}
*_depth = maxDepth + 2;
}
- (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
WOComponent *comp;
BOOL doTable;
int i, cnt, depth;
[self updateConfigInContext:_ctx];
comp = [_ctx component];
/* check for browser */
if (self->noTable == nil) {
WEClientCapabilities *ccaps;
ccaps = [[_ctx request] clientCapabilities];
doTable = [ccaps isFastTableBrowser];
}
else
doTable = ![self->noTable boolValueInComponent:comp];
if (!doTable)
[_ctx setObject:@"1" forKey:WETreeView_RenderNoTable];
if (doTable) {
[_response appendContentString:
@"otherTagString) {
[_response appendContentCharacter:' '];
[_response appendContentString:
[self->otherTagString stringValueInComponent:
[_ctx component]]];
}
[_response appendContentCharacter:'>'];
}
[self _calcMatrixInContext:_ctx depth:&depth];
/* append table title */
if (doTable)
[_response appendContentString:@""];
[_ctx setObject:[NSNumber numberWithInt:depth]
forKey:WETreeView_HEADER_MODE];
[_ctx appendElementIDComponent:@"h"];
[self->template appendToResponse:_response inContext:_ctx];
[_ctx deleteLastElementIDComponent];
[_ctx removeObjectForKey:WETreeView_HEADER_MODE];
if (doTable)
[_response appendContentString:@"
"];
else if (_ctx->wcFlags.xmlStyleEmptyElements)
[_response appendContentString:@"
"];
else
[_response appendContentString:@"
"];
cnt = [self->matrix count];
for (i = 0; i < cnt; i++) {
_WETreeMatrixElement *element;
element = [self->matrix objectAtIndex:i];
if ([self->index isValueSettable])
[self->index setUnsignedIntValue:[element index]
inComponent:comp];
if ([self->item isValueSettable])
[self->item setValue:[element item]
inComponent:comp];
if ([self->currentPath isValueSettable])
[self->currentPath setValue:[element currentPath]
inComponent:comp];
[_ctx setObject:element forKey:WETreeView_TreeElement];
[_ctx appendElementIDComponent:[element elementID]];
[_ctx appendElementIDComponent:@"end"];
if (doTable)
[_response appendContentString:@""];
[self->template appendToResponse:_response inContext:_ctx];
if (doTable)
[_response appendContentString:@"
"];
else if (_ctx->wcFlags.xmlStyleEmptyElements)
[_response appendContentString:@"
"];
else
[_response appendContentString:@"
"];
[_ctx deleteLastElementIDComponent]; // delete "end"
[_ctx deleteLastElementIDComponent]; // delete eids
}
[_ctx removeObjectForKey:WETreeView_TreeElement];
if (doTable)
[_response appendContentString:@"
"];
[self removeConfigInContext:_ctx];
[_ctx removeObjectForKey:WETreeView_RenderNoTable];
[self->matrix release]; self->matrix = nil;
}
@end /* WETreeView */