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