thomascube
2006-09-24 6b47de3f49e880f08fa4a9226e70d18129095306
Partial client re-write with a common list class

1 files added
10 files modified
2151 ■■■■■ changed files
CHANGELOG 8 ●●●●● patch | view | raw | blame | history
program/include/main.inc 2 ●●● patch | view | raw | blame | history
program/js/app.js 1275 ●●●● patch | view | raw | blame | history
program/js/common.js 123 ●●●●● patch | view | raw | blame | history
program/js/list.js 704 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/edit.inc 2 ●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 1 ●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 22 ●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 1 ●●●● patch | view | raw | blame | history
program/steps/settings/identities.inc 1 ●●●● patch | view | raw | blame | history
skins/default/common.css 12 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,14 @@
CHANGELOG RoundCube Webmail
---------------------------
2006/09/24 (thomasb)
----------
- Partial client re-write with a common list class
- Re-enabled multi select of contacts (Bug #1484017)
- Enable contact editing right after creation (Bug #1459641)
- Updated Hungarian, Estonian and Traditional Chinese localization
2006/09/19 (thomasb)
----------
- Correct UTF-7 to UTF-8 conversion if mbstring is not available
program/include/main.inc
@@ -365,7 +365,7 @@
  
  // add common javascripts
  $javascript = "var $JS_OBJECT_NAME = new rcube_webmail();\n";
  $javascript .= "$JS_OBJECT_NAME.set_env('comm_path', '$COMM_PATH');\n";
  $javascript .= sprintf("%s.set_env('comm_path', '%s');\n", $JS_OBJECT_NAME, str_replace('&', '&', $COMM_PATH));
  if (isset($CONFIG['javascript_config'] )){
    foreach ($CONFIG['javascript_config'] as $js_config_var){
program/js/app.js
@@ -3,21 +3,19 @@
 | RoundCube Webmail Client Script                                       |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
 | Copyright (C) 2005-2006, RoundCube Dev, - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
 |          Charles McNulty <charles@charlesmcnulty.com>                 |
 +-----------------------------------------------------------------------+
 | Requires: common.js, list.js                                          |
 +-----------------------------------------------------------------------+
  $Id$
*/
// Constants
var CONTROL_KEY = 1;
var SHIFT_KEY = 2;
var CONTROL_SHIFT_KEY = 3;
var rcube_webmail_client;
@@ -28,9 +26,6 @@
  this.buttons = new Object();
  this.gui_objects = new Object();
  this.commands = new Object();
  this.selection = new Array();
  this.last_selected = 0;
  this.in_message_list = false;
  // create public reference to myself
  rcube_webmail_client = this;
@@ -93,12 +88,13 @@
  // initialize webmail client
  this.init = function()
    {
    var p = this;
    this.task = this.env.task;
    
    // check browser
    if (!bw.dom || !bw.xmlhttp_test())
      {
      location.href = this.env.comm_path+'&_action=error&_code=0x199';
      this.goto_url('error', '_code=0x199');
      return;
      }
    
@@ -116,13 +112,26 @@
    switch (this.task)
      {
      case 'mail':
        var msg_list_frame = this.gui_objects.mailcontframe;
        var msg_list = this.gui_objects.messagelist;
        if (msg_list)
        if (this.gui_objects.messagelist)
          {
          msg_list_frame.onmousedown = function(e){return rcube_webmail_client.click_on_list(e);};
          this.init_messagelist(msg_list);
          this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time});
          this.message_list.row_init = function(o){ p.init_message_row(o); };
          this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
          this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
          this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
          this.message_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
          this.message_list.addEventListener('dragend', function(o){ p.drag_active = false; });
          this.message_list.init();
          this.enable_command('toggle_status', true);
          if (this.gui_objects.mailcontframe)
            {
            this.gui_objects.mailcontframe.onmousedown = function(e){ return p.click_on_list(e); };
            document.onmouseup = function(e){ return p.doc_mouse_up(e); };
            }
          else
            this.message_list.focus();
          }
        // enable mail commands
@@ -183,26 +192,36 @@
      case 'addressbook':
        var contacts_list      = this.gui_objects.contactslist;
        var ldap_contacts_list = this.gui_objects.ldapcontactslist;
        if (this.gui_objects.contactslist)
          {
          this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:false, keyboard:true});
          this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
          this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
          this.contact_list.init();
        if (contacts_list)
          this.init_contactslist(contacts_list);
        if (ldap_contacts_list)
          this.init_ldapsearchlist(ldap_contacts_list);
          if (this.env.cid)
            this.contact_list.highlight_row(this.env.cid);
          if (this.gui_objects.contactslist.parentNode)
            {
            this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
            document.onmouseup = function(e){ return p.doc_mouse_up(e); };
            }
          else
            this.contact_list.focus();
          }
        this.set_page_buttons();
        if (this.env.cid)
          this.enable_command('show', 'edit', true);
        if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
          this.enable_command('save', true);
        this.enable_command('list', 'add', true);
        this.enable_command('ldappublicsearch', this.env.ldappublicsearch);
        // this.enable_command('ldappublicsearch', this.env.ldappublicsearch);
        break;
@@ -218,10 +237,17 @@
          
        if (this.env.action=='folders')
          this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);
        var identities_list = this.gui_objects.identitieslist;
        if (identities_list)
          this.init_identitieslist(identities_list);
        if (this.gui_objects.identitieslist)
          {
          this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false});
          this.identity_list.addEventListener('select', function(o){ p.identity_select(o); });
          this.identity_list.init();
          this.identity_list.focus();
          if (this.env.iid)
            this.identity_list.highlight_row(this.env.iid);
          }
        break;
@@ -244,13 +270,6 @@
    // enable basic commands
    this.enable_command('logout', true);
    // disable browser's contextmenus
    // document.oncontextmenu = function(){ return false; }
    // load body click event
    document.onmousedown = function(){ return rcube_webmail_client.reset_click(); };
    document.onkeydown   = function(e){ return rcube_webmail_client.key_pressed(e, msg_list_frame); };
    // flag object as complete
    this.loaded = true;
@@ -273,154 +292,25 @@
    }
  // reset last clicked if user clicks on anything other than the message table
  this.reset_click = function()
    {
    var id;
    this.in_message_list = false;
    for (var n=0; n<this.selection.length; n++)
      {
      id = this.selection[n];
      if (this.list_rows[id] && this.list_rows[id].obj)
        {
        this.set_classname(this.list_rows[id].obj, 'selected', false);
        this.set_classname(this.list_rows[id].obj, 'unfocused', true);
        }
      }
    };
  this.click_on_list = function(e)
    {
    if (!e)
      e = window.event;
    for (var n=0; n<this.selection.length; n++)
      {
      id = this.selection[n];
      if (this.list_rows[id].obj)
        {
        this.set_classname(this.list_rows[id].obj, 'selected', true);
        this.set_classname(this.list_rows[id].obj, 'unfocused', false);
        }
      }
    var mbox_li;
    if (mbox_li = this.get_mailbox_li())
      this.set_classname(mbox_li, 'unfocused', true);
    this.in_message_list = true;
    e.cancelBubble = true;
    };
  this.key_pressed = function(e, msg_list_frame) {
    if (this.in_message_list != true)
      return true;
    var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
    var mod_key = this.get_modifier(e);
    switch (keyCode) {
      case 13:
        this.command('show','',this);
        break;
      case 40:
      case 38:
        return this.use_arrow_key(keyCode, mod_key, msg_list_frame);
        break;
      case 46:
        return this.use_delete_key(keyCode, mod_key, msg_list_frame);
        break;
      default:
        return true;
    }
    return true;
  }
  this.use_arrow_key = function(keyCode, mod_key, msg_list_frame) {
    var scroll_to = 0;
    if (keyCode == 40) { // down arrow key pressed
      new_row = this.get_next_row();
      if (!new_row) return false;
      scroll_to = (Number(new_row.offsetTop) + Number(new_row.offsetHeight)) - Number(msg_list_frame.offsetHeight);
    } else if (keyCode == 38) { // up arrow key pressed
      new_row = this.get_prev_row();
      if (!new_row) return false;
      scroll_to = new_row.offsetTop;
    } else {return true;}
    this.select_row(new_row.uid,mod_key,true);
    if (((Number(new_row.offsetTop)) < (Number(msg_list_frame.scrollTop))) ||
       ((Number(new_row.offsetTop) + Number(new_row.offsetHeight)) > (Number(msg_list_frame.scrollTop) + Number(msg_list_frame.offsetHeight)))) {
      msg_list_frame.scrollTop = scroll_to;
    }
    return false;
  };
  this.use_delete_key = function(keyCode, mod_key, msg_list_frame){
    this.command('delete','',this);
    return false;
  }
  // get all message rows from HTML table and init each row
  this.init_messagelist = function(msg_list)
    {
    if (msg_list && msg_list.tBodies[0])
      {
      this.message_rows = new Array();
      var row;
      for(var r=0; r<msg_list.tBodies[0].childNodes.length; r++)
        {
        row = msg_list.tBodies[0].childNodes[r];
        while (row && (row.nodeType != 1 || row.style.display == 'none')) {
          row = row.nextSibling;
          r++;
        }
        //row = msg_list.tBodies[0].rows[r];
        if (row) this.init_message_row(row);
        }
      }
    // alias to common rows array
    this.list_rows = this.message_rows;
    };
  // make references in internal array and set event handlers
  this.init_message_row = function(row)
    {
    var uid, msg_icon;
    if (String(row.id).match(/rcmrow([0-9]+)/))
  {
    var uid = row.uid;
    if (uid && this.env.messages[uid])
      {
      uid = RegExp.$1;
      row.uid = uid;
      this.message_rows[uid] = {id:row.id, obj:row,
                                classname:row.className,
                                deleted:this.env.messages[uid] ? this.env.messages[uid].deleted : null,
                                unread:this.env.messages[uid] ? this.env.messages[uid].unread : null,
                                replied:this.env.messages[uid] ? this.env.messages[uid].replied : null};
      // set eventhandlers to table row
      row.onmousedown = function(e){ return rcube_webmail_client.drag_row(e, this.uid); };
      row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.uid); };
      if (document.all)
        row.onselectstart = function() { return false; };
      // set eventhandler to message icon
      if ((msg_icon = row.cells[0].childNodes[0]) && row.cells[0].childNodes[0].nodeName=='IMG')
        {
        msg_icon.id = 'msgicn_'+uid;
        msg_icon._row = row;
        msg_icon.onmousedown = function(e) { rcube_webmail_client.command('toggle_status', this); };
        // get message icon and save original icon src
        this.message_rows[uid].icon = msg_icon;
        }
      row.deleted = this.env.messages[uid].deleted;
      row.unread = this.env.messages[uid].unread;
      row.replied = this.env.messages[uid].replied;
      }
    };
    // set eventhandler to message icon
    if ((row.icon = row.obj.cells[0].childNodes[0]) && row.icon.nodeName=='IMG')
      {
      var p = this;
      row.icon.id = 'msgicn_'+row.uid;
      row.icon._row = row.obj;
      row.icon.onmousedown = function(e) { p.command('toggle_status', this); };
      }
  };
  // init message compose form: set focus and eventhandlers
