]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailTree.m
disabled banner
[scalable-opengroupware.org] / UI / MailerUI / 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 @interface NSString(DotCutting)
50
51 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength;
52
53 - (NSString *)titleForSOGoIMAP4String;
54
55 @end
56
57 @implementation UIxMailTree
58
59 static BOOL debugBlocks = NO;
60
61 + (void)initialize {
62   [UIxMailTreeBlock class]; // ensure that globals are initialized
63 }
64
65 - (void)dealloc {
66   [self->treeFolderAction release];
67   [self->rootClassName    release];
68   [self->rootNodes release];
69   [self->item      release];
70   [super dealloc];
71 }
72
73 /* icons */
74
75 - (NSString *)defaultIconName {
76   return @"tbtv_leaf_corner_17x17.gif";
77 }
78
79 - (NSString *)iconNameForType:(NSString *)_type {
80   if (![_type isNotNull])
81     return [self defaultIconName];
82   
83   //return @"tbtv_drafts_17x17.gif";
84   
85   return [self defaultIconName];
86 }
87
88 /* accessors */
89
90 - (void)setRootClassName:(id)_rootClassName {
91   ASSIGNCOPY(self->rootClassName, _rootClassName);
92 }
93 - (id)rootClassName {
94   return self->rootClassName;
95 }
96
97 - (void)setItem:(id)_item {
98   ASSIGN(self->item, _item);
99 }
100 - (id)item {
101   return self->item;
102 }
103
104 - (void)setTreeFolderAction:(NSString *)_action {
105   ASSIGNCOPY(self->treeFolderAction, _action);
106 }
107 - (NSString *)treeFolderAction {
108   return self->treeFolderAction;
109 }
110
111 - (NSString *)itemIconName {
112   // TODO: only called once!
113   NSString *ftype;
114   
115   ftype = [[self item] valueForKey:@"outlookFolderClass"];
116   return [self iconNameForType:ftype];
117 }
118
119 /* fetching subfolders */
120
121 - (NSArray *)fetchSubfoldersOfObject:(id)_object {
122   /* Walk over toManyRelationshipKeys and lookup the controllers for them. */
123   NSMutableArray *ma;
124   NSArray  *names;
125   unsigned i, count;
126   
127   if ((names = [_object toManyRelationshipKeys]) == nil) {
128     if (debugBlocks) [self logWithFormat:@"no to-many: %@", _object];
129     return nil;
130   }
131   
132   if (debugBlocks) {
133     [self logWithFormat:@"to-many: %@ %@", _object,
134           [names componentsJoinedByString:@","]];
135   }
136   
137   count = [names count];
138   ma    = [NSMutableArray arrayWithCapacity:(count + 1)];
139   for (i = 0; i < count; i++) {
140     id folder;
141     
142     // TODO: use some context or reuse the main context?
143     folder = [_object lookupName:[names objectAtIndex:i] inContext:nil 
144                       acquire:NO];
145     if (folder == nil) {
146       if (debugBlocks) {
147         [self logWithFormat:@"  DID NOT FIND FOLDER %@: %@",
148                 _object,
149                 [names objectAtIndex:i]];
150       }
151       continue;
152     }
153     if ([folder isKindOfClass:[NSException class]]) {
154       if (debugBlocks) {
155         [self logWithFormat:@"  FOLDER LOOKUP EXCEPTION %@: %@",
156                 [names objectAtIndex:i], folder];
157       }
158       continue;
159     }
160     
161     [ma addObject:folder];
162   }
163   if (debugBlocks)
164     [self logWithFormat:@"  returning: %@ %@", _object, ma];
165   return ma;
166 }
167
168 /* navigation nodes */
169
170 - (BOOL)isRootObject:(id)_object {
171   if (![_object isNotNull]) {
172     [self warnWithFormat:@"(%s): got to root by nil lookup ...",
173             __PRETTY_FUNCTION__];
174     return YES;
175   }
176
177   if ([_object isKindOfClass:NSClassFromString(@"SOGoUserFolder")])
178     return YES;
179   
180   return [_object isKindOfClass:NSClassFromString([self rootClassName])];
181 }
182
183 - (NSString *)treeNavigationLinkForObject:(id)_object atDepth:(int)_depth {
184   NSString *link;
185   unsigned i;
186   
187   link = [[_object nameInContainer] stringByAppendingString:@"/"];
188   link = [link stringByAppendingString:[self treeFolderAction]];
189   
190   switch (_depth) {
191   case 0: return link;
192   case 1: return [@"../"       stringByAppendingString:link];
193   case 2: return [@"../../"    stringByAppendingString:link];
194   case 3: return [@"../../../" stringByAppendingString:link];
195   }
196   
197   for (i = 0; i < _depth; i++)
198     link = [@"../" stringByAppendingString:link];
199   return link;
200 }
201
202 - (void)getTitle:(NSString **)_t andIcon:(NSString **)_icon
203   forObject:(id)_object
204 {
205   // TODO: need to refactor for reuse!
206   NSString *ftype;
207   unsigned len;
208   
209   ftype = [_object valueForKey:@"outlookFolderClass"];
210   len   = [ftype length];
211   
212   switch (len) {
213   case 8:
214     if ([ftype isEqualToString:@"IPF.Sent"]) {
215       *_t = [self labelForKey:@"SentFolderName"];
216       *_icon = @"tbtv_sent_17x17.gif";
217       return;
218     }
219     break;
220   case 9:
221     if ([ftype isEqualToString:@"IPF.Inbox"]) {
222       *_t = [self labelForKey:@"InboxFolderName"];
223       *_icon = @"tbtv_inbox_17x17.gif";
224       return;
225     }
226     if ([ftype isEqualToString:@"IPF.Trash"]) {
227       *_t = [self labelForKey:@"TrashFolderName"];
228       *_icon = @"tbtv_trash_17x17.gif";
229       return;
230     }
231     break;
232   case 10:
233     if ([ftype isEqualToString:@"IPF.Drafts"]) {
234       *_t = [self labelForKey:@"DraftsFolderName"];
235       *_icon = @"tbtv_drafts_17x17.gif";
236       return;
237     }
238     if ([ftype isEqualToString:@"IPF.Filter"]) {
239       *_t = [self labelForKey:@"SieveFolderName"];
240       *_icon = nil;
241       return;
242     }
243     break;
244   }
245   
246   *_t    = [_object davDisplayName];
247   *_icon = nil;
248   
249   if ([_object isKindOfClass:NSClassFromString(@"SOGoMailFolder")])
250     *_icon = nil;
251   else if ([_object isKindOfClass:NSClassFromString(@"SOGoMailAccount")]) {
252     *_icon = @"tbtv_inbox_17x17.gif";
253     
254     /* title processing is somehow Agenor specific and should be done in UI */
255     *_t = [[_object nameInContainer] titleForSOGoIMAP4String];
256   }
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";
261   else {
262     // TODO: use drafts icon for other SOGo folders
263     *_icon = @"tbtv_drafts_17x17.gif";
264   }
265   
266   return;
267 }
268
269 - (UIxMailTreeBlock *)treeNavigationBlockForLeafNode:(id)_o atDepth:(int)_d {
270   UIxMailTreeBlock *md;
271   NSString *n, *i;
272   id blocks;
273   
274   /* 
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.
277   */
278   blocks = [[_o toManyRelationshipKeys] count] > 0
279     ? UIxMailTreeHasChildrenMarker
280     : nil;
281   
282   [self getTitle:&n andIcon:&i forObject:_o];
283   
284   md = [UIxMailTreeBlock blockWithName:nil
285                          title:n iconName:i
286                          link:[self treeNavigationLinkForObject:_o atDepth:_d]
287                          isPathNode:NO isActiveNode:NO
288                          childBlocks:blocks];
289   return md;
290 }
291
292 - (UIxMailTreeBlock *)treeNavigationBlockForRootNode:(id)_object {
293   /*
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).
296   */
297   UIxMailTreeBlock *md;
298   NSMutableArray   *blocks;
299   NSArray          *folders;
300   NSString         *title, *icon;
301   unsigned         i, count;
302
303   if (debugBlocks) {
304     [self logWithFormat:@"block for root node 0x%08X<%@>", 
305             _object, NSStringFromClass([_object class])];
306   }
307   
308   /* process child folders */
309   
310   folders = [self fetchSubfoldersOfObject:_object];
311   count   = [folders count];
312   blocks  = [NSMutableArray arrayWithCapacity:count];
313   for (i = 0; i < count; i++) {
314     id block;
315     
316     block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
317                   atDepth:0];
318     if ([block isNotNull]) [blocks addObject:block];
319   }
320   if ([blocks count] == 0)
321     blocks = nil;
322   
323   /* build block */
324   
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
331                          childBlocks:blocks];
332   return md;
333 }
334
335 - (UIxMailTreeBlock *)treeNavigationBlockForActiveNode:(id)_object {
336   /* 
337      This generates the block for the clientObject (the object which has the 
338      focus)
339   */
340   UIxMailTreeBlock *md;
341   NSMutableArray   *blocks;
342   NSArray  *folders;
343   NSString *title, *icon;
344   unsigned i, count;
345
346   // TODO: maybe we can join the two implementations, this might not be
347   //       necessary
348   if ([self isRootObject:_object]) /* we are at the top */
349     return [self treeNavigationBlockForRootNode:_object];
350   
351   if (debugBlocks) {
352     [self logWithFormat:@"block for active node 0x%08X<%@> - %@", 
353             _object, NSStringFromClass([_object class]),
354             [_object davDisplayName]];
355   }
356   
357   /* process child folders */
358   
359   folders = [self fetchSubfoldersOfObject:_object];
360   count   = [folders count];
361   blocks  = [NSMutableArray arrayWithCapacity:count];
362   for (i = 0; i < count; i++) {
363     UIxMailTreeBlock *block;
364     
365     block = [self treeNavigationBlockForLeafNode:[folders objectAtIndex:i]
366                   atDepth:0];
367     if ([block isNotNull]) [blocks addObject:block];
368   }
369   if ([blocks count] == 0) blocks = nil;
370   
371   /* build block */
372   
373   [self getTitle:&title andIcon:&icon forObject:_object];
374   md = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
375                          title:title iconName:icon
376                          link:@"."
377                          isPathNode:YES isActiveNode:YES
378                          childBlocks:blocks];
379   return md;
380 }
381
382 - (UIxMailTreeBlock *)treeNavigationBlockForObject:(id)_object
383   withActiveChildBlock:(UIxMailTreeBlock *)_activeChildBlock 
384   depth:(int)_depth
385 {
386   /*
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
389           list of subfolders.
390   */
391   UIxMailTreeBlock *resultBlock;
392   NSMutableArray   *blocks;
393   NSString         *activeName;
394   NSArray          *folders;
395   NSString         *title, *icon;
396   unsigned         i, count;
397   
398   activeName = [_activeChildBlock valueForKey:@"name"];
399   
400   /* process child folders */
401   
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;
407     id folder;
408     
409     folder = [folders objectAtIndex:i];
410     block = [activeName isEqualToString:[folder nameInContainer]]
411       ? _activeChildBlock
412       : [self treeNavigationBlockForLeafNode:folder atDepth:_depth];
413     
414     if ([block isNotNull]) [blocks addObject:block];
415   }
416   if ([blocks count] == 0) {
417     if (_activeChildBlock != nil) // if the parent has no proper fetchmethod!
418       [blocks addObject:_activeChildBlock];
419     else
420       blocks = nil;
421   }
422   
423   /* build block */
424   
425   [self getTitle:&title andIcon:&icon forObject:_object];
426   resultBlock = [UIxMailTreeBlock blockWithName:[_object nameInContainer]
427                                   title:title iconName:icon
428                                   link:
429                                     [self treeNavigationLinkForObject:_object 
430                                           atDepth:(_depth + 1)] 
431                                   isPathNode:YES isActiveNode:NO
432                                   childBlocks:blocks];
433   
434   /* recurse up unless we are at the root */
435
436   if ([self isRootObject:_object]) /* we are at the top */
437     return resultBlock;
438   
439   return [self treeNavigationBlockForObject:[_object container] 
440                withActiveChildBlock:resultBlock
441                depth:(_depth + 1)];
442 }
443
444 - (UIxMailTreeBlock *)buildNavigationNodesForObject:(id)_object {
445   /*
446     This is the top-level 'flattening' method. The _object is the active
447     object in the tree, that is, usually a "current folder".
448     
449     The tree will show:
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.
453   */
454   UIxMailTreeBlock *block;
455   
456   /* 
457      This is the cursor, we create nodes below that for direct subfolders
458   */
459   if (debugBlocks) [self logWithFormat:@"ACTIVE block ..."];
460   block = [self treeNavigationBlockForActiveNode:_object];
461   if (debugBlocks) [self logWithFormat:@"  ACTIVE block: %@", block];
462   
463   if ([self isRootObject:_object]) {
464     if (debugBlocks) [self logWithFormat:@"  active block is root."];
465     return block;
466   }
467   
468   /* 
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.
471   */
472   if (debugBlocks) [self logWithFormat:@"ACTIVE parent block ..."];
473   block = [self treeNavigationBlockForObject:[_object container] 
474                 withActiveChildBlock:block
475                 depth:1];
476   if (debugBlocks) [self logWithFormat:@"done: %@", block];
477   return block;
478 }
479
480 /* tree */
481
482 - (NSArray *)rootNodes {
483   UIxMailTreeBlock *navNode;
484   
485   if (self->rootNodes != nil)
486     return self->rootNodes;
487   
488   navNode = [self buildNavigationNodesForObject:[self clientObject]];
489   
490   if ([navNode hasChildren] && [navNode areChildrenLoaded])
491     self->rootNodes = [[navNode children] retain];
492   else if (navNode)
493     self->rootNodes = [[NSArray alloc] initWithObjects:&navNode count:1];
494   
495   return self->rootNodes;
496 }
497
498 /* notifications */
499
500 - (void)sleep {
501   [self->item      release]; self->item      = nil;
502   [self->rootNodes release]; self->rootNodes = nil;
503   [super sleep];
504 }
505
506 @end /* UIxMailTree */
507
508
509 @implementation NSString(DotCutting)
510
511 - (NSString *)stringByCuttingOffAtDotsWhenExceedingLength:(int)_maxLength {
512   NSRange  r, r2;
513   NSString *s;
514   int      i;
515   
516   if ([self length] <= _maxLength) /* if length is small, return as is */
517     return self;
518   
519   if ((r = [self rangeOfString:@"."]).length == 0)
520     /* no dots in share, return even if longer than boundary */
521     return self;
522   
523   s = self;
524   i  = r.location + r.length;
525   r2 = [s rangeOfString:@"." options:NSLiteralSearch 
526           range:NSMakeRange(i, [s length] - i)];
527     
528   if (r2.length > 0) {
529     s = [s substringToIndex:r2.location];
530     if ([s length] <= _maxLength) /* if length is small, return as is */
531       return s;
532   }
533   
534   /* no second dot, and the whole was too long => cut off after first */
535   return [s substringToIndex:r.location];
536 }
537
538 - (NSString *)titleForSOGoIMAP4String {
539   /* 
540      eg:
541        guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo@\
542        amelie-01.ac.melanie2.i2
543   */
544   static int CutOffLength = 16;
545   NSString *s;
546   NSRange  r;
547   
548   s = self;
549   
550   /* check for connect strings without hostnames */
551   
552   r = [s rangeOfString:@"@"];
553   if (r.length == 0) {
554     /* no login provide, just use the hostname (without domain) */
555     r = [s rangeOfString:@"."];
556     return r.length > 0 ? [s substringToIndex:r.location] : s;
557   }
558   
559   s = [s substringToIndex:r.location];
560   
561   /* check for shares */
562   
563   r = [s rangeOfString:@".-."];
564   if (r.length > 0) {
565     /* eg: 'baluh.hommes.tests-montee-en-charge-ogo' */
566     s = [s substringFromIndex:(r.location + r.length)];
567     
568     return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
569   }
570   
571   /* just the login name, possibly long (test.et.di.cete-lyon) */
572   return [s stringByCuttingOffAtDotsWhenExceedingLength:CutOffLength];
573 }
574
575 @end /* NSString(DotCutting) */