]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/Mailer/UIxMailTree.m
improved fb.view on contacts
[scalable-opengroupware.org] / SOGo / UI / Mailer / UIxMailTree.m
1 /*
2   Copyright (C) 2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #include <SOGoUI/UIxComponent.h>
23
24 @interface UIxMailTree : UIxComponent
25 {
26   NSString *rootClassName;
27   NSString *treeFolderAction;
28   id rootNodes;
29   id item;
30 }
31 @end
32
33 #include "UIxMailTreeBlock.h"
34 #include <SoObjects/Mailer/SOGoMailBaseObject.h>
35 #include <SoObjects/Mailer/SOGoMailAccount.h>
36 #include "common.h"
37 #include <NGObjWeb/SoComponent.h>
38 #include <NGObjWeb/SoObject+SoDAV.h>
39
40 /*
41   Support special icons:
42     tbtv_leaf_corner_17x17.gif
43     tbtv_inbox_17x17.gif
44     tbtv_drafts_17x17.gif
45     tbtv_sent_17x17.gif
46     tbtv_trash_17x17.gif
47 */
48
49 @implementation UIxMailTree
50
51 static BOOL debugBlocks = NO;
52
53 + (void)initialize {
54   [UIxMailTreeBlock class]; // ensure that globals are initialized
55 }
56
57 - (void)dealloc {
58   [self->treeFolderAction release];
59   [self->rootClassName    release];
60   [self->rootNodes release];
61   [self->item      release];
62   [super dealloc];
63 }
64
65 /* icons */
66
67 - (NSString *)defaultIconName {
68   return @"tbtv_leaf_corner_17x17.gif";
69 }
70
71 - (NSString *)iconNameForType:(NSString *)_type {
72   if (![_type isNotNull])
73     return [self defaultIconName];
74   
75   //return @"tbtv_drafts_17x17.gif";
76   
77   return [self defaultIconName];
78 }
79
80 /* accessors */
81
82 - (void)setRootClassName:(id)_rootClassName {
83   ASSIGNCOPY(self->rootClassName, _rootClassName);
84 }
85 - (id)rootClassName {
86   return self->rootClassName;
87 }
88
89 - (void)setItem:(id)_item {
90   ASSIGN(self->item, _item);
91 }
92 - (id)item {
93   return self->item;
94 }
95
96 - (void)setTreeFolderAction:(NSString *)_action {
97   ASSIGNCOPY(self->treeFolderAction, _action);
98 }
99 - (NSString *)treeFolderAction {
100   return self->treeFolderAction;
101 }
102
103 - (NSString *)itemIconName {
104   // TODO: only called once!
105   NSString *ftype;
106   
107   ftype = [[self item] valueForKey:@"outlookFolderClass"];
108   return [self iconNameForType:ftype];
109 }
110
111 /* fetching subfolders */
112
113 - (NSArray *)fetchSubfoldersOfObject:(id)_object {
114   /* Walk over toManyRelationshipKeys and lookup the controllers for them. */
115   NSMutableArray *ma;
116   NSArray  *names;
117   unsigned i, count;
118   
119   if ((names = [_object toManyRelationshipKeys]) == nil) {
120     if (debugBlocks) [self logWithFormat:@"no to-many: %@", _object];
121     return nil;
122   }
123   
124   if (debugBlocks) {
125     [self logWithFormat:@"to-many: %@ %@", _object,
126           [names componentsJoinedByString:@","]];
127   }
128   
129   count = [names count];
130   ma    = [NSMutableArray arrayWithCapacity:(count + 1)];
131   for (i = 0; i < count; i++) {
132     id folder;
133     
134     folder = [_object lookupName:[names objectAtIndex:i] inContext:nil 
135                       acquire:NO];
136     if (folder == nil)
137       continue;
138     if ([folder isKindOfClass:[NSException class]])
139       continue;
140     
141     [ma addObject:folder];
142   }
143   return ma;
144 }
145
146 /* navigation nodes */
147
148 - (BOOL)isRootObject:(id)_object {
149   if (![_object isNotNull]) {
150     [self warnWithFormat:@"(%s): got to root by nil lookup ...",
151             __PRETTY_FUNCTION__];
152     return YES;
153   }
154
155   if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
156     return YES;
157   
158   return [_object isKindOfClass:NSClassFromString([self rootClassName])];
159 }
160
161 - (NSString *)treeNavigationLinkForObject:(id)_object atDepth:(int)_depth {
162   NSString *link;
163   unsigned i;
164
165   link = [[_object nameInContainer] stringByAppendingString:@"/"];
166   link = [link stringByAppendingString:[self treeFolderAction]];
167   
168   switch (_depth) {
169   case 0: return link;
170   case 1: return [@"../"       stringByAppendingString:link];
171   case 2: return [@"../../"    stringByAppendingString:link];
172   case 3: return [@"../../../" stringByAppendingString:link];
173   }
174   
175   for (i = 0; i < _depth; i++)
176     link = [@"../" stringByAppendingString:link];
177   return link;
178 }
179
180 - (void)getTitle:(NSString **)_t andIcon:(NSString **)_icon
181   forObject:(id)_object
182 {
183   // TODO: need to refactor for reuse!
184   NSString *ftype;
185   unsigned len;
186   
187   ftype = [_object valueForKey:@"outlookFolderClass"];
188   len   = [ftype length];
189   
190   switch (len) {
191   case 8:
192     if ([ftype isEqualToString:@"IPF.Sent"]) {
193       *_t = [self labelForKey:@"SentFolderName"];
194       *_icon = @"tbtv_sent_17x17.gif";
195       return;
196     }
197     break;
198   case 9:
199     if ([ftype isEqualToString:@"IPF.Inbox"]) {
200       *_t = [self labelForKey:@"InboxFolderName"];
201       *_icon = @"tbtv_inbox_17x17.gif";
202       return;
203     }
204     if ([ftype isEqualToString:@"IPF.Trash"]) {
205       *_t = [self labelForKey:@"TrashFolderName"];
206       *_icon = @"tbtv_trash_17x17.gif";
207       return;
208     }
209     break;
210   case 10:
211     if ([ftype isEqualToString:@"IPF.Drafts"]) {
212       *_t = [self labelForKey:@"DraftsFolderName"];
213       *_icon = @"tbtv_drafts_17x17.gif";
214       return;
215     }
216     if ([ftype isEqualToString:@"IPF.Filter"]) {
217       *_t = [self labelForKey:@"SieveFolderName"];
218       *_icon = nil;
219       return;
220     }
221     break;
222   }
223   
224   *_t    = [_object davDisplayName];
225   *_icon = nil;
226   
227   if ([_object isKindOfClass:NSClassFromString(@"SOGoMailFolder")])
228     *_icon = nil;
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";
235   else {
236     // TODO: use drafts icon for other SOGo folders
237     *_icon = @"tbtv_drafts_17x17.gif";
238   }
239   
240   return;
241 }
242
243 - (UIxMailTreeBlock *)treeNavigationBlockForLeafNode:(id)_o atDepth:(int)_d {
244   UIxMailTreeBlock *md;
245   NSString *n, *i;
246   id blocks;
247   
248   /* 
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.
251   */
252   blocks = [[_o toManyRelationshipKeys] count] > 0
253     ? UIxMailTreeHasChildrenMarker
254     : nil;
255   
256   [self getTitle:&n andIcon:&i forObject:_o];
257   
258   md = [UIxMailTreeBlock blockWithName:nil
259                          title:n iconName:i
260                          link:[self treeNavigationLinkForObject:_o atDepth:_d]
261                          isPathNode:NO isActiveNode:NO
262                          childBlocks:blocks];
263   return md;
264 }
265
266 - (UIxMailTreeBlock *)treeNavigationBlockForRootNode:(id)_object {
267   /*
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).
270   */
271   UIxMailTreeBlock *md;
272   NSMutableArray   *blocks;
273   NSArray          *folders;
274   NSString         *title, *icon;
275   unsigned         i, count;
276
277   if (debugBlocks) {
278     [self logWithFormat:@"block for root node 0x%08X<%@>", 
279             _object, NSStringFromClass([_object class])];
280   }
281   
282   /* process child folders */
283   
284   folders = [self fetchSubfoldersOfObject:_object];
285   count   = [folders count];
286   blocks  = [NSMutableArray arrayWithCapacity:count];
287   for (i = 0; i < count; i++) {
288     id block;
289     
290     block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
291                   atDepth:0];
292     if ([block isNotNull]) [blocks addObject:block];
293   }
294   if ([blocks count] == 0)
295     blocks = nil;
296   
297   /* build block */
298   
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
305                          childBlocks:blocks];
306   return md;
307 }
308
309 - (UIxMailTreeBlock *)treeNavigationBlockForActiveNode:(id)_object {
310   /* 
311      This generates the block for the clientObject (the object which has the 
312      focus)
313   */
314   UIxMailTreeBlock *md;
315   NSMutableArray   *blocks;
316   NSArray  *folders;
317   NSString *title, *icon;
318   unsigned i, count;
319
320   // TODO: maybe we can join the two implementations, this might not be
321   //       necessary
322   if ([self isRootObject:_object]) /* we are at the top */
323     return [self treeNavigationBlockForRootNode:_object];
324   
325   if (debugBlocks) {
326     [self logWithFormat:@"block for active node 0x%08X<%@> - %@", 
327             _object, NSStringFromClass([_object class]),
328             [_object davDisplayName]];
329   }
330   
331   /* process child folders */
332   
333   folders = [self fetchSubfoldersOfObject:_object];
334   count   = [folders count];
335   blocks  = [NSMutableArray arrayWithCapacity:count];
336   for (i = 0; i < count; i++) {
337     UIxMailTreeBlock *block;
338     
339     block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
340                   atDepth:0];
341     if ([block isNotNull]) [blocks addObject:block];
342   }
343   if ([blocks count] == 0) blocks = nil;
344   
345   /* build block */
346   
347   [self getTitle:&title andIcon:&icon forObject:_object];
348   md = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
349                          title:title iconName:icon
350                          link:@"."
351                          isPathNode:YES isActiveNode:YES
352                          childBlocks:blocks];
353   return md;
354 }
355
356 - (UIxMailTreeBlock *)treeNavigationBlockForObject:(id)_object
357   withActiveChildBlock:(UIxMailTreeBlock *)_activeChildBlock 
358   depth:(int)_depth
359 {
360   /*
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
363           list of subfolders.
364   */
365   UIxMailTreeBlock *resultBlock;
366   NSMutableArray   *blocks;
367   NSString         *activeName;
368   NSArray          *folders;
369   NSString         *title, *icon;
370   unsigned         i, count;
371   
372   activeName = [_activeChildBlock valueForKey:@"name"];
373   
374   /* process child folders */
375   
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;
381     id folder;
382     
383     folder = [folders objectAtIndex:i];
384     block = [activeName isEqualToString:[folder nameInContainer]]
385       ? _activeChildBlock
386       : [self treeNavigationBlockForLeafNode:folder atDepth:_depth];
387     
388     if ([block isNotNull]) [blocks addObject:block];
389   }
390   if ([blocks count] == 0) {
391     if (_activeChildBlock != nil) // if the parent has no proper fetchmethod!
392       [blocks addObject:_activeChildBlock];
393     else
394       blocks = nil;
395   }
396   
397   /* build block */
398   
399   [self getTitle:&title andIcon:&icon forObject:_object];
400   resultBlock = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
401                                   title:title iconName:icon
402                                   link:
403                                     [self treeNavigationLinkForObject:_object 
404                                           atDepth:(_depth + 1)] 
405                                   isPathNode:YES isActiveNode:NO
406                                   childBlocks:blocks];
407   
408   /* recurse up unless we are at the root */
409
410   if ([self isRootObject:_object]) /* we are at the top */
411     return resultBlock;
412   
413   return [self treeNavigationBlockForObject:[_object container] 
414                withActiveChildBlock:resultBlock
415                depth:(_depth + 1)];
416 }
417
418 - (UIxMailTreeBlock *)buildNavigationNodesForObject:(id)_object {
419   /*
420     This is the top-level 'flattening' method. The _object is the active
421     object in the tree, that is, usually a "current folder".
422     
423     The tree will show:
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.
427   */
428   UIxMailTreeBlock *block;
429   
430   /* 
431      This is the cursor, we create nodes below that for direct subfolders
432   */
433   if (debugBlocks) [self logWithFormat:@"ACTIVE block ..."];
434   block = [self treeNavigationBlockForActiveNode:_object];
435   if (debugBlocks) [self logWithFormat:@"  ACTIVE block: %@", block];
436   
437   if ([self isRootObject:_object]) {
438     if (debugBlocks) [self logWithFormat:@"  active block is root."];
439     return block;
440   }
441   
442   /* 
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.
445   */
446   if (debugBlocks) [self logWithFormat:@"ACTIVE parent block ..."];
447   block = [self treeNavigationBlockForObject:[_object container] 
448                 withActiveChildBlock:block
449                 depth:1];
450   if (debugBlocks) [self logWithFormat:@"done: %@", block];
451   return block;
452 }
453
454 /* tree */
455
456 - (NSArray *)rootNodes {
457   UIxMailTreeBlock *navNode;
458   
459   if (self->rootNodes != nil)
460     return self->rootNodes;
461   
462   navNode = [self buildNavigationNodesForObject:[self clientObject]];
463   
464   if ([navNode hasChildren] && [navNode areChildrenLoaded])
465     self->rootNodes = [[navNode children] retain];
466   else if (navNode)
467     self->rootNodes = [[NSArray alloc] initWithObjects:&navNode count:1];
468   
469   return self->rootNodes;
470 }
471
472 /* notifications */
473
474 - (void)sleep {
475   [self->item      release]; self->item      = nil;
476   [self->rootNodes release]; self->rootNodes = nil;
477   [super sleep];
478 }
479
480 @end /* UIxMailTree */