@@ -455,7 +345,7 @@
    else if (input_subject && input_subject.value=='')
      input_subject.focus();
    else if (input_message)
      this.set_caret2start(input_message); // input_message.focus();
      this.set_caret2start(input_message);
    // get summary of all field values
    this.cmp_hash = this.compose_field_hash();
@@ -468,115 +358,20 @@
    {
    var handler = function(e){ return rcube_webmail_client.ksearch_keypress(e,this); };
    var handler2 = function(e){ return rcube_webmail_client.ksearch_blur(e,this); };
    if (bw.safari)
      {
      obj.addEventListener('keydown', handler, false);
      // obj.addEventListener('blur', handler2, false);
      }
    else if (bw.mz)
      {
      obj.addEventListener('keypress', handler, false);
      obj.addEventListener('blur', handler2, false);
      }
    else if (bw.ie)
      {
      obj.onkeydown = handler;
      //obj.attachEvent('onkeydown', handler);
      // obj.attachEvent('onblur', handler2, false);
      }
    obj.setAttribute('autocomplete', 'off');       
    };
  // get all contact rows from HTML table and init each row
  this.init_contactslist = function(contacts_list)
    {
    if (contacts_list && contacts_list.tBodies[0])
      {
      this.contact_rows = new Array();
      var row;
      for(var r=0; r<contacts_list.tBodies[0].childNodes.length; r++)
        {
        row = contacts_list.tBodies[0].childNodes[r];
        this.init_table_row(row, 'contact_rows');
        }
      }
    // alias to common rows array
    this.list_rows = this.contact_rows;
    if (this.env.cid)
      this.highlight_row(this.env.cid);
    };
  // get all contact rows from HTML table and init each row
  this.init_ldapsearchlist = function(ldap_contacts_list)
    {
    if (ldap_contacts_list && ldap_contacts_list.tBodies[0])
      {
      this.ldap_contact_rows = new Array();
      var row;
      for(var r=0; r<ldap_contacts_list.tBodies[0].childNodes.length; r++)
        {
        row = ldap_contacts_list.tBodies[0].childNodes[r];
        this.init_table_row(row, 'ldap_contact_rows');
        }
      }
    // alias to common rows array
    this.list_rows = this.ldap_contact_rows;
    };
  // make references in internal array and set event handlers
  this.init_table_row = function(row, array_name)
    {
    var cid;
    if (String(row.id).match(/rcmrow([0-9]+)/))
      {
      cid = RegExp.$1;
      row.cid = cid;
      this[array_name][cid] = {id:row.id,
                               obj:row,
                               classname:row.className};
      // set eventhandlers to table row
      row.onmousedown = function(e) { rcube_webmail_client.in_selection_before=this.cid; return false; };  // fake for drag handler
      row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.cid); };
      }
    };
  // get all contact rows from HTML table and init each row
  this.init_identitieslist = function(identities_list)
    {
    if (identities_list && identities_list.tBodies[0])
      {
      this.identity_rows = new Array();
      var row;
      for(var r=0; r<identities_list.tBodies[0].childNodes.length; r++)
        {
        row = identities_list.tBodies[0].childNodes[r];
        this.init_table_row(row, 'identity_rows');
        }
      }
    // alias to common rows array
    this.list_rows = this.identity_rows;
    if (this.env.iid)
      this.highlight_row(this.env.iid);
    };
  /*********************************************************/
@@ -621,7 +416,7 @@
        break;
      case 'logout':
        location.href = this.env.comm_path+'&_action=logout';
        this.goto_url('logout');
        break;      
      // commands to switch task
@@ -638,11 +433,6 @@
          {
          if (this.env.search_request<0 || (this.env.search_request && props != this.env.mailbox))
            this.reset_qsearch();
          // Reset message list header, unless returning from compose/read/etc
          // don't know what this is good for (thomasb, 2006/07/25)
          //if (this.env.mailbox != props && this.message_rows)
          //  this.clear_message_list_header();
          this.list_mailbox(props);
          }
@@ -711,11 +501,8 @@
          var uid = this.get_single_uid();
          if (uid && (!this.env.uid || uid != this.env.uid))
            {
            if (this.env.mailbox==this.env.drafts_mailbox)
              {
              this.set_busy(true);
              location.href = this.env.comm_path+'&_action=compose&_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
              }
            if (this.env.mailbox == this.env.drafts_mailbox)
              this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
            else
              this.show_message(uid);
            }
@@ -730,6 +517,9 @@
      case 'add':
        if (this.task=='addressbook')
          this.load_contact(0, 'add');
        /* LDAP stuff, has to be re-written with new address book
          if (!window.frames[this.env.contentframe].rcmail)
            this.load_contact(0, 'add');
          else
@@ -739,9 +529,10 @@
            else
              this.load_contact(0, 'add');
            }
        */
        else if (this.task=='settings')
          {
          this.clear_selection();
          this.identity_list.clear_selection();
          this.load_identity(0, 'add-identity');
          }
        break;
@@ -819,11 +610,11 @@
        if (props._row.uid)
          {
          uid = props._row.uid;
          this.dont_select = true;
          this.message_list.dont_select = true;
          // toggle read/unread
          if (this.message_rows[uid].deleted) {
              flag = 'undelete';
          } else if (!this.message_rows[uid].unread)
          if (this.message_list.rows[uid].deleted) {
            flag = 'undelete';
          } else if (!this.message_list.rows[uid].unread)
            flag = 'unread';
          }
          
@@ -836,12 +627,12 @@
        break;
      case 'load-attachment':
        var url = this.env.comm_path+'&_action=get&_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part;
        var qstring = '_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part;
        
        // open attachment in frame if it's of a supported mimetype
        if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0)
          {
          this.attachment_win = window.open(url+'&_frame=1', 'rcubemailattachment');
          this.attachment_win = window.open(this.env.comm_path+'&_action=get'+url+'&_frame=1', 'rcubemailattachment');
          if (this.attachment_win)
            {
            setTimeout(this.ref+'.attachment_win.focus()', 10);
@@ -849,27 +640,25 @@
            }
          }
        location.href = url;
        this.goto_url('get', qstring+'&_download=1');
        break;
        
      case 'select-all':
        this.select_all(props);
        this.message_list.select_all(props);
        break;
      case 'select-none':
        this.clear_selection();
        this.message_list.clear_selection();
        break;
      case 'nextmessage':
        if (this.env.next_uid)
          this.show_message(this.env.next_uid);
          //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.next_uid+'&_mbox='+this.env.mailbox;
        break;
      case 'previousmessage':
        if (this.env.prev_uid)
          this.show_message(this.env.prev_uid);
          //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.prev_uid+'&_mbox='+this.env.mailbox;
        break;
      
      case 'checkmail':
@@ -894,14 +683,18 @@
          // use contact_id passed as command parameter
          if (props)
            a_cids[a_cids.length] = props;
          // get selected contacts
          else
            {
            var selection = this.contact_list.get_selection();
            for (var n=0; n<selection.length; n++)
              a_cids[a_cids.length] = selection[n];
            /* LDAP stuff, has to be re-written with new address book
            if (!window.frames[this.env.contentframe].rcmail.selection.length)
              {
              for (var n=0; n<this.selection.length; n++)
                a_cids[a_cids.length] = this.selection[n];
              for (var n=0; n<selection.length; n++)
                a_cids[a_cids.length] = selection[n];
              }
            else
              {
@@ -910,6 +703,7 @@
              for (var n=0; n<frameRcmail.selection.length; n++)
                a_cids[a_cids.length] = frameRcmail.ldap_contact_rows[frameRcmail.selection[n]].obj.cells[1].innerHTML;
              }
            */
            }
          if (a_cids.length)
            url += '&_to='+a_cids.join(',');
@@ -918,12 +712,13 @@
            
          }
        else if (props)
           url += '&_to='+encodeURIComponent(props);
           url += '&_to='+urlencode(props);
        // don't know if this is necessary...
        url = url.replace(/&_framed=1/, "");
        this.set_busy(true);
        alert(url)
        // need parent in case we are coming from the contact frame
        if (this.env.framed)
