]> git.donarmstrong.com Git - roundcube.git/blob - program/js/app.js.src
Imported Upstream version 0.3.1
[roundcube.git] / program / js / app.js.src
1 /*
2  +-----------------------------------------------------------------------+
3  | RoundCube Webmail Client Script                                       |
4  |                                                                       |
5  | This file is part of the RoundCube Webmail client                     |
6  | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 |
7  | Licensed under the GNU GPL                                            |
8  |                                                                       |
9  +-----------------------------------------------------------------------+
10  | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
11  |          Charles McNulty <charles@charlesmcnulty.com>                 |
12  +-----------------------------------------------------------------------+
13  | Requires: jquery.js, common.js, list.js                               |
14  +-----------------------------------------------------------------------+
15
16   $Id: app.js 3059 2009-10-24 19:18:35Z alec $
17 */
18
19
20 function rcube_webmail()
21 {
22   this.env = new Object();
23   this.labels = new Object();
24   this.buttons = new Object();
25   this.buttons_sel = new Object();
26   this.gui_objects = new Object();
27   this.gui_containers = new Object();
28   this.commands = new Object();
29   this.command_handlers = new Object();
30   this.onloads = new Array();
31
32   // create protected reference to myself
33   this.ref = 'rcmail';
34   var ref = this;
35  
36   // webmail client settings
37   this.dblclick_time = 500;
38   this.message_time = 3000;
39   
40   this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
41   
42   // mimetypes supported by the browser (default settings)
43   this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
44                              'image/jpeg', 'image/gif', 'image/png',
45                              'application/x-javascript', 'application/pdf',
46                              'application/x-shockwave-flash');
47
48   // default environment vars
49   this.env.keep_alive = 60;        // seconds
50   this.env.request_timeout = 180;  // seconds
51   this.env.draft_autosave = 0;     // seconds
52   this.env.comm_path = './';
53   this.env.bin_path = './bin/';
54   this.env.blankpage = 'program/blank.gif';
55
56   // set jQuery ajax options
57   jQuery.ajaxSetup({ cache:false,
58     error:function(request, status, err){ ref.http_error(request, status, err); },
59     beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-RoundCube-Request', ref.env.request_token); }
60   });
61
62   // set environment variable(s)
63   this.set_env = function(p, value)
64     {
65     if (p != null && typeof(p) == 'object' && !value)
66       for (var n in p)
67         this.env[n] = p[n];
68     else
69       this.env[p] = value;
70     };
71
72   // add a localized label to the client environment
73   this.add_label = function(key, value)
74     {
75     this.labels[key] = value;
76     };
77
78   // add a button to the button list
79   this.register_button = function(command, id, type, act, sel, over)
80     {
81     if (!this.buttons[command])
82       this.buttons[command] = new Array();
83       
84     var button_prop = {id:id, type:type};
85     if (act) button_prop.act = act;
86     if (sel) button_prop.sel = sel;
87     if (over) button_prop.over = over;
88
89     this.buttons[command][this.buttons[command].length] = button_prop;    
90     };
91
92   // register a specific gui object
93   this.gui_object = function(name, id)
94     {
95     this.gui_objects[name] = id;
96     };
97   
98   // register a container object
99   this.gui_container = function(name, id)
100   {
101     this.gui_containers[name] = id;
102   };
103   
104   // add a GUI element (html node) to a specified container
105   this.add_element = function(elm, container)
106   {
107     if (this.gui_containers[container] && this.gui_containers[container].jquery)
108       this.gui_containers[container].append(elm);
109   };
110
111   // register an external handler for a certain command
112   this.register_command = function(command, callback, enable)
113   {
114     this.command_handlers[command] = callback;
115     
116     if (enable)
117       this.enable_command(command, true);
118   };
119   
120   // execute the given script on load
121   this.add_onload = function(f)
122   {
123     this.onloads[this.onloads.length] = f;
124   };
125
126   // initialize webmail client
127   this.init = function()
128     {
129     var p = this;
130     this.task = this.env.task;
131     
132     // check browser
133     if (!bw.dom || !bw.xmlhttp_test())
134       {
135       this.goto_url('error', '_code=0x199');
136       return;
137       }
138
139     // find all registered gui containers
140     for (var n in this.gui_containers)
141       this.gui_containers[n] = $('#'+this.gui_containers[n]);
142
143     // find all registered gui objects
144     for (var n in this.gui_objects)
145       this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
146       
147     // init registered buttons
148     this.init_buttons();
149
150     // tell parent window that this frame is loaded
151     if (this.env.framed && parent.rcmail && parent.rcmail.set_busy)
152       parent.rcmail.set_busy(false);
153
154     // enable general commands
155     this.enable_command('logout', 'mail', 'addressbook', 'settings', true);
156     
157     if (this.env.permaurl)
158       this.enable_command('permaurl', true);
159
160     switch (this.task)
161       {
162       case 'mail':
163         if (this.gui_objects.messagelist)
164           {
165           this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time});
166           this.message_list.row_init = function(o){ p.init_message_row(o); };
167           this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
168           this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
169           this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
170           this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
171           this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
172           this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
173           document.onmouseup = function(e){ return p.doc_mouse_up(e); };
174
175           this.message_list.init();
176           this.enable_command('toggle_status', 'toggle_flag', true);
177           
178           if (this.gui_objects.mailcontframe)
179             this.gui_objects.mailcontframe.onmousedown = function(e){ return p.click_on_list(e); };
180           else
181             this.message_list.focus();
182           }
183           
184         if (this.env.coltypes)
185           this.set_message_coltypes(this.env.coltypes);
186
187         // enable mail commands
188         this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
189
190         if (this.env.search_text != null && document.getElementById('quicksearchbox') != null)
191           document.getElementById('quicksearchbox').value = this.env.search_text;
192         
193         if (this.env.action=='show' || this.env.action=='preview')
194           {
195           this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete',
196             'open', 'mark', 'edit', 'viewsource', 'download', 'print', 'load-attachment', 'load-headers', true);
197
198           if (this.env.next_uid)
199             {
200             this.enable_command('nextmessage', true);
201             this.enable_command('lastmessage', true);
202             }
203           if (this.env.prev_uid)
204             {
205             this.enable_command('previousmessage', true);
206             this.enable_command('firstmessage', true);
207             }
208         
209           if (this.env.blockedobjects)
210             {
211             if (this.gui_objects.remoteobjectsmsg)
212               this.gui_objects.remoteobjectsmsg.style.display = 'block';
213             this.enable_command('load-images', 'always-load', true);
214             }
215           }
216
217         if (this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox)
218           this.set_alttext('delete', 'movemessagetotrash');
219         
220         // make preview/message frame visible
221         if (this.env.action == 'preview' && this.env.framed && parent.rcmail)
222           {
223           this.enable_command('compose', 'add-contact', false);
224           parent.rcmail.show_contentframe(true);
225           }
226
227         if (this.env.action=='compose')
228           {
229           this.enable_command('add-attachment', 'send-attachment', 'remove-attachment', 'send', true);
230           if (this.env.spellcheck)
231             {
232             this.env.spellcheck.spelling_state_observer = function(s){ ref.set_spellcheck_state(s); };
233             this.set_spellcheck_state('ready');
234             if ($("input[name='_is_html']").val() == '1')
235               this.display_spellcheck_controls(false);
236             }
237           if (this.env.drafts_mailbox)
238             this.enable_command('savedraft', true);
239             
240           document.onmouseup = function(e){ return p.doc_mouse_up(e); };
241
242           // init message compose form
243           this.init_messageform();
244           }
245
246         if (this.env.messagecount)
247           this.enable_command('select-all', 'select-none', 'expunge', true);
248
249         if (this.purge_mailbox_test())
250           this.enable_command('purge', true);
251
252         this.set_page_buttons();
253
254         // show printing dialog
255         if (this.env.action=='print')
256           window.print();
257
258         // get unread count for each mailbox
259         if (this.gui_objects.mailboxlist)
260         {
261           this.env.unread_counts = {};
262           this.gui_objects.folderlist = this.gui_objects.mailboxlist;
263           this.http_request('getunread', '');
264         }
265         
266         // ask user to send MDN
267         if (this.env.mdn_request && this.env.uid)
268         {
269           var mdnurl = '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox);
270           if (confirm(this.get_label('mdnrequest')))
271             this.http_post('sendmdn', mdnurl);
272           else
273             this.http_post('mark', mdnurl+'&_flag=mdnsent');
274         }
275
276         break;
277
278
279       case 'addressbook':
280         if (this.gui_objects.contactslist)
281           {
282           this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:true, keyboard:true});
283           this.contact_list.row_init = function(row){ p.triggerEvent('insertrow', { cid:row.uid, row:row }); };
284           this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
285           this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
286           this.contact_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
287           this.contact_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
288           this.contact_list.addEventListener('dragend', function(e){ p.drag_end(e); });
289           this.contact_list.init();
290
291           if (this.env.cid)
292             this.contact_list.highlight_row(this.env.cid);
293
294           if (this.gui_objects.contactslist.parentNode)
295             {
296             this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
297             document.onmouseup = function(e){ return p.doc_mouse_up(e); };
298             }
299           else
300             this.contact_list.focus();
301             
302           this.gui_objects.folderlist = this.gui_objects.contactslist;
303           }
304
305         this.set_page_buttons();
306         
307         if (this.env.address_sources && this.env.address_sources[this.env.source] && !this.env.address_sources[this.env.source].readonly)
308           this.enable_command('add', true);
309         
310         if (this.env.cid)
311           this.enable_command('show', 'edit', true);
312
313         if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
314           this.enable_command('save', true);
315         else
316           this.enable_command('search', 'reset-search', 'moveto', 'import', true);
317           
318         if (this.contact_list && this.contact_list.rowcount > 0)
319           this.enable_command('export', true);
320
321         this.enable_command('list', true);
322         break;
323
324
325       case 'settings':
326         this.enable_command('preferences', 'identities', 'save', 'folders', true);
327         
328         if (this.env.action=='identities') {
329           this.enable_command('add', this.env.identities_level < 2);
330         }
331         else if (this.env.action=='edit-identity' || this.env.action=='add-identity') {
332           this.enable_command('add', this.env.identities_level < 2);
333           this.enable_command('save', 'delete', 'edit', true);
334         }
335         else if (this.env.action=='folders')
336           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);
337
338         if (this.gui_objects.identitieslist)
339           {
340           this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false});
341           this.identity_list.addEventListener('select', function(o){ p.identity_select(o); });
342           this.identity_list.init();
343           this.identity_list.focus();
344
345           if (this.env.iid)
346             this.identity_list.highlight_row(this.env.iid);
347           }
348         else if (this.gui_objects.sectionslist)
349           {
350           this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false});
351           this.sections_list.addEventListener('select', function(o){ p.section_select(o); });
352           this.sections_list.init();
353           this.sections_list.focus();
354           this.sections_list.select_first();  // open first section by default
355         }
356         else if (this.gui_objects.subscriptionlist)
357           this.init_subscription_list();
358
359         break;
360
361       case 'login':
362         var input_user = $('#rcmloginuser');
363         input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); });
364         
365         if (input_user.val() == '')
366           input_user.focus();
367         else
368           $('#rcmloginpwd').focus();
369
370         // detect client timezone
371         $('#rcmlogintz').val(new Date().getTimezoneOffset() / -60);
372
373         this.enable_command('login', true);
374         break;
375       
376       default:
377         break;
378       }
379
380     // flag object as complete
381     this.loaded = true;
382
383     // show message
384     if (this.pending_message)
385       this.display_message(this.pending_message[0], this.pending_message[1]);
386       
387     // map implicit containers
388     if (this.gui_objects.folderlist)
389       this.gui_containers.foldertray = $(this.gui_objects.folderlist);
390
391     // trigger init event hook
392     this.triggerEvent('init', { task:this.task, action:this.env.action });
393     
394     // execute all foreign onload scripts
395     // @deprecated
396     for (var i=0; i<this.onloads.length; i++)
397       {
398       if (typeof(this.onloads[i]) == 'string')
399         eval(this.onloads[i]);
400       else if (typeof(this.onloads[i]) == 'function')
401         this.onloads[i]();
402       }
403
404     // start keep-alive interval
405     this.start_keepalive();
406   };
407
408   // start interval for keep-alive/recent_check signal
409   this.start_keepalive = function()
410     {
411     if (this.env.keep_alive && !this.env.framed && this.task=='mail' && this.gui_objects.mailboxlist)
412       this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
413     else if (this.env.keep_alive && !this.env.framed && this.task!='login')
414       this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000);
415     }
416
417   this.init_message_row = function(row)
418   {
419     var uid = row.uid;
420     if (uid && this.env.messages[uid])
421       {
422       row.deleted = this.env.messages[uid].deleted ? true : false;
423       row.unread = this.env.messages[uid].unread ? true : false;
424       row.replied = this.env.messages[uid].replied ? true : false;
425       row.flagged = this.env.messages[uid].flagged ? true : false;
426       row.forwarded = this.env.messages[uid].forwarded ? true : false;
427       }
428
429     // set eventhandler to message icon
430     if (row.icon = row.obj.getElementsByTagName('td')[0].getElementsByTagName('img')[0])
431       {
432       var p = this;
433       row.icon.id = 'msgicn_'+row.uid;
434       row.icon._row = row.obj;
435       row.icon.onmousedown = function(e) { p.command('toggle_status', this); };
436       }
437
438     // global variable 'flagged_col' may be not defined yet
439     if (!this.env.flagged_col && this.env.coltypes)
440       {
441       var found;
442       if((found = find_in_array('flag', this.env.coltypes)) >= 0)
443         this.set_env('flagged_col', found+1);
444       }
445
446     // set eventhandler to flag icon, if icon found
447     if (this.env.flagged_col && (row.flagged_icon = row.obj.getElementsByTagName('td')[this.env.flagged_col].getElementsByTagName('img')[0]))
448       {
449       var p = this;
450       row.flagged_icon.id = 'flaggedicn_'+row.uid;
451       row.flagged_icon._row = row.obj;
452       row.flagged_icon.onmousedown = function(e) { p.command('toggle_flag', this); };
453       }
454       
455     this.triggerEvent('insertrow', { uid:uid, row:row });
456   };
457
458   // init message compose form: set focus and eventhandlers
459   this.init_messageform = function()
460     {
461     if (!this.gui_objects.messageform)
462       return false;
463     
464     //this.messageform = this.gui_objects.messageform;
465     var input_from = $("[name='_from']");
466     var input_to = $("[name='_to']");
467     var input_subject = $("input[name='_subject']");
468     var input_message = $("[name='_message']").get(0);
469     var html_mode = $("input[name='_is_html']").val() == '1';
470
471     // init live search events
472     this.init_address_input_events(input_to);
473     this.init_address_input_events($("[name='_cc']"));
474     this.init_address_input_events($("[name='_bcc']"));
475
476     // add signature according to selected identity
477     if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == ''
478         && !html_mode) {  // if we have HTML editor, signature is added in callback
479       this.change_identity(input_from[0]);
480     }
481
482     if (input_to.val() == '')
483       input_to.focus();
484     else if (input_subject.val() == '')
485       input_subject.focus();
486     else if (input_message && !html_mode)
487       input_message.focus();
488
489     // get summary of all field values
490     this.compose_field_hash(true);
491  
492     // start the auto-save timer
493     this.auto_save_start();
494     };
495
496   this.init_address_input_events = function(obj)
497     {
498     var handler = function(e){ return ref.ksearch_keypress(e,this); };
499     obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler);
500     obj.attr('autocomplete', 'off');
501     };
502
503
504   /*********************************************************/
505   /*********       client command interface        *********/
506   /*********************************************************/
507
508   // execute a specific command on the web client
509   this.command = function(command, props, obj)
510     {
511     if (obj && obj.blur)
512       obj.blur();
513
514     if (this.busy)
515       return false;
516
517     // command not supported or allowed
518     if (!this.commands[command])
519       {
520       // pass command to parent window
521       if (this.env.framed && parent.rcmail && parent.rcmail.command)
522         parent.rcmail.command(command, props);
523
524       return false;
525       }
526       
527    // check input before leaving compose step
528    if (this.task=='mail' && this.env.action=='compose' && (command=='list' || command=='mail' || command=='addressbook' || command=='settings'))
529      {
530      if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
531         return false;
532      }
533
534     // process external commands
535     if (typeof this.command_handlers[command] == 'function')
536     {
537       var ret = this.command_handlers[command](props, obj);
538       return ret !== null ? ret : (obj ? false : true);
539     }
540     else if (typeof this.command_handlers[command] == 'string')
541     {
542       var ret = window[this.command_handlers[command]](props, obj);
543       return ret !== null ? ret : (obj ? false : true);
544     }
545     
546     // trigger plugin hook
547     var event_ret = this.triggerEvent('before'+command, props);
548     if (typeof event_ret != 'undefined') {
549       // abort if one the handlers returned false
550       if (event_ret === false)
551         return false;
552       else
553         props = event_ret;
554     }
555
556     // process internal command
557     switch (command)
558       {
559       case 'login':
560         if (this.gui_objects.loginform)
561           this.gui_objects.loginform.submit();
562         break;
563
564       // commands to switch task
565       case 'mail':
566       case 'addressbook':
567       case 'settings':
568       case 'logout':
569         this.switch_task(command);
570         break;
571
572       case 'permaurl':
573         if (obj && obj.href && obj.target)
574           return true;
575         else if (this.env.permaurl)
576           parent.location.href = this.env.permaurl;
577         break;
578
579       case 'open':
580         var uid;
581         if (uid = this.get_single_uid())
582         {
583           obj.href = '?_task='+this.env.task+'&_action=show&_mbox='+urlencode(this.env.mailbox)+'&_uid='+uid;
584           return true;
585         }
586         break;
587
588       // misc list commands
589       case 'list':
590         if (this.task=='mail')
591           {
592           if (this.env.search_request<0 || (props != '' && (this.env.search_request && props != this.env.mailbox)))
593             this.reset_qsearch();
594
595           this.list_mailbox(props);
596
597           if (this.env.trash_mailbox)
598             this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
599           }
600         else if (this.task=='addressbook')
601           {
602           if (this.env.search_request<0 || (this.env.search_request && props != this.env.source))
603             this.reset_qsearch();
604
605           this.list_contacts(props);
606           this.enable_command('add', (this.env.address_sources && !this.env.address_sources[props].readonly));
607           }
608         break;
609
610
611       case 'load-headers':
612         this.load_headers(obj);
613         break;
614
615
616       case 'sort':
617         var sort_order, sort_col = props;
618
619         if (this.env.sort_col==sort_col)
620           sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
621         else
622           sort_order = 'ASC';
623         
624         // set table header class
625         $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
626         $('#rcm'+sort_col).addClass('sorted'+sort_order);
627
628         // save new sort properties
629         this.env.sort_col = sort_col;
630         this.env.sort_order = sort_order;
631
632         // reload message list
633         this.list_mailbox('', '', sort_col+'_'+sort_order);
634         break;
635
636       case 'nextpage':
637         this.list_page('next');
638         break;
639
640       case 'lastpage':
641         this.list_page('last');
642         break;
643
644       case 'previouspage':
645         this.list_page('prev');
646         break;
647
648       case 'firstpage':
649         this.list_page('first');
650         break;
651
652       case 'expunge':
653         if (this.env.messagecount)
654           this.expunge_mailbox(this.env.mailbox);
655         break;
656
657       case 'purge':
658       case 'empty-mailbox':
659         if (this.env.messagecount)
660           this.purge_mailbox(this.env.mailbox);
661         break;
662
663
664       // common commands used in multiple tasks
665       case 'show':
666         if (this.task=='mail')
667           {
668           var uid = this.get_single_uid();
669           if (uid && (!this.env.uid || uid != this.env.uid))
670             {
671             if (this.env.mailbox == this.env.drafts_mailbox)
672               this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
673             else
674               this.show_message(uid);
675             }
676           }
677         else if (this.task=='addressbook')
678           {
679           var cid = props ? props : this.get_single_cid();
680           if (cid && !(this.env.action=='show' && cid==this.env.cid))
681             this.load_contact(cid, 'show');
682           }
683         break;
684
685       case 'add':
686         if (this.task=='addressbook')
687           this.load_contact(0, 'add');
688         else if (this.task=='settings')
689           {
690           this.identity_list.clear_selection();
691           this.load_identity(0, 'add-identity');
692           }
693         break;
694
695       case 'edit':
696         var cid;
697         if (this.task=='addressbook' && (cid = this.get_single_cid()))
698           this.load_contact(cid, 'edit');
699         else if (this.task=='settings' && props)
700           this.load_identity(props, 'edit-identity');
701         else if (this.task=='mail' && (cid = this.get_single_uid())) {
702           var url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid=';
703           this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true);
704         }
705         break;
706
707       case 'save-identity':
708       case 'save':
709         if (this.gui_objects.editform)
710           {
711           var input_pagesize = $("input[name='_pagesize']");
712           var input_name  = $("input[name='_name']");
713           var input_email = $("input[name='_email']");
714
715           // user prefs
716           if (input_pagesize.length && isNaN(parseInt(input_pagesize.val())))
717             {
718             alert(this.get_label('nopagesizewarning'));
719             input_pagesize.focus();
720             break;
721             }
722           // contacts/identities
723           else
724             {
725             if (input_name.length && input_name.val() == '')
726               {
727               alert(this.get_label('nonamewarning'));
728               input_name.focus();
729               break;
730               }
731             else if (input_email.length && !rcube_check_email(input_email.val()))
732               {
733               alert(this.get_label('noemailwarning'));
734               input_email.focus();
735               break;
736               }
737             }
738
739           this.gui_objects.editform.submit();
740           }
741         break;
742
743       case 'delete':
744         // mail task
745         if (this.task=='mail')
746           this.delete_messages();
747         // addressbook task
748         else if (this.task=='addressbook')
749           this.delete_contacts();
750         // user settings task
751         else if (this.task=='settings')
752           this.delete_identity();
753         break;
754
755
756       // mail task commands
757       case 'move':
758       case 'moveto':
759         if (this.task == 'mail')
760           this.move_messages(props);
761         else if (this.task == 'addressbook' && this.drag_active)
762           this.copy_contact(null, props);
763         break;
764
765       case 'mark':
766         if (props)
767           this.mark_message(props);
768         break;
769       
770       case 'toggle_status':
771         if (props && !props._row)
772           break;
773         
774         var uid;
775         var flag = 'read';
776         
777         if (props._row.uid)
778           {
779           uid = props._row.uid;
780           
781           // toggle read/unread
782           if (this.message_list.rows[uid].deleted) {
783             flag = 'undelete';
784           } else if (!this.message_list.rows[uid].unread)
785             flag = 'unread';
786           }
787           
788         this.mark_message(flag, uid);
789         break;
790         
791       case 'toggle_flag':
792         if (props && !props._row)
793           break;
794
795         var uid;
796         var flag = 'flagged';
797
798         if (props._row.uid)
799           {
800           uid = props._row.uid;
801           // toggle flagged/unflagged
802           if (this.message_list.rows[uid].flagged)
803             flag = 'unflagged';
804           }
805         this.mark_message(flag, uid);
806         break;
807
808       case 'always-load':
809         if (this.env.uid && this.env.sender) {
810           this.add_contact(urlencode(this.env.sender));
811           window.setTimeout(function(){ ref.command('load-images'); }, 300);
812           break;
813         }
814         
815       case 'load-images':
816         if (this.env.uid)
817           this.show_message(this.env.uid, true, this.env.action=='preview');
818         break;
819
820       case 'load-attachment':
821         var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part;
822         
823         // open attachment in frame if it's of a supported mimetype
824         if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0)
825           {
826           if (props.mimetype == 'text/html')
827             qstring += '&_safe=1';
828           this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment');
829           if (this.attachment_win)
830             {
831             window.setTimeout(function(){ ref.attachment_win.focus(); }, 10);
832             break;
833             }
834           }
835
836         this.goto_url('get', qstring+'&_download=1', false);
837         break;
838         
839       case 'select-all':
840         if (props == 'invert')
841           this.message_list.invert_selection();
842         else
843           this.message_list.select_all(props);
844         break;
845
846       case 'select-none':
847         this.message_list.clear_selection();
848         break;
849
850       case 'nextmessage':
851         if (this.env.next_uid)
852           this.show_message(this.env.next_uid, false, this.env.action=='preview');
853         break;
854
855       case 'lastmessage':
856         if (this.env.last_uid)
857           this.show_message(this.env.last_uid);
858         break;
859
860       case 'previousmessage':
861         if (this.env.prev_uid)
862           this.show_message(this.env.prev_uid, false, this.env.action=='preview');
863         break;
864
865       case 'firstmessage':
866         if (this.env.first_uid)
867           this.show_message(this.env.first_uid);
868         break;
869       
870       case 'checkmail':
871         this.check_for_recent(true);
872         break;
873       
874       case 'compose':
875         var url = this.env.comm_path+'&_action=compose';
876        
877         if (this.task=='mail')
878         {
879           url += '&_mbox='+urlencode(this.env.mailbox);
880           
881           if (this.env.mailbox==this.env.drafts_mailbox)
882           {
883             var uid;
884             if (uid = this.get_single_uid())
885               url += '&_draft_uid='+uid;
886           }
887           else if (props)
888              url += '&_to='+urlencode(props);
889         }
890         // modify url if we're in addressbook
891         else if (this.task=='addressbook')
892           {
893           // switch to mail compose step directly
894           if (props && props.indexOf('@') > 0)
895             {
896             url = this.get_task_url('mail', url);
897             this.redirect(url + '&_to='+urlencode(props));
898             break;
899             }
900           
901           // use contact_id passed as command parameter
902           var a_cids = new Array();
903           if (props)
904             a_cids[a_cids.length] = props;
905           // get selected contacts
906           else if (this.contact_list)
907             {
908             var selection = this.contact_list.get_selection();
909             for (var n=0; n<selection.length; n++)
910               a_cids[a_cids.length] = selection[n];
911             }
912             
913           if (a_cids.length)
914             this.http_request('mailto', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source), true);
915
916           break;
917           }
918
919         // don't know if this is necessary...
920         url = url.replace(/&_framed=1/, "");
921
922         this.redirect(url);
923         break;
924         
925       case 'spellcheck':
926         if (window.tinyMCE && tinyMCE.get(this.env.composebody)) {
927           tinyMCE.execCommand('mceSpellCheck', true);
928         }
929         else if (this.env.spellcheck && this.env.spellcheck.spellCheck && this.spellcheck_ready) {
930           this.env.spellcheck.spellCheck();
931           this.set_spellcheck_state('checking');
932         }
933         break;
934
935       case 'savedraft':
936         // Reset the auto-save timer
937         self.clearTimeout(this.save_timer);
938
939         if (!this.gui_objects.messageform)
940           break;
941
942         // if saving Drafts is disabled in main.inc.php
943         // or if compose form did not change
944         if (!this.env.drafts_mailbox || this.cmp_hash == this.compose_field_hash())
945           break;
946
947         this.set_busy(true, 'savingmessage');
948         var form = this.gui_objects.messageform;
949         form.target = "savetarget";
950         form._draft.value = '1';
951         form.submit();
952         break;
953
954       case 'send':
955         if (!this.gui_objects.messageform)
956           break;
957
958         if (!this.check_compose_input())
959           break;
960
961         // Reset the auto-save timer
962         self.clearTimeout(this.save_timer);
963
964         // all checks passed, send message
965         this.set_busy(true, 'sendingmessage');
966         var form = this.gui_objects.messageform;
967         form.target = "savetarget";     
968         form._draft.value = '';
969         form.submit();
970         
971         // clear timeout (sending could take longer)
972         clearTimeout(this.request_timer);
973         break;
974
975       case 'add-attachment':
976         this.show_attachment_form(true);
977         
978       case 'send-attachment':
979         // Reset the auto-save timer
980         self.clearTimeout(this.save_timer);
981
982         this.upload_file(props)      
983         break;
984       
985       case 'remove-attachment':
986         this.remove_attachment(props);
987         break;
988
989       case 'reply-all':
990       case 'reply':
991         var uid;
992         if (uid = this.get_single_uid())
993           this.goto_url('compose', '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(command=='reply-all' ? '&_all=1' : ''), true);
994         break;      
995
996       case 'forward':
997         var uid;
998         if (uid = this.get_single_uid())
999           this.goto_url('compose', '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
1000         break;
1001         
1002       case 'print':
1003         var uid;
1004         if (uid = this.get_single_uid())
1005         {
1006           ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
1007           if (this.printwin)
1008           {
1009             window.setTimeout(function(){ ref.printwin.focus(); }, 20);
1010             if (this.env.action != 'show')
1011               this.mark_message('read', uid);
1012           }
1013         }
1014         break;
1015
1016       case 'viewsource':
1017         var uid;
1018         if (uid = this.get_single_uid())
1019           {
1020           ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox));
1021           if (this.sourcewin)
1022             window.setTimeout(function(){ ref.sourcewin.focus(); }, 20);
1023           }
1024         break;
1025
1026       case 'download':
1027         var uid;
1028         if (uid = this.get_single_uid())
1029           this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1');
1030         break;
1031
1032       case 'add-contact':
1033         this.add_contact(props);
1034         break;
1035       
1036       // quicksearch
1037       case 'search':
1038         if (!props && this.gui_objects.qsearchbox)
1039           props = this.gui_objects.qsearchbox.value;
1040         if (props)
1041         {
1042           this.qsearch(props);
1043           break;
1044         }
1045
1046       // reset quicksearch
1047       case 'reset-search':
1048         var s = this.env.search_request;
1049         this.reset_qsearch();
1050         
1051         if (s && this.env.mailbox)
1052           this.list_mailbox(this.env.mailbox);
1053         else if (s && this.task == 'addressbook')
1054           this.list_contacts(this.env.source);
1055         break;
1056
1057       case 'import':
1058         if (this.env.action == 'import' && this.gui_objects.importform) {
1059           var file = document.getElementById('rcmimportfile');
1060           if (file && !file.value) {
1061             alert(this.get_label('selectimportfile'));
1062             break;
1063           }
1064           this.gui_objects.importform.submit();
1065           this.set_busy(true, 'importwait');
1066           this.lock_form(this.gui_objects.importform, true);
1067         }
1068         else
1069           this.goto_url('import');
1070         break;
1071         
1072       case 'export':
1073         if (this.contact_list.rowcount > 0) {
1074           var add_url = (this.env.source ? '_source='+urlencode(this.env.source)+'&' : '');
1075           if (this.env.search_request)
1076             add_url += '_search='+this.env.search_request;
1077         
1078           this.goto_url('export', add_url);
1079         }
1080         break;
1081
1082       // collapse/expand folder
1083       case 'collapse-folder':
1084         if (props)
1085           this.collapse_folder(props);
1086         break;
1087
1088       // user settings commands
1089       case 'preferences':
1090         this.goto_url('');
1091         break;
1092
1093       case 'identities':
1094         this.goto_url('identities');
1095         break;
1096           
1097       case 'delete-identity':
1098         this.delete_identity();
1099         
1100       case 'folders':
1101         this.goto_url('folders');
1102         break;
1103
1104       case 'subscribe':
1105         this.subscribe_folder(props);
1106         break;
1107
1108       case 'unsubscribe':
1109         this.unsubscribe_folder(props);
1110         break;
1111         
1112       case 'create-folder':
1113         this.create_folder(props);
1114         break;
1115
1116       case 'rename-folder':
1117         this.rename_folder(props);
1118         break;
1119
1120       case 'delete-folder':
1121         this.delete_folder(props);
1122         break;
1123
1124       }
1125       
1126     this.triggerEvent('after'+command, props);
1127
1128     return obj ? false : true;
1129     };
1130
1131   // set command enabled or disabled
1132   this.enable_command = function()
1133     {
1134     var args = arguments;
1135     if(!args.length) return -1;
1136
1137     var command;
1138     var enable = args[args.length-1];
1139     
1140     for(var n=0; n<args.length-1; n++)
1141       {
1142       command = args[n];
1143       this.commands[command] = enable;
1144       this.set_button(command, (enable ? 'act' : 'pas'));
1145       }
1146       return true;
1147     };
1148
1149   // lock/unlock interface
1150   this.set_busy = function(a, message)
1151     {
1152     if (a && message)
1153       {
1154       var msg = this.get_label(message);
1155       if (msg==message)        
1156         msg = 'Loading...';
1157
1158       this.display_message(msg, 'loading', true);
1159       }
1160     else if (!a)
1161       this.hide_message();
1162
1163     this.busy = a;
1164     //document.body.style.cursor = a ? 'wait' : 'default';
1165     
1166     if (this.gui_objects.editform)
1167       this.lock_form(this.gui_objects.editform, a);
1168       
1169     // clear pending timer
1170     if (this.request_timer)
1171       clearTimeout(this.request_timer);
1172
1173     // set timer for requests
1174     if (a && this.env.request_timeout)
1175       this.request_timer = window.setTimeout(function(){ ref.request_timed_out(); }, this.env.request_timeout * 1000);
1176     };
1177
1178   // return a localized string
1179   this.get_label = function(name, domain)
1180     {
1181     if (domain && this.labels[domain+'.'+name])
1182       return this.labels[domain+'.'+name];
1183     else if (this.labels[name])
1184       return this.labels[name];
1185     else
1186       return name;
1187     };
1188   
1189   // alias for convenience reasons
1190   this.gettext = this.get_label;
1191
1192   // switch to another application task
1193   this.switch_task = function(task)
1194     {
1195     if (this.task===task && task!='mail')
1196       return;
1197
1198     var url = this.get_task_url(task);
1199     if (task=='mail')
1200       url += '&_mbox=INBOX';
1201
1202     this.redirect(url);
1203     };
1204
1205   this.get_task_url = function(task, url)
1206     {
1207     if (!url)
1208       url = this.env.comm_path;
1209
1210     return url.replace(/_task=[a-z]+/, '_task='+task);
1211     };
1212     
1213   // called when a request timed out
1214   this.request_timed_out = function()
1215     {
1216     this.set_busy(false);
1217     this.display_message('Request timed out!', 'error');
1218     };
1219   
1220   this.reload = function(delay)
1221   {
1222     if (this.env.framed && parent.rcmail)
1223       parent.rcmail.reload(delay);
1224     else if (delay)
1225       window.setTimeout(function(){ rcmail.reload(); }, delay);
1226     else if (window.location)
1227       location.href = this.env.comm_path;
1228   };
1229
1230
1231   /*********************************************************/
1232   /*********        event handling methods         *********/
1233   /*********************************************************/
1234
1235   this.doc_mouse_up = function(e)
1236   {
1237     var model, list, li;
1238
1239     if (this.message_list) {
1240       if (!rcube_mouse_is_over(e, this.message_list.list))
1241         this.message_list.blur();
1242       list = this.message_list;
1243       model = this.env.mailboxes;
1244     }
1245     else if (this.contact_list) {
1246       if (!rcube_mouse_is_over(e, this.contact_list.list))
1247         this.contact_list.blur();
1248       list = this.contact_list;
1249       model = this.env.address_sources;
1250     }
1251     else if (this.ksearch_value) {
1252       this.ksearch_blur();
1253     }
1254
1255     // handle mouse release when dragging
1256     if (this.drag_active && model && this.env.last_folder_target) {
1257       $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
1258       this.command('moveto', model[this.env.last_folder_target].id);
1259       this.env.last_folder_target = null;
1260       list.draglayer.hide();
1261     }
1262     
1263     // reset 'pressed' buttons
1264     if (this.buttons_sel) {
1265       for (var id in this.buttons_sel)
1266         if (typeof id != 'function')
1267           this.button_out(this.buttons_sel[id], id);
1268       this.buttons_sel = {};
1269     }
1270   };
1271
1272   this.drag_start = function(list)
1273   {
1274     var model = this.task == 'mail' ? this.env.mailboxes : this.env.address_sources;
1275
1276     this.drag_active = true;
1277     if (this.preview_timer)
1278       clearTimeout(this.preview_timer);
1279     
1280     // save folderlist and folders location/sizes for droptarget calculation in drag_move()
1281     if (this.gui_objects.folderlist && model)
1282       {
1283       this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset;
1284       this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop;
1285
1286       var li, pos, list, height;
1287       list = $(this.gui_objects.folderlist);
1288       pos = list.offset();
1289       this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() };
1290
1291       this.env.folder_coords = new Array();
1292       for (var k in model) {
1293         if (li = this.get_folder_li(k)) {
1294           // only visible folders
1295           if (height = li.firstChild.offsetHeight) {
1296             pos = $(li.firstChild).offset();
1297             this.env.folder_coords[k] = { x1:pos.left, y1:pos.top,
1298               x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height, on:0 };
1299           }
1300         }
1301       }
1302     }
1303   };
1304
1305   this.drag_end = function(e)
1306   {
1307     this.drag_active = false;
1308     this.env.last_folder_target = null;
1309     
1310     if (this.folder_auto_timer) {
1311       window.clearTimeout(this.folder_auto_timer);
1312       this.folder_auto_timer = null;
1313       this.folder_auto_expand = null;
1314     }
1315
1316     // over the folders
1317     if (this.gui_objects.folderlist && this.env.folder_coords) {
1318       for (var k in this.env.folder_coords) {
1319         if (this.env.folder_coords[k].on)
1320           $(this.get_folder_li(k)).removeClass('droptarget');
1321       }
1322     }
1323   };
1324   
1325   this.drag_move = function(e)
1326   {
1327     if (this.gui_objects.folderlist && this.env.folder_coords) {
1328       // offsets to compensate for scrolling while dragging a message
1329       var boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop;
1330       var moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop;
1331       var toffset = -moffset-boffset;
1332
1333       var li, div, pos, mouse;
1334       mouse = rcube_event.get_mouse_pos(e);
1335       pos = this.env.folderlist_coords;
1336       mouse.y += toffset;
1337
1338       // if mouse pointer is outside of folderlist
1339       if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) {
1340         if (this.env.last_folder_target) {
1341           $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
1342           this.env.folder_coords[this.env.last_folder_target].on = 0;
1343           this.env.last_folder_target = null;
1344         }
1345         return;
1346       }
1347     
1348       // over the folders
1349       for (var k in this.env.folder_coords) {
1350         pos = this.env.folder_coords[k];
1351         if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2
1352             && this.check_droptarget(k)) {
1353
1354           li = this.get_folder_li(k);
1355           div = $(li.getElementsByTagName("div")[0]);
1356
1357           // if the folder is collapsed, expand it after 1sec and restart the drag & drop process.
1358           if (div.hasClass('collapsed')) {
1359             if (this.folder_auto_timer)
1360               window.clearTimeout(this.folder_auto_timer);
1361             
1362             this.folder_auto_expand = k;
1363             this.folder_auto_timer = window.setTimeout(function() {
1364                 rcmail.command("collapse-folder", rcmail.folder_auto_expand);
1365                 rcmail.drag_start(null);
1366               }, 1000);
1367           } else if (this.folder_auto_timer) {
1368             window.clearTimeout(this.folder_auto_timer);
1369             this.folder_auto_timer = null;
1370             this.folder_auto_expand = null;
1371           }
1372           
1373           $(li).addClass('droptarget');
1374           this.env.last_folder_target = k;
1375           this.env.folder_coords[k].on = 1;
1376         }
1377         else if (pos.on) {
1378           $(this.get_folder_li(k)).removeClass('droptarget');
1379           this.env.folder_coords[k].on = 0;
1380         }
1381       }
1382     }
1383   };
1384
1385   this.collapse_folder = function(id)
1386     {
1387     var div;
1388     if ((li = this.get_folder_li(id)) &&
1389         (div = $(li.getElementsByTagName("div")[0])) &&
1390         (div.hasClass('collapsed') || div.hasClass('expanded')))
1391       {
1392       var ul = $(li.getElementsByTagName("ul")[0]);
1393       if (div.hasClass('collapsed'))
1394         {
1395         ul.show();
1396         div.removeClass('collapsed').addClass('expanded');
1397         var reg = new RegExp('&'+urlencode(id)+'&');
1398         this.set_env('collapsed_folders', this.env.collapsed_folders.replace(reg, ''));
1399         }
1400       else
1401         {
1402         ul.hide();
1403         div.removeClass('expanded').addClass('collapsed');
1404         this.set_env('collapsed_folders', this.env.collapsed_folders+'&'+urlencode(id)+'&');
1405
1406         // select parent folder if one of its childs is currently selected
1407         if (this.env.mailbox.indexOf(id + this.env.delimiter) == 0)
1408           this.command('list', id);
1409         }
1410
1411       // Work around a bug in IE6 and IE7, see #1485309
1412       if ((bw.ie6 || bw.ie7) &&
1413           li.nextSibling &&
1414           (li.nextSibling.getElementsByTagName("ul").length>0) &&
1415           li.nextSibling.getElementsByTagName("ul")[0].style &&
1416           (li.nextSibling.getElementsByTagName("ul")[0].style.display!='none'))
1417         {
1418           li.nextSibling.getElementsByTagName("ul")[0].style.display = 'none';
1419           li.nextSibling.getElementsByTagName("ul")[0].style.display = '';
1420         }
1421
1422       this.http_post('save-pref', '_name=collapsed_folders&_value='+urlencode(this.env.collapsed_folders));
1423       this.set_unread_count_display(id, false);
1424       }
1425     }
1426
1427   this.click_on_list = function(e)
1428     {
1429     if (this.gui_objects.qsearchbox)
1430       this.gui_objects.qsearchbox.blur();
1431
1432     if (this.message_list)
1433       this.message_list.focus();
1434     else if (this.contact_list)
1435       this.contact_list.focus();
1436
1437     return rcube_event.get_button(e) == 2 ? true : rcube_event.cancel(e);
1438     };
1439
1440   this.msglist_select = function(list)
1441     {
1442     if (this.preview_timer)
1443       clearTimeout(this.preview_timer);
1444
1445     var selected = list.selection.length==1;
1446
1447     // Hide certain command buttons when Drafts folder is selected
1448     if (this.env.mailbox == this.env.drafts_mailbox)
1449       {
1450       this.enable_command('reply', 'reply-all', 'forward', false);
1451       this.enable_command('show', 'print', 'open', 'edit', 'download', 'viewsource', selected);
1452       this.enable_command('delete', 'moveto', 'mark', (list.selection.length > 0 ? true : false));
1453       }
1454     else
1455       {
1456       this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', 'edit', 'open', 'download', 'viewsource', selected);
1457       this.enable_command('delete', 'moveto', 'mark', (list.selection.length > 0 ? true : false));
1458       }
1459
1460     // start timer for message preview (wait for double click)
1461     if (selected && this.env.contentframe && !list.multi_selecting)
1462       this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
1463     else if (this.env.contentframe)
1464       this.show_contentframe(false);
1465     };
1466
1467   this.msglist_dbl_click = function(list)
1468     {
1469       if (this.preview_timer)
1470         clearTimeout(this.preview_timer);
1471
1472     var uid = list.get_single_selection();
1473     if (uid && this.env.mailbox == this.env.drafts_mailbox)
1474       this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
1475     else if (uid)
1476       this.show_message(uid, false, false);
1477     };
1478
1479   this.msglist_keypress = function(list)
1480     {
1481     if (list.key_pressed == list.ENTER_KEY)
1482       this.command('show');
1483     else if (list.key_pressed == list.DELETE_KEY)
1484       this.command('delete');
1485     else if (list.key_pressed == list.BACKSPACE_KEY)
1486       this.command('delete');
1487     else
1488       list.shiftkey = false;
1489     };
1490
1491   this.msglist_get_preview = function()
1492   {
1493     var uid = this.get_single_uid();
1494     if (uid && this.env.contentframe && !this.drag_active)
1495       this.show_message(uid, false, true);
1496     else if (this.env.contentframe)
1497       this.show_contentframe(false);
1498   };
1499   
1500   this.check_droptarget = function(id)
1501   {
1502     if (this.task == 'mail')
1503       return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual);
1504     else if (this.task == 'addressbook')
1505       return (id != this.env.source && this.env.address_sources[id] && !this.env.address_sources[id].readonly);
1506     else if (this.task == 'settings')
1507       return (id != this.env.folder);
1508   };
1509
1510
1511   /*********************************************************/
1512   /*********     (message) list functionality      *********/
1513   /*********************************************************/
1514
1515   // when user doble-clicks on a row
1516   this.show_message = function(id, safe, preview)
1517     {
1518     if (!id) return;
1519     
1520     var add_url = '';
1521     var action = preview ? 'preview': 'show';
1522     var target = window;
1523     
1524     if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1525       {
1526       target = window.frames[this.env.contentframe];
1527       add_url = '&_framed=1';
1528       }
1529
1530     if (safe)
1531       add_url = '&_safe=1';
1532
1533     // also send search request to get the right messages
1534     if (this.env.search_request)
1535       add_url += '&_search='+this.env.search_request;
1536
1537     var url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox)+add_url;
1538     if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
1539       this.show_contentframe(true);
1540     else
1541       {
1542       this.set_busy(true, 'loading');
1543       target.location.href = this.env.comm_path+url;
1544
1545       // mark as read and change mbox unread counter
1546       if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread)
1547         {
1548         this.set_message(id, 'unread', false);
1549         if (this.env.unread_counts[this.env.mailbox])
1550           {
1551           this.env.unread_counts[this.env.mailbox] -= 1;
1552           this.set_unread_count(this.env.mailbox, this.env.unread_counts[this.env.mailbox], this.env.mailbox == 'INBOX');
1553           }
1554         }
1555       }
1556     };
1557
1558   this.show_contentframe = function(show)
1559     {
1560     var frm;
1561     if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length)
1562       {
1563       if (!show && window.frames[this.env.contentframe])
1564         {
1565         if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)<0)
1566           window.frames[this.env.contentframe].location.href = this.env.blankpage;
1567         }
1568       else if (!bw.safari && !bw.konq)
1569         frm[show ? 'show' : 'hide']();
1570       }
1571
1572     if (!show && this.busy)
1573       this.set_busy(false);
1574     };
1575
1576   // list a specific page
1577   this.list_page = function(page)
1578     {
1579     if (page=='next')
1580       page = this.env.current_page+1;
1581     if (page=='last')
1582       page = this.env.pagecount;
1583     if (page=='prev' && this.env.current_page>1)
1584       page = this.env.current_page-1;
1585     if (page=='first' && this.env.current_page>1)
1586       page = 1;
1587       
1588     if (page > 0 && page <= this.env.pagecount)
1589       {
1590       this.env.current_page = page;
1591       
1592       if (this.task=='mail')
1593         this.list_mailbox(this.env.mailbox, page);
1594       else if (this.task=='addressbook')
1595         this.list_contacts(this.env.source, page);
1596       }
1597     };
1598
1599   // list messages of a specific mailbox using filter
1600   this.filter_mailbox = function(filter)
1601     {
1602       var search;
1603       if (this.gui_objects.qsearchbox)
1604         search = this.gui_objects.qsearchbox.value;
1605       
1606       this.message_list.clear();
1607
1608       // reset vars
1609       this.env.current_page = 1;
1610       this.set_busy(true, 'searching');
1611       this.http_request('search', '_filter='+filter
1612             + (search ? '&_q='+urlencode(search) : '')
1613             + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : ''), true);
1614     }
1615
1616
1617   // list messages of a specific mailbox
1618   this.list_mailbox = function(mbox, page, sort)
1619     {
1620     var add_url = '';
1621     var target = window;
1622
1623     if (!mbox)
1624       mbox = this.env.mailbox;
1625
1626     // add sort to url if set
1627     if (sort)
1628       add_url += '&_sort=' + sort;
1629
1630     // also send search request to get the right messages
1631     if (this.env.search_request)
1632       add_url += '&_search='+this.env.search_request;
1633
1634     // set page=1 if changeing to another mailbox
1635     if (!page && this.env.mailbox != mbox)
1636       {
1637       page = 1;
1638       this.env.current_page = page;
1639       this.show_contentframe(false);
1640       }
1641
1642     if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
1643       add_url += '&_refresh=1';
1644
1645     // unselect selected messages
1646     this.last_selected = 0;
1647     if (this.message_list)
1648       this.message_list.clear_selection();
1649     
1650     this.select_folder(mbox, this.env.mailbox);
1651     this.env.mailbox = mbox;
1652
1653     // load message list remotely
1654     if (this.gui_objects.messagelist)
1655       {
1656       this.list_mailbox_remote(mbox, page, add_url);
1657       return;
1658       }
1659     
1660     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1661       {
1662       target = window.frames[this.env.contentframe];
1663       add_url += '&_framed=1';
1664       }
1665
1666     // load message list to target frame/window
1667     if (mbox)
1668       {
1669       this.set_busy(true, 'loading');
1670       target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+add_url;
1671       }
1672     };
1673
1674   // send remote request to load message list
1675   this.list_mailbox_remote = function(mbox, page, add_url)
1676     {
1677     // clear message list first
1678     this.message_list.clear();
1679
1680     // send request to server
1681     var url = '_mbox='+urlencode(mbox)+(page ? '&_page='+page : '');
1682     this.set_busy(true, 'loading');
1683     this.http_request('list', url+add_url, true);
1684     };
1685
1686   this.expunge_mailbox = function(mbox)
1687     {
1688     var lock = false;
1689     var add_url = '';
1690     
1691     // lock interface if it's the active mailbox
1692     if (mbox == this.env.mailbox)
1693        {
1694        lock = true;
1695        this.set_busy(true, 'loading');
1696        add_url = '&_reload=1';
1697        }
1698
1699     // send request to server
1700     var url = '_mbox='+urlencode(mbox);
1701     this.http_post('expunge', url+add_url, lock);
1702     };
1703
1704   this.purge_mailbox = function(mbox)
1705     {
1706     var lock = false;
1707     var add_url = '';
1708     
1709     if (!confirm(this.get_label('purgefolderconfirm')))
1710       return false;
1711     
1712     // lock interface if it's the active mailbox
1713     if (mbox == this.env.mailbox)
1714        {
1715        lock = true;
1716        this.set_busy(true, 'loading');
1717        add_url = '&_reload=1';
1718        }
1719
1720     // send request to server
1721     var url = '_mbox='+urlencode(mbox);
1722     this.http_post('purge', url+add_url, lock);
1723     return true;
1724     };
1725
1726   // test if purge command is allowed
1727   this.purge_mailbox_test = function()
1728   {
1729     return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox 
1730       || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter)) 
1731       || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
1732   };
1733
1734   // set message icon
1735   this.set_message_icon = function(uid)
1736   {
1737     var icn_src;
1738     var rows = this.message_list.rows;
1739
1740     if (!rows[uid])
1741       return false;
1742
1743     if (rows[uid].deleted && this.env.deletedicon)
1744       icn_src = this.env.deletedicon;
1745     else if (rows[uid].replied && this.env.repliedicon)
1746       {
1747       if (rows[uid].forwarded && this.env.forwardedrepliedicon)
1748         icn_src = this.env.forwardedrepliedicon;
1749       else
1750         icn_src = this.env.repliedicon;
1751       }
1752     else if (rows[uid].forwarded && this.env.forwardedicon)
1753       icn_src = this.env.forwardedicon;
1754     else if (rows[uid].unread && this.env.unreadicon)
1755       icn_src = this.env.unreadicon;
1756     else if (this.env.messageicon)
1757       icn_src = this.env.messageicon;
1758       
1759     if (icn_src && rows[uid].icon)
1760       rows[uid].icon.src = icn_src;
1761
1762     icn_src = '';
1763     
1764     if (rows[uid].flagged && this.env.flaggedicon)
1765       icn_src = this.env.flaggedicon;
1766     else if (!rows[uid].flagged && this.env.unflaggedicon)
1767       icn_src = this.env.unflaggedicon;
1768
1769     if (rows[uid].flagged_icon && icn_src)
1770       rows[uid].flagged_icon.src = icn_src;
1771   }
1772
1773   // set message status
1774   this.set_message_status = function(uid, flag, status)
1775     {
1776     var rows = this.message_list.rows;
1777
1778     if (!rows[uid]) return false;
1779
1780     if (flag == 'unread')
1781       rows[uid].unread = status;
1782     else if(flag == 'deleted')
1783       rows[uid].deleted = status;
1784     else if (flag == 'replied')
1785       rows[uid].replied = status;
1786     else if (flag == 'forwarded')
1787       rows[uid].forwarded = status;
1788     else if (flag == 'flagged')
1789       rows[uid].flagged = status;
1790
1791     this.env.messages[uid] = rows[uid];
1792     }
1793
1794   // set message row status, class and icon
1795   this.set_message = function(uid, flag, status)
1796     {
1797     var rows = this.message_list.rows;
1798
1799     if (!rows[uid]) return false;
1800     
1801     if (flag)
1802       this.set_message_status(uid, flag, status);
1803     
1804     var rowobj = $(rows[uid].obj);
1805     if (rows[uid].unread && rows[uid].classname.indexOf('unread')<0)
1806       {
1807       rows[uid].classname += ' unread';
1808       rowobj.addClass('unread');
1809       }
1810     else if (!rows[uid].unread && rows[uid].classname.indexOf('unread')>=0)
1811       {
1812       rows[uid].classname = rows[uid].classname.replace(/\s*unread/, '');
1813       rowobj.removeClass('unread');
1814       }
1815     
1816     if (rows[uid].deleted && rows[uid].classname.indexOf('deleted')<0)
1817       {
1818       rows[uid].classname += ' deleted';
1819       rowobj.addClass('deleted');
1820       }
1821     else if (!rows[uid].deleted && rows[uid].classname.indexOf('deleted')>=0)
1822       {
1823       rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, '');
1824       rowobj.removeClass('deleted');
1825       }
1826
1827     if (rows[uid].flagged && rows[uid].classname.indexOf('flagged')<0)
1828       {
1829       rows[uid].classname += ' flagged';
1830       rowobj.addClass('flagged');
1831       }
1832     else if (!rows[uid].flagged && rows[uid].classname.indexOf('flagged')>=0)
1833       {
1834       rows[uid].classname = rows[uid].classname.replace(/\s*flagged/, '');
1835       rowobj.removeClass('flagged');
1836       }
1837
1838     this.set_message_icon(uid);
1839     }
1840
1841   // move selected messages to the specified mailbox
1842   this.move_messages = function(mbox)
1843     {
1844     // exit if current or no mailbox specified or if selection is empty
1845     if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)))
1846       return;
1847
1848     var lock = false;
1849     var add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : '');
1850
1851     // show wait message
1852     if (this.env.action=='show')
1853       {
1854       lock = true;
1855       this.set_busy(true, 'movingmessage');
1856       }
1857     else
1858       this.show_contentframe(false);
1859
1860     // Hide message command buttons until a message is selected
1861     this.enable_command('reply', 'reply-all', 'forward', 'delete', 'mark', 'print', 'open', 'edit', 'viewsource', 'download', false);
1862
1863     this._with_selected_messages('moveto', lock, add_url);
1864     };
1865
1866   // delete selected messages from the current mailbox
1867   this.delete_messages = function()
1868     {
1869     var selection = this.message_list ? this.message_list.get_selection() : new Array();
1870
1871     // exit if no mailbox specified or if selection is empty
1872     if (!this.env.uid && !selection.length)
1873       return;
1874
1875     // if config is set to flag for deletion
1876     if (this.env.flag_for_deletion)
1877       this.mark_message('delete');
1878     // if there isn't a defined trash mailbox or we are in it
1879     else if (!this.env.trash_mailbox || String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase()) 
1880       this.permanently_remove_messages();
1881     // if there is a trash mailbox defined and we're not currently in it
1882     else {
1883       // if shift was pressed delete it immediately
1884       if (this.message_list && this.message_list.shiftkey)
1885         {
1886         if (confirm(this.get_label('deletemessagesconfirm')))
1887           this.permanently_remove_messages();
1888         }
1889       else
1890         this.move_messages(this.env.trash_mailbox);
1891       }
1892   };
1893
1894   // delete the selected messages permanently
1895   this.permanently_remove_messages = function()
1896     {
1897     // exit if no mailbox specified or if selection is empty
1898     if (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))
1899       return;
1900       
1901     this.show_contentframe(false);
1902     this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''));
1903     };
1904
1905   // Send a specifc request with UIDs of all selected messages
1906   // @private
1907   this._with_selected_messages = function(action, lock, add_url, remove)
1908   {
1909     var a_uids = new Array();
1910
1911     if (this.env.uid)
1912       a_uids[0] = this.env.uid;
1913     else
1914     {
1915       var selection = this.message_list.get_selection();
1916       var rows = this.message_list.rows;
1917       var id;
1918       for (var n=0; n<selection.length; n++) {
1919         id = selection[n];
1920         a_uids[a_uids.length] = id;
1921         this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
1922       }
1923       // make sure there are no selected rows
1924       if (!this.env.display_next)
1925         this.message_list.clear_selection();
1926     }
1927
1928     // also send search request to get the right messages 
1929     if (this.env.search_request) 
1930       add_url += '&_search='+this.env.search_request;
1931
1932     if (this.env.display_next && this.env.next_uid)
1933       add_url += '&_next_uid='+this.env.next_uid;
1934
1935     // send request to server
1936     this.http_post(action, '_uid='+a_uids.join(',')+'&_mbox='+urlencode(this.env.mailbox)+add_url, lock);
1937   };
1938
1939   // set a specific flag to one or more messages
1940   this.mark_message = function(flag, uid)
1941     {
1942     var a_uids = new Array();
1943     var r_uids = new Array();
1944     var selection = this.message_list ? this.message_list.get_selection() : new Array();
1945
1946     if (uid)
1947       a_uids[0] = uid;
1948     else if (this.env.uid)
1949       a_uids[0] = this.env.uid;
1950     else if (this.message_list)
1951       {
1952       for (var n=0; n<selection.length; n++)
1953         {
1954           a_uids[a_uids.length] = selection[n];
1955         }
1956       }
1957
1958     if (!this.message_list)
1959       r_uids = a_uids;
1960     else
1961       for (var id, n=0; n<a_uids.length; n++)
1962       {
1963         id = a_uids[n];
1964         if ((flag=='read' && this.message_list.rows[id].unread) 
1965             || (flag=='unread' && !this.message_list.rows[id].unread)
1966             || (flag=='delete' && !this.message_list.rows[id].deleted)
1967             || (flag=='undelete' && this.message_list.rows[id].deleted)
1968             || (flag=='flagged' && !this.message_list.rows[id].flagged)
1969             || (flag=='unflagged' && this.message_list.rows[id].flagged))
1970         {
1971           r_uids[r_uids.length] = id;
1972         }
1973       }
1974
1975     // nothing to do
1976     if (!r_uids.length)
1977       return;
1978
1979     switch (flag)
1980       {
1981         case 'read':
1982         case 'unread':
1983           this.toggle_read_status(flag, r_uids);
1984           break;
1985         case 'delete':
1986         case 'undelete':
1987           this.toggle_delete_status(r_uids);
1988           break;
1989         case 'flagged':
1990         case 'unflagged':
1991           this.toggle_flagged_status(flag, a_uids);
1992           break;
1993       }
1994     };
1995
1996   // set class to read/unread
1997   this.toggle_read_status = function(flag, a_uids)
1998   {
1999     // mark all message rows as read/unread
2000     for (var i=0; i<a_uids.length; i++)
2001       this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
2002
2003     this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
2004   };
2005
2006   // set image to flagged or unflagged
2007   this.toggle_flagged_status = function(flag, a_uids)
2008   {
2009     // mark all message rows as flagged/unflagged
2010     for (var i=0; i<a_uids.length; i++)
2011       this.set_message(a_uids[i], 'flagged', (flag=='flagged' ? true : false));
2012
2013     this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
2014   };
2015   
2016   // mark all message rows as deleted/undeleted
2017   this.toggle_delete_status = function(a_uids)
2018   {
2019     var rows = this.message_list ? this.message_list.rows : new Array();
2020     
2021     if (a_uids.length==1)
2022     {
2023       if (!rows.length || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
2024         this.flag_as_deleted(a_uids);
2025       else
2026         this.flag_as_undeleted(a_uids);
2027
2028       return true;
2029     }
2030     
2031     var all_deleted = true;
2032     for (var i=0; i<a_uids.length; i++)
2033     {
2034       uid = a_uids[i];
2035       if (rows[uid]) {
2036         if (!rows[uid].deleted)
2037         {
2038           all_deleted = false;
2039           break;
2040         }
2041       }
2042     }
2043     
2044     if (all_deleted)
2045       this.flag_as_undeleted(a_uids);
2046     else
2047       this.flag_as_deleted(a_uids);
2048     
2049     return true;
2050   };
2051
2052   this.flag_as_undeleted = function(a_uids)
2053   {
2054     for (var i=0; i<a_uids.length; i++)
2055       this.set_message(a_uids[i], 'deleted', false);
2056
2057     this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag=undelete');
2058     return true;
2059   };
2060
2061   this.flag_as_deleted = function(a_uids)
2062   {
2063     var add_url = '';
2064     var r_uids = new Array();
2065     var rows = this.message_list ? this.message_list.rows : new Array();
2066     
2067     for (var i=0; i<a_uids.length; i++)
2068       {
2069       uid = a_uids[i];
2070       if (rows[uid])
2071         {
2072         if (rows[uid].unread)
2073           r_uids[r_uids.length] = uid;
2074
2075         if (this.env.skip_deleted)
2076           this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
2077         else
2078           this.set_message(uid, 'deleted', true);
2079         }
2080       }
2081
2082     // make sure there are no selected rows
2083     if (this.env.skip_deleted && !this.env.display_next && this.message_list)
2084       this.message_list.clear_selection();
2085
2086     add_url = '&_from='+(this.env.action ? this.env.action : '');
2087     
2088     if (r_uids.length)
2089       add_url += '&_ruid='+r_uids.join(',');
2090
2091     if (this.env.skip_deleted) {
2092       // also send search request to get the right messages 
2093       if (this.env.search_request) 
2094         add_url += '&_search='+this.env.search_request;
2095       if (this.env.display_next && this.env.next_uid)
2096         add_url += '&_next_uid='+this.env.next_uid;
2097     }
2098     
2099     this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag=delete'+add_url);
2100     return true;  
2101   };
2102
2103   // flag as read without mark request (called from backend)
2104   // argument should be a coma-separated list of uids
2105   this.flag_deleted_as_read = function(uids)
2106   {
2107     var icn_src;
2108     var rows = this.message_list ? this.message_list.rows : new Array();
2109     var str = String(uids);
2110     var a_uids = new Array();
2111
2112     a_uids = str.split(',');
2113
2114     for (var uid, i=0; i<a_uids.length; i++)
2115       {
2116       uid = a_uids[i];
2117       if (rows[uid])
2118         this.set_message(uid, 'unread', false);
2119       }
2120   };
2121   
2122   
2123   /*********************************************************/
2124   /*********           login form methods          *********/
2125   /*********************************************************/
2126
2127   // handler for keyboard events on the _user field
2128   this.login_user_keyup = function(e)
2129   {
2130     var key = rcube_event.get_keycode(e);
2131     var passwd = $('#rcmloginpwd');
2132
2133     // enter
2134     if (key == 13 && passwd.length && !passwd.val()) {
2135       passwd.focus();
2136       return rcube_event.cancel(e);
2137     }
2138     
2139     return true;
2140   };
2141
2142
2143   /*********************************************************/
2144   /*********        message compose methods        *********/
2145   /*********************************************************/
2146   
2147   // checks the input fields before sending a message
2148   this.check_compose_input = function()
2149     {
2150     // check input fields
2151     var input_to = $("[name='_to']");
2152     var input_cc = $("[name='_cc']");
2153     var input_bcc = $("[name='_bcc']");
2154     var input_from = $("[name='_from']");
2155     var input_subject = $("[name='_subject']");
2156     var input_message = $("[name='_message']");
2157
2158     // check sender (if have no identities)
2159     if (input_from.attr('type') == 'text' && !rcube_check_email(input_from.val(), true))
2160       {
2161       alert(this.get_label('nosenderwarning'));
2162       input_from.focus();
2163       return false;
2164       }
2165
2166     // check for empty recipient
2167     var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
2168     if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true))
2169       {
2170       alert(this.get_label('norecipientwarning'));
2171       input_to.focus();
2172       return false;
2173       }
2174
2175     // check if all files has been uploaded
2176     for (var key in this.env.attachments) {
2177       if (typeof this.env.attachments[key] == 'object' && !this.env.attachments[key].complete) {
2178         alert(this.get_label('notuploadedwarning'));
2179         return false;
2180       }
2181     }
2182     
2183     // display localized warning for missing subject
2184     if (input_subject.val() == '')
2185       {
2186       var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject'));
2187
2188       // user hit cancel, so don't send
2189       if (!subject && subject !== '')
2190         {
2191         input_subject.focus();
2192         return false;
2193         }
2194       else
2195         {
2196         input_subject.val((subject ? subject : this.get_label('nosubject')));
2197         }
2198       }
2199
2200     // check for empty body
2201     if ((!window.tinyMCE || !tinyMCE.get(this.env.composebody))
2202         && input_message.val() == '' && !confirm(this.get_label('nobodywarning')))
2203       {
2204       input_message.focus();
2205       return false;
2206       }
2207     else if (window.tinyMCE && tinyMCE.get(this.env.composebody)
2208         && !tinyMCE.get(this.env.composebody).getContent()
2209         && !confirm(this.get_label('nobodywarning')))
2210       {
2211       tinyMCE.get(this.env.composebody).focus();
2212       return false;
2213       }
2214
2215     // Apply spellcheck changes if spell checker is active
2216     this.stop_spellchecking();
2217
2218     // move body from html editor to textarea (just to be sure, #1485860)
2219     if (window.tinyMCE && tinyMCE.get(this.env.composebody))
2220       tinyMCE.triggerSave();
2221
2222     return true;
2223     };
2224
2225   this.stop_spellchecking = function()
2226     {
2227     if (this.env.spellcheck && !this.spellcheck_ready) {
2228       $(this.env.spellcheck.spell_span).trigger('click');
2229       this.set_spellcheck_state('ready');
2230       }
2231     };
2232
2233   this.display_spellcheck_controls = function(vis)
2234     {
2235     if (this.env.spellcheck) {
2236       // stop spellchecking process
2237       if (!vis)
2238         this.stop_spellchecking();
2239
2240       $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden');
2241       }
2242     };
2243
2244   this.set_spellcheck_state = function(s)
2245     {
2246     this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
2247     this.enable_command('spellcheck', this.spellcheck_ready);
2248     };
2249
2250   this.set_draft_id = function(id)
2251     {
2252     $("input[name='_draft_saveid']").val(id);
2253     };
2254
2255   this.auto_save_start = function()
2256     {
2257     if (this.env.draft_autosave)
2258       this.save_timer = self.setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
2259
2260     // Unlock interface now that saving is complete
2261     this.busy = false;
2262     };
2263
2264   this.compose_field_hash = function(save)
2265     {
2266     // check input fields
2267     var value_to = $("[name='_to']").val();
2268     var value_cc = $("[name='_cc']").val();
2269     var value_bcc = $("[name='_bcc']").val();
2270     var value_subject = $("[name='_subject']").val();
2271     var str = '';
2272     
2273     if (value_to)
2274       str += value_to+':';
2275     if (value_cc)
2276       str += value_cc+':';
2277     if (value_bcc)
2278       str += value_bcc+':';
2279     if (value_subject)
2280       str += value_subject+':';
2281     
2282     var editor = tinyMCE.get(this.env.composebody);
2283     if (editor)
2284       str += editor.getContent();
2285     else
2286       str += $("[name='_message']").val();
2287
2288     if (this.env.attachments)
2289       for (var upload_id in this.env.attachments)
2290         str += upload_id;
2291
2292     if (save)
2293       this.cmp_hash = str;
2294
2295     return str;
2296     };
2297     
2298   this.change_identity = function(obj)
2299     {
2300     if (!obj || !obj.options)
2301       return false;
2302
2303     var id = obj.options[obj.selectedIndex].value;
2304     var input_message = $("[name='_message']");
2305     var message = input_message.val();
2306     var is_html = ($("input[name='_is_html']").val() == '1');
2307     var sig, p, len;
2308
2309     if (!this.env.identity)
2310       this.env.identity = id
2311   
2312     if (!is_html)
2313       {
2314       // remove the 'old' signature
2315       if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity])
2316         {
2317         if (this.env.signatures[this.env.identity]['is_html'])
2318           sig = this.env.signatures[this.env.identity]['plain_text'];
2319         else
2320           sig = this.env.signatures[this.env.identity]['text'];
2321         
2322         if (sig.indexOf('-- ')!=0)
2323           sig = '-- \n'+sig;
2324
2325         p = message.lastIndexOf(sig);
2326         if (p>=0)
2327           message = message.substring(0, p-1) + message.substring(p+sig.length, message.length);
2328         }
2329
2330       message = message.replace(/[\r\n]+$/, '');
2331       len = message.length;
2332
2333       // add the new signature string
2334       if (this.env.signatures && this.env.signatures[id])
2335         {
2336         sig = this.env.signatures[id]['text'];
2337         if (this.env.signatures[id]['is_html'])
2338           {
2339           sig = this.env.signatures[id]['plain_text'];
2340           }
2341         if (sig.indexOf('-- ')!=0)
2342           sig = '-- \n'+sig;
2343         message += '\n\n'+sig;
2344         if (len) len += 1;
2345         }
2346       }
2347     else
2348       {
2349       var editor = tinyMCE.get(this.env.composebody);
2350
2351       if (this.env.signatures)
2352         {
2353         // Append the signature as a div within the body
2354         var sigElem = editor.dom.get('_rc_sig');
2355         var newsig = '';
2356         var htmlsig = true;
2357
2358         if (!sigElem)
2359           {
2360           // add empty line before signature on IE
2361           if (bw.ie)
2362             editor.getBody().appendChild(editor.getDoc().createElement('br'));
2363
2364           sigElem = editor.getDoc().createElement('div');
2365           sigElem.setAttribute('id', '_rc_sig');
2366           editor.getBody().appendChild(sigElem);
2367           }
2368
2369         if (this.env.signatures[id])
2370         {
2371           newsig = this.env.signatures[id]['text'];
2372           htmlsig = this.env.signatures[id]['is_html'];
2373
2374           if (newsig) {
2375             if (htmlsig && this.env.signatures[id]['plain_text'].indexOf('-- ')!=0)
2376               newsig = '<p>-- </p>' + newsig;
2377             else if (!htmlsig && newsig.indexOf('-- ')!=0)
2378               newsig = '-- \n' + newsig;
2379           }
2380         }
2381
2382         if (htmlsig)
2383           sigElem.innerHTML = newsig;
2384         else
2385           sigElem.innerHTML = '<pre>' + newsig + '</pre>';
2386         }
2387       }
2388
2389     input_message.val(message);
2390
2391     // move cursor before the signature
2392     if (!is_html)
2393       this.set_caret_pos(input_message.get(0), len);
2394
2395     this.env.identity = id;
2396     return true;
2397     };
2398
2399   this.show_attachment_form = function(a)
2400     {
2401     if (!this.gui_objects.uploadbox)
2402       return false;
2403       
2404     var elm, list;
2405     if (elm = this.gui_objects.uploadbox)
2406       {
2407       if (a && (list = this.gui_objects.attachmentlist))
2408         {
2409         var pos = $(list).offset();
2410         elm.style.top = (pos.top + list.offsetHeight + 10) + 'px';
2411         elm.style.left = pos.left + 'px';
2412         }
2413       
2414       elm.style.visibility = a ? 'visible' : 'hidden';
2415       }
2416       
2417     // clear upload form
2418     try {
2419       if (!a && this.gui_objects.attachmentform != this.gui_objects.messageform)
2420         this.gui_objects.attachmentform.reset();
2421     }
2422     catch(e){}  // ignore errors
2423     
2424     return true;
2425     };
2426
2427   // upload attachment file
2428   this.upload_file = function(form)
2429     {
2430     if (!form)
2431       return false;
2432       
2433     // get file input fields
2434     var send = false;
2435     for (var n=0; n<form.elements.length; n++)
2436       if (form.elements[n].type=='file' && form.elements[n].value)
2437         {
2438         send = true;
2439         break;
2440         }
2441     
2442     // create hidden iframe and post upload form
2443     if (send)
2444       {
2445       var ts = new Date().getTime();
2446       var frame_name = 'rcmupload'+ts;
2447
2448       // have to do it this way for IE
2449       // otherwise the form will be posted to a new window
2450       if(document.all)
2451         {
2452         var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
2453         document.body.insertAdjacentHTML('BeforeEnd',html);
2454         }
2455       else  // for standards-compilant browsers
2456         {
2457         var frame = document.createElement('iframe');
2458         frame.name = frame_name;
2459         frame.style.border = 'none';
2460         frame.style.width = 0;
2461         frame.style.height = 0;
2462         frame.style.visibility = 'hidden';
2463         document.body.appendChild(frame);
2464         }
2465
2466       // handle upload errors, parsing iframe content in onload
2467       var fr = document.getElementsByName(frame_name)[0];
2468       $(fr).bind('load', {ts:ts}, function(e) {
2469         var content = '';
2470         try {
2471           if (this.contentDocument) {
2472             var d = this.contentDocument;
2473           } else if (this.contentWindow) {
2474             var d = this.contentWindow.document;
2475           }
2476           content = d.childNodes[0].innerHTML;
2477         } catch (e) {}
2478
2479         if (!String(content).match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
2480           rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
2481           rcmail.remove_from_attachment_list(e.data.ts);
2482         }
2483         // Opera hack: handle double onload
2484         if (bw.opera)
2485           rcmail.env.uploadframe = e.data.ts;
2486       });
2487
2488       form.target = frame_name;
2489       form.action = this.env.comm_path+'&_action=upload&_uploadid='+ts;
2490       form.setAttribute('enctype', 'multipart/form-data');
2491       form.submit();
2492       
2493       // hide upload form
2494       this.show_attachment_form(false);
2495       // display upload indicator and cancel button
2496       var content = this.get_label('uploading');
2497       if (this.env.loadingicon)
2498         content = '<img src="'+this.env.loadingicon+'" alt="" />'+content;
2499       if (this.env.cancelicon)
2500         content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content;
2501       this.add2attachment_list(ts, { name:'', html:content, complete:false });
2502       }
2503     
2504     // set reference to the form object
2505     this.gui_objects.attachmentform = form;
2506     return true;
2507     };
2508
2509   // add file name to attachment list
2510   // called from upload page
2511   this.add2attachment_list = function(name, att, upload_id)
2512   {
2513     if (!this.gui_objects.attachmentlist)
2514       return false;
2515     
2516     var li = $('<li>').attr('id', name).html(att.html);
2517     var indicator;
2518     
2519     // replace indicator's li
2520     if (upload_id && (indicator = document.getElementById(upload_id))) {
2521       li.replaceAll(indicator);
2522     }
2523     else { // add new li
2524       li.appendTo(this.gui_objects.attachmentlist);
2525     }
2526     
2527     if (upload_id && this.env.attachments[upload_id])
2528       delete this.env.attachments[upload_id];
2529     
2530     this.env.attachments[name] = att;
2531     
2532     return true;
2533   };
2534
2535   this.remove_from_attachment_list = function(name)
2536   {
2537     if (this.env.attachments[name])
2538       delete this.env.attachments[name];
2539     
2540     if (!this.gui_objects.attachmentlist)
2541       return false;
2542
2543     var list = this.gui_objects.attachmentlist.getElementsByTagName("li");
2544     for (i=0;i<list.length;i++)
2545       if (list[i].id == name)
2546         this.gui_objects.attachmentlist.removeChild(list[i]);
2547   };
2548
2549   this.remove_attachment = function(name)
2550     {
2551     if (name && this.env.attachments[name])
2552       this.http_post('remove-attachment', '_file='+urlencode(name));
2553
2554     return true;
2555     };
2556
2557   this.cancel_attachment_upload = function(name, frame_name)
2558     {
2559     if (!name || !frame_name)
2560       return false;
2561
2562     this.remove_from_attachment_list(name);
2563     $("iframe[name='"+frame_name+"']").remove();
2564     return false;
2565     };
2566
2567   // send remote request to add a new contact
2568   this.add_contact = function(value)
2569     {
2570     if (value)
2571       this.http_post('addcontact', '_address='+value);
2572     
2573     return true;
2574     };
2575
2576   // send remote request to search mail or contacts
2577   this.qsearch = function(value)
2578     {
2579     if (value != '')
2580       {
2581       var addurl = '';
2582       if (this.message_list) {
2583         this.message_list.clear();
2584         if (this.env.search_mods) {
2585           var head_arr = new Array();
2586           for (var n in this.env.search_mods)
2587             head_arr.push(n);
2588           addurl += '&_headers='+head_arr.join(',');
2589           }
2590         } else if (this.contact_list) {
2591         this.contact_list.clear(true);
2592         this.show_contentframe(false);
2593         }
2594
2595       if (this.gui_objects.search_filter)
2596         addurl += '&_filter=' + this.gui_objects.search_filter.value;
2597
2598       // reset vars
2599       this.env.current_page = 1;
2600       this.set_busy(true, 'searching');
2601       this.http_request('search', '_q='+urlencode(value)
2602         + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : '')
2603         + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
2604         + (addurl ? addurl : ''), true);
2605       }
2606     return true;
2607     };
2608
2609   // reset quick-search form
2610   this.reset_qsearch = function()
2611     {
2612     if (this.gui_objects.qsearchbox)
2613       this.gui_objects.qsearchbox.value = '';
2614       
2615     this.env.search_request = null;
2616     return true;
2617     };
2618
2619   this.sent_successfully = function(type, msg)
2620     {
2621     this.list_mailbox();
2622     this.display_message(msg, type, true);
2623     }
2624
2625
2626   /*********************************************************/
2627   /*********     keyboard live-search methods      *********/
2628   /*********************************************************/
2629
2630   // handler for keyboard events on address-fields
2631   this.ksearch_keypress = function(e, obj)
2632   {
2633     if (this.ksearch_timer)
2634       clearTimeout(this.ksearch_timer);
2635
2636     var highlight;
2637     var key = rcube_event.get_keycode(e);
2638     var mod = rcube_event.get_modifier(e);
2639
2640     switch (key)
2641       {
2642       case 38:  // key up
2643       case 40:  // key down
2644         if (!this.ksearch_pane)
2645           break;
2646           
2647         var dir = key==38 ? 1 : 0;
2648         
2649         highlight = document.getElementById('rcmksearchSelected');
2650         if (!highlight)
2651           highlight = this.ksearch_pane.__ul.firstChild;
2652         
2653         if (highlight)
2654           this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
2655
2656         return rcube_event.cancel(e);
2657
2658       case 9:  // tab
2659         if(mod == SHIFT_KEY)
2660           break;
2661
2662       case 13:  // enter
2663         if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
2664           break;
2665
2666         // insert selected address and hide ksearch pane
2667         this.insert_recipient(this.ksearch_selected);
2668         this.ksearch_hide();
2669
2670         return rcube_event.cancel(e);
2671
2672       case 27:  // escape
2673         this.ksearch_hide();
2674         break;
2675       
2676       case 37:  // left
2677       case 39:  // right
2678         if (mod != SHIFT_KEY)
2679           return;
2680       }
2681
2682     // start timer
2683     this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(); }, 200);
2684     this.ksearch_input = obj;
2685     
2686     return true;
2687   };
2688   
2689   this.ksearch_select = function(node)
2690   {
2691     var current = $('#rcmksearchSelected');
2692     if (current[0] && node) {
2693       current.removeAttr('id').removeClass('selected');
2694     }
2695
2696     if (node) {
2697       $(node).attr('id', 'rcmksearchSelected').addClass('selected');
2698       this.ksearch_selected = node._rcm_id;
2699     }
2700   };
2701
2702   this.insert_recipient = function(id)
2703   {
2704     if (!this.env.contacts[id] || !this.ksearch_input)
2705       return;
2706     
2707     // get cursor pos
2708     var inp_value = this.ksearch_input.value;
2709     var cpos = this.get_caret_pos(this.ksearch_input);
2710     var p = inp_value.lastIndexOf(this.ksearch_value, cpos);
2711
2712     // replace search string with full address
2713     var pre = this.ksearch_input.value.substring(0, p);
2714     var end = this.ksearch_input.value.substring(p+this.ksearch_value.length, this.ksearch_input.value.length);
2715     var insert  = this.env.contacts[id]+', ';
2716     this.ksearch_input.value = pre + insert + end;
2717
2718     // set caret to insert pos
2719     cpos = p+insert.length;
2720     if (this.ksearch_input.setSelectionRange)
2721       this.ksearch_input.setSelectionRange(cpos, cpos);
2722   };
2723
2724   // address search processor
2725   this.ksearch_get_results = function()
2726   {
2727     var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
2728     if (inp_value === null)
2729       return;
2730       
2731     if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
2732       this.ksearch_pane.hide();
2733
2734     // get string from current cursor pos to last comma
2735     var cpos = this.get_caret_pos(this.ksearch_input);
2736     var p = inp_value.lastIndexOf(',', cpos-1);
2737     var q = inp_value.substring(p+1, cpos);
2738
2739     // trim query string
2740     q = q.replace(/(^\s+|\s+$)/g, '');
2741
2742     // Don't (re-)search if the last results are still active
2743     if (q == this.ksearch_value)
2744       return;
2745     
2746     var old_value = this.ksearch_value;
2747     this.ksearch_value = q;
2748     
2749     // ...string is empty
2750     if (!q.length)
2751       return;
2752
2753     // ...new search value contains old one and previous search result was empty
2754     if (old_value && old_value.length && this.env.contacts && !this.env.contacts.length && q.indexOf(old_value) == 0)
2755       return;
2756     
2757     this.display_message(this.get_label('searching'), 'loading', true);
2758     this.http_post('autocomplete', '_search='+urlencode(q));
2759   };
2760
2761   this.ksearch_query_results = function(results, search)
2762   {
2763     // ignore this outdated search response
2764     if (this.ksearch_value && search != this.ksearch_value)
2765       return;
2766       
2767     this.hide_message();
2768     this.env.contacts = results ? results : [];
2769     this.ksearch_display_results(this.env.contacts);
2770   };
2771
2772   this.ksearch_display_results = function (a_results)
2773   {
2774     // display search results
2775     if (a_results.length && this.ksearch_input) {
2776       var p, ul, li;
2777       
2778       // create results pane if not present
2779       if (!this.ksearch_pane) {
2780         ul = $('<ul>');
2781         this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
2782         this.ksearch_pane.__ul = ul[0];
2783       }
2784
2785       // remove all search results
2786       ul = this.ksearch_pane.__ul;
2787       ul.innerHTML = '';
2788             
2789       // add each result line to list
2790       for (i=0; i<a_results.length; i++) {
2791         li = document.createElement('LI');
2792         li.innerHTML = a_results[i].replace(new RegExp('('+this.ksearch_value+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
2793         li.onmouseover = function(){ ref.ksearch_select(this); };
2794         li.onmouseup = function(){ ref.ksearch_click(this) };
2795         li._rcm_id = i;
2796         ul.appendChild(li);
2797       }
2798
2799       // select the first
2800       $(ul.firstChild).attr('id', 'rcmksearchSelected').addClass('selected');
2801       this.ksearch_selected = 0;
2802
2803       // move the results pane right under the input box and make it visible
2804       var pos = $(this.ksearch_input).offset();
2805       this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px' }).show();
2806     }
2807     // hide results pane
2808     else
2809       this.ksearch_hide();
2810   };
2811   
2812   this.ksearch_click = function(node)
2813   {
2814     if (this.ksearch_input)
2815       this.ksearch_input.focus();
2816
2817     this.insert_recipient(node._rcm_id);
2818     this.ksearch_hide();
2819   };
2820
2821   this.ksearch_blur = function()
2822     {
2823     if (this.ksearch_timer)
2824       clearTimeout(this.ksearch_timer);
2825
2826     this.ksearch_value = '';
2827     this.ksearch_input = null;
2828     
2829     this.ksearch_hide();
2830     };
2831
2832
2833   this.ksearch_hide = function()
2834     {
2835     this.ksearch_selected = null;
2836     
2837     if (this.ksearch_pane)
2838       this.ksearch_pane.hide();
2839     };
2840
2841
2842   /*********************************************************/
2843   /*********         address book methods          *********/
2844   /*********************************************************/
2845
2846   this.contactlist_keypress = function(list)
2847     {
2848       if (list.key_pressed == list.DELETE_KEY)
2849         this.command('delete');
2850     };
2851
2852   this.contactlist_select = function(list)
2853     {
2854       if (this.preview_timer)
2855         clearTimeout(this.preview_timer);
2856
2857       var id, frame, ref = this;
2858       if (id = list.get_single_selection())
2859         this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
2860       else if (this.env.contentframe)
2861         this.show_contentframe(false);
2862
2863       this.enable_command('compose', list.selection.length > 0);
2864       this.enable_command('edit', (id && this.env.address_sources && !this.env.address_sources[this.env.source].readonly) ? true : false);
2865       this.enable_command('delete', list.selection.length && this.env.address_sources && !this.env.address_sources[this.env.source].readonly);
2866
2867       return false;
2868     };
2869
2870   this.list_contacts = function(src, page)
2871     {
2872     var add_url = '';
2873     var target = window;
2874     
2875     if (!src)
2876       src = this.env.source;
2877     
2878     if (page && this.current_page==page && src == this.env.source)
2879       return false;
2880       
2881     if (src != this.env.source)
2882       {
2883       page = 1;
2884       this.env.current_page = page;
2885       this.reset_qsearch();
2886       }
2887
2888     this.select_folder(src, this.env.source);
2889     this.env.source = src;
2890
2891     // load contacts remotely
2892     if (this.gui_objects.contactslist)
2893       {
2894       this.list_contacts_remote(src, page);
2895       return;
2896       }
2897
2898     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
2899       {
2900       target = window.frames[this.env.contentframe];
2901       add_url = '&_framed=1';
2902       }
2903
2904     // also send search request to get the correct listing
2905     if (this.env.search_request)
2906       add_url += '&_search='+this.env.search_request;
2907
2908     this.set_busy(true, 'loading');
2909     target.location.href = this.env.comm_path+(src ? '&_source='+urlencode(src) : '')+(page ? '&_page='+page : '')+add_url;
2910     };
2911
2912   // send remote request to load contacts list
2913   this.list_contacts_remote = function(src, page)
2914     {
2915     // clear message list first
2916     this.contact_list.clear(true);
2917     this.show_contentframe(false);
2918     this.enable_command('delete', 'compose', false);
2919
2920     // send request to server
2921     var url = (src ? '_source='+urlencode(src) : '') + (page ? (src?'&':'') + '_page='+page : '');
2922     this.env.source = src;
2923     
2924     // also send search request to get the right messages 
2925     if (this.env.search_request) 
2926       url += '&_search='+this.env.search_request;
2927
2928     this.set_busy(true, 'loading');
2929     this.http_request('list', url, true);
2930     };
2931
2932   // load contact record
2933   this.load_contact = function(cid, action, framed)
2934     {
2935     var add_url = '';
2936     var target = window;
2937     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
2938       {
2939       add_url = '&_framed=1';
2940       target = window.frames[this.env.contentframe];
2941       this.show_contentframe(true);
2942       }
2943     else if (framed)
2944       return false;
2945       
2946     if (action && (cid || action=='add') && !this.drag_active)
2947       {
2948       this.set_busy(true);
2949       target.location.href = this.env.comm_path+'&_action='+action+'&_source='+urlencode(this.env.source)+'&_cid='+urlencode(cid) + add_url;
2950       }
2951     return true;
2952     };
2953
2954   // copy a contact to the specified target (group or directory)
2955   this.copy_contact = function(cid, to)
2956     {
2957     if (!cid)
2958       cid = this.contact_list.get_selection().join(',');
2959
2960     if (to != this.env.source && cid && this.env.address_sources[to] && !this.env.address_sources[to].readonly)
2961       this.http_post('copy', '_cid='+urlencode(cid)+'&_source='+urlencode(this.env.source)+'&_to='+urlencode(to));
2962     };
2963
2964
2965   this.delete_contacts = function()
2966     {
2967     // exit if no mailbox specified or if selection is empty
2968     var selection = this.contact_list.get_selection();
2969     if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
2970       return;
2971       
2972     var a_cids = new Array();
2973     var qs = '';
2974
2975     if (this.env.cid)
2976       a_cids[a_cids.length] = this.env.cid;
2977     else
2978       {
2979       var id;
2980       for (var n=0; n<selection.length; n++)
2981         {
2982         id = selection[n];
2983         a_cids[a_cids.length] = id;
2984         this.contact_list.remove_row(id, (n == selection.length-1));
2985         }
2986
2987       // hide content frame if we delete the currently displayed contact
2988       if (selection.length == 1)
2989         this.show_contentframe(false);
2990       }
2991
2992     // also send search request to get the right records from the next page
2993     if (this.env.search_request) 
2994       qs += '&_search='+this.env.search_request;
2995
2996     // send request to server
2997     this.http_post('delete', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source)+'&_from='+(this.env.action ? this.env.action : '')+qs);
2998     return true;
2999     };
3000
3001   // update a contact record in the list
3002   this.update_contact_row = function(cid, cols_arr, newcid)
3003   {
3004     var row;
3005     if (this.contact_list.rows[cid] && (row = this.contact_list.rows[cid].obj)) {
3006       for (var c=0; c<cols_arr.length; c++)
3007         if (row.cells[c])
3008           $(row.cells[c]).html(cols_arr[c]);
3009
3010       // cid change
3011       if (newcid) {
3012         row.id = 'rcmrow' + newcid;
3013         this.contact_list.remove_row(cid);
3014         this.contact_list.init_row(row);
3015         this.contact_list.selection[0] = newcid;
3016         row.style.display = '';
3017       }
3018
3019       return true;
3020     }
3021
3022     return false;
3023   };
3024
3025   // add row to contacts list
3026   this.add_contact_row = function(cid, cols, select)
3027     {
3028     if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
3029       return false;
3030     
3031     var tbody = this.gui_objects.contactslist.tBodies[0];
3032     var rowcount = tbody.rows.length;
3033     var even = rowcount%2;
3034     
3035     var row = document.createElement('tr');
3036     row.id = 'rcmrow'+cid;
3037     row.className = 'contact '+(even ? 'even' : 'odd');
3038             
3039     if (this.contact_list.in_selection(cid))
3040       row.className += ' selected';
3041
3042     // add each submitted col
3043     for (var c in cols) {
3044       col = document.createElement('td');
3045       col.className = String(c).toLowerCase();
3046       col.innerHTML = cols[c];
3047       row.appendChild(col);
3048     }
3049     
3050     this.contact_list.insert_row(row);
3051     
3052     this.enable_command('export', (this.contact_list.rowcount > 0));
3053     };
3054
3055
3056   /*********************************************************/
3057   /*********        user settings methods          *********/
3058   /*********************************************************/
3059
3060   this.init_subscription_list = function()
3061     {
3062     var p = this;
3063     this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist, {multiselect:false, draggable:true, keyboard:false, toggleselect:true});
3064     this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
3065     this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
3066     this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
3067     this.subscription_list.row_init = function (row)
3068       {
3069       var anchors = row.obj.getElementsByTagName('a');
3070       if (anchors[0])
3071         anchors[0].onclick = function() { p.rename_folder(row.id); return false; };
3072       if (anchors[1])
3073         anchors[1].onclick = function() { p.delete_folder(row.id); return false; };
3074       row.obj.onmouseover = function() { p.focus_subscription(row.id); };
3075       row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
3076       }
3077     this.subscription_list.init();
3078     }
3079
3080   // preferences section select and load options frame
3081   this.section_select = function(list)
3082     {
3083     var id = list.get_single_selection();
3084     
3085     if (id) {
3086       var add_url = '';
3087       var target = window;
3088       this.set_busy(true);
3089
3090       if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
3091         add_url = '&_framed=1';
3092         target = window.frames[this.env.contentframe];
3093         }
3094       target.location.href = this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url;
3095       }
3096
3097     return true;
3098     };
3099
3100   this.identity_select = function(list)
3101     {
3102     var id;
3103     if (id = list.get_single_selection())
3104       this.load_identity(id, 'edit-identity');
3105     };
3106
3107   // load identity record
3108   this.load_identity = function(id, action)
3109     {
3110     if (action=='edit-identity' && (!id || id==this.env.iid))
3111       return false;
3112
3113     var add_url = '';
3114     var target = window;
3115     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
3116       {
3117       add_url = '&_framed=1';
3118       target = window.frames[this.env.contentframe];
3119       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
3120       }
3121
3122     if (action && (id || action=='add-identity'))
3123       {
3124       this.set_busy(true);
3125       target.location.href = this.env.comm_path+'&_action='+action+'&_iid='+id+add_url;
3126       }
3127     return true;
3128     };
3129
3130   this.delete_identity = function(id)
3131     {
3132     // exit if no mailbox specified or if selection is empty
3133     var selection = this.identity_list.get_selection();
3134     if (!(selection.length || this.env.iid))
3135       return;
3136     
3137     if (!id)
3138       id = this.env.iid ? this.env.iid : selection[0];
3139
3140     // append token to request
3141     this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
3142     
3143     return true;
3144     };
3145
3146   this.focus_subscription = function(id)
3147     {
3148     var row, folder;
3149     var reg = RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$');
3150
3151     if (this.drag_active && this.env.folder && (row = document.getElementById(id)))
3152       if (this.env.subscriptionrows[id] &&
3153           (folder = this.env.subscriptionrows[id][0]))
3154         {
3155         if (this.check_droptarget(folder) &&
3156             !this.env.subscriptionrows[this.get_folder_row_id(this.env.folder)][2] &&
3157             (folder != this.env.folder.replace(reg, '')) &&
3158             (!folder.match(new RegExp('^'+RegExp.escape(this.env.folder+this.env.delimiter)))))
3159           {
3160           this.set_env('dstfolder', folder);
3161           $(row).addClass('droptarget');
3162           }
3163         }
3164       else if (this.env.folder.match(new RegExp(RegExp.escape(this.env.delimiter))))
3165         {
3166         this.set_env('dstfolder', this.env.delimiter);
3167         $(this.subscription_list.frame).addClass('droptarget');
3168         }
3169     }
3170
3171   this.unfocus_subscription = function(id)
3172     {
3173       var row = $('#'+id);
3174       this.set_env('dstfolder', null);
3175       if (this.env.subscriptionrows[id] && row[0])
3176         row.removeClass('droptarget');
3177       else
3178         $(this.subscription_list.frame).removeClass('droptarget');
3179     }
3180
3181   this.subscription_select = function(list)
3182     {
3183     var id, folder;
3184     if ((id = list.get_single_selection()) &&
3185         this.env.subscriptionrows['rcmrow'+id] &&
3186         (folder = this.env.subscriptionrows['rcmrow'+id][0]))
3187       this.set_env('folder', folder);
3188     else
3189       this.set_env('folder', null);
3190       
3191     if (this.gui_objects.createfolderhint)
3192       $(this.gui_objects.createfolderhint).html(this.env.folder ? this.get_label('addsubfolderhint') : '');
3193     };
3194
3195   this.subscription_move_folder = function(list)
3196     {
3197     var reg = RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$');
3198     if (this.env.folder && this.env.dstfolder && (this.env.dstfolder != this.env.folder) &&
3199         (this.env.dstfolder != this.env.folder.replace(reg, '')))
3200       {
3201       var reg = new RegExp('[^'+RegExp.escape(this.env.delimiter)+']*['+RegExp.escape(this.env.delimiter)+']', 'g');
3202       var basename = this.env.folder.replace(reg, '');
3203       var newname = this.env.dstfolder==this.env.delimiter ? basename : this.env.dstfolder+this.env.delimiter+basename;
3204
3205       this.set_busy(true, 'foldermoving');
3206       this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.folder)+'&_folder_newname='+urlencode(newname), true);
3207       }
3208     this.drag_active = false;
3209     this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder));
3210     };
3211
3212   // tell server to create and subscribe a new mailbox
3213   this.create_folder = function(name)
3214     {
3215     if (this.edit_folder)
3216       this.reset_folder_rename();
3217
3218     var form;
3219     if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
3220       {
3221       name = form.elements['_folder_name'].value;
3222
3223       if (name.indexOf(this.env.delimiter)>=0)
3224         {
3225         alert(this.get_label('forbiddencharacter')+' ('+this.env.delimiter+')');
3226         return false;
3227         }
3228
3229       if (this.env.folder && name != '')
3230         name = this.env.folder+this.env.delimiter+name;
3231
3232       this.set_busy(true, 'foldercreating');
3233       this.http_post('create-folder', '_name='+urlencode(name), true);
3234       }
3235     else if (form.elements['_folder_name'])
3236       form.elements['_folder_name'].focus();
3237     };
3238
3239   // start renaming the mailbox name.
3240   // this will replace the name string with an input field
3241   this.rename_folder = function(id)
3242     {
3243     var temp, row, form;
3244
3245     // reset current renaming
3246     if (temp = this.edit_folder)
3247       {
3248       this.reset_folder_rename();
3249       if (temp == id)
3250         return;
3251       }
3252
3253     if (id && this.env.subscriptionrows[id] && (row = document.getElementById(id)))
3254       {
3255       var reg = new RegExp('.*['+RegExp.escape(this.env.delimiter)+']');
3256       this.name_input = document.createElement('input');
3257       this.name_input.type = 'text';
3258       this.name_input.value = this.env.subscriptionrows[id][0].replace(reg, '');
3259
3260       reg = new RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$');
3261       this.name_input.__parent = this.env.subscriptionrows[id][0].replace(reg, '');
3262       this.name_input.onkeypress = function(e){ rcmail.name_input_keypress(e); };
3263       
3264       row.cells[0].replaceChild(this.name_input, row.cells[0].firstChild);
3265       this.edit_folder = id;
3266       this.name_input.select();
3267       
3268       if (form = this.gui_objects.editform)
3269         form.onsubmit = function(){ return false; };
3270       }
3271     };
3272
3273   // remove the input field and write the current mailbox name to the table cell
3274   this.reset_folder_rename = function()
3275     {
3276     var cell = this.name_input ? this.name_input.parentNode : null;
3277
3278     if (cell && this.edit_folder && this.env.subscriptionrows[this.edit_folder])
3279       $(cell).html(this.env.subscriptionrows[this.edit_folder][1]);
3280       
3281     this.edit_folder = null;
3282     };
3283
3284   // handler for keyboard events on the input field
3285   this.name_input_keypress = function(e)
3286     {
3287     var key = rcube_event.get_keycode(e);
3288
3289     // enter
3290     if (key==13)
3291       {
3292       var newname = this.name_input ? this.name_input.value : null;
3293       if (this.edit_folder && newname)
3294         {
3295         if (newname.indexOf(this.env.delimiter)>=0)
3296           {
3297           alert(this.get_label('forbiddencharacter')+' ('+this.env.delimiter+')');
3298           return false;
3299           }
3300
3301         if (this.name_input.__parent)
3302           newname = this.name_input.__parent + this.env.delimiter + newname;
3303
3304         this.set_busy(true, 'folderrenaming');
3305         this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.subscriptionrows[this.edit_folder][0])+'&_folder_newname='+urlencode(newname), true);
3306         }
3307       }
3308     // escape
3309     else if (key==27)
3310       this.reset_folder_rename();
3311     };
3312
3313   // delete a specific mailbox with all its messages
3314   this.delete_folder = function(id)
3315     {
3316     var folder = this.env.subscriptionrows[id][0];
3317
3318     if (this.edit_folder)
3319       this.reset_folder_rename();
3320
3321     if (folder && confirm(this.get_label('deletefolderconfirm')))
3322       {
3323       this.set_busy(true, 'folderdeleting');
3324       this.http_post('delete-folder', '_mboxes='+urlencode(folder), true);
3325       this.set_env('folder', null);
3326
3327       $(this.gui_objects.createfolderhint).html('');
3328       }
3329     };
3330
3331   // add a new folder to the subscription list by cloning a folder row
3332   this.add_folder_row = function(name, display_name, replace, before)
3333     {
3334     if (!this.gui_objects.subscriptionlist)
3335       return false;
3336
3337     // find not protected folder    
3338     for (var refid in this.env.subscriptionrows)
3339       if (this.env.subscriptionrows[refid]!=null && !this.env.subscriptionrows[refid][2])
3340         break;
3341
3342     var refrow, form;
3343     var tbody = this.gui_objects.subscriptionlist.tBodies[0];
3344     var id = 'rcmrow'+(tbody.childNodes.length+1);
3345     var selection = this.subscription_list.get_single_selection();
3346     
3347     if (replace && replace.id)
3348     {
3349       id = replace.id;
3350       refid = replace.id;
3351     }
3352
3353     if (!id || !(refrow = document.getElementById(refid)))
3354       {
3355       // Refresh page if we don't have a table row to clone
3356       this.goto_url('folders');
3357       }
3358     else
3359       {
3360       // clone a table row if there are existing rows
3361       var row = this.clone_table_row(refrow);
3362       row.id = id;
3363
3364       if (before && (before = this.get_folder_row_id(before)))
3365         tbody.insertBefore(row, document.getElementById(before));
3366       else
3367         tbody.appendChild(row);
3368       
3369       if (replace)
3370         tbody.removeChild(replace);
3371       }
3372
3373     // add to folder/row-ID map
3374     this.env.subscriptionrows[row.id] = [name, display_name, 0];
3375
3376     // set folder name
3377     row.cells[0].innerHTML = display_name;
3378     
3379     // set messages count to zero
3380     if (!replace)
3381       row.cells[1].innerHTML = '*';
3382     
3383     if (!replace && row.cells[2] && row.cells[2].firstChild.tagName.toLowerCase()=='input')
3384       {
3385       row.cells[2].firstChild.value = name;
3386       row.cells[2].firstChild.checked = true;
3387       }
3388     
3389     // add new folder to rename-folder list and clear input field
3390     if (!replace && (form = this.gui_objects.editform))
3391       {
3392       if (form.elements['_folder_oldname'])
3393         form.elements['_folder_oldname'].options[form.elements['_folder_oldname'].options.length] = new Option(name,name);
3394       if (form.elements['_folder_name'])
3395         form.elements['_folder_name'].value = ''; 
3396       }
3397
3398     this.init_subscription_list();
3399     if (selection && document.getElementById('rcmrow'+selection))
3400       this.subscription_list.select_row(selection);
3401
3402     if (document.getElementById(id).scrollIntoView)
3403       document.getElementById(id).scrollIntoView();
3404     };
3405
3406   // replace an existing table row with a new folder line
3407   this.replace_folder_row = function(oldfolder, newfolder, display_name, before)
3408     {
3409     var id = this.get_folder_row_id(oldfolder);
3410     var row = document.getElementById(id);
3411     
3412     // replace an existing table row (if found)
3413     this.add_folder_row(newfolder, display_name, row, before);
3414     
3415     // rename folder in rename-folder dropdown
3416     var form, elm;
3417     if ((form = this.gui_objects.editform) && (elm = form.elements['_folder_oldname']))
3418       {
3419       for (var i=0;i<elm.options.length;i++)
3420         {
3421         if (elm.options[i].value == oldfolder)
3422           {
3423           elm.options[i].text = display_name;
3424           elm.options[i].value = newfolder;
3425           break;
3426           }
3427         }
3428
3429       form.elements['_folder_newname'].value = '';
3430       }
3431     };
3432
3433   // remove the table row of a specific mailbox from the table
3434   // (the row will not be removed, just hidden)
3435   this.remove_folder_row = function(folder)
3436     {
3437     var row;
3438     var id = this.get_folder_row_id(folder);
3439     if (id && (row = document.getElementById(id)))
3440       row.style.display = 'none';
3441
3442     // remove folder from rename-folder list
3443     var form;
3444     if ((form = this.gui_objects.editform) && form.elements['_folder_oldname'])
3445       {
3446       for (var i=0;i<form.elements['_folder_oldname'].options.length;i++)
3447         {
3448         if (form.elements['_folder_oldname'].options[i].value == folder) 
3449           {
3450           form.elements['_folder_oldname'].options[i] = null;
3451           break;
3452           }
3453         }
3454       }
3455     
3456     if (form && form.elements['_folder_newname'])
3457       form.elements['_folder_newname'].value = '';
3458     };
3459
3460   this.subscribe_folder = function(folder)
3461     {
3462     if (folder)
3463       this.http_post('subscribe', '_mbox='+urlencode(folder));
3464     };
3465
3466   this.unsubscribe_folder = function(folder)
3467     {
3468     if (folder)
3469       this.http_post('unsubscribe', '_mbox='+urlencode(folder));
3470     };
3471     
3472   // helper method to find a specific mailbox row ID
3473   this.get_folder_row_id = function(folder)
3474     {
3475     for (var id in this.env.subscriptionrows)
3476       if (this.env.subscriptionrows[id] && this.env.subscriptionrows[id][0] == folder)
3477         break;
3478         
3479     return id;
3480     };
3481
3482   // duplicate a specific table row
3483   this.clone_table_row = function(row)
3484     {
3485     var cell, td;
3486     var new_row = document.createElement('tr');
3487     for(var n=0; n<row.cells.length; n++)
3488       {
3489       cell = row.cells[n];
3490       td = document.createElement('td');
3491
3492       if (cell.className)
3493         td.className = cell.className;
3494       if (cell.align)
3495         td.setAttribute('align', cell.align);
3496         
3497       td.innerHTML = cell.innerHTML;
3498       new_row.appendChild(td);
3499       }
3500     
3501     return new_row;
3502     };
3503
3504
3505   /*********************************************************/
3506   /*********           GUI functionality           *********/
3507   /*********************************************************/
3508
3509   // eable/disable buttons for page shifting
3510   this.set_page_buttons = function()
3511   {
3512     this.enable_command('nextpage', (this.env.pagecount > this.env.current_page));
3513     this.enable_command('lastpage', (this.env.pagecount > this.env.current_page));
3514     this.enable_command('previouspage', (this.env.current_page > 1));
3515     this.enable_command('firstpage', (this.env.current_page > 1));
3516   };
3517   
3518   // set event handlers on registered buttons
3519   this.init_buttons = function()
3520   {
3521     for (var cmd in this.buttons) {
3522       if (typeof cmd != 'string')
3523         continue;
3524       
3525       for (var i=0; i< this.buttons[cmd].length; i++) {
3526         var prop = this.buttons[cmd][i];
3527         var elm = document.getElementById(prop.id);
3528         if (!elm)
3529           continue;
3530
3531         var preload = false;
3532         if (prop.type == 'image') {
3533           elm = elm.parentNode;
3534           preload = true;
3535         }
3536         
3537         elm._command = cmd;
3538         elm._id = prop.id;
3539         if (prop.sel) {
3540           elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
3541           elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
3542           if (preload)
3543             new Image().src = prop.sel;
3544         }
3545         if (prop.over) {
3546           elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
3547           elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
3548           if (preload)
3549             new Image().src = prop.over;
3550         }
3551       }
3552     }
3553   };
3554
3555   // set button to a specific state
3556   this.set_button = function(command, state)
3557     {
3558     var a_buttons = this.buttons[command];
3559     var button, obj;
3560
3561     if(!a_buttons || !a_buttons.length)
3562       return false;
3563
3564     for(var n=0; n<a_buttons.length; n++)
3565       {
3566       button = a_buttons[n];
3567       obj = document.getElementById(button.id);
3568
3569       // get default/passive setting of the button
3570       if (obj && button.type=='image' && !button.status) {
3571         button.pas = obj._original_src ? obj._original_src : obj.src;
3572         // respect PNG fix on IE browsers
3573         if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
3574           button.pas = RegExp.$1;
3575       }
3576       else if (obj && !button.status)
3577         button.pas = String(obj.className);
3578
3579       // set image according to button state
3580       if (obj && button.type=='image' && button[state])
3581         {
3582         button.status = state;        
3583         obj.src = button[state];
3584         }
3585       // set class name according to button state
3586       else if (obj && typeof(button[state])!='undefined')
3587         {
3588         button.status = state;        
3589         obj.className = button[state];        
3590         }
3591       // disable/enable input buttons
3592       if (obj && button.type=='input')
3593         {
3594         button.status = state;
3595         obj.disabled = !state;
3596         }
3597       }
3598     };
3599
3600   // display a specific alttext
3601   this.set_alttext = function(command, label)
3602     {
3603       if (!this.buttons[command] || !this.buttons[command].length)
3604         return;
3605       
3606       var button, obj, link;
3607       for (var n=0; n<this.buttons[command].length; n++)
3608       {
3609         button = this.buttons[command][n];
3610         obj = document.getElementById(button.id);
3611         
3612         if (button.type=='image' && obj)
3613         {
3614           obj.setAttribute('alt', this.get_label(label));
3615           if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
3616             link.setAttribute('title', this.get_label(label));
3617         }
3618         else if (obj)
3619           obj.setAttribute('title', this.get_label(label));
3620       }
3621     };
3622
3623   // mouse over button
3624   this.button_over = function(command, id)
3625   {
3626     var a_buttons = this.buttons[command];
3627     var button, elm;
3628
3629     if(!a_buttons || !a_buttons.length)
3630       return false;
3631
3632     for(var n=0; n<a_buttons.length; n++)
3633     {
3634       button = a_buttons[n];
3635       if(button.id==id && button.status=='act')
3636       {
3637         elm = document.getElementById(button.id);
3638         if (elm && button.over) {
3639           if (button.type == 'image')
3640             elm.src = button.over;
3641           else
3642             elm.className = button.over;
3643         }
3644       }
3645     }
3646   };
3647
3648   // mouse down on button
3649   this.button_sel = function(command, id)
3650   {
3651     var a_buttons = this.buttons[command];
3652     var button, elm;
3653
3654     if(!a_buttons || !a_buttons.length)
3655       return;
3656
3657     for(var n=0; n<a_buttons.length; n++)
3658     {
3659       button = a_buttons[n];
3660       if(button.id==id && button.status=='act')
3661       {
3662         elm = document.getElementById(button.id);
3663         if (elm && button.sel) {
3664           if (button.type == 'image')
3665             elm.src = button.sel;
3666           else
3667             elm.className = button.sel;
3668         }
3669         this.buttons_sel[id] = command;
3670       }
3671     }
3672   };
3673
3674   // mouse out of button
3675   this.button_out = function(command, id)
3676   {
3677     var a_buttons = this.buttons[command];
3678     var button, elm;
3679
3680     if(!a_buttons || !a_buttons.length)
3681       return;
3682
3683     for(var n=0; n<a_buttons.length; n++)
3684     {
3685       button = a_buttons[n];
3686       if(button.id==id && button.status=='act')
3687       {
3688         elm = document.getElementById(button.id);
3689         if (elm && button.act) {
3690           if (button.type == 'image')
3691             elm.src = button.act;
3692           else
3693             elm.className = button.act;
3694         }
3695       }
3696     }
3697   };
3698
3699   // write to the document/window title
3700   this.set_pagetitle = function(title)
3701   {
3702     if (title && document.title)
3703       document.title = title;
3704   }
3705
3706   // display a system message
3707   this.display_message = function(msg, type, hold)
3708     {
3709     if (!this.loaded)  // save message in order to display after page loaded
3710       {
3711       this.pending_message = new Array(msg, type);
3712       return true;
3713       }
3714
3715     // pass command to parent window
3716     if (this.env.framed && parent.rcmail)
3717       return parent.rcmail.display_message(msg, type, hold);
3718
3719     if (!this.gui_objects.message)
3720       return false;
3721
3722     if (this.message_timer)
3723       clearTimeout(this.message_timer);
3724     
3725     var cont = msg;
3726     if (type)
3727       cont = '<div class="'+type+'">'+cont+'</div>';
3728
3729     var obj = $(this.gui_objects.message).html(cont).show();
3730     
3731     if (type!='loading')
3732       obj.bind('mousedown', function(){ ref.hide_message(); return true; });
3733     
3734     if (!hold)
3735       this.message_timer = window.setTimeout(function(){ ref.hide_message(true); }, this.message_time);
3736     };
3737
3738   // make a message row disapear
3739   this.hide_message = function(fade)
3740     {
3741     if (this.gui_objects.message)
3742       $(this.gui_objects.message).unbind()[(fade?'fadeOut':'hide')]();
3743     };
3744
3745   // mark a mailbox as selected and set environment variable
3746   this.select_folder = function(name, old)
3747   {
3748     if (this.gui_objects.folderlist)
3749     {
3750       var current_li, target_li;
3751       
3752       if ((current_li = this.get_folder_li(old))) {
3753         $(current_li).removeClass('selected').removeClass('unfocused');
3754       }
3755       if ((target_li = this.get_folder_li(name))) {
3756         $(target_li).removeClass('unfocused').addClass('selected');
3757       }
3758       
3759       // trigger event hook
3760       this.triggerEvent('selectfolder', { folder:name, old:old });
3761     }
3762   };
3763
3764   // helper method to find a folder list item
3765   this.get_folder_li = function(name)
3766   {
3767     if (this.gui_objects.folderlist)
3768     {
3769       name = String(name).replace(this.identifier_expr, '_');
3770       return document.getElementById('rcmli'+name);
3771     }
3772
3773     return null;
3774   };
3775
3776   // for reordering column array, Konqueror workaround
3777   this.set_message_coltypes = function(coltypes) 
3778   { 
3779     this.coltypes = coltypes;
3780     
3781     // set correct list titles
3782     var cell, col;
3783     var thead = this.gui_objects.messagelist ? this.gui_objects.messagelist.tHead : null;
3784     for (var n=0; thead && n<this.coltypes.length; n++) 
3785       {
3786       col = this.coltypes[n];
3787       if ((cell = thead.rows[0].cells[n+1]) && (col=='from' || col=='to'))
3788         {
3789         // if we have links for sorting, it's a bit more complicated...
3790         if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a')
3791           {
3792           cell.firstChild.innerHTML = this.get_label(this.coltypes[n]);
3793           cell.firstChild.onclick = function(){ return rcmail.command('sort', this.__col, this); };
3794           cell.firstChild.__col = col;
3795           }
3796         else
3797           cell.innerHTML = this.get_label(this.coltypes[n]);
3798
3799         cell.id = 'rcm'+col;
3800         }
3801       else if (col == 'subject' && this.message_list)
3802         this.message_list.subject_col = n+1;
3803       }
3804   };
3805
3806   // create a table row in the message list
3807   this.add_message_row = function(uid, cols, flags, attachment, attop)
3808     {
3809     if (!this.gui_objects.messagelist || !this.message_list)
3810       return false;
3811
3812     if (this.message_list.background)
3813       var tbody = this.message_list.background;
3814     else
3815       var tbody = this.gui_objects.messagelist.tBodies[0];
3816     
3817     var rowcount = tbody.rows.length;
3818     var even = rowcount%2;
3819     
3820     this.env.messages[uid] = {
3821       deleted: flags.deleted?1:0,
3822       replied: flags.replied?1:0,
3823       unread: flags.unread?1:0,
3824       forwarded: flags.forwarded?1:0,
3825       flagged:flags.flagged?1:0
3826     };
3827
3828     var css_class = 'message'
3829         + (even ? ' even' : ' odd')
3830         + (flags.unread ? ' unread' : '')
3831         + (flags.deleted ? ' deleted' : '')
3832         + (flags.flagged ? ' flagged' : '')
3833         + (this.message_list.in_selection(uid) ? ' selected' : '');
3834
3835     // for performance use DOM instead of jQuery here
3836     var row = document.createElement('tr');
3837     row.id = 'rcmrow'+uid;
3838     row.className = css_class;
3839     
3840     var icon = this.env.messageicon;
3841     if (flags.deleted && this.env.deletedicon)
3842       icon = this.env.deletedicon;
3843     else if (flags.replied && this.env.repliedicon)
3844       {
3845       if (flags.forwarded && this.env.forwardedrepliedicon)
3846         icon = this.env.forwardedrepliedicon;
3847       else
3848         icon = this.env.repliedicon;
3849       }
3850     else if (flags.forwarded && this.env.forwardedicon)
3851       icon = this.env.forwardedicon;
3852     else if(flags.unread && this.env.unreadicon)
3853       icon = this.env.unreadicon;
3854     
3855     // add icon col
3856     var col = document.createElement('td');
3857     col.className = 'icon';
3858     col.innerHTML = icon ? '<img src="'+icon+'" alt="" />' : '';
3859     row.appendChild(col);
3860                   
3861     // add each submitted col
3862     for (var n = 0; n < this.coltypes.length; n++) {
3863       var c = this.coltypes[n];
3864       col = document.createElement('td');
3865       col.className = String(c).toLowerCase();
3866             
3867       if (c=='flag') {
3868         if (flags.flagged && this.env.flaggedicon)
3869           col.innerHTML = '<img src="'+this.env.flaggedicon+'" alt="" />';
3870         else if(!flags.flagged && this.env.unflaggedicon)
3871           col.innerHTML = '<img src="'+this.env.unflaggedicon+'" alt="" />';
3872         }
3873       else if (c=='attachment')
3874         col.innerHTML = (attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;');
3875       else
3876         col.innerHTML = cols[c];
3877
3878       row.appendChild(col);
3879       }
3880
3881     this.message_list.insert_row(row, attop);
3882
3883     // remove 'old' row
3884     if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize) {
3885       var uid = this.message_list.get_last_row();
3886       this.message_list.remove_row(uid);
3887       this.message_list.clear_selection(uid);
3888       }
3889     };
3890
3891   // messages list handling in background (for performance)
3892   this.offline_message_list = function(flag)
3893     {
3894       if (this.message_list)
3895         this.message_list.set_background_mode(flag);
3896     };
3897
3898   // replace content of row count display
3899   this.set_rowcount = function(text)
3900     {
3901     $(this.gui_objects.countdisplay).html(text);
3902
3903     // update page navigation buttons
3904     this.set_page_buttons();
3905     };
3906
3907   // replace content of mailboxname display
3908   this.set_mailboxname = function(content)
3909     {
3910     if (this.gui_objects.mailboxname && content)
3911       this.gui_objects.mailboxname.innerHTML = content;
3912     };
3913
3914   // replace content of quota display
3915   this.set_quota = function(content)
3916     {
3917     if (content && this.gui_objects.quotadisplay) {
3918       if (typeof(content) == 'object')
3919         this.percent_indicator(this.gui_objects.quotadisplay, content);
3920       else
3921         $(this.gui_objects.quotadisplay).html(content);
3922       }
3923     };
3924
3925   // update the mailboxlist
3926   this.set_unread_count = function(mbox, count, set_title)
3927     {
3928     if (!this.gui_objects.mailboxlist)
3929       return false;
3930
3931     this.env.unread_counts[mbox] = count;
3932     this.set_unread_count_display(mbox, set_title);
3933     }
3934
3935   // update the mailbox count display
3936   this.set_unread_count_display = function(mbox, set_title)
3937     {
3938     var reg, text_obj, item, mycount, childcount, div;
3939     if (item = this.get_folder_li(mbox))
3940       {
3941       mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0;
3942       text_obj = item.getElementsByTagName('a')[0];
3943       reg = /\s+\([0-9]+\)$/i;
3944
3945       childcount = 0;
3946       if ((div = item.getElementsByTagName('div')[0]) &&
3947           div.className.match(/collapsed/))
3948         {
3949         // add children's counters
3950         for (var k in this.env.unread_counts) 
3951           if (k.indexOf(mbox + this.env.delimiter) == 0)
3952             childcount += this.env.unread_counts[k];
3953         }
3954
3955       if (mycount && text_obj.innerHTML.match(reg))
3956         text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+mycount+')');
3957       else if (mycount)
3958         text_obj.innerHTML += ' ('+mycount+')';
3959       else
3960         text_obj.innerHTML = text_obj.innerHTML.replace(reg, '');
3961
3962       // set parent's display
3963       reg = new RegExp(RegExp.escape(this.env.delimiter) + '[^' + RegExp.escape(this.env.delimiter) + ']+$');
3964       if (mbox.match(reg))
3965         this.set_unread_count_display(mbox.replace(reg, ''), false);
3966
3967       // set the right classes
3968       if ((mycount+childcount)>0)
3969         $(item).addClass('unread');
3970       else
3971         $(item).removeClass('unread');
3972       }
3973
3974     // set unread count to window title
3975     reg = /^\([0-9]+\)\s+/i;
3976     if (set_title && document.title)
3977       {
3978       var doc_title = String(document.title);
3979       var new_title = "";
3980
3981       if (mycount && doc_title.match(reg))
3982         new_title = doc_title.replace(reg, '('+mycount+') ');
3983       else if (mycount)
3984         new_title = '('+mycount+') '+doc_title;
3985       else
3986         new_title = doc_title.replace(reg, '');
3987         
3988       this.set_pagetitle(new_title);
3989       }
3990     };
3991
3992   // notifies that a new message(s) has hit the mailbox
3993   this.new_message_focus = function()
3994     {
3995     // focus main window
3996     if (this.env.framed && window.parent)
3997       window.parent.focus();
3998     else
3999       window.focus();
4000     }
4001
4002   this.toggle_prefer_html = function(checkbox)
4003     {
4004     var addrbook_show_images;
4005     if (addrbook_show_images = document.getElementById('rcmfd_addrbook_show_images'))
4006       addrbook_show_images.disabled = !checkbox.checked;
4007     }
4008
4009   // display fetched raw headers
4010   this.set_headers = function(content)
4011   {
4012     if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content) {
4013       $(this.gui_objects.all_headers_box).html(content).show();
4014
4015       if (this.env.framed && parent.rcmail)
4016         parent.rcmail.set_busy(false);
4017       else
4018         this.set_busy(false);
4019     }
4020   };
4021
4022   // display all-headers row and fetch raw message headers
4023   this.load_headers = function(elem)
4024     {
4025     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
4026       return;
4027     
4028     $(elem).removeClass('show-headers').addClass('hide-headers');
4029     $(this.gui_objects.all_headers_row).show();
4030     elem.onclick = function() { rcmail.hide_headers(elem); }
4031
4032     // fetch headers only once
4033     if (!this.gui_objects.all_headers_box.innerHTML)
4034       {
4035       this.display_message(this.get_label('loading'), 'loading', true);
4036       this.http_post('headers', '_uid='+this.env.uid);
4037       }
4038     }
4039
4040   // hide all-headers row
4041   this.hide_headers = function(elem)
4042     {
4043     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
4044       return;
4045
4046     $(elem).removeClass('hide-headers').addClass('show-headers');
4047     $(this.gui_objects.all_headers_row).hide();
4048     elem.onclick = function() { rcmail.load_headers(elem); }
4049     }
4050
4051   // percent (quota) indicator
4052   this.percent_indicator = function(obj, data)
4053     {
4054     if (!data || !obj)
4055       return false;
4056
4057     var limit_high = 80;
4058     var limit_mid  = 55;
4059     var width = data.width ? data.width : this.env.indicator_width ? this.env.indicator_width : 100;
4060     var height = data.height ? data.height : this.env.indicator_height ? this.env.indicator_height : 14;
4061     var quota = data.percent ? Math.abs(parseInt(data.percent)) : 0;
4062     var quota_width = parseInt(quota / 100 * width);
4063     var pos = $(obj).position();
4064
4065     this.env.indicator_width = width;
4066     this.env.indicator_height = height;
4067     
4068     // overlimit
4069     if (quota_width > width) {
4070       quota_width = width;
4071       quota = 100; 
4072       }
4073   
4074     // main div
4075     var main = $('<div>');
4076     main.css({position: 'absolute', top: pos.top, left: pos.left,
4077         width: width + 'px', height: height + 'px', zIndex: 100, lineHeight: height + 'px'})
4078         .attr('title', data.title).addClass('quota_text').html(quota + '%');
4079     // used bar
4080     var bar1 = $('<div>');
4081     bar1.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
4082         width: quota_width + 'px', height: height + 'px', zIndex: 99});
4083     // background
4084     var bar2 = $('<div>');
4085     bar2.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
4086         width: width + 'px', height: height + 'px', zIndex: 98})
4087         .addClass('quota_bg');
4088
4089     if (quota >= limit_high) {
4090       main.addClass(' quota_text_high');
4091       bar1.addClass('quota_high');
4092       }
4093     else if(quota >= limit_mid) {
4094       main.addClass(' quota_text_mid');
4095       bar1.addClass('quota_mid');
4096       }
4097     else {
4098       main.addClass(' quota_text_normal');
4099       bar1.addClass('quota_low');
4100       }
4101
4102     // replace quota image
4103     obj.innerHTML = '';
4104     $(obj).append(bar1).append(bar2).append(main);
4105     }
4106
4107   /********************************************************/
4108   /*********  html to text conversion functions   *********/
4109   /********************************************************/
4110
4111   this.html2plain = function(htmlText, id)
4112     {
4113     var url = this.env.bin_path+'html2text.php';
4114     var rcmail = this;
4115
4116     this.set_busy(true, 'converting');
4117     console.log('HTTP POST: '+url);
4118
4119     $.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
4120       error: function(o) { rcmail.http_error(o); },
4121       success: function(data) { rcmail.set_busy(false); $(document.getElementById(id)).val(data); console.log(data); }
4122       });
4123     }
4124
4125   this.plain2html = function(plainText, id)
4126     {
4127     this.set_busy(true, 'converting');
4128     $(document.getElementById(id)).val('<pre>'+plainText+'</pre>');
4129     this.set_busy(false);
4130     }
4131
4132
4133   /********************************************************/
4134   /*********        remote request methods        *********/
4135   /********************************************************/
4136
4137   this.redirect = function(url, lock)
4138     {
4139     if (lock || lock === null)
4140       this.set_busy(true);
4141
4142     if (this.env.framed && window.parent)
4143       parent.location.href = url;
4144     else  
4145       location.href = url;
4146     };
4147
4148   this.goto_url = function(action, query, lock)
4149     {
4150     var querystring = query ? '&'+query : '';
4151     this.redirect(this.env.comm_path+'&_action='+action+querystring, lock);
4152     };
4153
4154   // send a http request to the server
4155   this.http_request = function(action, querystring, lock)
4156   {
4157     querystring += (querystring ? '&' : '') + '_remote=1';
4158     var url = this.env.comm_path + '&_action=' + action + '&' + querystring
4159     
4160     // send request
4161     console.log('HTTP POST: ' + url);
4162     jQuery.get(url, { _unlock:(lock?1:0) }, function(data){ ref.http_response(data); }, 'json');
4163   };
4164
4165   // send a http POST request to the server
4166   this.http_post = function(action, postdata, lock)
4167   {
4168     var url = this.env.comm_path+'&_action=' + action;
4169     
4170     if (postdata && typeof(postdata) == 'object') {
4171       postdata._remote = 1;
4172       postdata._unlock = (lock ? 1 : 0);
4173     }
4174     else
4175       postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock=1' : '');
4176
4177     // send request
4178     console.log('HTTP POST: ' + url);
4179     jQuery.post(url, postdata, function(data){ ref.http_response(data); }, 'json');
4180   };
4181
4182   // handle HTTP response
4183   this.http_response = function(response)
4184   {
4185     var console_msg = '';
4186     
4187     if (response.unlock)
4188       this.set_busy(false);
4189
4190     // set env vars
4191     if (response.env)
4192       this.set_env(response.env);
4193
4194     // we have labels to add
4195     if (typeof response.texts == 'object') {
4196       for (var name in response.texts)
4197         if (typeof response.texts[name] == 'string')
4198           this.add_label(name, response.texts[name]);
4199     }
4200
4201     // if we get javascript code from server -> execute it
4202     if (response.exec) {
4203       console.log(response.exec);
4204       eval(response.exec);
4205     }
4206     
4207     // execute callback functions of plugins
4208     if (response.callbacks && response.callbacks.length) {
4209       for (var i=0; i < response.callbacks.length; i++)
4210         this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
4211     }
4212  
4213     // process the response data according to the sent action
4214     switch (response.action) {
4215       case 'delete':
4216         if (this.task == 'addressbook') {
4217           var uid = this.contact_list.get_selection();
4218           this.enable_command('compose', (uid && this.contact_list.rows[uid]));
4219           this.enable_command('delete', 'edit', (uid && this.contact_list.rows[uid] && this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
4220           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
4221         }
4222       
4223       case 'moveto':
4224         if (this.env.action == 'show') {
4225           // re-enable commands on move/delete error
4226           this.enable_command('reply', 'reply-all', 'forward', 'delete', 'mark', 'print', 'open', 'edit', 'viewsource', 'download', true);
4227         } else if (this.message_list)
4228           this.message_list.init();
4229         break;
4230         
4231       case 'purge':
4232       case 'expunge':
4233         if (!this.env.messagecount && this.task == 'mail') {
4234           // clear preview pane content
4235           if (this.env.contentframe)
4236             this.show_contentframe(false);
4237           // disable commands useless when mailbox is empty
4238           this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 
4239             'mark', 'viewsource', 'open', 'edit', 'download', 'print', 'load-attachment', 
4240             'purge', 'expunge', 'select-all', 'select-none', 'sort', false);
4241         }
4242         break;
4243
4244       case 'check-recent':
4245       case 'getunread':
4246       case 'list':
4247         if (this.task == 'mail') {
4248           if (this.message_list && response.action == 'list')
4249             this.msglist_select(this.message_list);
4250           this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
4251           this.enable_command('purge', this.purge_mailbox_test());
4252           
4253           if (response.action == 'list')
4254             this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
4255         }
4256         else if (this.task == 'addressbook') {
4257           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
4258           
4259           if (response.action == 'list')
4260             this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
4261         }
4262         break;
4263     }
4264   };
4265
4266   // handle HTTP request errors
4267   this.http_error = function(request, status, err)
4268     {
4269     var errmsg = request.statusText;
4270
4271     this.set_busy(false);
4272     request.abort();
4273     
4274     if (errmsg)
4275       this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
4276     };
4277
4278   // use an image to send a keep-alive siganl to the server
4279   this.send_keep_alive = function()
4280     {
4281     var d = new Date();
4282     this.http_request('keep-alive', '_t='+d.getTime());
4283     };
4284
4285   // send periodic request to check for recent messages
4286   this.check_for_recent = function(setbusy)
4287     {
4288     if (this.busy)
4289       return;
4290
4291     if (setbusy)
4292       this.set_busy(true, 'checkingmail');
4293
4294     var addurl = '_t=' + (new Date().getTime());
4295
4296     if (this.gui_objects.messagelist)
4297       addurl += '&_list=1';
4298     if (this.gui_objects.quotadisplay)
4299       addurl += '&_quota=1';
4300     if (this.env.search_request)
4301       addurl += '&_search=' + this.env.search_request;
4302
4303     this.http_request('check-recent', addurl, true);
4304     };
4305
4306
4307   /********************************************************/
4308   /*********            helper methods            *********/
4309   /********************************************************/
4310   
4311   // check if we're in show mode or if we have a unique selection
4312   // and return the message uid
4313   this.get_single_uid = function()
4314     {
4315     return this.env.uid ? this.env.uid : (this.message_list ? this.message_list.get_single_selection() : null);
4316     };
4317
4318   // same as above but for contacts
4319   this.get_single_cid = function()
4320     {
4321     return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null);
4322     };
4323
4324
4325   this.get_caret_pos = function(obj)
4326     {
4327     if (typeof(obj.selectionEnd)!='undefined')
4328       return obj.selectionEnd;
4329     else if (document.selection && document.selection.createRange)
4330       {
4331       var range = document.selection.createRange();
4332       if (range.parentElement()!=obj)
4333         return 0;
4334
4335       var gm = range.duplicate();
4336       if (obj.tagName=='TEXTAREA')
4337         gm.moveToElementText(obj);
4338       else
4339         gm.expand('textedit');
4340       
4341       gm.setEndPoint('EndToStart', range);
4342       var p = gm.text.length;
4343
4344       return p<=obj.value.length ? p : -1;
4345       }
4346     else
4347       return obj.value.length;
4348     };
4349
4350   this.set_caret_pos = function(obj, pos)
4351     {
4352     if (obj.setSelectionRange)
4353       obj.setSelectionRange(pos, pos);
4354     else if (obj.createTextRange)
4355       {
4356       var range = obj.createTextRange();
4357       range.collapse(true);
4358       range.moveEnd('character', pos);
4359       range.moveStart('character', pos);
4360       range.select();
4361       }
4362     }
4363
4364   // set all fields of a form disabled
4365   this.lock_form = function(form, lock)
4366     {
4367     if (!form || !form.elements)
4368       return;
4369     
4370     var type;
4371     for (var n=0; n<form.elements.length; n++)
4372       {
4373       type = form.elements[n];
4374       if (type=='hidden')
4375         continue;
4376         
4377       form.elements[n].disabled = lock;
4378       }
4379     };
4380     
4381 }  // end object rcube_webmail
4382
4383
4384 // copy event engine prototype
4385 rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
4386 rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
4387 rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;