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
31 @interface UIxMailListView : UIxComponent
33 NSArray *sortedUIDs; /* we always need to retrieve all anyway! */
35 unsigned firstMessageNumber;
39 - (NSString *)defaultSortKey;
40 - (NSString *)imap4SortKey;
41 - (NSString *)imap4SortOrdering;
43 - (BOOL)isSortedDescending;
48 #include <SOGo/SoObjects/Mailer/SOGoMailFolder.h>
49 #include <SOGo/SoObjects/Mailer/SOGoMailObject.h>
51 @implementation UIxMailListView
53 static int attachmentFlagSize = 8096;
56 [self->sortedUIDs release];
57 [self->messages release];
58 [self->message release];
65 [self->sortedUIDs release]; self->sortedUIDs = nil;
66 [self->messages release]; self->messages = nil;
67 [self->message release]; self->message = nil;
73 - (void)setMessage:(id)_msg {
74 ASSIGN(self->message, _msg);
80 - (BOOL)showToAddress {
81 // TODO: switch for Sent folder
87 - (NSString *)objectTitle {
88 return [[self clientObject] nameInContainer];
90 - (NSString *)panelTitle {
93 s = [self labelForKey:@"View Mail Folder"];
94 s = [s stringByAppendingString:@": "];
95 s = [s stringByAppendingString:[self objectTitle]];
99 /* derived accessors */
101 - (BOOL)isMessageDeleted {
104 flags = [[self message] valueForKey:@"flags"];
105 return [flags containsObject:@"deleted"];
108 - (BOOL)isMessageRead {
111 flags = [[self message] valueForKey:@"flags"];
112 return [flags containsObject:@"seen"];
114 - (NSString *)messageUidString {
115 return [[[self message] valueForKey:@"uid"] stringValue];
118 - (NSString *)messageSubjectStyleClass {
119 return [self isMessageRead]
120 ? @"mailer_readmailsubject"
121 : @"mailer_unreadmailsubject";
123 - (NSString *)messageCellStyleClass {
124 return [self isMessageDeleted]
125 ? @"mailer_listcell_deleted"
126 : @"mailer_listcell_regular";
129 - (BOOL)hasMessageAttachment {
130 /* we detect attachments by size ... */
133 size = [[[self message] valueForKey:@"size"] intValue];
134 return size > attachmentFlagSize;
137 /* fetching messages */
139 - (NSArray *)fetchKeys {
140 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
141 static NSArray *keys = nil;
143 keys = [[NSArray alloc] initWithObjects:
144 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
153 - (NSString *)defaultSortKey {
156 - (NSString *)imap4SortKey {
159 sort = [[[self context] request] formValueForKey:@"sort"];
161 if ([sort length] == 0)
162 sort = [self defaultSortKey];
163 return [sort uppercaseString];
166 - (BOOL)isSortedDescending {
169 desc = [[[self context] request] formValueForKey:@"desc"];
172 return [desc boolValue] ? YES : NO;
175 - (NSString *)imap4SortOrdering {
178 sort = [self imap4SortKey];
179 if(![self isSortedDescending])
181 return [@"REVERSE " stringByAppendingString:sort];
184 - (NSRange)fetchRange {
185 if (self->firstMessageNumber == 0)
186 return NSMakeRange(0, 50);
187 return NSMakeRange(self->firstMessageNumber - 1, 50);
190 - (NSArray *)sortedUIDs {
191 if (self->sortedUIDs != nil)
192 return self->sortedUIDs;
195 = [[[self clientObject] fetchUIDsMatchingQualifier:[self qualifier]
196 sortOrdering:[self imap4SortOrdering]] retain];
197 return self->sortedUIDs;
199 - (unsigned int)totalMessageCount {
200 return [self->sortedUIDs count];
202 - (BOOL)showsAllMessages {
203 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
206 - (NSRange)fetchBlock {
211 r = [self fetchRange];
212 uids = [self sortedUIDs];
214 /* only need to restrict if we have a lot */
215 if ((len = [uids count]) <= r.length) {
221 if (len < r.location) {
222 // TODO: CHECK CONDITION (< vs <=)
223 /* out of range, recover at first block */
228 if (r.location + r.length > len)
229 r.length = len - r.location;
232 - (unsigned int)firstMessageNumber {
233 return [self fetchBlock].location + 1;
235 - (unsigned int)lastMessageNumber {
238 r = [self fetchBlock];
239 return r.location + r.length;
241 - (BOOL)hasPrevious {
242 return [self fetchBlock].location == 0 ? NO : YES;
245 NSRange r = [self fetchBlock];
246 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
249 - (unsigned int)nextFirstMessageNumber {
250 return [self firstMessageNumber] + [self fetchRange].length;
252 - (unsigned int)prevFirstMessageNumber {
256 idx = [self firstMessageNumber];
257 r = [self fetchRange];
259 return (idx - r.length);
263 - (NSArray *)messages {
269 if (self->messages != nil)
270 return self->messages;
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];
278 msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
279 self->messages = [[msgs valueForKey:@"fetch"] retain];
280 return self->messages;
285 - (NSString *)messageViewTarget {
286 return [@"SOGo_msg_" stringByAppendingString:[self messageUidString]];
288 - (NSString *)messageViewURL {
289 // TODO: noframe only when view-target is empty
290 // TODO: markread only if the message is unread
293 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
294 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
297 - (NSString *)markReadURL {
298 return [@"markMessageRead?uid=" stringByAppendingString:
299 [self messageUidString]];
301 - (NSString *)markUnreadURL {
302 return [@"markMessageUnread?uid=" stringByAppendingString:
303 [self messageUidString]];
308 - (NSString *)msgRowID {
309 return [@"row_" stringByAppendingString:[self messageUidString]];
311 - (NSString *)msgDivID {
312 return [@"div_" stringByAppendingString:[self messageUidString]];
315 - (NSString *)msgIconReadDivID {
316 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
318 - (NSString *)msgIconUnreadDivID {
319 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
321 - (NSString *)msgIconReadVisibility {
322 return [self isMessageRead] ? nil : @"display: none;";
324 - (NSString *)msgIconUnreadVisibility {
325 return [self isMessageRead] ? @"display: none;" : nil;
328 - (NSString *)clickedMsgJS {
329 /* return 'false' aborts processing */
330 return [NSString stringWithFormat:@"clickedUid(this, '%@'); return false",
331 [self messageUidString]];
333 - (NSString *)dblClickedMsgJS {
334 return [NSString stringWithFormat:@"doubleClickedUid(this, '%@')",
335 [self messageUidString]];
337 - (NSString *)highlightRowJS {
338 return [NSString stringWithFormat:@"highlightUid(this, '%@')",
339 [self messageUidString]];
341 - (NSString *)lowlightRowJS {
342 return [NSString stringWithFormat:@"lowlightUid(this, '%@')",
343 [self messageUidString]];
346 - (NSString *)jsCode {
347 static NSString *script = \
348 @"var rowSelectionCount = 0;\n"
350 @"validateControls();\n"
352 @"function showElement(e, shouldShow) {\n"
353 @" e.style.display = shouldShow ? \"\" : \"none\";\n"
356 @"function enableElement(e, shouldEnable) {\n"
359 @" if(shouldEnable) {\n"
360 @" if(e.hasAttribute(\"disabled\"))\n"
361 @" e.removeAttribute(\"disabled\");\n"
364 @" e.setAttribute(\"disabled\", \"1\");\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"
376 @" tr.className = \"tableview\";\n"
377 @" rowSelectionCount -= 1;\n"
379 @" this.validateControls();\n"
382 @"function validateControls() {\n"
383 @" var e = document.getElementById(\"moveto\");\n"
384 @" this.enableElement(e, rowSelectionCount > 0);\n"
387 @"function moveTo(uri) {\n"
388 @" alert(\"MoveTo: \" + uri);\n"
396 - (SOGoMailObject *)lookupActiveMessage {
399 if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
402 return [[self clientObject] lookupName:uid inContext:[self context]
408 - (id)defaultAction {
410 [self logWithFormat:@"default action ..."];
412 self->firstMessageNumber =
413 [[[[self context] request] formValueForKey:@"idx"] intValue];
417 - (id)markMessageUnreadAction {
420 if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
421 // TODO: improve error handling
424 return [self redirectToLocation:@"view"];
426 - (id)markMessageReadAction {
429 if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
430 // TODO: improve error handling
433 return [self redirectToLocation:@"view"];
436 - (id)getMailAction {
437 // TODO: we might want to flush the caches?
440 if ((client = [self clientObject]) == nil) {
441 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
442 reason:@"did not find mail folder"];
445 if (![client respondsToSelector:@selector(flushMailCaches)]) {
446 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
448 @"invalid client object (does not support flush)"];
451 [client flushMailCaches];
452 return [self redirectToLocation:@"view"];
455 - (id)expungeAction {
456 // TODO: we might want to flush the caches?
460 if ((client = [self clientObject]) == nil) {
461 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
462 reason:@"did not find mail folder"];
465 if ((error = [[self clientObject] expunge]) != nil)
468 if ([client respondsToSelector:@selector(flushMailCaches)])
469 [client flushMailCaches];
470 return [self redirectToLocation:@"view"];
473 @end /* UIxMailListView */