@@ -997,19 +792,13 @@
      case 'reply':
        var uid;
        if (uid = this.get_single_uid())
          {
          this.set_busy(true);
          location.href = this.env.comm_path+'&_action=compose&_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(command=='reply-all' ? '&_all=1' : '');
          }
          this.goto_url('compose', '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(command=='reply-all' ? '&_all=1' : ''), true);
        break;      
      case 'forward':
        var uid;
        if (uid = this.get_single_uid())
          {
          this.set_busy(true);
          location.href = this.env.comm_path+'&_action=compose&_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
          }
          this.goto_url('compose', '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
        break;
        
      case 'print':
@@ -1064,18 +853,18 @@
      // user settings commands
      case 'preferences':
        location.href = this.env.comm_path;
        this.goto_url('');
        break;
      case 'identities':
        location.href = this.env.comm_path+'&_action=identities';
        this.goto_url('identities');
        break;
          
      case 'delete-identity':
        this.delete_identity();
        
      case 'folders':
        location.href = this.env.comm_path+'&_action=folders';
        this.goto_url('folders');
        break;
      case 'subscribe':
@@ -1201,6 +990,15 @@
  /*********************************************************/
  this.doc_mouse_up = function(e)
    {
    if (this.message_list)
      this.message_list.blur();
    else if (this.contact_list)
      this.contact_list.blur();
    };
  // onmouseup handler for mailboxlist item
  this.mbox_mouse_up = function(mbox)
    {
@@ -1216,121 +1014,53 @@
    };
  // onmousedown-handler of message list row
  this.drag_row = function(e, id)
  this.click_on_list = function(e)
    {
    this.in_selection_before = this.in_selection(id) ? id : false;
    if (this.message_list)
      this.message_list.focus();
    else if (this.contact_list)
        this.contact_list.focus();
    // don't do anything (another action processed before)
    if (this.dont_select)
      return false;
    var mbox_li;
    if (mbox_li = this.get_mailbox_li())
      this.set_classname(mbox_li, 'unfocused', true);
    // selects currently unselected row
    if (!this.in_selection_before && !this.list_rows[id].clicked)
    {
      var mod_key = this.get_modifier(e);
      this.select_row(id,mod_key,false);
    }
    if (this.selection.length)
      {
      this.drag_start = true;
      document.onmousemove = function(e){ return rcube_webmail_client.drag_mouse_move(e); };
      document.onmouseup = function(e){ return rcube_webmail_client.drag_mouse_up(e); };
      }
    return false;
    rcube_event.cancel(e);
    };
  // onmouseup-handler of message list row
  this.click_row = function(e, id)
  this.msglist_select = function(list)
    {
    var mod_key = this.get_modifier(e);
    // don't do anything (another action processed before)
    if (this.dont_select)
    var selected = list.selection.length==1;
    if (this.env.mailbox == this.env.drafts_mailbox)
      {
      this.dont_select = false;
      return false;
      this.enable_command('show', selected);
      this.enable_command('delete', 'moveto', list.selection.length>0 ? true : false);
      }
    // unselects currently selected row
    if (!this.drag_active && this.in_selection_before==id && !this.list_rows[id].clicked)
      this.select_row(id,mod_key,false);
    this.drag_start = false;
    this.in_selection_before = false;
    // row was double clicked
    if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && this.in_selection(id))
    else
      {
      if (this.env.mailbox==this.env.drafts_mailbox)
        {
        this.set_busy(true);
        location.href = this.env.comm_path+'&_action=compose&_draft_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
        }
      else
        {
        this.show_message(id);
        }
      return false;
      this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', selected);
      this.enable_command('delete', 'moveto', list.selection.length>0 ? true : false);
      }
    else if (this.task=='addressbook')
      {
      if (this.contact_rows && this.selection.length==1)
        {
        this.load_contact(this.selection[0], 'show', true);
        // change the text for the add contact button
        var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
        for (i = 0; i < links.length; i++)
          {
          var onclickstring = new String(links[i].onclick);
          if (onclickstring.search('\"add\"') != -1)
            links[i].title = this.env.newcontact;
          }
        }
      else if (this.contact_rows && this.contact_rows[id].clicked)
        {
        this.load_contact(id, 'show');
        return false;
        }
      else if (this.ldap_contact_rows && !this.ldap_contact_rows[id].clicked)
        {
        // clear selection
        parent.rcmail.clear_selection();
   };
        // disable delete
        parent.rcmail.set_button('delete', 'pas');
        // change the text for the add contact button
        var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
        for (i = 0; i < links.length; i++)
          {
          var onclickstring = new String(links[i].onclick);
          if (onclickstring.search('\"add\"') != -1)
            links[i].title = this.env.addcontact;
          }
        }
      // handle double click event
      else if (this.ldap_contact_rows && this.selection.length==1 && this.ldap_contact_rows[id].clicked)
        this.command('compose', this.ldap_contact_rows[id].obj.cells[1].innerHTML);
      else if (this.env.contentframe)
        {
        var elm = document.getElementById(this.env.contentframe);
        elm.style.visibility = 'hidden';
        }
      }
    else if (this.task=='settings')
      {
      if (this.selection.length==1)
        this.command('edit', this.selection[0]);
      }
  this.msglist_dbl_click = function(list)
    {
    var uid = list.get_single_selection();
    if (uid && this.env.mailbox == this.env.drafts_mailbox)
      this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
    else if (uid)
      this.show_message(uid);
    };
    this.list_rows[id].clicked = true;
    setTimeout(this.ref+'.list_rows['+id+'].clicked=false;', this.dblclick_time);
    return false;
  this.msglist_keypress = function(list)
    {
    if (list.key_pressed == list.ENTER_KEY)
      this.command('show');
    else if (list.key_pressed == list.DELETE_KEY)
      this.command('delete');
    };
@@ -1339,177 +1069,6 @@
  /*********     (message) list functionality      *********/
  /*********************************************************/
  // get next and previous rows that are not hidden
  this.get_next_row = function(){
      if (!this.list_rows) return false;
    var last_selected_row = this.list_rows[this.last_selected];
    var new_row = last_selected_row.obj.nextSibling;
    while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) {
      new_row = new_row.nextSibling;
    }
    return new_row;
  }
  this.get_prev_row = function(){
    if (!this.list_rows) return false;
    var last_selected_row = this.list_rows[this.last_selected];
    var new_row = last_selected_row.obj.previousSibling;
    while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) {
      new_row = new_row.previousSibling;
    }
    return new_row;
  }
  // highlight/unhighlight a row
  this.highlight_row = function(id, multiple)
    {
    var selected = false
    if (this.list_rows[id] && !multiple)
      {
      this.clear_selection();
      this.selection[0] = id;
      this.list_rows[id].obj.className += ' selected';
      selected = true;
      }
    else if (this.list_rows[id])
      {
      if (!this.in_selection(id))  // select row
        {
        this.selection[this.selection.length] = id;
        this.set_classname(this.list_rows[id].obj, 'selected', true);
        }
      else  // unselect row
        {
        var p = find_in_array(id, this.selection);
        var a_pre = this.selection.slice(0, p);
        var a_post = this.selection.slice(p+1, this.selection.length);
        this.selection = a_pre.concat(a_post);
        this.set_classname(this.list_rows[id].obj, 'selected', false);
        this.set_classname(this.list_rows[id].obj, 'unfocused', false);
        }
      selected = (this.selection.length==1);
      }
    // enable/disable commands for message
    if (this.task=='mail')
      {
      if (this.env.mailbox==this.env.drafts_mailbox)
    {
    this.enable_command('show', selected);
    this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
        }
      else
        {
        this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', selected);
        this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
        }
      }
    else if (this.task=='addressbook')
      {
      this.enable_command('edit', /*'print',*/ selected);
      this.enable_command('delete', 'compose', this.selection.length>0 ? true : false);
      }
    };
// selects or unselects the proper row depending on the modifier key pressed
  this.select_row = function(id,mod_key,with_mouse)  {
      if (!mod_key) {
      this.shift_start = id;
        this.highlight_row(id, false);
    } else {
      switch (mod_key) {
        case SHIFT_KEY: {
          this.shift_select(id,false);
          break; }
        case CONTROL_KEY: {
          this.shift_start = id;
          if (!with_mouse)
            this.highlight_row(id, true);
          break;
          }
        case CONTROL_SHIFT_KEY: {
          this.shift_select(id,true);
          break;
          }
        default: {
          this.highlight_row(id, false);
          break;
          }
      }
    }
    if (this.last_selected != 0 && this.list_rows[this.last_selected])
      this.set_classname(this.list_rows[this.last_selected].obj, 'focused', false);
    this.last_selected = id;
    this.set_classname(this.list_rows[id].obj, 'focused', true);
  };
  this.shift_select = function(id, control) {
    var from_rowIndex = this.list_rows[this.shift_start].obj.rowIndex;
    var to_rowIndex = this.list_rows[id].obj.rowIndex;
    var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
    var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
    // iterate through the entire message list
    for (var n in this.list_rows) {
      if ((this.list_rows[n].obj.rowIndex >= i) && (this.list_rows[n].obj.rowIndex <= j)) {
        if (!this.in_selection(n))
          this.highlight_row(n, true);
      } else {
        if  (this.in_selection(n) && !control)
          this.highlight_row(n, true);
      }
    }
  };
  this.clear_selection = function()
    {
    for(var n=0; n<this.selection.length; n++)
      if (this.list_rows[this.selection[n]]) {
        this.set_classname(this.list_rows[this.selection[n]].obj, 'selected', false);
        this.set_classname(this.list_rows[this.selection[n]].obj, 'unfocused', false);
      }
    this.selection = new Array();
    };
  // check if given id is part of the current selection
  this.in_selection = function(id)
    {
    for(var n in this.selection)
      if (this.selection[n]==id)
        return true;
    return false;
    };
  // select each row in list
  this.select_all = function(filter)
    {
    if (!this.list_rows || !this.list_rows.length)
      return false;
    // reset selection first
    this.clear_selection();
    for (var n in this.list_rows)
      {
      if (!filter || this.list_rows[n][filter]==true)
        {
        this.last_selected = n;
        this.highlight_row(n, true);
        }
      }
    return true;
    };
  // when user doble-clicks on a row
  this.show_message = function(id, safe)
