2 Copyright (C) 2004-2005 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
22 #include <SOGoUI/UIxComponent.h>
27 This component represent a list of mails and is attached to an SOGoMailFolder
33 @interface UIxMailListView : UIxComponent
35 NSArray *sortedUIDs; /* we always need to retrieve all anyway! */
37 unsigned firstMessageNumber;
39 EOQualifier *qualifier;
42 - (NSString *)defaultSortKey;
43 - (NSString *)imap4SortKey;
44 - (NSString *)imap4SortOrdering;
46 - (BOOL)isSortedDescending;
51 #include <SoObjects/Mailer/SOGoMailFolder.h>
52 #include <SoObjects/Mailer/SOGoMailObject.h>
53 #include <NGObjWeb/SoObject+SoDAV.h>
55 @implementation UIxMailListView
57 static int attachmentFlagSize = 8096;
60 [self->qualifier release];
61 [self->sortedUIDs release];
62 [self->messages release];
63 [self->message release];
70 return [[[[self context] request] formValueForKey:@"noframe"] boolValue];
76 [self->qualifier release]; self->qualifier = nil;
77 [self->sortedUIDs release]; self->sortedUIDs = nil;
78 [self->messages release]; self->messages = nil;
79 [self->message release]; self->message = nil;
85 - (void)setMessage:(id)_msg {
86 ASSIGN(self->message, _msg);
92 - (void)setQualifier:(EOQualifier *)_msg {
93 ASSIGN(self->qualifier, _msg);
95 - (EOQualifier *)qualifier {
96 return self->qualifier;
99 - (BOOL)showToAddress {
102 ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
103 return [ftype isEqual:@"IPF.Sent"];
108 - (NSString *)objectTitle {
109 return [[self clientObject] nameInContainer];
111 - (NSString *)panelTitle {
114 s = [self labelForKey:@"View Mail Folder"];
115 s = [s stringByAppendingString:@": "];
116 s = [s stringByAppendingString:[self objectTitle]];
120 /* derived accessors */
122 - (BOOL)isMessageDeleted {
125 flags = [[self message] valueForKey:@"flags"];
126 return [flags containsObject:@"deleted"];
129 - (BOOL)isMessageRead {
132 flags = [[self message] valueForKey:@"flags"];
133 return [flags containsObject:@"seen"];
135 - (NSString *)messageUidString {
136 return [[[self message] valueForKey:@"uid"] stringValue];
139 - (NSString *)messageSubjectStyleClass {
140 return [self isMessageRead]
141 ? @"mailer_readmailsubject"
142 : @"mailer_unreadmailsubject";
144 - (NSString *)messageCellStyleClass {
145 return [self isMessageDeleted]
146 ? @"mailer_listcell_deleted"
147 : @"mailer_listcell_regular";
150 - (BOOL)hasMessageAttachment {
151 /* we detect attachments by size ... */
154 size = [[[self message] valueForKey:@"size"] intValue];
155 return size > attachmentFlagSize;
158 /* fetching messages */
160 - (NSArray *)fetchKeys {
161 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
162 static NSArray *keys = nil;
164 keys = [[NSArray alloc] initWithObjects:
165 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
170 - (NSString *)defaultSortKey {
173 - (NSString *)imap4SortKey {
176 sort = [[[self context] request] formValueForKey:@"sort"];
178 if ([sort length] == 0)
179 sort = [self defaultSortKey];
180 return [sort uppercaseString];
183 - (BOOL)isSortedDescending {
186 desc = [[[self context] request] formValueForKey:@"desc"];
189 return [desc boolValue] ? YES : NO;
192 - (NSString *)imap4SortOrdering {
195 sort = [self imap4SortKey];
196 if(![self isSortedDescending])
198 return [@"REVERSE " stringByAppendingString:sort];
201 - (NSRange)fetchRange {
202 if (self->firstMessageNumber == 0)
203 return NSMakeRange(0, 50);
204 return NSMakeRange(self->firstMessageNumber - 1, 50);
207 - (NSArray *)sortedUIDs {
208 if (self->sortedUIDs != nil)
209 return self->sortedUIDs;
212 = [[[self clientObject] fetchUIDsMatchingQualifier:[self qualifier]
213 sortOrdering:[self imap4SortOrdering]] retain];
214 return self->sortedUIDs;
216 - (unsigned int)totalMessageCount {
217 return [self->sortedUIDs count];
219 - (BOOL)showsAllMessages {
220 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
223 - (NSRange)fetchBlock {
228 r = [self fetchRange];
229 uids = [self sortedUIDs];
231 /* only need to restrict if we have a lot */
232 if ((len = [uids count]) <= r.length) {
238 if (len < r.location) {
239 // TODO: CHECK CONDITION (< vs <=)
240 /* out of range, recover at first block */
245 if (r.location + r.length > len)
246 r.length = len - r.location;
249 - (unsigned int)firstMessageNumber {
250 return [self fetchBlock].location + 1;
252 - (unsigned int)lastMessageNumber {
255 r = [self fetchBlock];
256 return r.location + r.length;
258 - (BOOL)hasPrevious {
259 return [self fetchBlock].location == 0 ? NO : YES;
262 NSRange r = [self fetchBlock];
263 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
266 - (unsigned int)nextFirstMessageNumber {
267 return [self firstMessageNumber] + [self fetchRange].length;
269 - (unsigned int)prevFirstMessageNumber {
273 idx = [self firstMessageNumber];
274 r = [self fetchRange];
276 return (idx - r.length);
280 - (NSArray *)messages {
286 if (self->messages != nil)
287 return self->messages;
289 r = [self fetchBlock];
290 uids = [self sortedUIDs];
291 if ((len = [uids count]) > r.length)
292 /* only need to restrict if we have a lot */
293 uids = [uids subarrayWithRange:r];
295 msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
296 self->messages = [[msgs valueForKey:@"fetch"] retain];
297 return self->messages;
302 - (NSString *)messageViewTarget {
303 return [@"SOGo_msg_" stringByAppendingString:[self messageUidString]];
305 - (NSString *)messageViewURL {
306 // TODO: noframe only when view-target is empty
307 // TODO: markread only if the message is unread
310 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
311 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
314 - (NSString *)markReadURL {
315 return [@"markMessageRead?uid=" stringByAppendingString:
316 [self messageUidString]];
318 - (NSString *)markUnreadURL {
319 return [@"markMessageUnread?uid=" stringByAppendingString:
320 [self messageUidString]];
325 - (NSString *)msgRowID {
326 return [@"row_" stringByAppendingString:[self messageUidString]];
328 - (NSString *)msgDivID {
329 return [@"div_" stringByAppendingString:[self messageUidString]];
332 - (NSString *)msgIconReadDivID {
333 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
335 - (NSString *)msgIconUnreadDivID {
336 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
338 - (NSString *)msgIconReadVisibility {
339 return [self isMessageRead] ? nil : @"display: none;";
341 - (NSString *)msgIconUnreadVisibility {
342 return [self isMessageRead] ? @"display: none;" : nil;
345 - (NSString *)clickedMsgJS {
346 /* return 'false' aborts processing */
347 return [NSString stringWithFormat:@"clickedUid(this, '%@'); return false",
348 [self messageUidString]];
351 // the following are unused?
352 - (NSString *)dblClickedMsgJS {
353 return [NSString stringWithFormat:@"doubleClickedUid(this, '%@')",
354 [self messageUidString]];
357 // the following are unused?
358 - (NSString *)highlightRowJS {
359 return [NSString stringWithFormat:@"highlightUid(this, '%@')",
360 [self messageUidString]];
362 - (NSString *)lowlightRowJS {
363 return [NSString stringWithFormat:@"lowlightUid(this, '%@')",
364 [self messageUidString]];
367 - (NSString *)markUnreadJS {
368 return [NSString stringWithFormat:
369 @"mailListMarkMessage(this, 'markMessageUnread', "
371 [self messageUidString]];
373 - (NSString *)markReadJS {
374 return [NSString stringWithFormat:
375 @"mailListMarkMessage(this, 'markMessageRead', "
377 [self messageUidString]];
380 /* error redirects */
382 - (id)redirectToViewWithError:(id)_error {
383 // TODO: DUP in UIxMailAccountView
384 // TODO: improve, localize
385 // TODO: there is a bug in the treeview which preserves the current URL for
386 // the active object (displaying the error again)
389 if (![_error isNotNull])
390 return [self redirectToLocation:@"view"];
392 if ([_error isKindOfClass:[NSException class]])
393 _error = [_error reason];
394 else if ([_error isKindOfClass:[NSString class]])
395 _error = [_error stringValue];
397 url = [_error stringByEscapingURL];
398 url = [@"view?error=" stringByAppendingString:url];
399 return [self redirectToLocation:url];
404 - (SOGoMailObject *)lookupActiveMessage {
407 if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
410 return [[self clientObject] lookupName:uid inContext:[self context]
416 - (id)defaultAction {
417 self->firstMessageNumber =
418 [[[[self context] request] formValueForKey:@"idx"] intValue];
422 - (BOOL)isJavaScriptRequest {
423 return [[[[self context] request] formValueForKey:@"jsonly"] boolValue];
428 r = [[self context] response];
429 [r setStatus:200 /* OK */];
433 - (id)markMessageUnreadAction {
436 if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
437 // TODO: improve error handling
440 if ([self isJavaScriptRequest])
441 return [self javaScriptOK];
443 return [self redirectToLocation:@"view"];
445 - (id)markMessageReadAction {
448 if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
449 // TODO: improve error handling
452 if ([self isJavaScriptRequest])
453 return [self javaScriptOK];
455 return [self redirectToLocation:@"view"];
458 - (id)getMailAction {
459 // TODO: we might want to flush the caches?
462 if ((client = [self clientObject]) == nil) {
463 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
464 reason:@"did not find mail folder"];
467 if (![client respondsToSelector:@selector(flushMailCaches)]) {
468 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
470 @"invalid client object (does not support flush)"];
473 [client flushMailCaches];
474 return [self redirectToLocation:@"view"];
477 - (id)expungeAction {
478 // TODO: we might want to flush the caches?
482 if ((client = [self clientObject]) == nil) {
483 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
484 reason:@"did not find mail folder"];
487 if ((error = [[self clientObject] expunge]) != nil)
490 if ([client respondsToSelector:@selector(flushMailCaches)])
491 [client flushMailCaches];
492 return [self redirectToLocation:@"view"];
495 - (id)emptyTrashAction {
496 // TODO: we might want to flush the caches?
500 if ((client = [self clientObject]) == nil) {
501 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
502 reason:@"did not find mail folder"];
503 return [self redirectToViewWithError:error];
506 if (![client isKindOfClass:NSClassFromString(@"SOGoTrashFolder")]) {
507 /* would be better to move the method to an own class, but well .. */
508 error = [NSException exceptionWithHTTPStatus:400 /* Bad Request */
509 reason:@"method cannot be invoked on "
510 @"the specified object"];
511 return [self redirectToViewWithError:error];
514 /* mark all as deleted */
516 [self logWithFormat:@"TODO: must mark all as deleted for empty-trash"];
518 error = [[self clientObject] addFlagsToAllMessages:@"deleted"];
520 // TODO: improve error
521 return [self redirectToViewWithError:error];
525 if ((error = [[self clientObject] expunge]) != nil)
526 // TODO: improve error
527 return [self redirectToViewWithError:error];
529 if ([client respondsToSelector:@selector(flushMailCaches)])
530 [client flushMailCaches];
531 return [self redirectToLocation:@"view"];
534 /* folder operations */
536 - (id)createFolderAction {
538 NSString *folderName;
541 folderName = [[[self context] request] formValueForKey:@"name"];
542 if ([folderName length] == 0) {
543 error = [NSException exceptionWithHTTPStatus:400 /* Bad Request */
544 reason:@"missing 'name' query parameter!"];
545 return [self redirectToViewWithError:error];
548 if ((client = [self clientObject]) == nil) {
549 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
550 reason:@"did not find mail folder"];
551 return [self redirectToViewWithError:error];
554 if ((error = [[self clientObject] davCreateCollection:folderName
555 inContext:[self context]]) != nil) {
556 return [self redirectToViewWithError:error];
559 return [self redirectToLocation:[folderName stringByAppendingString:@"/"]];
562 - (id)deleteFolderAction {
567 if ((client = [self clientObject]) == nil) {
568 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
569 reason:@"did not find mail folder"];
570 return [self redirectToViewWithError:error];
573 /* jump to parent folder afterwards */
574 url = [[client container] baseURLInContext:[self context]];
575 if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
577 if ((error = [[self clientObject] delete]) != nil)
578 return [self redirectToViewWithError:error];
580 return [self redirectToLocation:url];
583 @end /* UIxMailListView */