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]))
63 flattenedNodes = [NSMutableDictionary new];
70 [self->treeFolderAction release];
71 [self->rootClassName release];
72 [self->rootNodes release];
74 [flattenedNodes release];
80 - (NSString *) defaultIconName
82 return @"tbtv_leaf_corner_17x17.gif";
85 - (NSString *)iconNameForType:(NSString *)_type {
86 if (![_type isNotNull])
87 return [self defaultIconName];
89 //return @"tbtv_drafts_17x17.gif";
91 return [self defaultIconName];
96 - (void)setRootClassName:(id)_rootClassName {
97 ASSIGNCOPY(self->rootClassName, _rootClassName);
100 return self->rootClassName;
103 - (void)setItem:(id)_item {
104 ASSIGN(self->item, _item);
110 - (void)setTreeFolderAction:(NSString *)_action {
111 ASSIGNCOPY(self->treeFolderAction, _action);
113 - (NSString *)treeFolderAction {
114 return self->treeFolderAction;
117 - (NSString *)itemIconName {
118 // TODO: only called once!
121 ftype = [[self item] valueForKey:@"outlookFolderClass"];
122 return [self iconNameForType:ftype];
125 /* fetching subfolders */
127 - (NSArray *)fetchSubfoldersOfObject:(id)_object {
128 /* Walk over toManyRelationshipKeys and lookup the controllers for them. */
133 if ((names = [_object toManyRelationshipKeys]) == nil) {
134 if (debugBlocks) [self logWithFormat:@"no to-many: %@", _object];
139 [self logWithFormat:@"to-many: %@ %@", _object,
140 [names componentsJoinedByString:@","]];
143 count = [names count];
144 ma = [NSMutableArray arrayWithCapacity:(count + 1)];
145 for (i = 0; i < count; i++) {
148 // TODO: use some context or reuse the main context?
149 folder = [_object lookupName:[names objectAtIndex:i] inContext:nil
153 [self logWithFormat:@" DID NOT FIND FOLDER %@: %@",
155 [names objectAtIndex:i]];
159 if ([folder isKindOfClass:[NSException class]]) {
161 [self logWithFormat:@" FOLDER LOOKUP EXCEPTION %@: %@",
162 [names objectAtIndex:i], folder];
167 [ma addObject:folder];
170 [self logWithFormat:@" returning: %@ %@", _object, ma];
174 /* navigation nodes */
176 - (BOOL)isRootObject:(id)_object {
177 if (![_object isNotNull]) {
178 [self warnWithFormat:@"(%s): got to root by nil lookup ...",
179 __PRETTY_FUNCTION__];
183 if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
186 return [_object isKindOfClass:NSClassFromString([self rootClassName])];
189 - (NSString *)treeNavigationLinkForObject:(id)_object
192 NSMutableString *link;
195 link = [NSMutableString new];
198 for (i = 0; i < _depth; i++)
199 [link appendString: @"../"];
201 [link appendFormat: @"%@/%@",
202 [_object nameInContainer],
203 [self treeFolderAction]];
208 - (void) getTitle: (NSString **)_t
209 folderType: (NSString **)_ft
210 andIcon: (NSString **)_icon
211 forObject: (id)_object
213 // TODO: need to refactor for reuse!
217 // if ([_object respondsToSelector: @selector (outlookFolderClass)])
218 // ftype = [_object outlookFolderClass];
220 ftype = [_object valueForKey:@"outlookFolderClass"];
221 len = [ftype length];
227 if ([ftype isEqualToString:@"IPF.Sent"]) {
228 *_t = [self labelForKey:@"SentFolderName"];
229 *_icon = @"tbtv_sent_17x17.gif";
235 if ([ftype isEqualToString:@"IPF.Inbox"]) {
236 *_t = [self labelForKey:@"InboxFolderName"];
237 *_icon = @"tbtv_inbox_17x17.gif";
241 if ([ftype isEqualToString:@"IPF.Trash"]) {
242 *_t = [self labelForKey:@"TrashFolderName"];
243 *_icon = @"tbtv_trash_17x17.gif";
249 if ([ftype isEqualToString:@"IPF.Drafts"]) {
250 *_t = [self labelForKey:@"DraftsFolderName"];
251 *_icon = @"tbtv_drafts_17x17.gif";
255 // if ([ftype isEqualToString:@"IPF.Filter"]) {
256 // *_t = [self labelForKey:@"SieveFolderName"];
264 *_t = [_object davDisplayName];
267 if ([_object isKindOfClass:NSClassFromString(@"SOGoMailFolder")])
269 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccount")]) {
270 *_icon = @"tbtv_account_17x17.gif";
274 /* title processing is somehow Agenor specific and should be done in UI */
275 *_t = [[_object nameInContainer] titleForSOGoIMAP4String];
277 else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccounts")])
278 *_icon = @"tbtv_account_17x17.gif";
279 else if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
280 *_icon = @"tbtv_inbox_17x17.gif";
282 // TODO: use drafts icon for other SOGo folders
283 *_icon = @"tbtv_drafts_17x17.gif";
287 - (UIxMailTreeBlock *) treeNavigationBlockForLeafNode: (id) _o
290 UIxMailTreeBlock *md;
291 NSString *n, *i, *ft;
295 Trigger plus in treeview if it has subfolders. It is an optimization that
296 we do not generate blocks for folders which are not displayed anyway.
298 blocks = [[_o toManyRelationshipKeys] count] > 0
299 ? UIxMailTreeHasChildrenMarker
302 [self getTitle: &n folderType: &ft andIcon: &i forObject:_o];
304 md = [UIxMailTreeBlock blockWithName: nil
307 link: [self treeNavigationLinkForObject:_o atDepth:_d]
310 childBlocks: blocks];
314 - (UIxMailTreeBlock *)treeNavigationBlockForRootNode:(id)_object {
316 This generates the block for the root object (root of the tree, we get
317 there by walking up the chain starting with the client object).
319 UIxMailTreeBlock *md;
320 NSMutableArray *blocks;
322 NSString *title, *icon, *ft;
326 [self logWithFormat:@"block for root node 0x%08X<%@>",
327 _object, NSStringFromClass([_object class])];
330 /* process child folders */
332 folders = [self fetchSubfoldersOfObject:_object];
333 count = [folders count];
334 blocks = [NSMutableArray arrayWithCapacity:count];
335 for (i = 0; i < count; i++) {
338 block = [self treeNavigationBlockForLeafNode: [folders objectAtIndex:i]
340 if ([block isNotNull]) [blocks addObject:block];
342 if ([blocks count] == 0)
347 [self getTitle:&title folderType: &ft andIcon:&icon forObject:_object];
349 md = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
352 link: [@"../" stringByAppendingString:
353 [_object nameInContainer]]
356 childBlocks: blocks];
360 - (UIxMailTreeBlock *) fullTreeNavigationBlockForNode: (id)_object
362 UIxMailTreeBlock *md;
363 NSMutableArray *blocks;
365 NSString *title, *icon, *ft;
369 [self logWithFormat:@"block for root node 0x%08X<%@>",
370 _object, NSStringFromClass([_object class])];
372 folders = [self fetchSubfoldersOfObject: _object];
373 count = [folders count];
374 blocks = [NSMutableArray arrayWithCapacity: count];
375 for (i = 0; i < count; i++)
379 block = [self fullTreeNavigationBlockForNode: [folders objectAtIndex:i]];
380 if ([block isNotNull]) [blocks addObject:block];
386 [self getTitle: &title folderType: &ft andIcon: &icon forObject: _object];
387 // NSLog (@"*********** title = '%@'/icon = '%@'", title, icon);
389 md = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
392 link: [@"../" stringByAppendingString:
393 [_object nameInContainer]]
396 childBlocks: blocks];
397 [md setFolderType: ft];
402 - (UIxMailTreeBlock *) treeNavigationBlockForActiveNode: (id) _object
405 This generates the block for the clientObject (the object which has the
408 UIxMailTreeBlock *md;
409 NSMutableArray *blocks;
411 NSString *title, *icon, *ft;
414 // TODO: maybe we can join the two implementations, this might not be
416 if ([self isRootObject:_object]) /* we are at the top */
417 return [self treeNavigationBlockForRootNode:_object];
420 [self logWithFormat:@"block for active node 0x%08X<%@> - %@",
421 _object, NSStringFromClass([_object class]),
422 [_object davDisplayName]];
425 /* process child folders */
427 folders = [self fetchSubfoldersOfObject:_object];
428 count = [folders count];
429 blocks = [NSMutableArray arrayWithCapacity:count];
430 for (i = 0; i < count; i++) {
431 UIxMailTreeBlock *block;
433 block = [self treeNavigationBlockForLeafNode: [folders objectAtIndex:i]
435 if ([block isNotNull]) [blocks addObject:block];
437 if ([blocks count] == 0) blocks = nil;
441 [self getTitle:&title folderType: &ft andIcon:&icon forObject:_object];
442 md = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
448 childBlocks: blocks];
452 - (UIxMailTreeBlock *)
453 treeNavigationBlockForObject: (id) _object
454 withActiveChildBlock: (UIxMailTreeBlock *) _activeChildBlock
458 Note: 'activeChildBlock' here doesn't mean that the block is the selected
459 folder in the tree. Its just the element which is active in the
462 UIxMailTreeBlock *resultBlock;
463 NSMutableArray *blocks;
464 NSString *activeName;
466 NSString *title, *icon, *ft;
469 activeName = [_activeChildBlock valueForKey:@"name"];
471 /* process child folders */
473 folders = [self fetchSubfoldersOfObject:_object];
474 count = [folders count];
475 blocks = [NSMutableArray arrayWithCapacity:count == 0 ? 1 : count];
476 for (i = 0; i < count; i++) {
477 UIxMailTreeBlock *block;
480 folder = [folders objectAtIndex:i];
481 block = [activeName isEqualToString:[folder nameInContainer]]
483 : [self treeNavigationBlockForLeafNode: folder
486 if ([block isNotNull]) [blocks addObject:block];
488 if ([blocks count] == 0) {
489 if (_activeChildBlock != nil) // if the parent has no proper fetchmethod!
490 [blocks addObject:_activeChildBlock];
497 [self getTitle:&title folderType: &ft andIcon:&icon forObject:_object];
499 = [UIxMailTreeBlock blockWithName: [_object nameInContainer]
503 [self treeNavigationLinkForObject: _object
504 atDepth: (_depth + 1)]
505 isPathNode:YES isActiveNode:NO
508 /* recurse up unless we are at the root */
510 if ([self isRootObject:_object]) /* we are at the top */
513 return [self treeNavigationBlockForObject:[_object container]
514 withActiveChildBlock:resultBlock
518 - (UIxMailTreeBlock *)buildNavigationNodesForObject:(id)_object {
520 This is the top-level 'flattening' method. The _object is the active
521 object in the tree, that is, usually a "current folder".
524 all subfolders of the current folder,
525 all parent folders of the current folder up to some root,
526 all siblings along the parent chain.
528 UIxMailTreeBlock *block;
531 This is the cursor, we create nodes below that for direct subfolders
533 if (debugBlocks) [self logWithFormat:@"ACTIVE block ..."];
534 block = [self treeNavigationBlockForActiveNode:_object];
535 if (debugBlocks) [self logWithFormat:@" ACTIVE block: %@", block];
537 if ([self isRootObject:_object]) {
538 if (debugBlocks) [self logWithFormat:@" active block is root."];
543 The following returns the root block. It calculates the chain up to the
544 root folder starting with the parent of the current object.
546 if (debugBlocks) [self logWithFormat:@"ACTIVE parent block ..."];
547 block = [self treeNavigationBlockForObject:[_object container]
548 withActiveChildBlock:block
550 if (debugBlocks) [self logWithFormat:@"done: %@", block];
556 - (NSArray *)rootNodes {
557 UIxMailTreeBlock *navNode;
559 if (self->rootNodes != nil)
560 return self->rootNodes;
562 navNode = [self buildNavigationNodesForObject:[self clientObject]];
564 if ([navNode hasChildren] && [navNode areChildrenLoaded])
565 self->rootNodes = [[navNode children] retain];
567 self->rootNodes = [[NSArray alloc] initWithObjects:&navNode count:1];
569 return self->rootNodes;
572 - (int) addNodes: (NSArray *) nodes
573 atSerial: (int) startSerial
574 forParent: (int) parent
575 withRootName: (NSString *) rootName
576 toArray: (NSMutableArray *) array
578 unsigned int count, max, currentSerial;
579 UIxMailTreeBlock *curNode;
583 currentSerial = startSerial;
584 for (count = 0; count < max; count++)
586 curNode = [nodes objectAtIndex: count];
587 fullName = [rootName stringByAppendingFormat: @"/%@", [curNode name]];
588 [curNode setName: fullName];
589 [curNode setSerial: currentSerial];
590 [curNode setParent: parent];
591 [array addObject: curNode];
592 if ([curNode hasChildren])
593 currentSerial = [self addNodes: [curNode children]
594 atSerial: currentSerial + 1
595 forParent: currentSerial
596 withRootName: fullName
602 return currentSerial;
605 - (NSArray *) flattenedNodes
607 NSMutableArray *flattenedBlocks = nil;
609 UIxMailTreeBlock *rootNode; // , *curNode;
611 // unsigned int count, max;
613 userKey = [[self user] login];
614 flattenedBlocks = [flattenedNodes objectForKey: userKey];
615 if (!flattenedBlocks)
617 flattenedBlocks = [NSMutableArray new];
619 if (![[self clientObject] isKindOfClass: NSClassFromString(@"SOGoMailAccounts")])
620 mailAccounts = [[self clientObject] mailAccountsFolder];
622 mailAccounts = [self clientObject];
624 rootNode = [self fullTreeNavigationBlockForNode: mailAccounts];
625 [self addNodes: [rootNode children]
629 toArray: flattenedBlocks];
631 [flattenedNodes setObject: flattenedBlocks forKey: userKey];
632 // max = [flattenedBlocks count];
633 // for (count = 0; count < max; count++)
635 // curNode = [flattenedBlocks objectAtIndex: count];
636 // NSLog (@"%d: %@/%@", count, [curNode title], [curNode iconName]);
640 return flattenedBlocks;
646 [self->item release]; self->item = nil;
647 [self->rootNodes release]; self->rootNodes = nil;
651 @end /* UIxMailTree */
654 @implementation NSString(DotCutting)
656 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength {
661 if ([self length] <= _maxLength) /* if length is small, return as is */
664 if ((r = [self rangeOfString:@"."]).length == 0)
665 /* no dots in share, return even if longer than boundary */
669 i = r.location + r.length;
670 r2 = [s rangeOfString:@"." options:NSLiteralSearch
671 range:NSMakeRange(i, [s length] - i)];
674 s = [s substringToIndex:r2.location];
675 if ([s length] <= _maxLength) /* if length is small, return as is */
679 /* no second dot, and the whole was too long => cut off after first */
680 return [s substringToIndex:r.location];
683 - (NSString *)titleForSOGoIMAP4String {
686 guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo@\
687 amelie-01.ac.melanie2.i2
689 static int CutOffLength = 16;
695 /* check for connect strings without hostnames */
697 r = [s rangeOfString:@"@"];
699 /* no login provide, just use the hostname (without domain) */
700 r = [s rangeOfString:@"."];
701 return r.length > 0 ? [s substringToIndex:r.location] : s;
704 s = [s substringToIndex:r.location];
706 /* check for shares */
708 r = [s rangeOfString:@".-."];
710 /* eg: 'baluh.hommes.tests-montee-en-charge-ogo' */
711 s = [s substringFromIndex:(r.location + r.length)];
713 return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
716 /* just the login name, possibly long (test.et.di.cete-lyon) */
717 return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
720 @end /* NSString(DotCutting) */