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 @interface NSString(DotCutting)
51 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength;
53 - (NSString *)titleForSOGoIMAP4String;
57 @implementation UIxMailTree
59 static BOOL debugBlocks = NO;
62 [UIxMailTreeBlock class]; // ensure that globals are initialized
66 [self->treeFolderAction release];
67 [self->rootClassName release];
68 [self->rootNodes release];
75 - (NSString *)defaultIconName {
76 return @"tbtv_leaf_corner_17x17.gif";
79 - (NSString *)iconNameForType:(NSString *)_type {
80 if (![_type isNotNull])
81 return [self defaultIconName];
83 //return @"tbtv_drafts_17x17.gif";
85 return [self defaultIconName];
90 - (void)setRootClassName:(id)_rootClassName {
91 ASSIGNCOPY(self->rootClassName, _rootClassName);
94 return self->rootClassName;
97 - (void)setItem:(id)_item {
98 ASSIGN(self->item, _item);
104 - (void)setTreeFolderAction:(NSString *)_action {
105 ASSIGNCOPY(self->treeFolderAction, _action);
107 - (NSString *)treeFolderAction {
108 return self->treeFolderAction;
111 - (NSString *)itemIconName {
112 // TODO: only called once!
115 ftype = [[self item] valueForKey:@"outlookFolderClass"];
116 return [self iconNameForType:ftype];
119 /* fetching subfolders */
121 - (NSArray *)fetchSubfoldersOfObject:(id)_object {
122 /* Walk over toManyRelationshipKeys and lookup the controllers for them. */
127 if ((names = [_object toManyRelationshipKeys]) == nil) {
128 if (debugBlocks) [self logWithFormat:@"no to-many: %@", _object];
133 [self logWithFormat:@"to-many: %@ %@", _object,
134 [names componentsJoinedByString:@","]];
137 count = [names count];
138 ma = [NSMutableArray arrayWithCapacity:(count + 1)];
139 for (i = 0; i < count; i++) {
142 // TODO: use some context or reuse the main context?
143 folder = [_object lookupName:[names objectAtIndex:i] inContext:nil
147 [self logWithFormat:@" DID NOT FIND FOLDER %@: %@",
149 [names objectAtIndex:i]];
153 if ([folder isKindOfClass:[NSException class]]) {
155 [self logWithFormat:@" FOLDER LOOKUP EXCEPTION %@: %@",
156 [names objectAtIndex:i], folder];
161 [ma addObject:folder];
164 [self logWithFormat:@" returning: %@ %@", _object, ma];
168 /* navigation nodes */
170 - (BOOL)isRootObject:(id)_object {
171 if (![_object isNotNull]) {
172 [self warnWithFormat:@"(%s): got to root by nil lookup ...",
173 __PRETTY_FUNCTION__];
177 if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
180 return [_object isKindOfClass:NSClassFromString([self rootClassName])];
183 - (NSString *)treeNavigationLinkForObject:(id)_object atDepth:(int)_depth {
187 link = [[_object nameInContainer] stringByAppendingString:@"/"];
188 link = [link stringByAppendingString:[self treeFolderAction]];
192 case 1: return [@"../" stringByAppendingString:link];
193 case 2: return [@"../../" stringByAppendingString:link];
194 case 3: return [@"../../../" stringByAppendingString:link];
197 for (i = 0; i < _depth; i++)
198 link = [@"../" stringByAppendingString:link];
202 - (void)getTitle:(NSString **)_t andIcon:(NSString **)_icon
203 forObject:(id)_object
205 // TODO: need to refactor for reuse!
209 ftype = [_object valueForKey:@"outlookFolderClass"];
210 len = [ftype length];
214 if ([ftype isEqualToString:@"IPF.Sent"]) {
215 *_t = [self labelForKey:@"SentFolderName"];
216 *_icon = @"tbtv_sent_17x17.gif";
221 if ([ftype isEqualToString:@"IPF.Inbox"]) {
222 *_t = [self labelForKey:@"InboxFolderName"];
223 *_icon = @"tbtv_inbox_17x17.gif";
226 if ([ftype isEqualToString:@"IPF.Trash"]) {
227 *_t = [self labelForKey:@"TrashFolderName"];
228 *_icon = @"tbtv_trash_17x17.gif";
233 if ([ftype isEqualToString:@"IPF.Drafts"]) {
234 *_t = [self labelForKey:@"DraftsFolderName"];
235 *_icon = @"tbtv_drafts_17x17.gif";
238 if ([ftype isEqualToString:@"IPF.Filter"]) {
239 *_t = [self labelForKey:@"SieveFolderName"];
246 *_t = [_object davDisplayName];
249 if ([_object isKindOfClass:NSClassFromString(@"SOGoMailFolder")])
251 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccount")]) {
252 *_icon = @"tbtv_inbox_17x17.gif";
254 /* title processing is somehow Agenor specific and should be done in UI */
255 *_t = [[_object nameInContainer] titleForSOGoIMAP4String];
257 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccounts")])
258 *_icon = @"tbtv_inbox_17x17.gif";
259 else if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
260 *_icon = @"tbtv_inbox_17x17.gif";
262 // TODO: use drafts icon for other SOGo folders
263 *_icon = @"tbtv_drafts_17x17.gif";
269 - (UIxMailTreeBlock *)treeNavigationBlockForLeafNode:(id)_o atDepth:(int)_d {
270 UIxMailTreeBlock *md;
275 Trigger plus in treeview if it has subfolders. It is an optimization that
276 we do not generate blocks for folders which are not displayed anyway.
278 blocks = [[_o toManyRelationshipKeys] count] > 0
279 ? UIxMailTreeHasChildrenMarker
282 [self getTitle:&n andIcon:&i forObject:_o];
284 md = [UIxMailTreeBlock blockWithName:nil
286 link:[self treeNavigationLinkForObject:_o atDepth:_d]
287 isPathNode:NO isActiveNode:NO
292 - (UIxMailTreeBlock *)treeNavigationBlockForRootNode:(id)_object {
294 This generates the block for the root object (root of the tree, we get
295 there by walking up the chain starting with the client object).
297 UIxMailTreeBlock *md;
298 NSMutableArray *blocks;
300 NSString *title, *icon;
304 [self logWithFormat:@"block for root node 0x%08X<%@>",
305 _object, NSStringFromClass([_object class])];
308 /* process child folders */
310 folders = [self fetchSubfoldersOfObject:_object];
311 count = [folders count];
312 blocks = [NSMutableArray arrayWithCapacity:count];
313 for (i = 0; i < count; i++) {
316 block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
318 if ([block isNotNull]) [blocks addObject:block];
320 if ([blocks count] == 0)
325 [self getTitle:&title andIcon:&icon forObject:_object];
326 md = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
327 title:title iconName:icon
328 link:[@"../" stringByAppendingString:
329 [_object nameInContainer]]
330 isPathNode:YES isActiveNode:YES
335 - (UIxMailTreeBlock *)treeNavigationBlockForActiveNode:(id)_object {
337 This generates the block for the clientObject (the object which has the
340 UIxMailTreeBlock *md;
341 NSMutableArray *blocks;
343 NSString *title, *icon;
346 // TODO: maybe we can join the two implementations, this might not be
348 if ([self isRootObject:_object]) /* we are at the top */
349 return [self treeNavigationBlockForRootNode:_object];
352 [self logWithFormat:@"block for active node 0x%08X<%@> - %@",
353 _object, NSStringFromClass([_object class]),
354 [_object davDisplayName]];
357 /* process child folders */
359 folders = [self fetchSubfoldersOfObject:_object];
360 count = [folders count];
361 blocks = [NSMutableArray arrayWithCapacity:count];
362 for (i = 0; i < count; i++) {
363 UIxMailTreeBlock *block;
365 block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
367 if ([block isNotNull]) [blocks addObject:block];
369 if ([blocks count] == 0) blocks = nil;
373 [self getTitle:&title andIcon:&icon forObject:_object];
374 md = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
375 title:title iconName:icon
377 isPathNode:YES isActiveNode:YES
382 - (UIxMailTreeBlock *)treeNavigationBlockForObject:(id)_object
383 withActiveChildBlock:(UIxMailTreeBlock *)_activeChildBlock
387 Note: 'activeChildBlock' here doesn't mean that the block is the selected
388 folder in the tree. Its just the element which is active in the
391 UIxMailTreeBlock *resultBlock;
392 NSMutableArray *blocks;
393 NSString *activeName;
395 NSString *title, *icon;
398 activeName = [_activeChildBlock valueForKey:@"name"];
400 /* process child folders */
402 folders = [self fetchSubfoldersOfObject:_object];
403 count = [folders count];
404 blocks = [NSMutableArray arrayWithCapacity:count == 0 ? 1 : count];
405 for (i = 0; i < count; i++) {
406 UIxMailTreeBlock *block;
409 folder = [folders objectAtIndex:i];
410 block = [activeName isEqualToString:[folder nameInContainer]]
412 : [self treeNavigationBlockForLeafNode:folder atDepth:_depth];
414 if ([block isNotNull]) [blocks addObject:block];
416 if ([blocks count] == 0) {
417 if (_activeChildBlock != nil) // if the parent has no proper fetchmethod!
418 [blocks addObject:_activeChildBlock];
425 [self getTitle:&title andIcon:&icon forObject:_object];
426 resultBlock = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
427 title:title iconName:icon
429 [self treeNavigationLinkForObject:_object
430 atDepth:(_depth + 1)]
431 isPathNode:YES isActiveNode:NO
434 /* recurse up unless we are at the root */
436 if ([self isRootObject:_object]) /* we are at the top */
439 return [self treeNavigationBlockForObject:[_object container]
440 withActiveChildBlock:resultBlock
444 - (UIxMailTreeBlock *)buildNavigationNodesForObject:(id)_object {
446 This is the top-level 'flattening' method. The _object is the active
447 object in the tree, that is, usually a "current folder".
450 all subfolders of the current folder,
451 all parent folders of the current folder up to some root,
452 all siblings along the parent chain.
454 UIxMailTreeBlock *block;
457 This is the cursor, we create nodes below that for direct subfolders
459 if (debugBlocks) [self logWithFormat:@"ACTIVE block ..."];
460 block = [self treeNavigationBlockForActiveNode:_object];
461 if (debugBlocks) [self logWithFormat:@" ACTIVE block: %@", block];
463 if ([self isRootObject:_object]) {
464 if (debugBlocks) [self logWithFormat:@" active block is root."];
469 The following returns the root block. It calculates the chain up to the
470 root folder starting with the parent of the current object.
472 if (debugBlocks) [self logWithFormat:@"ACTIVE parent block ..."];
473 block = [self treeNavigationBlockForObject:[_object container]
474 withActiveChildBlock:block
476 if (debugBlocks) [self logWithFormat:@"done: %@", block];
482 - (NSArray *)rootNodes {
483 UIxMailTreeBlock *navNode;
485 if (self->rootNodes != nil)
486 return self->rootNodes;
488 navNode = [self buildNavigationNodesForObject:[self clientObject]];
490 if ([navNode hasChildren] && [navNode areChildrenLoaded])
491 self->rootNodes = [[navNode children] retain];
493 self->rootNodes = [[NSArray alloc] initWithObjects:&navNode count:1];
495 return self->rootNodes;
501 [self->item release]; self->item = nil;
502 [self->rootNodes release]; self->rootNodes = nil;
506 @end /* UIxMailTree */
509 @implementation NSString(DotCutting)
511 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength {
516 if ([self length] <= _maxLength) /* if length is small, return as is */
519 if ((r = [self rangeOfString:@"."]).length == 0)
520 /* no dots in share, return even if longer than boundary */
524 i = r.location + r.length;
525 r2 = [s rangeOfString:@"." options:NSLiteralSearch
526 range:NSMakeRange(i, [s length] - i)];
529 s = [s substringToIndex:r2.location];
530 if ([s length] <= _maxLength) /* if length is small, return as is */
534 /* no second dot, and the whole was too long => cut off after first */
535 return [s substringToIndex:r.location];
538 - (NSString *)titleForSOGoIMAP4String {
541 guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo@\
542 amelie-01.ac.melanie2.i2
544 static int CutOffLength = 16;
550 /* check for connect strings without hostnames */
552 r = [s rangeOfString:@"@"];
554 /* no login provide, just use the hostname (without domain) */
555 r = [s rangeOfString:@"."];
556 return r.length > 0 ? [s substringToIndex:r.location] : s;
559 s = [s substringToIndex:r.location];
561 /* check for shares */
563 r = [s rangeOfString:@".-."];
565 /* eg: 'baluh.hommes.tests-montee-en-charge-ogo' */
566 s = [s substringFromIndex:(r.location + r.length)];
568 return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
571 /* just the login name, possibly long (test.et.di.cete-lyon) */
572 return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
575 @end /* NSString(DotCutting) */