@@ -1521,7 +1080,7 @@
      target = window.frames[this.env.contentframe];
      add_url = '&_framed=1';
      }
    if (safe)
      add_url = '&_safe=1';
@@ -1574,7 +1133,7 @@
      page = 1;
      add_url += '&_refresh=1';
      this.env.current_page = page;
      this.clear_selection();
      this.message_list.clear_selection();
      }
    
    // also send search request to get the right messages
@@ -1609,44 +1168,12 @@
  this.list_mailbox_remote = function(mbox, page, add_url)
    {
    // clear message list first
    this.clear_message_list();
    this.message_list.clear();
    // send request to server
    var url = '_mbox='+urlencode(mbox)+(page ? '&_page='+page : '');
    this.set_busy(true, 'loading');
    this.http_request('list', url+add_url, true);
    };
  this.clear_message_list = function()
    {
    var table = this.gui_objects.messagelist;
    var tbody = document.createElement('TBODY');
    table.insertBefore(tbody, table.tBodies[0]);
    table.removeChild(table.tBodies[1]);
    this.message_rows = new Array();
    this.list_rows = this.message_rows;
    };
  this.clear_message_list_header = function()
    {
    var table;
    if (table = this.gui_objects.messagelist)
      {
      if (table.colgroup)
        table.removeChild(table.colgroup);
      if (table.tHead)
        table.removeChild(table.tHead);
      var colgroup = document.createElement('COLGROUP');
      var thead = document.createElement('THEAD');
      table.insertBefore(colgroup, table.tBodies[0]);
      table.insertBefore(thead, table.tBodies[0]);
      }
    };
@@ -1694,14 +1221,14 @@
  this.focus_mailbox = function(mbox)
    {
    var mbox_li;
      if (this.drag_active && mbox != this.env.mailbox && (mbox_li = this.get_mailbox_li(mbox)))
    if (this.drag_active && mbox != this.env.mailbox && (mbox_li = this.get_mailbox_li(mbox)))
      this.set_classname(mbox_li, 'droptarget', true);
    }
    
  this.unfocus_mailbox = function(mbox)
    {
    var mbox_li;
      if (this.drag_active && (mbox_li = this.get_mailbox_li(mbox)))
    if (this.drag_active && (mbox_li = this.get_mailbox_li(mbox)))
      this.set_classname(mbox_li, 'droptarget', false);
    }
  
@@ -1709,7 +1236,8 @@
  this.move_messages = function(mbox)
    {
    // exit if no mailbox specified or if selection is empty
    if (!mbox || !(this.selection.length || this.env.uid) || mbox==this.env.mailbox)
    var selection = this.message_list.get_selection();
    if (!mbox || !(selection.length || this.env.uid) || mbox==this.env.mailbox)
      return;
    
    var a_uids = new Array();
@@ -1719,19 +1247,14 @@
    else
      {
      var id;
      for (var n=0; n<this.selection.length; n++)
      for (var n=0; n<selection.length; n++)
        {
        id = this.selection[n];
        id = selection[n];
        a_uids[a_uids.length] = id;
        // 'remove' message row from list (just hide it)
        if (this.message_rows[id].obj)
          this.message_rows[id].obj.style.display = 'none';
        this.message_list.remove_row(id);
        }
      next_row = this.get_next_row();
      prev_row = this.get_prev_row();
      new_row = (next_row) ? next_row : prev_row;
      if (new_row) this.select_row(new_row.uid,false,false);
      this.message_list.select_next();
      }
      
    var lock = false;
@@ -1742,13 +1265,16 @@
      lock = true;
      this.set_busy(true, 'movingmessage');
      }
    // send request to server
    this.http_request('moveto', '_uid='+a_uids.join(',')+'&_mbox='+urlencode(this.env.mailbox)+'&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : ''), lock);
    };
  this.permanently_remove_messages = function() {
  this.permanently_remove_messages = function()
    {
    // exit if no mailbox specified or if selection is empty
    if (!(this.selection.length || this.env.uid))
    var selection = this.message_list.get_selection();
    if (!(selection.length || this.env.uid))
      return;
    
    var a_uids = new Array();
@@ -1758,21 +1284,16 @@
    else
      {
      var id;
      for (var n=0; n<this.selection.length; n++)
      for (var n=0; n<selection.length; n++)
        {
        id = this.selection[n];
        id = selection[n];
        a_uids[a_uids.length] = id;
        // 'remove' message row from list (just hide it)
        if (this.message_rows[id].obj)
          this.message_rows[id].obj.style.display = 'none';
        this.message_list.remove_row(id);
        }
      }
      next_row = this.get_next_row();
      prev_row = this.get_prev_row();
      new_row = (next_row) ? next_row : prev_row;
      if (new_row) this.select_row(new_row.uid,false,false);
      this.message_list.select_next();
      }
    // send request to server
    this.http_request('delete', '_uid='+a_uids.join(',')+'&_mbox='+urlencode(this.env.mailbox)+'&_from='+(this.env.action ? this.env.action : ''));
  }
@@ -1782,8 +1303,10 @@
  this.delete_messages = function()
    {
    // exit if no mailbox specified or if selection is empty
    if (!(this.selection.length || this.env.uid))
    var selection = this.message_list.get_selection();
    if (!(selection.length || this.env.uid))
      return;
    // if there is a trash mailbox defined and we're not currently in it:
    if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase()!=String(this.env.trash_mailbox).toLowerCase())
      this.move_messages(this.env.trash_mailbox);
@@ -1791,22 +1314,18 @@
    else if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase())
      this.permanently_remove_messages();
    // if there isn't a defined trash mailbox and the config is set to flag for deletion
    else if (!this.env.trash_mailbox && this.env.flag_for_deletion) {
    else if (!this.env.trash_mailbox && this.env.flag_for_deletion)
      {
      flag = 'delete';
      this.mark_message(flag);
      if(this.env.action=="show"){
      if(this.env.action=="show")
        this.command('nextmessage','',this);
      } else if (this.selection.length == 1) {
        next_row = this.get_next_row();
        prev_row = this.get_prev_row();
        new_row = (next_row) ? next_row : prev_row;
        if (new_row) this.select_row(new_row.uid,false,false);
      else if (selection.length == 1)
        this.message_list.select_next();
      }
    // if there isn't a defined trash mailbox and the config is set NOT to flag for deletion
    }else if (!this.env.trash_mailbox && !this.env.flag_for_deletion) {
    else if (!this.env.trash_mailbox)
      this.permanently_remove_messages();
    }
    return;
  };
