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