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
24 #import <SoObjects/Mailer/SOGoMailBaseObject.h>
25 #import <SoObjects/Mailer/SOGoMailAccount.h>
26 #import <SoObjects/Mailer/SOGoMailFolder.h>
27 #import <NGObjWeb/SoComponent.h>
28 #import <NGObjWeb/SoObject+SoDAV.h>
30 #import "UIxMailTree.h"
31 #import "UIxMailTreeBlock.h"
34 Support special icons:
35 tbtv_leaf_corner_17x17.gif
42 @interface NSString(DotCutting)
44 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength;
46 - (NSString *)titleForSOGoIMAP4String;
50 @implementation UIxMailTree
52 static BOOL debugBlocks = NO;
56 [UIxMailTreeBlock class]; // ensure that globals are initialized
61 if ((self = [super init]))
71 [treeFolderAction release];
72 [rootClassName release];
75 [flattenedNodes release];
81 - (NSString *) defaultIconName
83 return @"tbtv_leaf_corner_17x17.gif";
86 - (NSString *)iconNameForType:(NSString *)_type {
87 if (![_type isNotNull])
88 return [self defaultIconName];
90 //return @"tbtv_drafts_17x17.gif";
92 return [self defaultIconName];
97 - (void)setRootClassName:(id)_rootClassName {
98 ASSIGNCOPY(rootClassName, _rootClassName);
100 - (id)rootClassName {
101 return rootClassName;
104 - (void)setItem:(id)_item {
111 - (void)setTreeFolderAction:(NSString *)_action {
112 ASSIGNCOPY(treeFolderAction, _action);
114 - (NSString *)treeFolderAction {
115 return treeFolderAction;
118 - (NSString *)itemIconName {
119 // TODO: only called once!
122 ftype = [[self item] valueForKey:@"outlookFolderClass"];
123 return [self iconNameForType:ftype];
126 /* fetching subfolders */
128 - (NSArray *)fetchSubfoldersOfObject:(id)_object {
129 /* Walk over toManyRelationshipKeys and lookup the controllers for them. */
134 if ((names = [_object toManyRelationshipKeys]) == nil) {
135 if (debugBlocks) [self logWithFormat:@"no to-many: %@", _object];
140 [self logWithFormat:@"to-many: %@ %@", _object,
141 [names componentsJoinedByString:@","]];
144 count = [names count];
145 ma = [NSMutableArray arrayWithCapacity:(count + 1)];
146 for (i = 0; i < count; i++) {
149 // TODO: use some context or reuse the main context?
150 folder = [_object lookupName:[names objectAtIndex:i] inContext:nil
154 [self logWithFormat:@" DID NOT FIND FOLDER %@: %@",
156 [names objectAtIndex:i]];
160 if ([folder isKindOfClass:[NSException class]]) {
162 [self logWithFormat:@" FOLDER LOOKUP EXCEPTION %@: %@",
163 [names objectAtIndex:i], folder];
168 [ma addObject:folder];
171 [self logWithFormat:@" returning: %@ %@", _object, ma];
175 /* navigation nodes */
177 - (BOOL)isRootObject:(id)_object {
178 if (![_object isNotNull]) {
179 [self warnWithFormat:@"(%s): got to root by nil lookup ...",
180 __PRETTY_FUNCTION__];
184 if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
187 return [_object isKindOfClass:NSClassFromString([self rootClassName])];
190 - (NSString *)treeNavigationLinkForObject:(id)_object
193 NSMutableString *link;
196 link = [NSMutableString new];
199 for (i = 0; i < _depth; i++)
200 [link appendString: @"../"];
202 [link appendFormat: @"%@/%@",
203 [_object nameInContainer],
204 [self treeFolderAction]];
209 - (void) getTitle: (NSString **)_t
210 folderType: (NSString **)_ft
211 andIcon: (NSString **)_icon
212 forObject: (id)_object
214 // TODO: need to refactor for reuse!
218 // if ([_object respondsToSelector: @selector (outlookFolderClass)])
219 // ftype = [_object outlookFolderClass];
221 ftype = [_object valueForKey:@"outlookFolderClass"];
222 len = [ftype length];
228 if ([ftype isEqualToString:@"IPF.Sent"]) {
229 *_t = [self labelForKey:@"SentFolderName"];
230 *_icon = @"tbtv_sent_17x17.gif";
236 if ([ftype isEqualToString:@"IPF.Inbox"]) {
237 *_t = [self labelForKey:@"InboxFolderName"];
238 *_icon = @"tbtv_inbox_17x17.gif";
242 if ([ftype isEqualToString:@"IPF.Trash"]) {
243 *_t = [self labelForKey:@"TrashFolderName"];
244 *_icon = @"tbtv_trash_17x17.gif";
250 if ([ftype isEqualToString:@"IPF.Drafts"]) {
251 *_t = [self labelForKey:@"DraftsFolderName"];
252 *_icon = @"tbtv_drafts_17x17.gif";
256 // if ([ftype isEqualToString:@"IPF.Filter"]) {
257 // *_t = [self labelForKey:@"SieveFolderName"];
265 *_t = [_object davDisplayName];
268 if ([_object isKindOfClass:NSClassFromString(@"SOGoMailFolder")])
270 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccount")]) {
271 *_icon = @"tbtv_account_17x17.gif";
275 /* title processing is somehow Agenor specific and should be done in UI */
276 *_t = [[_object nameInContainer] titleForSOGoIMAP4String];
278 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccounts")])
279 *_icon = @"tbtv_account_17x17.gif";
280 else if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
281 *_icon = @"tbtv_inbox_17x17.gif";
283 // TODO: use drafts icon for other SOGo folders
284 *_icon = @"tbtv_drafts_17x17.gif";
288 - (UIxMailTreeBlock *) treeNavigationBlockForLeafNode: (id) _o
291 UIxMailTreeBlock *md;
292 NSString *n, *i, *ft;
296 Trigger plus in treeview if it has subfolders. It is an optimization that
297 we do not generate blocks for folders which are not displayed anyway.
299 blocks = [[_o toManyRelationshipKeys] count] > 0
300 ? UIxMailTreeHasChildrenMarker
303 [self getTitle: &n folderType: &ft andIcon: &i forObject:_o];
305 md = [UIxMailTreeBlock blockWithName: nil
308 link: [self treeNavigationLinkForObject:_o atDepth:_d]
311 childBlocks: blocks];
315 - (UIxMailTreeBlock *)treeNavigationBlockForRootNode:(id)_object {
317 This generates the block for the root object (root of the tree, we get
318 there by walking up the chain starting with the client object).
320 UIxMailTreeBlock *md;
321 NSMutableArray *blocks;
323 NSString *title, *icon, *ft;
327 [self logWithFormat:@"block for root node 0x%08X<%@>",
328 _object, NSStringFromClass([_object class])];
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++) {
339 block = [self treeNavigationBlockForLeafNode: [folders objectAtIndex:i]
341 if ([block isNotNull]) [blocks addObject:block];
343 if ([blocks count] == 0)
348 [self getTitle:&title folderType: &ft andIcon:&icon forObject:_object];
350 md = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
353 link: [@"../" stringByAppendingString:
354 [_object nameInContainer]]
357 childBlocks: blocks];
361 - (UIxMailTreeBlock *) fullTreeNavigationBlockForNode: (id)_object
363 UIxMailTreeBlock *md;
364 NSMutableArray *blocks;
366 NSString *title, *icon, *ft;
370 [self logWithFormat:@"block for root node 0x%08X<%@>",
371 _object, NSStringFromClass([_object class])];
373 folders = [self fetchSubfoldersOfObject: _object];
374 count = [folders count];
375 blocks = [NSMutableArray arrayWithCapacity: count];
376 for (i = 0; i < count; i++)
380 block = [self fullTreeNavigationBlockForNode: [folders objectAtIndex:i]];
381 if ([block isNotNull]) [blocks addObject:block];
387 [self getTitle: &title folderType: &ft andIcon: &icon forObject: _object];
388 // NSLog (@"*********** title = '%@'/icon = '%@'", title, icon);
390 md = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
393 link: [@"../" stringByAppendingString:
394 [_object nameInContainer]]
397 childBlocks: blocks];
398 [md setFolderType: ft];
403 - (UIxMailTreeBlock *) treeNavigationBlockForActiveNode: (id) _object
406 This generates the block for the clientObject (the object which has the
409 UIxMailTreeBlock *md;
410 NSMutableArray *blocks;
412 NSString *title, *icon, *ft;
415 // TODO: maybe we can join the two implementations, this might not be
417 if ([self isRootObject:_object]) /* we are at the top */
418 return [self treeNavigationBlockForRootNode:_object];
421 [self logWithFormat:@"block for active node 0x%08X<%@> - %@",
422 _object, NSStringFromClass([_object class]),
423 [_object davDisplayName]];
426 /* process child folders */
428 folders = [self fetchSubfoldersOfObject:_object];
429 count = [folders count];
430 blocks = [NSMutableArray arrayWithCapacity:count];
431 for (i = 0; i < count; i++) {
432 UIxMailTreeBlock *block;
434 block = [self treeNavigationBlockForLeafNode: [folders objectAtIndex:i]
436 if ([block isNotNull]) [blocks addObject:block];
438 if ([blocks count] == 0) blocks = nil;
442 [self getTitle:&title folderType: &ft andIcon:&icon forObject:_object];
443 md = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
449 childBlocks: blocks];
453 - (UIxMailTreeBlock *)
454 treeNavigationBlockForObject: (id) _object
455 withActiveChildBlock: (UIxMailTreeBlock *) _activeChildBlock
459 Note: 'activeChildBlock' here doesn't mean that the block is the selected
460 folder in the tree. Its just the element which is active in the
463 UIxMailTreeBlock *resultBlock;
464 NSMutableArray *blocks;
465 NSString *activeName;
467 NSString *title, *icon, *ft;
470 activeName = [_activeChildBlock valueForKey:@"name"];
472 /* process child folders */
474 folders = [self fetchSubfoldersOfObject:_object];
475 count = [folders count];
476 blocks = [NSMutableArray arrayWithCapacity:count == 0 ? 1 : count];
477 for (i = 0; i < count; i++) {
478 UIxMailTreeBlock *block;
481 folder = [folders objectAtIndex:i];
482 block = [activeName isEqualToString:[folder nameInContainer]]
484 : [self treeNavigationBlockForLeafNode: folder
487 if ([block isNotNull]) [blocks addObject:block];
489 if ([blocks count] == 0) {
490 if (_activeChildBlock != nil) // if the parent has no proper fetchmethod!
491 [blocks addObject:_activeChildBlock];
498 [self getTitle:&title folderType: &ft andIcon:&icon forObject:_object];
500 = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
504 [self treeNavigationLinkForObject: _object
505 atDepth: (_depth + 1)]
506 isPathNode:YES isActiveNode:NO
509 /* recurse up unless we are at the root */
511 if ([self isRootObject:_object]) /* we are at the top */
514 return [self treeNavigationBlockForObject:[_object container]
515 withActiveChildBlock:resultBlock
519 - (UIxMailTreeBlock *)buildNavigationNodesForObject:(id)_object {
521 This is the top-level 'flattening' method. The _object is the active
522 object in the tree, that is, usually a "current folder".
525 all subfolders of the current folder,
526 all parent folders of the current folder up to some root,
527 all siblings along the parent chain.
529 UIxMailTreeBlock *block;
532 This is the cursor, we create nodes below that for direct subfolders
534 if (debugBlocks) [self logWithFormat:@"ACTIVE block ..."];
535 block = [self treeNavigationBlockForActiveNode:_object];
536 if (debugBlocks) [self logWithFormat:@" ACTIVE block: %@", block];
538 if ([self isRootObject:_object]) {
539 if (debugBlocks) [self logWithFormat:@" active block is root."];
544 The following returns the root block. It calculates the chain up to the
545 root folder starting with the parent of the current object.
547 if (debugBlocks) [self logWithFormat:@"ACTIVE parent block ..."];
548 block = [self treeNavigationBlockForObject:[_object container]
549 withActiveChildBlock:block
551 if (debugBlocks) [self logWithFormat:@"done: %@", block];
557 - (NSArray *)rootNodes {
558 UIxMailTreeBlock *navNode;
560 if (rootNodes != nil)
563 navNode = [self buildNavigationNodesForObject:[self clientObject]];
565 if ([navNode hasChildren] && [navNode areChildrenLoaded])
566 rootNodes = [[navNode children] retain];
568 rootNodes = [[NSArray alloc] initWithObjects:&navNode count:1];
573 - (int) addNodes: (NSArray *) nodes
574 atSerial: (int) startSerial
575 forParent: (int) parent
576 withRootName: (NSString *) rootName
577 toArray: (NSMutableArray *) array
579 unsigned int count, max, currentSerial;
580 UIxMailTreeBlock *curNode;
584 currentSerial = startSerial;
585 for (count = 0; count < max; count++)
587 curNode = [nodes objectAtIndex: count];
588 fullName = [rootName stringByAppendingFormat: @"/%@", [curNode name]];
589 [curNode setName: fullName];
590 [curNode setSerial: currentSerial];
591 [curNode setParent: parent];
592 [array addObject: curNode];
593 if ([curNode hasChildren])
594 currentSerial = [self addNodes: [curNode children]
595 atSerial: currentSerial + 1
596 forParent: currentSerial
597 withRootName: fullName
603 return currentSerial;
606 - (NSArray *) flattenedNodes
608 UIxMailTreeBlock *rootNode; // , *curNode;
610 // unsigned int count, max;
614 flattenedNodes = [NSMutableArray new];
616 if (![[self clientObject] isKindOfClass: NSClassFromString(@"SOGoMailAccounts")])
617 mailAccounts = [[self clientObject] mailAccountsFolder];
619 mailAccounts = [self clientObject];
621 rootNode = [self fullTreeNavigationBlockForNode: mailAccounts];
622 [self addNodes: [rootNode children]
626 toArray: flattenedNodes];
627 // max = [flattenedBlocks count];
628 // for (count = 0; count < max; count++)
630 // curNode = [flattenedBlocks objectAtIndex: count];
631 // NSLog (@"%d: %@/%@", count, [curNode title], [curNode iconName]);
635 return flattenedNodes;
642 [item release]; item = nil;
643 [rootNodes release]; rootNodes = nil;
647 @end /* UIxMailTree */
650 @implementation NSString(DotCutting)
652 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength {
657 if ([self length] <= _maxLength) /* if length is small, return as is */
660 if ((r = [self rangeOfString:@"."]).length == 0)
661 /* no dots in share, return even if longer than boundary */
665 i = r.location + r.length;
666 r2 = [s rangeOfString:@"." options:NSLiteralSearch
667 range:NSMakeRange(i, [s length] - i)];
670 s = [s substringToIndex:r2.location];
671 if ([s length] <= _maxLength) /* if length is small, return as is */
675 /* no second dot, and the whole was too long => cut off after first */
676 return [s substringToIndex:r.location];
679 - (NSString *)titleForSOGoIMAP4String {
682 guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo@\
683 amelie-01.ac.melanie2.i2
685 static int CutOffLength = 16;
691 /* check for connect strings without hostnames */
693 r = [s rangeOfString:@"@"];
695 /* no login provide, just use the hostname (without domain) */
696 r = [s rangeOfString:@"."];
697 return r.length > 0 ? [s substringToIndex:r.location] : s;
700 s = [s substringToIndex:r.location];
702 /* check for shares */
704 r = [s rangeOfString:@".-."];
706 /* eg: 'baluh.hommes.tests-montee-en-charge-ogo' */
707 s = [s substringFromIndex:(r.location + r.length)];
709 return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
712 /* just the login name, possibly long (test.et.di.cete-lyon) */
713 return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
716 @end /* NSString(DotCutting) */