@@ -1814,6 +1333,7 @@
  this.mark_message = function(flag, uid)
    {
    var a_uids = new Array();
    var selection = this.message_list.get_selection();
    
    if (uid)
      a_uids[0] = uid;
@@ -1822,16 +1342,18 @@
    else
      {
      var id;
      for (var n=0; n<this.selection.length; n++)
      for (var n=0; n<selection.length; n++)
        {
        id = this.selection[n];
        id = selection[n];
        a_uids[a_uids.length] = id;
        }
      }
      switch (flag) {
    switch (flag)
      {
        case 'read':
        case 'unread':
          this.toggle_read_status(flag,a_uids);
          this.toggle_read_status(flag, a_uids);
          break;
        case 'delete':
        case 'undelete':
@@ -1841,66 +1363,70 @@
    };
  // set class to read/unread
  this.toggle_read_status = function(flag, a_uids) {
  this.toggle_read_status = function(flag, a_uids)
  {
    // mark all message rows as read/unread
    var icn_src;
    var rows = this.message_list.rows;
    for (var i=0; i<a_uids.length; i++)
      {
      uid = a_uids[i];
      if (this.message_rows[uid])
      if (rows[uid])
        {
        this.message_rows[uid].unread = (flag=='unread' ? true : false);
        rows[uid].unread = (flag=='unread' ? true : false);
        
        if (this.message_rows[uid].classname.indexOf('unread')<0 && this.message_rows[uid].unread)
        if (rows[uid].classname.indexOf('unread')<0 && rows[uid].unread)
          {
          this.message_rows[uid].classname += ' unread';
          this.set_classname(this.message_rows[uid].obj, 'unread', true);
          rows[uid].classname += ' unread';
          this.set_classname(rows[uid].obj, 'unread', true);
          if (this.env.unreadicon)
            icn_src = this.env.unreadicon;
          }
        else if (!this.message_rows[uid].unread)
        else if (!rows[uid].unread)
          {
          this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*unread/, '');
          this.set_classname(this.message_rows[uid].obj, 'unread', false);
          rows[uid].classname = rows[uid].classname.replace(/\s*unread/, '');
          this.set_classname(rows[uid].obj, 'unread', false);
          if (this.message_rows[uid].replied && this.env.repliedicon)
          if (rows[uid].replied && this.env.repliedicon)
            icn_src = this.env.repliedicon;
          else if (this.env.messageicon)
            icn_src = this.env.messageicon;
          }
        if (this.message_rows[uid].icon && icn_src)
          this.message_rows[uid].icon.src = icn_src;
        if (rows[uid].icon && icn_src)
          rows[uid].icon.src = icn_src;
        }
      }
      this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
  }
    this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
  };
  
  // mark all message rows as deleted/undeleted
  this.toggle_delete_status = function(a_uids) {
    if (this.env.read_when_deleted) {
  this.toggle_delete_status = function(a_uids)
  {
    if (this.env.read_when_deleted)
      this.toggle_read_status('read',a_uids);
    }
    // if deleting message from "view message" don't bother with delete icon
    if (this.env.action == "show")
      return false;
    if (a_uids.length==1){
      if(this.message_rows[a_uids[0]].classname.indexOf('deleted') < 0 ){
          this.flag_as_deleted(a_uids)
      } else {
          this.flag_as_undeleted(a_uids)
      }
    var rows = this.message_list.rows;
    if (a_uids.length==1) {
      if (rows[a_uids[0]] && rows[a_uids[0]].classname.indexOf('deleted') < 0)
        this.flag_as_deleted(a_uids);
      else
        this.flag_as_undeleted(a_uids);
      return true;
    }
    
    var all_deleted = true;
    for (var i=0; i<a_uids.length; i++) {
      uid = a_uids[i];
      if (this.message_rows[uid]) {
        if (this.message_rows[uid].classname.indexOf('deleted')<0) {
      if (rows[uid]) {
        if (rows[uid].classname.indexOf('deleted')<0) {
          all_deleted = false;
          break;
        }
@@ -1913,59 +1439,67 @@
      this.flag_as_deleted(a_uids);
    
    return true;
  }
  };
  this.flag_as_undeleted = function(a_uids){
  this.flag_as_undeleted = function(a_uids)
  {
    // if deleting message from "view message" don't bother with delete icon
    if (this.env.action == "show")
      return false;
    var icn_src;
    var rows = this.message_list.rows;
      
    for (var i=0; i<a_uids.length; i++) {
      uid = a_uids[i];
      if (this.message_rows[uid]) {
        this.message_rows[uid].deleted = false;
      if (rows[uid]) {
        rows[uid].deleted = false;
        
        if (this.message_rows[uid].classname.indexOf('deleted') > 0) {
          this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*deleted/, '');
          this.set_classname(this.message_rows[uid].obj, 'deleted', false);
        if (rows[uid].classname.indexOf('deleted') > 0) {
          rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, '');
          this.set_classname(rows[uid].obj, 'deleted', false);
        }
        if (this.message_rows[uid].unread && this.env.unreadicon)
        if (rows[uid].unread && this.env.unreadicon)
          icn_src = this.env.unreadicon;
        else if (this.message_rows[uid].replied && this.env.repliedicon)
        else if (rows[uid].replied && this.env.repliedicon)
          icn_src = this.env.repliedicon;
        else if (this.env.messageicon)
          icn_src = this.env.messageicon;
        if (this.message_rows[uid].icon && icn_src)
          this.message_rows[uid].icon.src = icn_src;
        if (rows[uid].icon && icn_src)
          rows[uid].icon.src = icn_src;
      }
    }
    this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=undelete');
    return true;
  }
  };
  
  this.flag_as_deleted = function(a_uids) {
  this.flag_as_deleted = function(a_uids)
  {
    // if deleting message from "view message" don't bother with delete icon
    if (this.env.action == "show")
      return false;
    var rows = this.message_list.rows;
    for (var i=0; i<a_uids.length; i++) {
      uid = a_uids[i];
      if (this.message_rows[uid]) {
        this.message_rows[uid].deleted = true;
      if (rows[uid]) {
        rows[uid].deleted = true;
        
        if (this.message_rows[uid].classname.indexOf('deleted')<0) {
          this.message_rows[uid].classname += ' deleted';
          this.set_classname(this.message_rows[uid].obj, 'deleted', true);
        if (rows[uid].classname.indexOf('deleted')<0) {
          rows[uid].classname += ' deleted';
          this.set_classname(rows[uid].obj, 'deleted', true);
        }
        if (this.message_rows[uid].icon && this.env.deletedicon)
          this.message_rows[uid].icon.src = this.env.deletedicon;
        if (rows[uid].icon && this.env.deletedicon)
          rows[uid].icon.src = this.env.deletedicon;
      }
    }
    this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=delete');
    return true;  
  }
  };
  this.get_mailbox_li = function(mbox)
@@ -2034,9 +1568,9 @@
  this.set_spellcheck_state = function(s)
    {
    this.spellcheck_ready = (s=='check_spelling' || s=='ready');
  this.spellcheck_ready = (s=='check_spelling' || s=='ready');
    this.enable_command('spellcheck', this.spellcheck_ready);
    };
  };
  this.auto_save_start = function()
@@ -2272,7 +1806,7 @@
    {
    if (value && mbox)
      {
      this.clear_message_list();
      this.message_list.clear();
      this.set_busy(true, 'searching');
      this.http_request('search', '_search='+value+'&_mbox='+mbox, true);
      }
@@ -2527,6 +2061,30 @@
  /*********************************************************/
  this.contactlist_keypress = function(list)
    {
      if (list.key_pressed == list.DELETE_KEY)
        this.command('delete');
    };
  this.contactlist_select = function(list)
    {
      var id, frame;
      if (id = list.get_single_selection())
        this.load_contact(id, 'show');
      else if (frame = document.getElementById(this.env.contentframe))
        frame.style.visibility = 'hidden';
      this.enable_command('edit', id?true:false);
      if (list.selection.length)
        this.enable_command('delete', 'compose', true);
      return false;
    };
  this.list_contacts = function(page)
    {
    var add_url = '';
@@ -2549,21 +2107,15 @@
      }
    this.set_busy(true, 'loading');
    location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
    target.location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
    };
  // send remote request to load contacts list
  this.list_contacts_remote = function(page)
    {
    // clear list
    var table = this.gui_objects.contactslist;
    var tbody = document.createElement('TBODY');
    table.insertBefore(tbody, table.tBodies[0]);
    table.tBodies[1].style.display = 'none';
    this.contact_rows = new Array();
    this.list_rows = this.contact_rows;
    // clear message list first
    this.contact_list.clear();
    // send request to server
    var url = page ? '&_page='+page : '';
@@ -2585,11 +2137,7 @@
      }
    else if (framed)
      return false;
    //if (this.env.framed && add_url=='')
    //  add_url = '&_framed=1';
    if (action && (cid || action=='add'))
      {
      this.set_busy(true);
@@ -2602,7 +2150,8 @@
  this.delete_contacts = function()
    {
    // exit if no mailbox specified or if selection is empty
    if (!(this.selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
    var selection = this.contact_list.get_selection();
    if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
      return;
      
    var a_cids = new Array();
@@ -2612,18 +2161,15 @@
    else
      {
      var id;
      for (var n=0; n<this.selection.length; n++)
      for (var n=0; n<selection.length; n++)
        {
        id = this.selection[n];
        id = selection[n];
        a_cids[a_cids.length] = id;
        // 'remove' row from list (just hide it)
        if (this.contact_rows[id].obj)
          this.contact_rows[id].obj.style.display = 'none';
        this.contact_list.remove_row(id);
        }
      // hide content frame if we delete the currently displayed contact
      if (this.selection.length==1 && this.env.contentframe)
      if (selection.length==1 && this.env.contentframe)
        {
        var elm = document.getElementById(this.env.contentframe);
        elm.style.visibility = 'hidden';
@@ -2639,19 +2185,21 @@
  // update a contact record in the list
  this.update_contact_row = function(cid, cols_arr)
    {
    if (!this.contact_rows[cid] || !this.contact_rows[cid].obj)
      return false;
    var row = this.contact_rows[cid].obj;
    for (var c=0; c<cols_arr.length; c++){
      if (row.cells[c])
        row.cells[c].innerHTML = cols_arr[c];
    }
    return true;
    var row;
    if (this.contact_list.rows[cid] && (row = this.contact_list.rows[cid].obj))
      {
      for (var c=0; c<cols_arr.length; c++)
        if (row.cells[c])
          row.cells[c].innerHTML = cols_arr[c];
      return true;
      }
    return false;
    };
  // load ldap search form
  // deprecated
  this.ldappublicsearch = function(action)
    {
    var add_url = '';
@@ -2663,8 +2211,7 @@
      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
      }
    else
      return false;
      return false;
    if (action == 'ldappublicsearch')
      target.location.href = this.env.comm_path+'&_action='+action+add_url;
@@ -2695,13 +2242,18 @@
      }
    return false;
    }
  /*********************************************************/
  /*********        user settings methods          *********/
  /*********************************************************/
  this.identity_select = function(list)
    {
    var id;
    if (id = list.get_single_selection())
      this.load_identity(id, 'edit-identity');
    };
  // load contact record
  this.load_identity = function(id, action)
@@ -2731,11 +2283,12 @@
  this.delete_identity = function(id)
    {
    // exit if no mailbox specified or if selection is empty
    if (!(this.selection.length || this.env.iid))
    var selection = this.identity_list.get_selection();
    if (!(selection.length || this.env.iid))
      return;
    
    if (!id)
      id = this.env.iid ? this.env.iid : this.selection[0];
      id = this.env.iid ? this.env.iid : selection[0];
/*
    // 'remove' row from list (just hide it)
@@ -2747,10 +2300,7 @@
*/
    // if (this.env.framed && id)
      this.set_busy(true);
      location.href = this.env.comm_path+'&_action=delete-identity&_iid='+id;
    // else if (id)
    //  this.http_request('delete-identity', '_iid='+id);
    this.goto_url('delete-identity', '_iid='+id, true);
    return true;
    };
@@ -2758,8 +2308,8 @@
  // tell server to create and subscribe a new mailbox
  this.create_folder = function(name)
    {
    if (this.edit_folder)
      this.reset_folder_rename();
  if (this.edit_folder)
    this.reset_folder_rename();
    var form;
    if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
@@ -2801,12 +2351,12 @@
    var id = this.get_folder_row_id(folder);
    // reset current renaming
    if (temp = this.edit_folder)
      {
      this.reset_folder_rename();
      if (temp == id)
        return;
      }
  if (temp = this.edit_folder)
    {
    this.reset_folder_rename();
    if (temp == id)
      return;
    }
    if (id && (row = document.getElementById(id)))
      {
@@ -2857,8 +2407,8 @@
  // delete a specific mailbox with all its messages
  this.delete_folder = function(folder)
    {
    if (this.edit_folder)
      this.reset_folder_rename();
  if (this.edit_folder)
    this.reset_folder_rename();
    
    if (folder)
      this.http_request('delete-folder', '_mboxes='+urlencode(folder));
@@ -2883,7 +2433,7 @@
    if (!id || !(refrow = document.getElementById(refid)))
      {
      // Refresh page if we don't have a table row to clone
      location.href = this.env.comm_path+'&_action=folders';
      this.goto_url('folders');
      }
    else
      {
@@ -3271,7 +2821,7 @@
        this.set_classname(mbox_li, 'selected', true);
        }
      }
    // also update mailbox name in window title
    if (document.title)
      {
@@ -3317,9 +2867,9 @@
  // create a table row in the message list
  this.add_message_row = function(uid, cols, flags, attachment, attop)
    {
    if (!this.gui_objects.messagelist || !this.gui_objects.messagelist.tBodies[0])
    if (!this.gui_objects.messagelist || !this.message_list)
      return false;
    var tbody = this.gui_objects.messagelist.tBodies[0];
    var rowcount = tbody.rows.length;
    var even = rowcount%2;
@@ -3331,8 +2881,8 @@
    var row = document.createElement('TR');
    row.id = 'rcmrow'+uid;
    row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : '')+(flags.deleted ? ' deleted' : '');
    if (this.in_selection(uid))
    if (this.message_list.in_selection(uid))
      row.className += ' selected';
    var icon = flags.deleted && this.env.deletedicon ? this.env.deletedicon:
@@ -3358,13 +2908,8 @@
    col.className = 'icon';
    col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" border="0" />' : '';
    row.appendChild(col);
    if (attop && tbody.rows.length)
      tbody.insertBefore(row, tbody.firstChild);
    else
      tbody.appendChild(row);
    this.init_message_row(row);
    this.message_list.insert_row(row, attop);
    };
@@ -3384,7 +2929,7 @@
     if (this.gui_objects.quotadisplay)
       this.gui_objects.quotadisplay.innerHTML = text;
     };
  // update the mailboxlist
  this.set_unread_count = function(mbox, count, set_title)
@@ -3433,7 +2978,7 @@
  // add row to contacts list
  this.add_contact_row = function(cid, cols)
  this.add_contact_row = function(cid, cols, select)
    {
    if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
      return false;
@@ -3446,7 +2991,7 @@
    row.id = 'rcmrow'+cid;
    row.className = 'contact '+(even ? 'even' : 'odd');
    
    if (this.in_selection(cid))
    if (this.contact_list.in_selection(cid))
      row.className += ' selected';
    // add each submitted col
@@ -3458,8 +3003,7 @@
      row.appendChild(col);
      }
    
    tbody.appendChild(row);
    this.init_table_row(row, 'contact_rows');
    this.contact_list.insert_row(row);
    };
@@ -3474,69 +3018,6 @@
      {
        tinyMCE.execCommand('mceRemoveControl', true, textElementName);
      }
    }
  /********************************************************/
  /*********          drag & drop methods         *********/
  /********************************************************/
  this.drag_mouse_move = function(e)
    {
    if (this.drag_start)
      {
      if (!this.draglayer)
        this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
      // get subjects of selectedd messages
      var names = '';
      var c, subject, obj;
      for(var n=0; n<this.selection.length; n++)
        {
        if (n>12)  // only show 12 lines
          {
          names += '...';
          break;
          }
        if (this.message_rows[this.selection[n]].obj)
          {
          obj = this.message_rows[this.selection[n]].obj;
          subject = '';
          for(c=0; c<obj.childNodes.length; c++)
            if (!subject && obj.childNodes[c].nodeName=='TD' && obj.childNodes[c].firstChild && obj.childNodes[c].firstChild.nodeType==3)
              {
              subject = obj.childNodes[c].firstChild.data;
              names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
              }
          }
        }
      this.draglayer.write(names);
      this.draglayer.show(1);
      }
    var pos = this.get_mouse_pos(e);
    this.draglayer.move(pos.x+20, pos.y-5);
    this.drag_start = false;
    this.drag_active = true;
    return false;
    };
  this.drag_mouse_up = function()
    {
    document.onmousemove = null;
    if (this.draglayer && this.draglayer.visible)
      this.draglayer.show(0);
    this.drag_active = false;
    return false;
    };
@@ -3544,6 +3025,16 @@
  /********************************************************/
  /*********        remote request methods        *********/
  /********************************************************/
  this.goto_url = function(action, query, lock)
    {
    if (lock)
    this.set_busy(true);
    var querystring = query ? '&'+query : '';
    location.href = this.env.comm_path+'&_action='+action+querystring;
    };
  this.http_sockets = new Array();
@@ -3677,91 +3168,16 @@
  // and return the message uid
  this.get_single_uid = function()
    {
    return this.env.uid ? this.env.uid : (this.selection.length==1 ? this.selection[0] : null);
    return this.env.uid ? this.env.uid : (this.message_list ? this.message_list.get_single_selection() : null);
    };
  // same as above but for contacts
  this.get_single_cid = function()
    {
    return this.env.cid ? this.env.cid : (this.selection.length==1 ? this.selection[0] : null);
    return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null);
    };
/* deprecated methods
  // check if Shift-key is pressed on event
  this.check_shiftkey = function(e)
    {
    if(!e && window.event)
      e = window.event;
    if(bw.linux && bw.ns4 && e.modifiers)
      return true;
    else if((bw.ns4 && e.modifiers & Event.SHIFT_MASK) || (e && e.shiftKey))
      return true;
    else
      return false;
    }
  // check if Shift-key is pressed on event
  this.check_ctrlkey = function(e)
    {
    if(!e && window.event)
      e = window.event;
    if(bw.linux && bw.ns4 && e.modifiers)
      return true;
   else if (bw.mac)
       return this.check_shiftkey(e);
    else if((bw.ns4 && e.modifiers & Event.CTRL_MASK) || (e && e.ctrlKey))
      return true;
    else
      return false;
    }
*/
  // returns modifier key (constants defined at top of file)
  this.get_modifier = function(e)
    {
    var opcode = 0;
    e = e || window.event;
    if (bw.mac && e)
      {
      opcode += (e.metaKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
      return opcode;
      }
    if (e)
      {
      opcode += (e.ctrlKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
      return opcode;
      }
    if (e.cancelBubble)
      {
      e.cancelBubble = true;
      e.returnValue = false;
      }
    else if (e.preventDefault)
      e.preventDefault();
  }
  this.get_mouse_pos = function(e)
    {
    if(!e) e = window.event;
    var mX = (e.pageX) ? e.pageX : e.clientX;
    var mY = (e.pageY) ? e.pageY : e.clientY;
    if(document.body && document.all)
      {
      mX += document.body.scrollLeft;
      mY += document.body.scrollTop;
      }
    return { x:mX, y:mY };
    };
  this.get_caret_pos = function(obj)
    {
    if (typeof(obj.selectionEnd)!='undefined')
@@ -3826,7 +3242,10 @@
// class for HTTP requests
/**
 * Class for sending HTTP requests
 * @constructor
 */
function rcube_http_request()
  {
  this.url = '';
@@ -3889,7 +3308,7 @@
    {
    // default value for contentType if not provided
    contentType = typeof(contentType) != 'undefined' ?
        contentType : 'application/x-www-form-urlencoded';
      contentType : 'application/x-www-form-urlencoded';
    this.build();
    
program/js/common.js
@@ -3,7 +3,7 @@
 | RoundCube common js library                                           |
 |                                                                       |
 | This file is part of the RoundCube web development suite              |
 | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
 | Copyright (C) 2005-2006, RoundCube Dev, - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
@@ -13,8 +13,16 @@
 $Id$
*/
// Constants
var CONTROL_KEY = 1;
var SHIFT_KEY = 2;
var CONTROL_SHIFT_KEY = 3;
// default browsercheck
/**
 * Default browser check class
 * @construcotr
 */
function roundcube_browser()
  {
  this.ver = parseFloat(navigator.appVersion);
@@ -92,10 +100,118 @@
  }
// static functions for event handling
var rcube_event = {
/**
 * returns modifier key (constants defined at top of file)
 */
get_modifier: function(e)
{
  var opcode = 0;
  e = e || window.event;
  if (bw.mac && e)
    {
    opcode += (e.metaKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
    return opcode;
    }
  if (e)
    {
    opcode += (e.ctrlKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
    return opcode;
    }
},
/**
 * Return absolute mouse position of an event
 */
get_mouse_pos: function(e)
{
  if (!e) e = window.event;
  var mX = (e.pageX) ? e.pageX : e.clientX;
  var mY = (e.pageY) ? e.pageY : e.clientY;
  if (document.body && document.all)
  {
    mX += document.body.scrollLeft;
    mY += document.body.scrollTop;
  }
  return { x:mX, y:mY };
},
/**
 * Add an object method as event listener to a certain element
 */
add_listener: function(p)
{
  if (!p.object || !p.method)  // not enough arguments
    return;
  if (!p.element)
    p.element = document;
  if (!p.object._rc_events)
    p.object._rc_events = [];
  var key = p.event + '*' + p.method;
  if (!p.object._rc_events[key])
    p.object._rc_events[key] = function(e){ return p.object[p.method](e); };
  if (p.element.addEventListener)
    p.element.addEventListener(p.event, p.object._rc_events[key], false);
  else if (p.element.attachEvent)
    p.element.attachEvent('on'+p.event, p.object._rc_events[key]);
  else
    p.element['on'+p.event] = p.object._rc_events[key];
},
/**
 * Remove event listener
 */
remove_listener: function(p)
{
  if (!p.element)
    p.element = document;
  var key = p.event + '*' + p.method;
  if (p.object && p.object._rc_events && p.object._rc_events[key]) {
    if (p.element.removeEventListener)
      p.element.removeEventListener(p.event, p.object._rc_events[key], false);
    else if (p.element.detachEvent)
      p.element.detachEvent('on'+p.event, p.object._rc_events[key]);
    else
      p.element['on'+p.event] = null;
  }
},
/**
 * Prevent event propagation and bubbeling
 */
cancel: function(evt)
{
  var e = evt ? evt : window.event;
  if (e.preventDefault)
    e.preventDefault();
  if (e.stopPropagation)
    e.stopPropagation();
  e.cancelBubble = true;
  e.returnValue = false;
  return false;
}
};
var rcube_layer_objects = new Array();
/**
 * RoundCube generic layer (floating box) class
 *
 * @constructor
 */
function rcube_layer(id, attributes)
  {
  this.name = id;
@@ -263,6 +379,7 @@
    }
  }
// check if input is a valid email address
// By Cal Henderson <cal@iamcal.com>
// http://code.iamcal.com/php/rfc822/
@@ -346,7 +463,7 @@
// make a string URL safe
function urlencode(str)
{
  return window.encodeURI ? encodeURI(str).replace(/&/g, '%26') : escape(str);
  return window.encodeURIComponent ? encodeURIComponent(str) : escape(str);
}
program/js/list.js
New file
@@ -0,0 +1,704 @@
/*
 +-----------------------------------------------------------------------+
 | RoundCube List Widget                                                 |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2006, RoundCube Dev, - Switzerland                      |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
 |          Charles McNulty <charles@charlesmcnulty.com>                 |
 +-----------------------------------------------------------------------+
 | Requires: common.js                                                   |
 +-----------------------------------------------------------------------+
  $Id: list.js 344 2006-09-18 03:49:28Z thomasb $
*/
/**
 * RoundCube List Widget class
 * @contructor
 */
function rcube_list_widget(list, p)
  {
  // static contants
  this.ENTER_KEY = 13;
  this.DELETE_KEY = 46;
  this.list = list ? list : null;
  this.frame = null;
  this.rows = [];
  this.selection = [];
  this.multiselect = false;
  this.draggable = false;
  this.keyboard = false;
  this.dont_select = false;
  this.drag_active = false;
  this.last_selected = 0;
  this.in_selection_before = false;
  this.focused = false;
  this.drag_mouse_start = null;
  this.dblclick_time = 600;
  this.row_init = function(){};
  this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragend:[] };
  // overwrite default paramaters
  if (p && typeof(p)=='object')
    for (var n in p)
      this[n] = p[n];
  }
rcube_list_widget.prototype = {
/**
 * get all message rows from HTML table and init each row
 */
init: function()
{
  if (this.list && this.list.tBodies[0])
  {
    this.rows = new Array();
    var row;
    for(var r=0; r<this.list.tBodies[0].childNodes.length; r++)
    {
      row = this.list.tBodies[0].childNodes[r];
      while (row && (row.nodeType != 1 || row.style.display == 'none'))
      {
        row = row.nextSibling;
        r++;
      }
      this.init_row(row);
    }
    this.frame = this.list.parentNode;
    // set body events
    if (this.keyboard)
      rcube_event.add_listener({element:document, event:'keydown', object:this, method:'key_press'});
  }
},
/**
 *
 */
init_row: function(row)
{
  // make references in internal array and set event handlers
  if (row && String(row.id).match(/rcmrow([0-9]+)/))
  {
    var p = this;
    var uid = RegExp.$1;
    row.uid = uid;
    this.rows[uid] = {uid:uid, id:row.id, obj:row, classname:row.className};
    // set eventhandlers to table row
    row.onmousedown = function(e){ return p.drag_row(e, this.uid); };
    row.onmouseup = function(e){ return p.click_row(e, this.uid); };
    if (document.all)
      row.onselectstart = function() { return false; };
    this.row_init(this.rows[uid]);
  }
},
/**
 *
 */
clear: function()
{
  var tbody = document.createElement('TBODY');
  this.list.insertBefore(tbody, this.list.tBodies[0]);
  this.list.removeChild(this.list.tBodies[1]);
  this.rows = new Array();
},
/**
 * 'remove' message row from list (just hide it)
 */
remove_row: function(uid)
{
  if (this.rows[uid].obj)
    this.rows[uid].obj.style.display = 'none';
  this.rows[uid] = null;
},
/**
 *
 */
insert_row: function(row, attop)
{
  var tbody = this.list.tBodies[0];
  if (attop && tbody.rows.length)
    tbody.insertBefore(row, tbody.firstChild);
  else
    tbody.appendChild(row);
  this.init_row(row);
},
/**
 * Set focur to the list
 */
focus: function(e)
{
  this.focused = true;
  for (var n=0; n<this.selection.length; n++)
  {
    id = this.selection[n];
    if (this.rows[id].obj)
    {
      this.set_classname(this.rows[id].obj, 'selected', true);
      this.set_classname(this.rows[id].obj, 'unfocused', false);
    }
  }
  if (e || (e = window.event))
    rcube_event.cancel(e);
},
/**
 * remove focus from the list
 */
blur: function()
{
  var id;
  this.focused = false;
  for (var n=0; n<this.selection.length; n++)
  {
    id = this.selection[n];
    if (this.rows[id] && this.rows[id].obj)
    {
      this.set_classname(this.rows[id].obj, 'selected', false);
      this.set_classname(this.rows[id].obj, 'unfocused', true);
    }
  }
},
/**
 * onmousedown-handler of message list row
 */
drag_row: function(e, id)
{
  this.in_selection_before = this.in_selection(id) ? id : false;
  // don't do anything (another action processed before)
  if (this.dont_select)
    return false;
  // selects currently unselected row
  if (!this.in_selection_before)
  {
    var mod_key = rcube_event.get_modifier(e);
    this.select_row(id, mod_key, false);
  }
  if (this.draggable && this.selection.length)
  {
    this.drag_start = true;
    this.drag_mouse_start = rcube_event.get_mouse_pos(e);
    rcube_event.add_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
    rcube_event.add_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
  }
  return false;
},
/**
 * onmouseup-handler of message list row
 */
click_row: function(e, id)
{
  var now = new Date().getTime();
  var mod_key = rcube_event.get_modifier(e);
  // don't do anything (another action processed before)
  if (this.dont_select)
    {
    this.dont_select = false;
    return false;
    }
  var dblclicked = now - this.rows[id].clicked < this.dblclick_time;
  // unselects currently selected row
  if (!this.drag_active && this.in_selection_before == id && !dblclicked)
    this.select_row(id, mod_key, false);
  this.drag_start = false;
  this.in_selection_before = false;
  // row was double clicked
  if (this.rows && dblclicked && this.in_selection(id))
    this.trigger_event('dblclick');
  else
    this.trigger_event('click');
  if (!this.drag_active)
    rcube_event.cancel(e);
  this.rows[id].clicked = now;
  return false;
},
/**
 * get next and previous rows that are not hidden
 */
get_next_row: function()
{
  if (!this.rows)
    return false;
  var last_selected_row = this.rows[this.last_selected];
  var new_row = last_selected_row && last_selected_row.obj.nextSibling;
  while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
    new_row = new_row.nextSibling;
  return new_row;
},
get_prev_row: function()
{
  if (!this.rows)
    return false;
  var last_selected_row = this.rows[this.last_selected];
  var new_row = last_selected_row && last_selected_row.obj.previousSibling;
  while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
    new_row = new_row.previousSibling;
  return new_row;
},
// selects or unselects the proper row depending on the modifier key pressed
select_row: function(id, mod_key, with_mouse)
{
  var select_before = this.selection.join(',');
  if (!this.multiselect)
    mod_key = 0;
  if (!mod_key)
  {
    this.shift_start = id;
    this.highlight_row(id, false);
  }
  else
  {
    switch (mod_key)
    {
      case SHIFT_KEY:
        this.shift_select(id, false);
        break;
      case CONTROL_KEY:
        this.shift_start = id;
        if (!with_mouse)
          this.highlight_row(id, true);
        break;
      case CONTROL_SHIFT_KEY:
        this.shift_select(id, true);
        break;
      default:
        this.highlight_row(id, false);
        break;
    }
  }
  // trigger event if selection changed
  if (this.selection.join(',') != select_before)
    this.trigger_event('select');
  if (this.last_selected != 0 && this.rows[this.last_selected])
    this.set_classname(this.rows[this.last_selected].obj, 'focused', false);
  this.last_selected = id;
  this.set_classname(this.rows[id].obj, 'focused', true);
},
/**
 * Alias method for select_row
 */
select: function(id)
{
  this.select_row(id, false);
  this.scrollto(id);
},
/**
 * Select row next to the last selected one.
 * Either below or above.
 */
select_next: function()
{
  var next_row = this.get_next_row();
  var prev_row = this.get_prev_row();
  var new_row = (next_row) ? next_row : prev_row;
  if (new_row)
    this.select_row(new_row.uid, false, false);
},
/**
 * Perform selection when shift key is pressed
 */
shift_select: function(id, control)
{
  var from_rowIndex = this.rows[this.shift_start].obj.rowIndex;
  var to_rowIndex = this.rows[id].obj.rowIndex;
  var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
  var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
  // iterate through the entire message list
  for (var n in this.rows)
  {
    if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j))
    {
      if (!this.in_selection(n))
        this.highlight_row(n, true);
    }
    else
    {
      if  (this.in_selection(n) && !control)
        this.highlight_row(n, true);
    }
  }
},
/**
 * Check if given id is part of the current selection
 */
in_selection: function(id)
{
  for(var n in this.selection)
    if (this.selection[n]==id)
      return true;
  return false;
},
/**
 * Select each row in list
 */
select_all: function(filter)
{
  if (!this.rows || !this.rows.length)
    return false;
  // reset selection first
  this.clear_selection();
  for (var n in this.rows)
  {
    if (!filter || this.rows[n][filter]==true)
    {
      this.last_selected = n;
      this.highlight_row(n, true);
    }
  }
  return true;
},
/**
 * Unselect all selected rows
 */
clear_selection: function()
{
  for(var n=0; n<this.selection.length; n++)
    if (this.rows[this.selection[n]])
    {
      this.set_classname(this.rows[this.selection[n]].obj, 'selected', false);
      this.set_classname(this.rows[this.selection[n]].obj, 'unfocused', false);
    }
  this.selection = new Array();
},
/**
 * Getter for the selection array
 */
get_selection: function()
{
  return this.selection;
},
/**
 * Return the ID if only one row is selected
 */
get_single_selection: function()
{
  if (this.selection.length == 1)
    return this.selection[0];
  else
    return null;
},
/**
 * Highlight/unhighlight a row
 */
highlight_row: function(id, multiple)
{
  if (this.rows[id] && !multiple)
  {
    this.clear_selection();
    this.selection[0] = id;
    this.set_classname(this.rows[id].obj, 'selected', true)
  }
  else if (this.rows[id])
  {
    if (!this.in_selection(id))  // select row
    {
      this.selection[this.selection.length] = id;
      this.set_classname(this.rows[id].obj, 'selected', true);
    }
    else  // unselect row
    {
      var p = find_in_array(id, this.selection);
      var a_pre = this.selection.slice(0, p);
      var a_post = this.selection.slice(p+1, this.selection.length);
      this.selection = a_pre.concat(a_post);
      this.set_classname(this.rows[id].obj, 'selected', false);
      this.set_classname(this.rows[id].obj, 'unfocused', false);
    }
  }
},
/**
 * Handler for keyboard events
 */
key_press: function(e)
{
  if (this.focused != true)
    return true;
  var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
  var mod_key = rcube_event.get_modifier(e);
  switch (keyCode)
  {
    case 40:
    case 38:
      return this.use_arrow_key(keyCode, mod_key);
      break;
    default:
      this.key_pressed = keyCode;
      this.trigger_event('keypress');
  }
  return true;
},
/**
 * Special handling method for arrow keys
 */
use_arrow_key: function(keyCode, mod_key)
{
  var new_row;
  if (keyCode == 40) // down arrow key pressed
    new_row = this.get_next_row();
  else if (keyCode == 38) // up arrow key pressed
    new_row = this.get_prev_row();
  if (new_row)
  {
    this.select_row(new_row.uid, mod_key, true);
    this.scrollto(new_row.uid);
  }
  return false;
},
/**
 * Try to scroll the list to make the specified row visible
 */
scrollto: function(id)
{
  var row = this.rows[id].obj;
  if (row && this.frame)
  {
    var scroll_to = Number(row.offsetTop);
    if (scroll_to < Number(this.frame.scrollTop))
      this.frame.scrollTop = scroll_to;
    else if (scroll_to + Number(row.offsetHeight) > Number(this.frame.scrollTop) + Number(this.frame.offsetHeight))
      this.frame.scrollTop = (scroll_to + Number(row.offsetHeight)) - Number(this.frame.offsetHeight);
  }
},
/**
 * Handler for mouse move events
 */
drag_mouse_move: function(e)
{
  if (this.drag_start)
  {
    // check mouse movement, of less than 3 pixels, don't start dragging
    var m = rcube_event.get_mouse_pos(e);
    if (!this.drag_mouse_start || (Math.abs(m.x - this.drag_mouse_start.x) < 3 && Math.abs(m.y - this.drag_mouse_start.y) < 3))
      return false;
    if (!this.draglayer)
      this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
    // get subjects of selectedd messages
    var names = '';
    var c, subject, obj;
    for(var n=0; n<this.selection.length; n++)
    {
      if (n>12)  // only show 12 lines
      {
        names += '...';
        break;
      }
      if (this.rows[this.selection[n]].obj)
      {
        obj = this.rows[this.selection[n]].obj;
        subject = '';
        for(c=0; c<obj.childNodes.length; c++)
          if (!subject && obj.childNodes[c].nodeName=='TD' && obj.childNodes[c].firstChild && obj.childNodes[c].firstChild.nodeType==3)
          {
            subject = obj.childNodes[c].firstChild.data;
            names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
          }
      }
    }
    this.draglayer.write(names);
    this.draglayer.show(1);
    this.drag_active = true;
    this.trigger_event('dragstart');
  }
  if (this.drag_active && this.draglayer)
  {
    var pos = rcube_event.get_mouse_pos(e);
    this.draglayer.move(pos.x+20, pos.y-5);
  }
  this.drag_start = false;
  return false;
},
/**
 * Handler for mouse up events
 */
drag_mouse_up: function(e)
{
  document.onmousemove = null;
  if (this.draglayer && this.draglayer.visible)
    this.draglayer.show(0);
  this.drag_active = false;
  this.trigger_event('dragend');
  rcube_event.remove_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
  rcube_event.remove_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
  return rcube_event.cancel(e);
},
/**
 * set/unset a specific class name
 */
set_classname: function(obj, classname, set)
{
  var reg = new RegExp('\s*'+classname, 'i');
  if (!set && obj.className.match(reg))
    obj.className = obj.className.replace(reg, '');
  else if (set && !obj.className.match(reg))
    obj.className += ' '+classname;
},
/**
 * Setter for object event handlers
 *
 * @param {String}   Event name
 * @param {Function} Handler function
 * @return Listener ID (used to remove this handler later on)
 */
addEventListener: function(evt, handler)
{
  if (this.events[evt]) {
    var handle = this.events[evt].length;
    this.events[evt][handle] = handler;
    return handle;
  }
  else
    return false;
},
/**
 * Removes a specific event listener
 *
 * @param {String} Event name
 * @param {Int}    Listener ID to remove
 */
removeEventListener: function(evt, handle)
{
  if (this.events[evt] && this.events[evt][handle])
    this.events[evt][handle] = null;
},
/**
 * This will execute all registered event handlers
 * @private
 */
trigger_event: function(evt)
{
  if (this.events[evt] && this.events[evt].length) {
    for (var i=0; i<this.events[evt].length; i++)
      if (typeof(this.events[evt][i]) == 'function')
        this.events[evt][i](this);
  }
}
};
program/steps/addressbook/edit.inc
@@ -94,7 +94,7 @@
    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
    $hiddenfields->add(array('name' => '_action', 'value' => 'save'));
    
    if ($_GET['_framed'] || $_POST['_framed'])
    if ($GLOBALS['_framed'])
      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
    
    if ($CONTACT_RECORD['contact_id'])
program/steps/addressbook/func.inc
@@ -90,6 +90,7 @@
  //$javascript .= sprintf("%s.set_env('contacts', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
  
  $OUTPUT->add_script($javascript);  
  $OUTPUT->include_script('list.js');
  // add some labels to client
  rcube_add_label('deletecontactconfirm');
program/steps/addressbook/save.inc
@@ -62,9 +62,6 @@
       
  if ($updated)
    {
    $_action = 'show';
    show_message('successfullysaved', 'confirmation');
    if ($_framed)
      {
      // define list of cols to be displayed
@@ -89,9 +86,11 @@
                          $_POST['_cid'],
                          array2js($a_js_cols)));
      // show confirmation
      show_message('successfullysaved', 'confirmation');
      }
    // show confirmation
    show_message('successfullysaved', 'confirmation');
    rcmail_overwrite_action('show');
    }
  else
    {
@@ -184,9 +183,6 @@
    {
    if (!$ldap_form)
      {
      $_action = 'show';
      $_GET['_cid'] = $insert_id;
      if ($_framed)
        {
        // add contact row or jump to the page where it should appear
@@ -198,11 +194,11 @@
                                  $_SESSION['user_id']);
        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
        $commands .= sprintf("if(parent.%s)parent.%s.select('%d');\n",
        $commands .= sprintf("if(parent.%s)parent.%s.contact_list.select('%d');\n",
                             $JS_OBJECT_NAME, 
                             $JS_OBJECT_NAME,
                             $insert_id);
        // update record count display
        $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
                             $JS_OBJECT_NAME, 
@@ -213,7 +209,8 @@
        }
      // show confirmation
      show_message('successfullysaved', 'confirmation');
      show_message('successfullysaved', 'confirmation');
      $_GET['_cid'] = $insert_id;
      }
    else 
      {
@@ -231,7 +228,7 @@
        }
      // display the last insert id
      $commands .= sprintf("if(parent.%s)parent.%s.select('%d');\n",
      $commands .= sprintf("if(parent.%s)parent.%s.contact_list.select('%d');\n",
                            $JS_OBJECT_NAME, 
                            $JS_OBJECT_NAME,
                            $last_id);
@@ -248,6 +245,7 @@
    // show confirmation
    show_message('successfullysaved', 'confirmation');      
    rcmail_overwrite_action('show');
    }
  else
    {
program/steps/mail/func.inc
@@ -495,6 +495,7 @@
  $javascript .= sprintf("%s.set_env('messages', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
  
  $OUTPUT->add_script($javascript);  
  $OUTPUT->include_script('list.js');
  
  return $out;
  }
program/steps/settings/identities.inc
@@ -22,6 +22,7 @@
if ($USER_DATA = $DB->fetch_assoc($sql_result))
  $PAGE_TITLE = sprintf('%s (%s@%s)', rcube_label('identities'), $USER_DATA['username'], $USER_DATA['mail_host']);
$OUTPUT->include_script('list.js');
// similar function as /steps/addressbook/func.inc::rcmail_contact_frame()
skins/default/common.css
@@ -252,6 +252,18 @@
  background-color: #CC3333;
}
table.records-table tr.focused td
{
  border-bottom: thin dotted;
  border-top: thin dotted;
}
table.records-table tr.unfocused td
{
  font-weight: bold;
  color: #FFFFFF;
  background-color: #929292;
}
/***** roundcube webmail pre-defined classes *****/