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