]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/Mailer/UIxMailListView.m
917edaf35d66966feca298f781cf9536b6671063
[scalable-opengroupware.org] / SOGo / UI / Mailer / UIxMailListView.m
1 /*
2   Copyright (C) 2004-2005 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 /*
25   UIxMailListView
26   
27   This component represent a list of mails and is attached to an SOGoMailFolder
28   object.
29 */
30
31 @interface UIxMailListView : UIxComponent
32 {
33   NSArray  *sortedUIDs; /* we always need to retrieve all anyway! */
34   NSArray  *messages;
35   unsigned firstMessageNumber;
36   id       message;
37 }
38
39 - (NSString *)defaultSortKey;
40 - (NSString *)imap4SortKey;
41 - (NSString *)imap4SortOrdering;
42
43 - (BOOL)isSortedDescending;
44
45 @end
46
47 #include "common.h"
48 #include <SOGo/SoObjects/Mailer/SOGoMailFolder.h>
49 #include <SOGo/SoObjects/Mailer/SOGoMailObject.h>
50
51 @implementation UIxMailListView
52
53 static int attachmentFlagSize = 8096;
54
55 - (void)dealloc {
56   [self->sortedUIDs release];
57   [self->messages   release];
58   [self->message    release];
59   [super dealloc];
60 }
61
62 /* notifications */
63
64 - (void)sleep {
65   [self->sortedUIDs release]; self->sortedUIDs = nil;
66   [self->messages   release]; self->messages = nil;
67   [self->message    release]; self->message  = nil;
68   [super sleep];
69 }
70
71 /* accessors */
72
73 - (void)setMessage:(id)_msg {
74   ASSIGN(self->message, _msg);
75 }
76 - (id)message {
77   return self->message;
78 }
79
80 - (BOOL)showToAddress {
81   // TODO: switch for Sent folder
82   return NO;
83 }
84
85 /* title */
86
87 - (NSString *)objectTitle {
88   return [[self clientObject] nameInContainer];
89 }
90 - (NSString *)panelTitle {
91   NSString *s;
92   
93   s = [self labelForKey:@"View Mail Folder"];
94   s = [s stringByAppendingString:@": "];
95   s = [s stringByAppendingString:[self objectTitle]];
96   return s;
97 }
98
99 /* derived accessors */
100
101 - (BOOL)isMessageDeleted {
102   NSArray *flags;
103   
104   flags = [[self message] valueForKey:@"flags"];
105   return [flags containsObject:@"deleted"];
106 }
107
108 - (BOOL)isMessageRead {
109   NSArray *flags;
110   
111   flags = [[self message] valueForKey:@"flags"];
112   return [flags containsObject:@"seen"];
113 }
114 - (NSString *)messageUidString {
115   return [[[self message] valueForKey:@"uid"] stringValue];
116 }
117
118 - (NSString *)messageSubjectStyleClass {
119   return [self isMessageRead]
120     ? @"mailer_readmailsubject"
121     : @"mailer_unreadmailsubject";
122 }
123 - (NSString *)messageCellStyleClass {
124   return [self isMessageDeleted]
125     ? @"mailer_listcell_deleted"
126     : @"mailer_listcell_regular";
127 }
128
129 - (BOOL)hasMessageAttachment {
130   /* we detect attachments by size ... */
131   unsigned size;
132   
133   size = [[[self message] valueForKey:@"size"] intValue];
134   return size > attachmentFlagSize;
135 }
136
137 /* fetching messages */
138
139 - (NSArray *)fetchKeys {
140   /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
141   static NSArray *keys = nil;
142   if (keys == nil) {
143     keys = [[NSArray alloc] initWithObjects:
144                               @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
145   }
146   return keys;
147 }
148
149 - (id)qualifier {
150   return nil;
151 }
152
153 - (NSString *)defaultSortKey {
154   return @"DATE";
155 }
156 - (NSString *)imap4SortKey {
157   NSString *sort;
158   
159   sort = [[[self context] request] formValueForKey:@"sort"];
160   
161   if ([sort length] == 0)
162     sort = [self defaultSortKey];
163   return [sort uppercaseString];
164 }
165
166 - (BOOL)isSortedDescending {
167   NSString *desc;
168   
169   desc = [[[self context] request] formValueForKey:@"desc"];
170   if(!desc)
171     return NO;
172   return [desc boolValue] ? YES : NO;
173 }
174
175 - (NSString *)imap4SortOrdering {
176   NSString *sort;
177   
178   sort = [self imap4SortKey];
179   if(![self isSortedDescending])
180     return sort;
181   return [@"REVERSE " stringByAppendingString:sort];
182 }
183
184 - (NSRange)fetchRange {
185   if (self->firstMessageNumber == 0)
186     return NSMakeRange(0, 50);
187   return NSMakeRange(self->firstMessageNumber - 1, 50);
188 }
189
190 - (NSArray *)sortedUIDs {
191   if (self->sortedUIDs != nil)
192     return self->sortedUIDs;
193   
194   self->sortedUIDs 
195     = [[[self clientObject] fetchUIDsMatchingQualifier:[self qualifier]
196                             sortOrdering:[self imap4SortOrdering]] retain];
197   return self->sortedUIDs;
198 }
199 - (unsigned int)totalMessageCount {
200   return [self->sortedUIDs count];
201 }
202 - (BOOL)showsAllMessages {
203   return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
204 }
205
206 - (NSRange)fetchBlock {
207   NSRange  r;
208   unsigned len;
209   NSArray  *uids;
210   
211   r    = [self fetchRange];
212   uids = [self sortedUIDs];
213   
214   /* only need to restrict if we have a lot */
215   if ((len = [uids count]) <= r.length) {
216     r.location = 0;
217     r.length   = len;
218     return r;
219   }
220   
221   if (len < r.location) {
222     // TODO: CHECK CONDITION (< vs <=)
223     /* out of range, recover at first block */
224     r.location = 0;
225     return r;
226   }
227   
228   if (r.location + r.length > len)
229     r.length = len - r.location;
230   return r;
231 }
232 - (unsigned int)firstMessageNumber {
233   return [self fetchBlock].location + 1;
234 }
235 - (unsigned int)lastMessageNumber {
236   NSRange r;
237   
238   r = [self fetchBlock];
239   return r.location + r.length;
240 }
241 - (BOOL)hasPrevious {
242   return [self fetchBlock].location == 0 ? NO : YES;
243 }
244 - (BOOL)hasNext {
245   NSRange r = [self fetchBlock];
246   return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
247 }
248
249 - (unsigned int)nextFirstMessageNumber {
250   return [self firstMessageNumber] + [self fetchRange].length;
251 }
252 - (unsigned int)prevFirstMessageNumber {
253   NSRange  r;
254   unsigned idx;
255   
256   idx = [self firstMessageNumber];
257   r   = [self fetchRange];
258   if (idx > r.length)
259     return (idx - r.length);
260   return 1;
261 }
262
263 - (NSArray *)messages {
264   NSArray  *uids;
265   NSArray  *msgs;
266   NSRange  r;
267   unsigned len;
268   
269   if (self->messages != nil)
270     return self->messages;
271   
272   r    = [self fetchBlock];
273   uids = [self sortedUIDs];
274   if ((len = [uids count]) > r.length)
275     /* only need to restrict if we have a lot */
276     uids = [uids subarrayWithRange:r];
277   
278   msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
279   self->messages = [[msgs valueForKey:@"fetch"] retain];
280   return self->messages;
281 }
282
283 /* URL processing */
284
285 - (NSString *)messageViewTarget {
286   return [@"SOGo_msg_" stringByAppendingString:[self messageUidString]];
287 }
288 - (NSString *)messageViewURL {
289   // TODO: noframe only when view-target is empty
290   // TODO: markread only if the message is unread
291   NSString *s;
292   
293   s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
294   if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
295   return s;
296 }
297 - (NSString *)markReadURL {
298   return [@"markMessageRead?uid=" stringByAppendingString:
299              [self messageUidString]];
300 }
301 - (NSString *)markUnreadURL {
302   return [@"markMessageUnread?uid=" stringByAppendingString:
303              [self messageUidString]];
304 }
305
306 /* JavaScript */
307
308 - (NSString *)msgRowID {
309   return [@"row_" stringByAppendingString:[self messageUidString]];
310 }
311 - (NSString *)msgDivID {
312   return [@"div_" stringByAppendingString:[self messageUidString]];
313 }
314
315 - (NSString *)msgIconReadDivID {
316   return [@"readdiv_" stringByAppendingString:[self messageUidString]];
317 }
318 - (NSString *)msgIconUnreadDivID {
319   return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
320 }
321 - (NSString *)msgIconReadVisibility {
322   return [self isMessageRead] ? nil : @"display: none;";
323 }
324 - (NSString *)msgIconUnreadVisibility {
325   return [self isMessageRead] ? @"display: none;" : nil;
326 }
327
328 - (NSString *)clickedMsgJS {
329   /* return 'false' aborts processing */
330   return [NSString stringWithFormat:@"clickedUid(this, '%@'); return false", 
331                      [self messageUidString]];
332 }
333 - (NSString *)dblClickedMsgJS {
334   return [NSString stringWithFormat:@"doubleClickedUid(this, '%@')", 
335                      [self messageUidString]];
336 }
337 - (NSString *)highlightRowJS {
338   return [NSString stringWithFormat:@"highlightUid(this, '%@')", 
339                      [self messageUidString]];
340 }
341 - (NSString *)lowlightRowJS {
342   return [NSString stringWithFormat:@"lowlightUid(this, '%@')", 
343                      [self messageUidString]];
344 }
345
346 - (NSString *)jsCode {
347   static NSString *script = \
348   @"var rowSelectionCount = 0;\n"
349   @"\n"
350   @"validateControls();\n"
351   @"\n"
352   @"function showElement(e, shouldShow) {\n"
353   @"    e.style.display = shouldShow ? \"\" : \"none\";\n"
354   @"}\n"
355   @"\n"
356   @"function enableElement(e, shouldEnable) {\n"
357   @"  if(!e)\n"
358   @"    return;\n"
359   @"  if(shouldEnable) {\n"
360   @"    if(e.hasAttribute(\"disabled\"))\n"
361   @"      e.removeAttribute(\"disabled\");\n"
362   @"  }\n"
363   @"  else {\n"
364   @"    e.setAttribute(\"disabled\", \"1\");\n"
365   @"  }\n"
366   @"}\n"
367   @"\n"
368   @"function toggleRowSelectionStatus(sender) {\n"
369   @"  rowID = sender.value;\n"
370   @"  tr = document.getElementById(rowID);\n"
371   @"  if(sender.checked) {\n"
372   @"    tr.className = \"tableview_selected\";\n"
373   @"    rowSelectionCount += 1;\n"
374   @"  }\n"
375   @"  else {\n"
376   @"    tr.className = \"tableview\";\n"
377   @"    rowSelectionCount -= 1;\n"
378   @"  }\n"
379   @"  this.validateControls();\n"
380   @"}\n"
381   @"\n"
382   @"function validateControls() {\n"
383   @"  var e = document.getElementById(\"moveto\");\n"
384   @"  this.enableElement(e, rowSelectionCount > 0);\n"
385   @"}\n"
386   @"\n"
387   @"function moveTo(uri) {\n"
388   @"  alert(\"MoveTo: \" + uri);\n"
389   @"}\n"
390   @"";
391   return script;
392 }
393
394 /* active message */
395
396 - (SOGoMailObject *)lookupActiveMessage {
397   NSString *uid;
398   
399   if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
400     return nil;
401
402   return [[self clientObject] lookupName:uid inContext:[self context]
403                               acquire:NO];
404 }
405
406 /* actions */
407
408 - (id)defaultAction {
409 #if 0
410   [self logWithFormat:@"default action ..."];
411 #endif
412   self->firstMessageNumber = 
413     [[[[self context] request] formValueForKey:@"idx"] intValue];
414   return self;
415 }
416
417 - (id)markMessageUnreadAction {
418   NSException *error;
419   
420   if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
421     // TODO: improve error handling
422     return error;
423   
424   return [self redirectToLocation:@"view"];
425 }
426 - (id)markMessageReadAction {
427   NSException *error;
428   
429   if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
430     // TODO: improve error handling
431     return error;
432   
433   return [self redirectToLocation:@"view"];
434 }
435
436 - (id)getMailAction {
437   // TODO: we might want to flush the caches?
438   id client;
439   
440   if ((client = [self clientObject]) == nil) {
441     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
442                         reason:@"did not find mail folder"];
443   }
444   
445   if (![client respondsToSelector:@selector(flushMailCaches)]) {
446     return [NSException exceptionWithHTTPStatus:500 /* Server Error */
447                         reason:
448                           @"invalid client object (does not support flush)"];
449   }
450   
451   [client flushMailCaches];
452   return [self redirectToLocation:@"view"];
453 }
454
455 - (id)expungeAction {
456   // TODO: we might want to flush the caches?
457   NSException *error;
458   id client;
459   
460   if ((client = [self clientObject]) == nil) {
461     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
462                         reason:@"did not find mail folder"];
463   }
464   
465   if ((error = [[self clientObject] expunge]) != nil)
466     return error;
467   
468   if ([client respondsToSelector:@selector(flushMailCaches)])
469     [client flushMailCaches];
470   return [self redirectToLocation:@"view"];
471 }
472
473 @end /* UIxMailListView */