2 Copyright (C) 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
22 #include <SOGoUI/UIxComponent.h>
24 @interface UIxMailTree : UIxComponent
26 NSString *rootClassName;
27 NSString *treeFolderAction;
33 #include "UIxMailTreeBlock.h"
34 #include <SoObjects/Mailer/SOGoMailBaseObject.h>
35 #include <SoObjects/Mailer/SOGoMailAccount.h>
37 #include <NGObjWeb/SoComponent.h>
38 #include <NGObjWeb/SoObject+SoDAV.h>
41 Support special icons:
42 tbtv_leaf_corner_17x17.gif
49 @implementation UIxMailTree
51 static BOOL debugBlocks = NO;
54 [UIxMailTreeBlock class]; // ensure that globals are initialized
58 [self->treeFolderAction release];
59 [self->rootClassName release];
60 [self->rootNodes release];
67 - (NSString *)defaultIconName {
68 return @"tbtv_leaf_corner_17x17.gif";
71 - (NSString *)iconNameForType:(NSString *)_type {
72 if (![_type isNotNull])
73 return [self defaultIconName];
75 //return @"tbtv_drafts_17x17.gif";
77 return [self defaultIconName];
82 - (void)setRootClassName:(id)_rootClassName {
83 ASSIGNCOPY(self->rootClassName, _rootClassName);
86 return self->rootClassName;
89 - (void)setItem:(id)_item {
90 ASSIGN(self->item, _item);
96 - (void)setTreeFolderAction:(NSString *)_action {
97 ASSIGNCOPY(self->treeFolderAction, _action);
99 - (NSString *)treeFolderAction {
100 return self->treeFolderAction;
103 - (NSString *)itemIconName {
104 // TODO: only called once!
107 ftype = [[self item] valueForKey:@"outlookFolderClass"];
108 return [self iconNameForType:ftype];
111 /* fetching subfolders */
113 - (NSArray *)fetchSubfoldersOfObject:(id)_object {
114 /* Walk over toManyRelationshipKeys and lookup the controllers for them. */
119 if ((names = [_object toManyRelationshipKeys]) == nil) {
120 if (debugBlocks) [self logWithFormat:@"no to-many: %@", _object];
125 [self logWithFormat:@"to-many: %@ %@", _object,
126 [names componentsJoinedByString:@","]];
129 count = [names count];
130 ma = [NSMutableArray arrayWithCapacity:(count + 1)];
131 for (i = 0; i < count; i++) {
134 folder = [_object lookupName:[names objectAtIndex:i] inContext:nil
138 if ([folder isKindOfClass:[NSException class]])
141 [ma addObject:folder];
146 /* navigation nodes */
148 - (BOOL)isRootObject:(id)_object {
149 if (![_object isNotNull]) {
150 [self warnWithFormat:@"(%s): got to root by nil lookup ...",
151 __PRETTY_FUNCTION__];
155 if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
158 return [_object isKindOfClass:NSClassFromString([self rootClassName])];
161 - (NSString *)treeNavigationLinkForObject:(id)_object atDepth:(int)_depth {
165 link = [[_object nameInContainer] stringByAppendingString:@"/"];
166 link = [link stringByAppendingString:[self treeFolderAction]];
170 case 1: return [@"../" stringByAppendingString:link];
171 case 2: return [@"../../" stringByAppendingString:link];
172 case 3: return [@"../../../" stringByAppendingString:link];
175 for (i = 0; i < _depth; i++)
176 link = [@"../" stringByAppendingString:link];
180 - (void)getTitle:(NSString **)_t andIcon:(NSString **)_icon
181 forObject:(id)_object
183 // TODO: need to refactor for reuse!
187 ftype = [_object valueForKey:@"outlookFolderClass"];
188 len = [ftype length];
192 if ([ftype isEqualToString:@"IPF.Sent"]) {
193 *_t = [self labelForKey:@"SentFolderName"];
194 *_icon = @"tbtv_sent_17x17.gif";
199 if ([ftype isEqualToString:@"IPF.Inbox"]) {
200 *_t = [self labelForKey:@"InboxFolderName"];
201 *_icon = @"tbtv_inbox_17x17.gif";
204 if ([ftype isEqualToString:@"IPF.Trash"]) {
205 *_t = [self labelForKey:@"TrashFolderName"];
206 *_icon = @"tbtv_trash_17x17.gif";
211 if ([ftype isEqualToString:@"IPF.Drafts"]) {
212 *_t = [self labelForKey:@"DraftsFolderName"];
213 *_icon = @"tbtv_drafts_17x17.gif";
216 if ([ftype isEqualToString:@"IPF.Filter"]) {
217 *_t = [self labelForKey:@"SieveFolderName"];
224 *_t = [_object davDisplayName];
227 if ([_object isKindOfClass:NSClassFromString(@"SOGoMailFolder")])
229 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccount")])
230 *_icon = @"tbtv_inbox_17x17.gif";
231 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccounts")])
232 *_icon = @"tbtv_inbox_17x17.gif";
233 else if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
234 *_icon = @"tbtv_inbox_17x17.gif";
236 // TODO: use drafts icon for other SOGo folders
237 *_icon = @"tbtv_drafts_17x17.gif";
243 - (UIxMailTreeBlock *)treeNavigationBlockForLeafNode:(id)_o atDepth:(int)_d {
244 UIxMailTreeBlock *md;
249 Trigger plus in treeview if it has subfolders. It is an optimization that
250 we do not generate blocks for folders which are not displayed anyway.
252 blocks = [[_o toManyRelationshipKeys] count] > 0
253 ? UIxMailTreeHasChildrenMarker
256 [self getTitle:&n andIcon:&i forObject:_o];
258 md = [UIxMailTreeBlock blockWithName:nil
260 link:[self treeNavigationLinkForObject:_o atDepth:_d]
261 isPathNode:NO isActiveNode:NO
266 - (UIxMailTreeBlock *)treeNavigationBlockForRootNode:(id)_object {
268 This generates the block for the root object (root of the tree, we get
269 there by walking up the chain starting with the client object).
271 UIxMailTreeBlock *md;
272 NSMutableArray *blocks;
274 NSString *title, *icon;
278 [self logWithFormat:@"block for root node 0x%08X<%@>",
279 _object, NSStringFromClass([_object class])];
282 /* process child folders */
284 folders = [self fetchSubfoldersOfObject:_object];
285 count = [folders count];
286 blocks = [NSMutableArray arrayWithCapacity:count];
287 for (i = 0; i < count; i++) {
290 block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
292 if ([block isNotNull]) [blocks addObject:block];
294 if ([blocks count] == 0)
299 [self getTitle:&title andIcon:&icon forObject:_object];
300 md = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
301 title:title iconName:icon
302 link:[@"../" stringByAppendingString:
303 [_object nameInContainer]]
304 isPathNode:YES isActiveNode:YES
309 - (UIxMailTreeBlock *)treeNavigationBlockForActiveNode:(id)_object {
311 This generates the block for the clientObject (the object which has the
314 UIxMailTreeBlock *md;
315 NSMutableArray *blocks;
317 NSString *title, *icon;
320 // TODO: maybe we can join the two implementations, this might not be
322 if ([self isRootObject:_object]) /* we are at the top */
323 return [self treeNavigationBlockForRootNode:_object];
326 [self logWithFormat:@"block for active node 0x%08X<%@> - %@",
327 _object, NSStringFromClass([_object class]),
328 [_object davDisplayName]];
331 /* process child folders */
333 folders = [self fetchSubfoldersOfObject:_object];
334 count = [folders count];
335 blocks = [NSMutableArray arrayWithCapacity:count];
336 for (i = 0; i < count; i++) {
337 UIxMailTreeBlock *block;
339 block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
341 if ([block isNotNull]) [blocks addObject:block];
343 if ([blocks count] == 0) blocks = nil;
347 [self getTitle:&title andIcon:&icon forObject:_object];
348 md = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
349 title:title iconName:icon
351 isPathNode:YES isActiveNode:YES
356 - (UIxMailTreeBlock *)treeNavigationBlockForObject:(id)_object
357 withActiveChildBlock:(UIxMailTreeBlock *)_activeChildBlock
361 Note: 'activeChildBlock' here doesn't mean that the block is the selected
362 folder in the tree. Its just the element which is active in the
365 UIxMailTreeBlock *resultBlock;
366 NSMutableArray *blocks;
367 NSString *activeName;
369 NSString *title, *icon;
372 activeName = [_activeChildBlock valueForKey:@"name"];
374 /* process child folders */
376 folders = [self fetchSubfoldersOfObject:_object];
377 count = [folders count];
378 blocks = [NSMutableArray arrayWithCapacity:count == 0 ? 1 : count];
379 for (i = 0; i < count; i++) {
380 UIxMailTreeBlock *block;
383 folder = [folders objectAtIndex:i];
384 block = [activeName isEqualToString:[folder nameInContainer]]
386 : [self treeNavigationBlockForLeafNode:folder atDepth:_depth];
388 if ([block isNotNull]) [blocks addObject:block];
390 if ([blocks count] == 0) {
391 if (_activeChildBlock != nil) // if the parent has no proper fetchmethod!
392 [blocks addObject:_activeChildBlock];
399 [self getTitle:&title andIcon:&icon forObject:_object];
400 resultBlock = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
401 title:title iconName:icon
403 [self treeNavigationLinkForObject:_object
404 atDepth:(_depth + 1)]
405 isPathNode:YES isActiveNode:NO
408 /* recurse up unless we are at the root */
410 if ([self isRootObject:_object]) /* we are at the top */
413 return [self treeNavigationBlockForObject:[_object container]
414 withActiveChildBlock:resultBlock
418 - (UIxMailTreeBlock *)buildNavigationNodesForObject:(id)_object {
420 This is the top-level 'flattening' method. The _object is the active
421 object in the tree, that is, usually a "current folder".
424 all subfolders of the current folder,
425 all parent folders of the current folder up to some root,
426 all siblings along the parent chain.
428 UIxMailTreeBlock *block;
431 This is the cursor, we create nodes below that for direct subfolders
433 if (debugBlocks) [self logWithFormat:@"ACTIVE block ..."];
434 block = [self treeNavigationBlockForActiveNode:_object];
435 if (debugBlocks) [self logWithFormat:@" ACTIVE block: %@", block];
437 if ([self isRootObject:_object]) {
438 if (debugBlocks) [self logWithFormat:@" active block is root."];
443 The following returns the root block. It calculates the chain up to the
444 root folder starting with the parent of the current object.
446 if (debugBlocks) [self logWithFormat:@"ACTIVE parent block ..."];
447 block = [self treeNavigationBlockForObject:[_object container]
448 withActiveChildBlock:block
450 if (debugBlocks) [self logWithFormat:@"done: %@", block];
456 - (NSArray *)rootNodes {
457 UIxMailTreeBlock *navNode;
459 if (self->rootNodes != nil)
460 return self->rootNodes;
462 navNode = [self buildNavigationNodesForObject:[self clientObject]];
464 if ([navNode hasChildren] && [navNode areChildrenLoaded])
465 self->rootNodes = [[navNode children] retain];
467 self->rootNodes = [[NSArray alloc] initWithObjects:&navNode count:1];
469 return self->rootNodes;
475 [self->item release]; self->item = nil;
476 [self->rootNodes release]; self->rootNodes = nil;
480 @end /* UIxMailTree */