/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/*
 *+SNOTICE
 *
 *	$TOG: MsgScrollingList.C /main/38 1998/12/10 19:08:02 mgreess $
 *
 *	RESTRICTED CONFIDENTIAL INFORMATION:
 *	
 *	The information in this document is subject to special
 *	restrictions in a confidential disclosure agreement between
 *	HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
 *	document outside HP, IBM, Sun, USL, SCO, or Univel without
 *	Sun's specific written approval.  This document and all copies
 *	and derivative works thereof must be returned or destroyed at
 *	Sun's request.
 *
 *	Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
 *
 *+ENOTICE
 */

#include <EUSCompat.h>
#include <ctype.h>
#include <assert.h>
#include <Xm/Text.h>
#include <Dt/Dts.h>

#include <DtMail/DtMailError.hh>
#include <DtMail/IO.hh>
#include "DtMailGenDialog.hh"
#include "DtMailHelp.hh"
#include "Help.hh"	// Remove after fixing problem with empty time headers
#include "MailMsg.h"
#include "MemUtils.hh"
#include "MsgHndArray.hh"
#include "MsgScrollingList.hh"
#ifdef DEAD_WOOD
#include "QueryDialogManager.hh"
#endif /* DEAD_WOOD */
#include "RoamApp.h"
#include "RoamMenuWindow.h"
#include "Sort.hh"


#include <X11/IntrinsicP.h> // Include for moving X location of titles

extern int force( Widget );

MsgScrollingList::MsgScrollingList(
    RoamMenuWindow *menuwindow,
    Widget parent,
    char *name
)
    : ScrollingList(
	parent,
	name
)
{
    _parent=menuwindow;
    _numbered = DTM_FALSE;
    _selected_item_position=-1;
    _displayed_item_position=-1;
    _selection_on = FALSE;
    _xmstr_collector = NULL;
    _xtarg_collector = NULL;
    _selected_items = NULL;
    _sorter = new Sort ();

    XtAddCallback( _w,
		   XmNextendedSelectionCallback,
		   (XtCallbackProc)
		   &MsgScrollingList::extendedSelectionCallback,
		   this );

    _msgs = new MsgHndArray(1024);
    _deleted_messages = new MsgHndArray(1024);
    num_new_messages = 0;
    num_deleted_messages = 0;
    session_message_number = 0;
   
    // Can later initialize these from the last use of the session.
    // Each folder will have some idea of which message was last
    // read.  We should select and display it at next load, no?

    _selected_item_position = 0;
    _displayed_item_position = 0;

    DtMailEnv mail_error;
    // Initialize the mail_error.
    mail_error.clear();
    DtMail::Session * d_session = theRoamApp.session()->session();
    DtMail::MailRc * mailrc = d_session->mailRc(mail_error);

    if (mailrc) {
    	const char * value = NULL;
    	mailrc->getValue(mail_error, "showto", &value);
	if (mail_error.isNotSet())
	  _header_info.number_of_names = 5;
    	else 
	  _header_info.number_of_names = 4;
	if (NULL != value)
	  free((void*) value);
    }
    else
	 _header_info.number_of_names = 4;

    // Set up array for 5 items. The DtMailMessageTo is used only 
    // if showto is set, but create placeholder for 5th item in case 
    // they apply props in this same session. Then we just have to
    // toggle between 4 or 5 number_of_nanes.

    _header_info.header_name = new (char* [5]);

    _header_info.header_name[0] = NULL;
    _header_info.header_name[1] = NULL;
    _header_info.header_name[2] = NULL;
    _header_info.header_name[3] = NULL;
    _header_info.header_name[4] = NULL;

    _header_info.header_name[0] = strdup(DtMailMessageSender);
    _header_info.header_name[1] = strdup(DtMailMessageReceivedTime);
    _header_info.header_name[2] = strdup(DtMailMessageContentLength);
    _header_info.header_name[3] = strdup(DtMailMessageSubject);
    _header_info.header_name[4] = strdup(DtMailMessageTo);
}

MsgScrollingList::~MsgScrollingList()
{
    MsgStruct	*ms;
    int 	i, length;

    for (i=0; i<5; i++)
    {
        if (_header_info.header_name[i])
	{
            free(_header_info.header_name[i]);
            _header_info.header_name[i] = NULL;
        }
    }
    delete _header_info.header_name;

    for (i=0, length=_deleted_messages->length(); i<length; i++)
    {
	ms = _deleted_messages->at(i);
        delete ms;
    }
    _deleted_messages->clear();
    delete _deleted_messages;

    for (i=0, length=_msgs->length(); i<length; i++)
    {
	ms = _msgs->at(i);
        delete ms;
    }
    _msgs->clear();
    delete _msgs;

    delete _sorter;
}

Widget
MsgScrollingList::get_scrolling_list()
{
    return(_w);
}

void
MsgScrollingList::items(
    XmString items[],
    int count )
{
    XtVaSetValues( _w,
		   XmNitems, items,
		   XmNitemCount, count,
		   NULL );
}

#ifdef DEAD_WOOD
void 
MsgScrollingList::addChooseCommand(
    ChooseCmd *cmd
) 
{
    _choose=cmd;
}

void 
MsgScrollingList::addDeleteCommand(
    DeleteCmd *cmd
) 
{
    _delete=cmd;
}
#endif /* DEAD_WOOD */


void
MsgScrollingList::select_next_item()
{
    INSERT_STACK_PROBE
    int num_msgs = 0;
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    DtMailEnv mail_error;

    // Initialize the mail_error.
    mail_error.clear();

    XtVaGetValues( _w,
		   XmNitemCount, &num_msgs,
		   NULL );
    
    _selected_item_position = _displayed_item_position + 1;

    if (_selected_item_position <= num_msgs && 
	_selected_item_position > 0 ) {

	tmpMS = get_message_struct(_selected_item_position);
	if (tmpMS == NULL) {
	    return;
	}
	else {
	    // Deselect all items currently selected.
	    // display_and_select_message() will select, highlight
	    // and display the "next" message.

	    XmListDeselectAllItems(_w);

	    this->display_and_select_message(mail_error,
					tmpMS->message_handle);
	    if (mail_error.isSet()) {
		// Post an exception here...
	    }
	}
    }
    else {
	return;
    }
}

void
MsgScrollingList::select_prev_item()
{
    INSERT_STACK_PROBE
    int num_msgs;
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    DtMailEnv mail_error;

    // Initialize the mail_error.
    mail_error.clear();

    XtVaGetValues( _w,
		   XmNitemCount, &num_msgs,
		   NULL );
    
    if( _displayed_item_position != 1 )
	_selected_item_position = _displayed_item_position - 1 ;
    else
	_selected_item_position = _displayed_item_position ;
    
    if (_selected_item_position >= 1) {
	tmpMS = get_message_struct(_selected_item_position);
	if (tmpMS == NULL) {
	    return;
	}
	else {
	    // Deselect all items currently selected.
	    // display_and_select_message() will select, highlight
	    // and display the "previous" message.

	    XmListDeselectAllItems(_w);

	    this->display_and_select_message(mail_error,
					tmpMS->message_handle);
	    if (mail_error.isSet()) {
		// Post an exception here...
	    }
	}
    }
    else {
	return;
    }
}

DtMailMessageHandle
MsgScrollingList::msgno( 
			 int index 
		     ) 
{ 
    if (index <= 0) {
	return(NULL);
    }
    else {
	return _msgs->at(index-1)->message_handle; 
    }
}

MsgStruct*
MsgScrollingList::get_message_struct(
				     int index 
				 ) 
{ 
    if (index <= 0) {
	return(NULL);
    }
    else {
	return(_msgs->at(index-1));
    }
}

int 
MsgScrollingList::position( 
			    DtMailMessageHandle msgno 
			) 
{ 
    return (_msgs->indexof(msgno))+1; 
}

#ifdef DEAD_WOOD
int 
MsgScrollingList::position( 
			    MsgStruct *a_msg_struct
			) 
{ 
    return (_msgs->indexof(a_msg_struct))+1; 
}

void
MsgScrollingList::appendMsg(
    DtMailMessageHandle msg_hndl
)
{
    MsgStruct *newMS;

    // A new message has come in.
    // Increase the session_message_number which keeps track of the
    // number of messages in this session for this folder (scrolling list).
    // 
    
    newMS = new MsgStruct();
    newMS->message_handle = msg_hndl;
    newMS->indexNumber = session_message_number-num_deleted_messages;
    newMS->sessionNumber = session_message_number;
    newMS->is_deleted = FALSE;
    _msgs->append(newMS);
    session_message_number++;

}
#endif /* DEAD_WOOD */

    
void
MsgScrollingList::insertMsg(
    DtMailMessageHandle msg_hndl
)
{
    MsgStruct *newMS;

    // A new message has come in.
    // Increase the session_message_number which keeps track of the
    // number of messages in this session for this folder (scrolling list).
    // 

    newMS = new MsgStruct();
    newMS->message_handle = msg_hndl;
    newMS->indexNumber = session_message_number-num_deleted_messages;
    newMS->sessionNumber = session_message_number;
    newMS->is_deleted = FALSE;
    _msgs->append(newMS);

    session_message_number++;
}

void
MsgScrollingList::insertDeletedMsg(DtMailMessageHandle msg_hndl)
{
    MsgStruct *newMS;

    // A new message has come in.
    // Increase the session_message_number which keeps track of the
    // number of messages in this session for this folder (scrolling list).
    // 

    newMS = new MsgStruct();
    newMS->message_handle = msg_hndl;
    newMS->indexNumber = num_deleted_messages;
    newMS->sessionNumber = session_message_number;
    newMS->is_deleted = FALSE;
    _deleted_messages->append(newMS);

    session_message_number++;
    num_deleted_messages++;
}

int
MsgScrollingList::load_headers(
    DtMailEnv &mail_error
)
{

    DtMailMessageHandle tmpMH;
#ifdef undef
    XmString new_status, read_status;
#endif
    XmString complete_header; // text of header w/ glyphs
    int num_items = 0;
    int select_item;
    DtMailHeaderLine info;
    DtMail::MailBox * mbox = this->parent()->mailbox();
    DtMailEnv error;
    DtMailBoolean first_new = DTM_TRUE;

    // Create a class to collect the mail header XmStrings
    // then get all the items from the current list.
    _xmstr_collector = new XmStrCollector();

#ifdef undef
/* NL_COMMENT
 * In a mailer container window's message scrolling list, a "N" appears
 * to the left of a mail message header indicating that the mail message
 * is "new" (just arrived and not yet viewed by the user).
 * There is only space to display 1 character.  If "N" needs to be translated,
 * please make sure the translation is only 1 character.
 */
   new_status = XmStringCreateLocalized(CATGETS(DT_catd, 1, 110, "N"));
   read_status = XmStringCreateLocalized(" ");
#endif

    // Allocate memory for the XmString array and initialize it.

    int visible;
    XtVaGetValues(_w, XmNvisibleItemCount, &visible, NULL);

    // Retrieve the message_handles, and from them their headers.
    // Create an XmString and toss it into the XmStrCollector. 

    select_item = 0;
    int n_vis = 0;
    MsgStruct *ms;

    for (tmpMH = mbox->getFirstMessageSummary(error, _header_info, info);
	 tmpMH && !error.isSet();
	 tmpMH = mbox->getNextMessageSummary(error, tmpMH, _header_info, info),
	 num_items++) {

	DtMail::Message * msg = mbox->getMessage(error, tmpMH);
	if (msg->flagIsSet(error, DtMailMessageDeletePending) == DTM_TRUE) {
	    insertDeletedMsg(tmpMH);
	    continue;
	}
 	else {
	    insertMsg(tmpMH);
	    n_vis += 1;
	}

    	ms = get_message_struct(get_num_messages());
	complete_header = formatHeader(
				info,
				ms->indexNumber,
				show_with_attachments(msg),
				msg->flagIsSet(error, DtMailMessageNew));

 
	if (msg->flagIsSet(error, DtMailMessageNew) == DTM_TRUE) {
	    num_new_messages++;

	    // We want to select the last read message before the
	    // first new message.  We will select the first new
	    // message if it is the first message.

 	    if (first_new) {
 		first_new = DTM_FALSE;
 		if (num_items > 0)
 		    select_item = num_items - 1;
 		else
 		    select_item = 0;
 	    }
	}

        // Insert the XmString into the array.
	_xmstr_collector->AddItemToList (complete_header);

	// Free the space allocated for info
	// delete []info.header_values;
	mbox->clearMessageSummary(info);
    }

    // If there were no new messages, select and display the last message.
    if (first_new) {
 	select_item = num_items - 1;
     }

    select_item += 1; // Message slots start at 1.

    select_item -= num_deleted_messages; // List does not have deleted msgs.

    // Add the items to the XmList.
    // All XmStrings are freed in the XmStrCollector destructor.
    _xtarg_collector = new XtArgCollector;

    // The first time the headers are loaded, they should all be loaded
    // at the same time.  XtVaSetValues is used for this rather than
    // XmListAddItems so that all the other resource will be set at
    // the same time.  This prevents multiple repaints.
    //
    // However, in the case where only an item or two are being added,
    // XmListAddItems should be used.  This prevents an unnecessary
    // repaint in this case.
    XmListAddItems (_w, _xmstr_collector->GetItems(),
    	    _xmstr_collector->GetNumItems(), 0);

    display_message_summary();
    display_message(mail_error, select_item);

    _xtarg_collector->SetValues(_w);
    if (_selected_items)
    {
	XmStringFree (_selected_items);
	_selected_items = NULL;
    }

    delete _xtarg_collector;
    delete _xmstr_collector;
    _xtarg_collector = NULL;
    _xmstr_collector = NULL;

    return(num_items);
}

void
MsgScrollingList::load_headers(
    DtMailEnv &mail_error,
    DtMailMessageHandle last
)
{
    DtMailMessageHandle tmpMH;
#ifdef undef
    XmString read_status, new_status;
#endif
    XmString complete_header; // read status + attach + header_text.
    int num_items;
    int num_new = 0, num_vis = 0;
    DtMailHeaderLine info;
    DtMailEnv error;
    DtMail::MailBox * mbox = this->parent()->mailbox();


    // Create a class to collect the mail header XmStrings
    // then get all the items from the current list.
    _xmstr_collector = new XmStrCollector();

    mail_error.clear();

#ifdef undef
/* NL_COMMENT
 * In a mailer container window's message scrolling list, a "N" appears
 * to the left of a mail message header indicating that the mail message
 * is "new" (just arrived and not yet viewed by the user).
 * There is only space to display 1 character.  If "N" needs to be translated,
 * please make sure the translation is only 1 character.
 */
    new_status = XmStringCreateLocalized(CATGETS(DT_catd, 1, 111, "N"));
    read_status = XmStringCreateLocalized(" ");
#endif

    // Allocate memory for the XmString array and initialize it.

    XtVaGetValues(_w, XmNvisibleItemCount, &num_items, NULL);

    MsgStruct *ms;

    for (tmpMH = mbox->getNextMessageSummary(error, last, _header_info, info);
	 tmpMH && !error.isSet();
	 tmpMH = mbox->getNextMessageSummary(error, tmpMH, _header_info, info),
	 num_new++) {

	DtMail::Message * msg = mbox->getMessage(error, tmpMH);
	if (msg->flagIsSet(error, DtMailMessageDeletePending) == DTM_TRUE) {
	    insertDeletedMsg(tmpMH);
	    continue;
	}
	else {
	    insertMsg(tmpMH);
	    num_vis += 1;
	}

    	ms = get_message_struct(get_num_messages());
	complete_header = formatHeader(
				info,
				ms->indexNumber,
				show_with_attachments(msg),
				msg->flagIsSet(error, DtMailMessageNew));

	if (msg->flagIsSet(error, DtMailMessageNew) == DTM_TRUE) {
	    num_new_messages++;
	}

        // Insert the XmString into the array.
	_xmstr_collector->AddItemToList (complete_header);

	// Free the space allocated for info
	// delete []info.header_values;
	mbox->clearMessageSummary(info);
    }

    // Add the items to the XmList.
    // All XmStrings are freed in the XmStrCollector destructor.
    _xtarg_collector = new XtArgCollector;

    // The first time the headers are loaded, they should all be loaded
    // at the same time.  XtVaSetValues is used for this rather than
    // XmListAddItems so that all the other resource will be set at
    // the same time.  This prevents multiple repaints.
    //
    // However, in the case where only an item or two are being added,
    // XmListAddItems should be used.  This prevents an unnecessary
    // repaint in this case.
    XmListAddItems (_w, _xmstr_collector->GetItems(),
    	    _xmstr_collector->GetNumItems(), 0);

	do_list_vis_adjustment();

    display_message_summary();

    _xtarg_collector->SetValues(_w);
    if (_selected_items)
    {
	XmStringFree (_selected_items);
	_selected_items = NULL;
    }

    delete _xtarg_collector;
    delete _xmstr_collector;
    _xtarg_collector = NULL;
    _xmstr_collector = NULL;
}

void MsgScrollingList::do_list_vis_adjustment()
{
    Widget list = _w; 
    int numNew = _xmstr_collector->GetNumItems();
    int focItm = _selected_item_position;

    int numItems;
    int cItmCnt, pItmCnt, sItmCnt;
    int cFocItm, cTopItm, cBotItm, cInvItm;
 
    XtVaGetValues(list, XmNvisibleItemCount, &numItems, NULL);
    XtVaGetValues(list, XmNitemCount, &cItmCnt, NULL);
    XtVaGetValues(list, XmNselectedItemCount, &sItmCnt, NULL);
    XtVaGetValues(list, XmNtopItemPosition, &cTopItm, NULL);
    pItmCnt = cItmCnt - numNew;
 
    cBotItm = cTopItm + numItems - 1; //[cTopItm...cBotItm is our window
 
    //[User has chosen to view some messages and that view needs to be
    //[maintained.
    if (cBotItm != pItmCnt)
        return;
 
    //[If Brand New Mailbox
    if (cItmCnt <= numItems)
        return;
 
    //[If no items selected, simply synch right till bottom
    if (sItmCnt == 0) {
        XmListSetBottomPos(list, cItmCnt);
        return;
    }
 
    // assert(cFocItm != -1);

    cFocItm = focItm;      
    cInvItm = pItmCnt - cBotItm; //[Num below bottom-most, or hidden
 
    if ((cFocItm <= cBotItm) && (cFocItm >= cTopItm)) {
        int winM = cFocItm - cTopItm - cInvItm;
        if (winM <= 0) { //[There is no scope of adjustment
            return;
        }
        //[All the new messages can be accommodated w/o scrolling curr selection
        if (numNew <= winM) {
            XmListSetBottomPos(list, cItmCnt);
            return;
        }
        //[All the new messages cannot be accommodated, but we will do best fit
        else {
            int numNotShow = numNew - winM;
            XmListSetBottomPos(list, cItmCnt - numNotShow);
            return;
        }
    }
    else {
        XmListSetBottomPos(list, cItmCnt);
        return;
    }
}

void 
MsgScrollingList::deleteSelected(Boolean silent)
{
    // SR - Added stuff below.  Made code more efficient also.
    FORCE_SEGV_DECL(MsgStruct, a_del_msg_struct);
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    FORCE_SEGV_DECL(ViewMsgDialog, tmpView);
    DtMailMessageHandle tmpMH;
    int  position_in_list, i;
    FORCE_SEGV_DECL(int, position_list);
    int position_count;
    Boolean any_selected;
    int num_msgs;
    UndelFromListDialog *undel_dialog;
    int first_selected_pos;
    DtMailEnv mail_error, error;
    DtMailBoolean	cur_state;
    char *status_message, *str;

    // Initialize the mail_error.
    mail_error.clear();

    XtVaGetValues( _w, XmNitemCount, &num_msgs, NULL );
    any_selected = XmListGetSelectedPos(_w, &position_list, &position_count);
    if (!any_selected) return;


    // Of the items in the list, potentially many could be selected
    // for delete.  And those selected for deleted need not be 
    // contiguous necessarily.  Note the position of the item that 
    // appears first in the list.  When the items are deleted, other
    // items if any exist will shift up to take the places of the 
    // deleted items.  You want to display the corresponding item that
    // takes the "first position".

    // Say, if you have messages A, B, C, D, Evisible in that order.
    // You select B and C for delete.
    // The first selected pos is 2.
    // When B and C are deleted, D (and E...) move up.
    // The new order is A, D, E...
    // You want to display the second message which is D.

    first_selected_pos = *position_list;
    undel_dialog = parent()->get_undel_dialog();

    for (i=0; i < position_count; i++ )
    {
	position_in_list = *(position_list + i);
	a_del_msg_struct = get_message_struct(position_in_list);
	_msgs->mark_for_delete(position_in_list - 1);
	_deleted_messages->append(a_del_msg_struct);
	if (undel_dialog)
	  undel_dialog->insertMsg(mail_error, a_del_msg_struct);
	if (mail_error.isSet()) parent()->postErrorDialog(mail_error);

	// See if there is a standalone view of the message.
	// If there is, quit it.

	tmpMH = a_del_msg_struct->message_handle;
	tmpView = parent()->ifViewExists(tmpMH);
	if (tmpView) tmpView->quit();

	// Update the status of the message in the persistent store.
	//
	DtMail::Message * msg = _parent->mailbox()->getMessage(
					error, 
					a_del_msg_struct->message_handle);
	msg->setFlag(error, DtMailMessageDeletePending);

	// Check if message is new.  If new and it is being deleted,
	// reduce the new messages count.

	cur_state = msg->flagIsSet(error, DtMailMessageNew);

	if (cur_state == DTM_TRUE) num_new_messages--;
    }

    _msgs->compact(0);

    // Delete the items from the scrolling list

     XmListDeletePositions(_w, position_list, position_count);
    num_deleted_messages += position_count;

    // XnListGetSelectedPos allocated memory for the array and
    // wants us to free it.  See documentation.

    num_msgs = _msgs->length();

    // Always remove the current attachments from the attachment area
    // regardless of whether this is the last message in the list or not
    // We call clearAttachArea if there are any other messages in the folder
    // as parseAttachments will be called which will clean up the attachment
    // status window. In the case of deleting the last message we must call
    // removeCurrentAttachments which cleans up the status window as well.
    //
    if (num_msgs == 0)
      parent()->get_editor()->attachArea()->removeCurrentAttachments();
    else
      parent()->get_editor()->attachArea()->clearAttachArea();

    if (num_msgs == 0)
    {
	//No more messages left; clear the editor.
	parent()->get_editor()->textEditor()->set_contents("", 1);

	// NOTE: Need to obtain the attachArea and clear it up too

	_displayed_item_position = 0;	// Not displaying any.
	_selected_item_position = 0;	// Reset...
	free(position_list);
	display_message_summary();
	return;
    }

    // If there are messages 1, 2 and 3 and 2 is selected and deleted
    // first_selected_pos will be 2;
    // after removing it, will need to display the 2nd message
    // (which will now be 3)
    // If 2 and 3 have been deleted, and there is no 2nd message,
    // display the nth message (in our example, n = 1)
    
    _selected_item_position = first_selected_pos;
    if (_selected_item_position > num_msgs)
      _selected_item_position = num_msgs; 

    // Having determined which message to display, confirm that it is
    // within the bounds.  Call display_message().
    // FYI, display_message() sets the _displayed_item_position.

    if ((_selected_item_position > 0) &&
	(_selected_item_position <= num_msgs))
    {
	XmListSelectPos(_w, _selected_item_position, FALSE);
	tmpMS = this->get_message_struct(_selected_item_position);
	if (tmpMS == NULL)
	{
	    free(position_list);
	    return;
	}
	else
	{
	    this->display_message(mail_error, tmpMS->message_handle);
	    if (mail_error.isSet()) parent()->postErrorDialog(mail_error);
	}
    }

    if (!silent)
    {
	if (position_count > 1)
	{
	    /* NL_COMMENT
	    * The following sentence means %d number of mail messages have 
	    * been deleted from the mail folder.  This is the plural form 
	    * of the message that gets printed if more than one message 
	    * is moved.
	    */
	    str = CATGETS(DT_catd, 3, 84, "%d messages deleted");
	}
	else
	{
	    /* NL_COMMENT
	    * The following sentence means %d number of mail messages have 
	    * been deleted from the mail folder.  This is the singular 
	    * form of the message that gets printed if only one message 
	    * is moved.
	    */
	    str = CATGETS(DT_catd, 3, 85, "%d message deleted");
	}
	status_message = new char[strlen(str) + 10];
	sprintf(status_message, str, position_count);
	parent()->message(status_message);
	delete [] status_message;
    }

    updateListItems(-1, TRUE, NULL);
    display_message_summary();

    XtFree((char*) position_list);
}

DtMailBoolean
copyCallback(
    DtMailCallbackOp,
    const char *,
    const char *,
    void *,
    ...
)
{
    return(DTM_FALSE);
}

// copySelected() will either copy or move the selected messages
// into the container called destname.  If the delete_after_copy
// flag is set to TRUE, it is effectively a move; otherwise, it
// is a copy.  If the container named by destname is a relative
// path, but it isn't prefixed with a '+', then a '+' will be
// prepended to the name so that the open() call will "do the
// right thing."
// If silent is TRUE then no status messages are displayed and the
// destname is not added to the copy/move cache.

int
MsgScrollingList::copySelected(
    DtMailEnv &mail_error,
    char *destname, 
    int delete_after_copy,
    int silent
)
{
    FORCE_SEGV_DECL(DtMail::MailBox, mbox);
    FORCE_SEGV_DECL(int, position_list);
    FORCE_SEGV_DECL(DtMail::MailBox, target);
    DtMail::MailRc * mailrc;
    DtMail::Session * d_session = theRoamApp.session()->session();
    int position_count, position, i;
    Boolean any_selected = FALSE;
    DtMailMessageHandle msg;
    char *status_message, *str;
    char *newdestname;
    RoamMenuWindow	*rmw;


    any_selected = XmListGetSelectedPos(_w,
					&position_list,
					&position_count);
    // If there aren't any selected messages, then there isn't
    // anything for us to do.

    if (!any_selected) {
	display_no_message();
	if (! silent)
	{
            char * helpId = NULL;
	    DtMailGenDialog *dialog = _parent->genDialog();

	    dialog->setToErrorDialog(
				CATGETS(DT_catd, 3, 50, "Mailer"),
				CATGETS(DT_catd, 2, 16, "No message selected."));
	    dialog->post_and_return(helpId);
	}
	return(1);
    }

    mbox = parent()->mailbox();
    mailrc = mbox->session()->mailRc(mail_error);
    if (!mailrc) {
    // NL_COMMENT
    // The following is an error message.  "mailrc" is the name of the
    // mail resource file.  Translate as appropriate.
    //
    parent()->message(CATGETS(DT_catd, 2, 15,"Error - Unable to get mailrc."));
    return(1);
    }

    // If the first character of destname is alphanumeric, we can
    // safely assume that it is a relative path, so we prepend a
    // '+' to it.
    if (isalnum(destname[0])) {
	// Make sure we allocate enough for the name + '+' + null terminator.
	newdestname = (char *) malloc(strlen(destname) + 2);
	memset(newdestname, 0, strlen(destname) + 2);
 	sprintf(newdestname, "+%s", destname);
        char *path = d_session->expandPath(mail_error, newdestname);
	target = theRoamApp.session()->open(mail_error,
					path,
					copyCallback,
					NULL,
					DTM_TRUE,  
					DTM_FALSE,
					DTM_FALSE);
	free(newdestname);
	free(path);
	newdestname = NULL;
    } else {
	target = theRoamApp.session()->open(mail_error,
					destname,
					copyCallback,
					NULL,
					DTM_TRUE,  
					DTM_FALSE,
					DTM_FALSE);
    }
    if (mail_error.isSet()) {
        // if the error is DTME_AlreadyOpened, we don't care.
        // go ahead to clear the error
        // otherwise, 
	// We couldn't open the container, so we want to post an
	// error dialog.
        if ((DTMailError_t)mail_error == DTME_AlreadyOpened)
         {
           mail_error.clear();
         } else {
	  parent()->postErrorDialog(mail_error);
	  return(0);
         }
    }

    // Go through the selected messages and copy them to the
    // specified container.

    if ( position_count > 0 )
        parent()->busyCursor() ;

    for (i=0; i < position_count; i++) {
	position = *(position_list + i);
	msg = msgno(position);
	DtMail::Message * dtmsg = mbox->getMessage(mail_error, msg);
	if (mail_error.isSet()) {
            parent()->normalCursor() ;
	    parent()->postErrorDialog(mail_error);
	    theRoamApp.session()->close(mail_error, target);
	    return(0);
	}

	mail_error.clear();
	target->copyMessage(mail_error, dtmsg);
	if (mail_error.isSet()) {
            parent()->normalCursor() ;
	    parent()->postErrorDialog(mail_error);
	    theRoamApp.session()->close(mail_error, target);
	    return(0);
	}

        parent()->normalCursor() ;
        
    }

    
    rmw = theRoamApp.session()->getRMW(target);
    mail_error.clear();
    if (rmw) rmw->checkForMail(mail_error);

    if (delete_after_copy) {
	deleteSelected();

	if (i > 1) {
	    // NL_COMMENT
	    // The following sentence means %d number of mail messages have 
	    // been moved to the %s mail folder.  The %s is the name of a 
	    // mail folder.  This is the plural form of the message that gets
	    // printed if more than one message is moved.
	    //
	    str = CATGETS(DT_catd, 3, 65, "%d messages moved to %s");
	} else {
	    // NL_COMMENT
	    // The following sentence means %d number of mail messages have 
	    // been moved to the %s mail folder.  The %s is the name of a 
	    // mail folder.  This is the singular form of the message that
	    // gets printed if only one message is moved.
	    //
	    str = CATGETS(DT_catd, 3, 66, "%d message moved to %s");
	}

    } else {
	if (i > 1) {
	    // NL_COMMENT
	    // The following sentence means %d number of mail messages have been
	    // copied to the %s mail folder.  This is the plural form of the
	    // message that gets printed if more than one message is copied.
	    //
	    str = CATGETS(DT_catd, 3, 67, "%d messages copied to %s");
	} else {
	    // NL_COMMENT
	    // The following sentence means %d number of mail messages have been
	    // copied to the %s mail folder.  This is the singular form of the
	    // message that gets printed if only one message is copied.
	    //
	    str = CATGETS(DT_catd, 3, 68, "%d message copied to %s");
	}

    }
    newdestname = d_session->getRelativePath(mail_error, destname);
    status_message = new char[strlen(str) + strlen(newdestname) + 10];
    sprintf(status_message, str, i, newdestname);

    if (!silent) {
    	theRoamApp.globalAddToCachedContainerList(newdestname);
    	parent()->message(status_message);
    }

    free(newdestname);

    theRoamApp.session()->close(mail_error, target);

    delete [] status_message;
    return(0);
}


//-----------------------------------------------------------------------------
// This method returns a list of the currently selected messages.  This list
// must be deleted by the calling method.
//-----------------------------------------------------------------------------

MsgHndArray * 
MsgScrollingList::selected()
{
    FORCE_SEGV_DECL(MsgStruct, a_msg_struct);
    FORCE_SEGV_DECL(int, position_list);
    int  position_in_list, i;
    int position_count;
    Boolean any_selected;

    // Find out first if any have been selected.
    // If i has been selected, how many?
    // We need the number selected so that we can allocate 
    // space for that many mesasgeStructs to be returned.

    any_selected = XmListGetSelectedPos(_w,
 					&position_list, 
 					&position_count);

    // If nothing selected, return

    if (!any_selected) return NULL;

    //  Allocate memory for position_count number of messageStructs
    // in MsgHndArray.

    MsgHndArray *msgList = new MsgHndArray(position_count);

    for (i=0; i < position_count; i++ ) {
	position_in_list = *(position_list + i);
	a_msg_struct = get_message_struct(position_in_list);
	msgList->append(a_msg_struct);
    }
    return msgList;
}

DtMailBoolean
MsgScrollingList::show_with_attachments(DtMailMessageHandle msg_num)
{
    DtMailBoolean	has_attachments = DTM_FALSE;
    DtMailEnv		mail_error;
    DtMail::MailBox	*mbox=parent()->mailbox();
    DtMail::Message	*msg = mbox->getMessage(mail_error, msg_num);

    has_attachments = show_with_attachments(msg);
    return has_attachments;
}


DtMailBoolean
MsgScrollingList::show_with_attachments(DtMail::Message * msg)
{
    DtMail::BodyPart	*bp;
    DtMailEnv		error;
    DtMailBoolean	has_attachments = DTM_FALSE;
    DtMailBoolean	is_multipart = DTM_FALSE;
    int			num_bodyParts;
    char		*type;

    is_multipart = msg->flagIsSet(error, DtMailMessageMultipart);
    if (! is_multipart)
      return DTM_FALSE;

    num_bodyParts = msg->getBodyCount(error);
    if (num_bodyParts > 1)
      return DTM_TRUE;

    bp = msg->getFirstBodyPart(error);
    bp->getContents(error, NULL, NULL, &type, NULL, NULL, NULL);
    if (NULL != type)
    {
	char *attr;

	attr = DtDtsDataTypeToAttributeValue(type, DtDTS_DA_IS_TEXT, NULL);
	if (attr && strcasecmp(attr, "true") != 0)
	  has_attachments = DTM_TRUE;
	
	if (type)
	  free(type);
	if (attr)
	  DtDtsFreeAttributeValue(attr);
    }
    return has_attachments;
}


void
MsgScrollingList::display_message(
    DtMailEnv	&mail_error,
    DtMailMessageHandle   msg_num
)
{
    int			item_index;
    DtMail::MailBox	*mbox=parent()->mailbox();
    Editor*		rmw_editor;
    DtMailBoolean	cur_state;
    int num_selected;
    int num_bodyParts;

    // If there is a status message displayed, clear it first.
    parent()->clear_message();

    
	// We could have called display_msg from anywhere.
	// Need to calculate what is the position of that item
	// in the scrolling list, given the DtMailMessageHandle.
	// Determine the index at the _msgs array; increment by 1
	// since array begins at 0 and XmList begins at 1.

    item_index = _msgs->indexof(msg_num);
    if (item_index < 0) return;
	
    _displayed_item_position = item_index + 1;

    // Make sure the header is visible in the scrolling list.
    this->scroll_to_position(_displayed_item_position);

    // Retrieve the header text and insert it. We need to retrieve
    // message from the handle and inset the headers.
    //

    DtMail::Message * msg = mbox->getMessage(mail_error, msg_num);
    DtMail::Envelope * env = msg->getEnvelope(mail_error);


    // There are multiple paths to this place:
    // 1) user has already read this message but is re-reading it;
    // 2) user has undeleted this message (implicitly, they have
    //    read this message);
    // 3) user has not read this message yet.
    //
    // For (1), we don't need to do anything fancy.
    // For (2), we have already reset the IsDeleted flag while 
    // undeleting.
    // For (3), we need to reset the flag in store to indicate
    // its read.

    cur_state = msg->flagIsSet(mail_error, DtMailMessageNew);

    // If the message was previously new, we need to reset the list
    // item to remove the "N" on the item.  We do this by reconstructing
    // the header line without the "N".
    //
    if (cur_state == DTM_TRUE) {
	DtMailHeaderLine  info;

	msg->resetFlag(mail_error, DtMailMessageNew);

	mbox->getMessageSummary(mail_error, msg_num, _header_info, info);

        MsgStruct *ms;
    	ms = get_message_struct(_displayed_item_position);

	XmString complete_header;
	complete_header = formatHeader(
			    info,
			    ms->indexNumber,
			    show_with_attachments(msg),
			    msg->flagIsSet(mail_error, DtMailMessageNew));

	mbox->clearMessageSummary(info);

	XmListReplaceItemsPos(_w, &complete_header,1, _displayed_item_position);
	XmStringFree(complete_header);

	// The default selection policy is extended_select.
	// Problem:
	// User extend selects multiple items
	// The last item selected is a "N" message
	// We display the "N" message and have to replace it without
	// the "N".
	// There is no way to replace with something else and select at 
	// the same time.  
	// Therefre, we need to explicitly select the replacement.
	// The problem is: selecting the replacement deselects the other
	// selected items.
	// To work around this, we switch temporarily to MULTIPLE_SELECT,
	// select the replacement, and switch back to extend_select.
	// This will select the replacement and *not*  drop the
	// selection on the other selected items.
	// Say "Amen" to  OSF for such convoluted thinking!

	XtVaGetValues(_w,
	    XmNselectedItemCount, &num_selected,
	    NULL);

	if (num_selected > 1) {
	    // Change to MULTIPLE_SELECT.
	    // Select item
	    // Change back to EXTEND_SELECT
	    XtVaSetValues (_w,
		XmNselectionPolicy, XmMULTIPLE_SELECT,
		XmNselectionMode,   XmNORMAL_MODE,
		NULL);
	  
	   // When loading mail headers, we need to make sure that
	   // the XmList resources are all set at the same time to
	   // avoid painting multiple times.  So here we collect
	   // these resources.
	   if (_xtarg_collector && _xmstr_collector)
	   {
		XmString *items = _xmstr_collector->GetItems();
	
		// Keep a handle to the malloced string copy so that
		// we can free it after the XtSetValues
		_selected_items 
		    = XmStringCopy (items[_displayed_item_position - 1]);

		_xtarg_collector->AddItemToList (XmNselectedItems,
		    (XtArgVal) &_selected_items);
		_xtarg_collector->AddItemToList (
		    XmNselectedItemCount, 1);
		_xtarg_collector->AddItemToList (
		    XmNselectionPolicy, XmEXTENDED_SELECT);
	    }
	    else
	    {
	        XmListSelectPos(_w, _displayed_item_position, FALSE);

	        XtVaSetValues (_w,
		    XmNselectionPolicy, XmEXTENDED_SELECT,
		    XmNselectionMode,   XmNORMAL_MODE,
		    NULL);
	    }
	}
	else {
	   if (_xtarg_collector && _xmstr_collector)
	   {
		XmString *items = _xmstr_collector->GetItems();

		_selected_items 
		    = XmStringCopy (items[_displayed_item_position - 1]);

		_xtarg_collector->AddItemToList (XmNselectedItems,
		    (XtArgVal) &_selected_items);
		_xtarg_collector->AddItemToList (
		    XmNselectedItemCount, 1);
	    }
	    else
	    {
	        XmListSelectPos(_w, _displayed_item_position, FALSE);
	    }
	}

	num_new_messages--;
    }

    display_message_summary();

    // Display the message now.
    rmw_editor = parent()->get_editor()->textEditor();
    rmw_editor->disable_redisplay();
    rmw_editor->auto_show_cursor_off();
    rmw_editor->clear_contents();

    num_bodyParts = msg->getBodyCount(mail_error);

    char * status_string;
    DtMailBoolean firstBPHandled;

    // Turn on the busy Cursor

    parent()->busyCursor();

    if (parent()->fullHeader())
      firstBPHandled =
	rmw_editor->set_message(msg, &status_string, Editor::HF_FULL);
    else
      firstBPHandled =
	rmw_editor->set_message(msg, &status_string, Editor::HF_ABBREV);

    if (status_string) parent()->message(status_string);
    if (mail_error.isSet()) {} // do something

    if ((num_bodyParts > 1) || (!firstBPHandled))
    {
	// If the message has attachments, then let the attach pane
	// handle attachments but not the first bodyPart (which has
	// already been handled here).

	if (firstBPHandled) {

	    //  The first bodyPart has already been handled.
	    // The others, beginning from the second, need to be parsed 
	    // and put into the attachPane.

	    parent()->get_editor()->attachArea()->parseAttachments(
 					mail_error,
 					msg, 
					TRUE,
 					2);
	}
	else {
	    // The first bodyPart was not handled.
	    // It may not have been of type text.
	    // The attachment pane needs to handle all the bodyParts
	    // beginning with the first.

	    parent()->get_editor()->attachArea()->parseAttachments(
 					mail_error,
 					msg, 
					TRUE,
 					1);
	}

	// Check for errors.
	// Manage the attach area to display attachments.
	if (mail_error.isSet()) {} // do something

	parent()->get_editor()->manageAttachArea();
	parent()->activate_default_attach_menu();
    }
    else
    {
	parent()->deactivate_default_attach_menu();
	parent()->get_editor()->unmanageAttachArea();
    }

    parent()->sync_work_area_size();
    rmw_editor->auto_show_cursor_restore();

    // Turn on text editor and manage attachPane
    rmw_editor->set_to_top();
    rmw_editor->enable_redisplay();

    parent()->normalCursor();
}

void
MsgScrollingList::display_and_select_message(
    DtMailEnv &mail_error,
    DtMailMessageHandle msg_num
)
{
    int		item_index;

    // Need to calculate what is the position of that item
    // in the scrolling list, given the DtMailMessageHandle.
    // Determine the index at the _msgs array; increment by 1
    // since array begins at 0 and XmList begins at 1.

    mail_error.clear();

    item_index = _msgs->indexof(msg_num);
    if (item_index < 0) return;
	
    _displayed_item_position = item_index + 1;
    _selected_item_position = _displayed_item_position;
    
    // Select this message in the scrolling list and display
    // the message.

   // When loading mail headers, we need to make sure that
   // the XmList resources are all set at the same time to
   // avoid painting multiple times.  So here we collect
   // these resources.
   if (_xtarg_collector && _xmstr_collector)
   {
	XmString *items = _xmstr_collector->GetItems();

        // Keep a handle to the malloced string copy so that
        // we can free it after the XtSetValues
	_selected_items 
	    = XmStringCopy (items[_displayed_item_position - 1]);

	_xtarg_collector->AddItemToList (XmNselectedItems,
	    (XtArgVal) &_selected_items);
	_xtarg_collector->AddItemToList (
	    XmNselectedItemCount, 1);
    }
    else
    {
	XmListSelectPos(_w, _displayed_item_position, FALSE);
    }
    this->display_message(mail_error, msg_num);

    return;
}

void
MsgScrollingList::select_all_and_display_last(
    DtMailEnv         & error
)
 
{
    int                item_pos;
    int				num_items;
    MsgHndArray			* msgHandles = get_messages();
 
    error.clear();

    if (this->get_num_messages() == 0) return;

    // If no message selected, return.

    if (this->get_selected_item() == 0) return;

    XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
    //
    // We have to go to the end of the list and go backwards.
    // We display the last one, and select the rest.
    //
    // A NULL terminated list.
    //
 
    XmListDeselectAllItems(baseWidget());
    XtVaGetValues(baseWidget(), XmNitemCount, &num_items, NULL);
 
    for (item_pos = 1; item_pos < num_items; item_pos++) {
        XmListSelectPos(baseWidget(), item_pos, FALSE);
    }
 
    // Invoke the selection callback on the last item.
    display_and_select_message(error, 
				msgHandles->at(item_pos-1)->message_handle);
    XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
    XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
}

void
MsgScrollingList::select_all_and_display_last(
    DtMailEnv 	& error,
    DtMailMessageHandle *handleArray,
    unsigned int	   elements
)
{
  int		handleOffset = 0;
  int		item_pos;

  error.clear();

  XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
  //
  // We have to go to the end of the list and go backwards.
  // We display the last one, and select the rest.
  //
  // A NULL terminated list.
  //
  handleOffset = elements;

  XmListDeselectAllItems(baseWidget());

  while (--handleOffset >= 0 && error.isNotSet()) {

    item_pos = _msgs->indexof(handleArray[handleOffset]);	// Get position
    if (item_pos < 0) {
      continue;
    }
    
    //
    // Select this message in the scrolling list and IF
    // it is the last message, display it.
    //
    if (handleOffset == elements - 1) {
      display_and_select_message(error, handleArray[handleOffset]);
    } else {
      XmListSelectPos(_w, item_pos + 1, FALSE);
    }
  }
  XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
  XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
  return;
}


void
MsgScrollingList::display_no_message()
{

	/* NL_COMMENT
	 * No mail message has been selected by the user.
	 */

    parent()->message(CATGETS(DT_catd, 2, 16, "No message selected."));
    _displayed_item_position = 0;
    _selected_item_position = 0;

    parent()->get_editor()->textEditor()->clear_contents();
    parent()->get_editor()->attachArea()->removeCurrentAttachments();
}

// In need of a simple but useful optimization here.
// By the time we get to this method, we already have displayed
// the selected message in the lower section of the combo window.
// Instead of having to parse the messageHandle and construct the
// text buffer to display, we can just get the parsed-and-formatted
// text from the lower section of the combo window and stick it into
// the VMD's editor.
// This will save us the effort of parsing and formatting a message
// 

void 
MsgScrollingList::viewInSeparateWindow(DtMailEnv &mail_error)
{
    FORCE_SEGV_DECL(const char, title);
    FORCE_SEGV_DECL(char, header_txt);
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    DtMailMessageHandle  msgHandle;
    DtMail::MailBox	*mbox=parent()->mailbox();
    ViewMsgDialog	*newview;
    int			 num_bodyParts;
    Editor*		 vmd_editor;

    DtMailEnv		 error;
    DtMail::Session	*d_session = theRoamApp.session()->session();
    DtMail::MailRc	*mail_rc = d_session->mailRc(error);
    const char		*value = NULL;  
 
    // If no message selected, return.
    if (this->get_selected_item() <= 0) return;

    // Double-check.  If none selected, return
    MsgHndArray *selected_msgs = this->selected();
    if (!selected_msgs) return;

    for (int a_msg_num = 0; a_msg_num < selected_msgs->length(); a_msg_num++)
    {
	tmpMS = selected_msgs->at(a_msg_num);
	if (tmpMS == NULL) return;
	   
	msgHandle = tmpMS->message_handle;
	newview = parent()->ifViewExists(msgHandle);
	   
	if (newview != NULL)
	{
            /* NL_COMMENT
	     * The current mail message selected is already displayed in a
	     * separate window.  Therefore this 'separate' window will be
	     * raised in front of existing windows so the user can see it.
	     */
	    parent()->message(
			CATGETS(
				DT_catd, 3, 69,
				"View already exists.  Raising it."));
	    newview->displayInCurrentWorkspace();
	    return;
	} 

	// No view exists.  Display it.  For feedback, set busyCursor().
	parent()->busyCursor();
        mail_rc->getValue(error, "separatemessageviewer", &value);
	if (error.isSet())
	  newview = new ViewMsgDialog(parent(), xmDialogShellWidgetClass);
	else
	{
	    if (value) free((void*) value);
	    newview = new ViewMsgDialog(parent(), topLevelShellWidgetClass);
	}
	parent()->registerDialog(newview);
	newview->initialize();
	vmd_editor = newview->get_editor()->textEditor();
	   
	// Set the VMD's msgHandle. This unique handle is what is
	// used to raise the VMD when the same message is double
	// clicked on later.
	newview->msgno(msgHandle);
	   
	DtMailEnv error;
	DtMail::Message * msg = mbox->getMessage(error, msgHandle);
	DtMail::Envelope * env = msg->getEnvelope(error);
	   
	DtMailValueSeq title_value;
	env->getHeader(error, DtMailMessageSubject, DTM_TRUE, title_value);
	if (error.isSet())
	  title = "NO SUBJECT!";
	else
	  title = *(title_value[0]);
	   
	newview->title((char *)title);
	newview->auto_show_cursor_off();
	   
	// Unset the error produced when obtaining title...
	mail_error.clear();
	vmd_editor->disable_redisplay();
	   
	char * status_string;
	DtMailBoolean firstBPHandled;
	   
	if (parent()->fullHeader())
	  firstBPHandled = newview->get_editor()->textEditor()->set_message(
					msg, &status_string, Editor::HF_FULL);
	else
	  firstBPHandled = newview->get_editor()->textEditor()->set_message(
					msg, &status_string, Editor::HF_ABBREV);
	   
	// If the message has attachments, then let the attach pane
	// handle attachments but not the first bodyPart (which has
	// already been handled here).
	num_bodyParts = msg->getBodyCount(mail_error);
	if (mail_error.isSet()) {} // do something
	   
	if ((num_bodyParts == 1) && firstBPHandled)
	{
	    newview->get_editor()->unmanageAttachArea();
	    newview->deactivate_default_attach_menu();
	}
	else if ((num_bodyParts > 1) || (!firstBPHandled))
	{
	    // If the message has attachments, then let the attach pane
	    // handle attachments but not the first bodyPart (which has
	    // already been handled here).
	       
	    if (firstBPHandled)
	    {
	        //  The first bodyPart has already been handled.
		// The others, beginning from the second, need to be parsed 
		// and put into the attachPane.
	        newview->get_editor()->attachArea()->parseAttachments(
		       				mail_error, msg, TRUE, 2);
	    }
	    else
	    {
		// The first bodyPart was not handled.
		// It may not have been of type text.
		// The attachment pane needs to handle all the bodyParts
		// beginning with the first.
		newview->get_editor()->attachArea()->parseAttachments(
		       				mail_error, msg, TRUE, 1);
	    }
	       
	    // Check for errors.
	    // Manage the attach area to display attachments.
	    if (mail_error.isSet()) {} // do something
	    newview->get_editor()->manageAttachArea();
	    newview->activate_default_attach_menu();
	}
	   
	newview->set_to_top();
	newview->auto_show_cursor_restore();
	vmd_editor->enable_redisplay();
	newview->manage();
    }

    parent()->normalCursor();
}

// defaultAction() gets called *after* extendedSelectionCallback() is
// called.  
// Display the message in extendedSelectionCallback().
// viewInSeparateWindow() in defaultAction().

void 
MsgScrollingList::defaultAction(Widget, XtPointer, XmListCallbackStruct *cbs)
{
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    DtMailEnv mail_error;

    // Initialize the mail_error.
    mail_error.clear();
   _selected_item_position = cbs->item_position;

   tmpMS = get_message_struct(_selected_item_position);
   if (tmpMS == NULL)
     return;
   else
   {
       this->viewInSeparateWindow(mail_error);
       if (mail_error.isSet()) {} // Post dialog indicating error
   }
}

    
void
MsgScrollingList::extendedSelectionCallback(
    Widget ,			// w
    XtPointer clientData,
    XmListCallbackStruct *cbs
)
{
    int last_clicked_on_pos;
    int above_selected_pos;
    int tmp_selected_pos;
    int i, num_selected;
    DtMailEnv mail_error;

    // Initialize the mail_error.
    mail_error.clear();

    
    Boolean IS_SELECTION = FALSE;
    Boolean SELECTION_MADE = FALSE;

    MsgScrollingList *obj=(MsgScrollingList *) clientData;

    // If all items have been deselected
    if (cbs->selected_item_count == 0) {
	obj->extended_selection(mail_error, 0);
	if (mail_error.isSet()) {
	    return;
	}
	SELECTION_MADE = TRUE;
    }
    else {
	
	last_clicked_on_pos = cbs->item_position;

    // Check to see if this was a selection or deselection
    // We do that by seeing if last_clicked_on_pos is in the 
    // selected_item_positions array.  If it is, then it is a 
    // selection; if its not there, then its a deselection.

	num_selected =  cbs->selected_item_count;
    
	for (i = 0; i < num_selected; i++) {
	    if (last_clicked_on_pos == cbs->selected_item_positions[i]) {
		// Yes, it was a selection
	    
		IS_SELECTION = TRUE;
		obj->extended_selection(mail_error, last_clicked_on_pos);
		if (mail_error.isSet()) {
		    return;
		}

		SELECTION_MADE = TRUE;
	    }
	}

    // If it was not a selection, then we need to find the selected
    // item nearest to the deselected item.  We have a choice there -
    // we can find the nearest one above, or the nearest one below 
    // the deselected item.  Let's find the nearest one above.
    // We do that by cruising through the selected_item_positions array.
    // Motif arranges the array in ascending order, no matter what order
    // you selected the items!  If you deselect an item, the items on
    // either side of it are the ones above and below it.  We use that
    // ordering to now select and display the one above it.
    //

	if (!IS_SELECTION) {

	// If the first selected item is below the deselected item,
	// it follows that all selected items are below the deselected
	// item.  Display the first selected item and be done.

	    if (cbs->selected_item_positions[0] > last_clicked_on_pos) {
		obj->extended_selection(mail_error,
					cbs->selected_item_positions[0]);
		if (mail_error.isSet()) {
		    return;
		}
		SELECTION_MADE = TRUE;
	    } 
	    // If the last selected item is above the deselected item,
	    // it follows that all selected items are above the deselected
	    // item.  Display the last selected item and be done.
	    else if (cbs->selected_item_positions[num_selected - 1] <
				last_clicked_on_pos) {
		obj->extended_selection(mail_error,
			   cbs->selected_item_positions[num_selected - 1]);
		if (mail_error.isSet()) {
		    return;
		}
		SELECTION_MADE = TRUE;
	    }
	    // Otherwise, the deselected item must lie in between other
	    // selected items.  We choose to find the closest selected
	    // item above the deselected item.
	    else {
		// There are selected items that are above the deselected 
		// item.
		// Need to find the one that is both above the deselected 
		// item and closest to it.
		// Iterate until you find the nearest above; select it and
		// drop out of loop when after selection.

		for (i = 0; i < (num_selected - 1) && !SELECTION_MADE; i++) {
		    above_selected_pos = cbs->selected_item_positions[i];
		    tmp_selected_pos = cbs->selected_item_positions[i + 1];
		    if (tmp_selected_pos > last_clicked_on_pos) {
			obj->extended_selection(mail_error,
					above_selected_pos);
			if (mail_error.isSet()) {
			    return;
			}
			SELECTION_MADE = TRUE;
		    }
		}
	    }
	}
    }
}


void
MsgScrollingList::extended_selection(
    DtMailEnv &mail_error,
    int position 
)
{
   FORCE_SEGV_DECL(MsgStruct, tmpMS);

   // Disable the SaveAttachments menu item first.  
   // It gets enabled when an attachment is selected.

   _parent->all_attachments_deselected();

   if (position == 0) {  // all items deselected.
       _parent->hideAttachArea();
       this->display_no_message();
       _selection_on = FALSE;
       _parent->deactivate_default_message_menu();
       return;
   }

   // if there were no selected item(s) before and now we
   // have one/some, we need to turn on the message menu at
   // the parent level.

   if (!_selection_on) {
       _selection_on = TRUE;
       _parent->activate_default_message_menu();
   }
   
   // If selected message is the displayed message, return.
   // No need to redisplay the same message.

   if (position == _selected_item_position) return;

   // Retrieve message...
   // Display the selected message...
   _selected_item_position = position;

   tmpMS = get_message_struct(_selected_item_position);
   if (tmpMS == NULL) {
       return;
    }
   else {
       display_message_summary();
       this->display_message(mail_error, tmpMS->message_handle);
       if (mail_error.isSet()) {
	   return;
       }
   }
}


int
MsgScrollingList::get_selected_item()
{
    return(_selected_item_position);
}

int
MsgScrollingList::get_displayed_item()
{
    return(_displayed_item_position);
}

void
MsgScrollingList::scroll_to_bottom()
{
    XmListSetBottomPos( this->baseWidget() , 0 );
}

// Scroll the list so that the item at position is in the
// middle of the scrolling list.  If the number of items
// that the scrolling list can display at one time is greater
// than the total number of items, then display them all.
// If the item at position is close to the bottom of the list
// make sure we make as many items visible as possible.
//
// If the item at position is already visible, then don't
// do anything.

void
MsgScrollingList::scroll_to_position(
    int position
)
{
    int top, visible, total;
    int top_pos;

    // Determine the position of the header that we want to select.
    // If the XtArgCollector exists, then add
    // the resources to the XtArgCollector so that we can prevent
    // multiple redisplays in the XmList widget.
    if (_xmstr_collector)
    {
        XtVaGetValues( _w,
		   XmNtopItemPosition, &top,
		   XmNvisibleItemCount, &visible,
		   NULL );
	total = _xmstr_collector->GetNumItems();
    }
    else
    {
        XtVaGetValues( _w,
		   XmNtopItemPosition, &top,
		   XmNvisibleItemCount, &visible,
		   XmNitemCount, &total,
		   NULL );
    }

    if (( position < top ) || ( position >= top+visible )) {

	if ((total <= visible) || (position <= visible/2)) {
	    // If we can display them all, make the first item appear
	    // at the top of the list.
	    top_pos = 1;
	} else if (position > (total-(visible/2))) {
	    top_pos = total - visible + 1;
	} else {
	    top_pos = position - visible/2 + 1;
	}

	// Determine the position of the header that we want to select.
	// If the XtArgCollector exists, then add
	// the resources to the XtArgCollector so that we can prevent
	// multiple redisplays in the XmList widget.
	if (_xtarg_collector)
	    _xtarg_collector->AddItemToList (
		XmNtopItemPosition, top_pos);
	else
	    XmListSetPos (_w, top_pos);
    }
}

void
MsgScrollingList::undelete_messages(MsgHndArray *tmpMHlist)
{
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    FORCE_SEGV_DECL(XmString, deleted_headers);
    int  i, num_entries, entry_position, del_pos;
    int whichToSelectDisplay = 0;
    DtMail::MailBox	*mbox=parent()->mailbox();
    DtMail::Message * tmpMsg;
    DtMailEnv mail_error;
    DtMailMessageHandle tmpMH;
#ifdef undef
    XmString read_status, new_status;
#endif
    XmString complete_header;	// read status + glyph + header_text.

#ifdef undef
/* NL_COMMENT
 * In a mailer container window's message scrolling list, a "N" appears
 * to the left of a mail message header indicating that the mail message
 * is "new" (just arrived and not yet viewed by the user).
 * There is only space to display 1 character.  If "N" needs to be translated,
 * please make sure the translation is only 1 character.
 */
//
// gregl - new_status and read_status are not used in this function.
//	   either comment them out (like I'm doing) or free them.
//
    new_status = XmStringCreateLocalized(CATGETS(DT_catd, 1, 112, "N"));
    read_status = XmStringCreateLocalized(" ");
#endif

    // Initialize the mail_error.
    mail_error.clear();
    num_entries = tmpMHlist->length();
    if (num_entries == 0) return;

    // Deselect all items currently selected.
    // display_and_select_message() will select, highlight
    // and display the last "undeleted" message.
    
    XmListDeselectAllItems(_w);

    XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);

    for (i = 0; i < num_entries; i++)
    {
	DtMailHeaderLine info;

	tmpMS = tmpMHlist->at(i);
	tmpMS->is_deleted = FALSE;

	// Reset the flag of the message in message store so that the  
	// message will not be expunged when the folder is quit.
	// 

	tmpMsg = mbox->getMessage(mail_error, tmpMS->message_handle);
	tmpMsg->resetFlag(mail_error, DtMailMessageDeletePending);
	tmpMH = tmpMS->message_handle;
	
	// Remove chosen item from list of deleted messages;
	// insert it back into _msgs at the right place (which is 
	// determined by session_number of retrieved MsgStruct).
	// Insert back into scrolling list for visual display
	// at the position session_number.

	entry_position = _msgs->insert(tmpMS);

	// Increment by one, because the index into the scrolling
	// list is always one greater than the index into the
	// message handle array that we got the index from.

	entry_position = entry_position + 1;

	// Maintain the assumption that the item at entry_position
	// is the selected item

	_selected_item_position = entry_position;

	mbox->getMessageSummary(mail_error, tmpMS->message_handle,
				_header_info, info);
	DtMail::Message * msg = mbox->getMessage(mail_error, tmpMH);
	complete_header = formatHeader(
			   info,
			   tmpMS->indexNumber,
			   show_with_attachments(msg),
			   msg->flagIsSet(mail_error, DtMailMessageNew));

	mbox->clearMessageSummary(info);

	if (msg->flagIsSet(mail_error, DtMailMessageNew) == DTM_TRUE)
	  num_new_messages++;

	XmListAddItem(_w, complete_header, entry_position);
	XmListSelectItem(_w, complete_header, FALSE);
	XmStringFree(complete_header);

	// Get position of undeleted message structure in _deleted_messages
	// and remove the entry from _deleted_messages

	del_pos = _deleted_messages->indexof(tmpMS);
	_deleted_messages->remove_entry(del_pos);
    }

    // Display this message and select it.

    this->display_message(mail_error, tmpMS->message_handle);
    XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
    XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);

    if (mail_error.isSet()) return;

    num_deleted_messages -= num_entries;

    MsgHndArray *selected_messages = selected();
    updateListItems(-1, TRUE, selected_messages);
    delete selected_messages;

    display_message_summary();
}

void
MsgScrollingList::undelete_last_deleted()
{
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    int entry_position;
    int len;
    DtMail::MailBox	*mbox=parent()->mailbox();
    UndelFromListDialog *undel_dialog;
    DtMailEnv mail_error;
    DtMailHeaderLine info;
    DtMail::Message * tmpMsg;
    DtMailMessageHandle tmpMH;
#ifdef undef
    XmString read_status, new_status;
#endif
    XmString complete_header;	// read status + glyph + header_text.

#ifdef undef
/* NL_COMMENT
 * In a mailer container window's message scrolling list, a "N" appears
 * to the left of a mail message header indicating that the mail message
 * is "new" (just arrived and not yet viewed by the user).
 * There is only space to display 1 character.  If "N" needs to be translated,
 * please make sure the translation is only 1 character.
 */
    new_status = XmStringCreateLocalized(CATGETS(DT_catd, 1, 113, "N"));
    read_status = XmStringCreateLocalized(" ");
#endif


    if (num_deleted_messages == 0) return;

    // Initialize the mail_error.
    mail_error.clear();


    // Delete the message from the Deleted Messages Dialog.
    undel_dialog = parent()->get_undel_dialog();
    if (undel_dialog)
	undel_dialog->undelLast();
    
    // Restore the message in RoamMenuWindow:MessageScrollingList.
    len = _deleted_messages->length();

    tmpMS = _deleted_messages->at(len - 1);
    tmpMS->is_deleted = FALSE;

    // Reset the flag of the message in message store so that the  
    // message will not be expunged when the folder is quit.
    // 

    tmpMsg = mbox->getMessage(mail_error, tmpMS->message_handle);
    tmpMsg->resetFlag(mail_error, DtMailMessageDeletePending);
    tmpMH = tmpMS->message_handle;

    // Remove chosen item from list of deleted messages;
    // insert it back into _msgs at the right place (which is 
    // determined by session_number of retrieved MsgStruct).
    // Insert back into scrolling list for visual display
    // at the position session_number.

    entry_position = _msgs->insert(tmpMS);

    // Increment by one, because the index into the scrolling
    // list is always greater than the index into the
    // message handle array that we got the index from.

    entry_position = entry_position + 1;

    mbox->getMessageSummary(mail_error, tmpMS->message_handle,
			    _header_info, info);
    DtMail::Message * msg = mbox->getMessage(mail_error, tmpMH);

    complete_header = formatHeader(
			info,
			tmpMS->indexNumber,
			show_with_attachments(msg),
			msg->flagIsSet(mail_error, DtMailMessageNew));

    mbox->clearMessageSummary(info);

    if (msg->flagIsSet(mail_error, DtMailMessageNew) == DTM_TRUE) {
	num_new_messages++;
    }

    _deleted_messages->remove_entry(len - 1);
    num_deleted_messages--;
    XmListAddItemUnselected(_w, complete_header, entry_position);
    XmStringFree(complete_header);

    
    // If the undeleted message is before the currently viewed message,
    // then need to readjust our numbers by adding one to them -- there
    // is one more message above the currently-viewed message.
    // Don't need to do anything if the undeleted message is after the
    // currently-viewed message.

    
    // Deselect all items currently selected.
    // display_and_select_message() will select, highlight
    // and display the last "undeleted" message.
    
    XmListDeselectAllItems(_w);

    _selected_item_position = entry_position;
    this->display_and_select_message(mail_error, tmpMS->message_handle);
    if (mail_error.isSet()) return;

    updateListItems(-1, TRUE, NULL);
    display_message_summary();
}

DtMailBoolean
MsgScrollingList::senderIsToHeaderWhenMailFromMe(void)
{
    if (_header_info.number_of_names == 5)
      return(DTM_TRUE);

    return(DTM_FALSE);
}

void
MsgScrollingList::checkDisplayProp(void)
{
    DtMail::MailBox * mbox;
    DtMail::MailRc * mailrc;
    DtMailEnv mail_error;
    Boolean state_changed = FALSE;

    mbox = parent()->mailbox();
    mailrc = mbox->session()->mailRc(mail_error);

    const char * value = NULL;
    mailrc->getValue(mail_error, "showto", &value);
    if (mail_error.isNotSet()) {
	// showto is set...if number_of_names is 4 then they
	// just applied props and the showto value
	if (_header_info.number_of_names == 4) {
	    _header_info.number_of_names = 5;
	    state_changed = TRUE;
	}
    }
    else if (_header_info.number_of_names == 5) {
	// They just applied props and changed the showto value
	_header_info.number_of_names = 4;
	state_changed = TRUE;
    }
    if (NULL != value)
      free((void*) value);

    // Here we need to adjust the header labels to maintain them left
    // justified. It looks better than centered. We only whant to change
    // the labels if message numbering has been enabled. Otherwise we will
    // leave them alone. Note: The _numbered stores the previous state for
    // message numbering. Check the error return when getting the value uses
    // reverse logic. ugly...
    DtMailBoolean use_msg_numbers;

    value = NULL;
    mail_error.clear();
    mailrc->getValue(mail_error, "showmsgnum", &value);

    use_msg_numbers = (mail_error.isNotSet()) ? DTM_TRUE : DTM_FALSE;
    if (NULL != value) free((void*) value);

    if (use_msg_numbers != _numbered) 
    {
	_numbered = use_msg_numbers;
	layoutLabels(_sender_lbl, _subject_lbl, _date_lbl, _size_lbl);
    }
    else if (!state_changed) return;

    // We have to build two lists from the current normal and deleted
    // lists. These will contain the new header lines.
    //
    DtMailHeaderLine info;
    XmString * normal_list = new XmString[_msgs->length()];

    for (int m = 0; m < _msgs->length(); m++) {
	MsgStruct * ms = _msgs->at(m);

	DtMail::Message * msg =
			mbox->getMessage(mail_error, ms->message_handle);
	mbox->getMessageSummary(
			mail_error, ms->message_handle,
			_header_info, info);
	normal_list[m] = formatHeader(
			    info,
			    ms->indexNumber,
			    show_with_attachments(msg),
			    msg->flagIsSet(mail_error, DtMailMessageNew));
	mbox->clearMessageSummary(info);
    }

    XmListReplaceItemsPos(_w, normal_list, _msgs->length(), 1);
    for (int fr = 0; fr < _msgs->length(); fr++) {
	XmStringFree(normal_list[fr]);
    }
    delete normal_list;

    UndelFromListDialog * del_dialog = _parent->get_undel_dialog();
    if (del_dialog) {
	XmString * del_list = new XmString[_deleted_messages->length()];
	
	for (int m2 = 0; m2 < _deleted_messages->length(); m2++) {
	    MsgStruct * ms = _deleted_messages->at(m2);
	    
	    DtMail::Message * msg =
		mbox->getMessage(mail_error, ms->message_handle);
	    mbox->getMessageSummary(
				mail_error, ms->message_handle,
				_header_info, info);
	    del_list[m2] = formatHeader(
			     info,
			     ms->indexNumber,
			     show_with_attachments(msg),
			     msg->flagIsSet(mail_error, DtMailMessageNew));
	    mbox->clearMessageSummary(info);
	}
	
	del_dialog->replaceItems(del_list, _deleted_messages->length());
	for (int fr2 = 0; fr2 < _deleted_messages->length(); fr2++) {
	    XmStringFree(del_list[fr2]);
	}
	delete del_list;
    }
}

//
// Update the scrolling list. Current is the index of the message
// to position the scrolling list to.  -1 to keep it as is.
//
void
MsgScrollingList::updateListItems(int current,
				  Boolean renumber_only,
				  MsgHndArray *selected_messages)
{
    DtMail::MailBox * mbox = NULL;
    DtMail::MailRc * mailrc;
    DtMailEnv mail_error;
    const char * value = NULL;
    int		nmsgs;

    mbox = parent()->mailbox();
    mailrc = mbox->session()->mailRc(mail_error);

    if (current < 0) current = _displayed_item_position;
    resetIndexNums();

    mailrc->getValue(mail_error, "showmsgnum", &value);
    if (mail_error.isSet() && renumber_only) return;

    if (selected_messages)
      XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);

    //
    // We need to build a new list of strings to display
    // in the scrolling list.  Initialize that now.
    //
    DtMailHeaderLine info;
    XmString * newList;
    MsgStruct *ms;

    nmsgs = _msgs->length();
    newList = new XmString[nmsgs];
    memset (newList, 0, nmsgs * sizeof (XmString *));

    // Loop through _msgs and create new strings to display in the
    // scrolling list. This is inefficient and dominates the time
    // spent in sort.  It may be worth while finding a way to just
    // rearrange the existing strings.
    for (int m = 0; m < nmsgs; m++)
    {
	DtMail::Message * msg = mbox->getMessage(mail_error,
			_msgs->at(m)->message_handle);

    	if (mail_error.isSet())
	  fprintf(stderr, "dtmail: getMessage: Couldn't get message #%d\n", m);
	
	mbox->getMessageSummary(
			mail_error, _msgs->at(m)->message_handle,
			_header_info, info);

    	if (mail_error.isSet())
	  fprintf(stderr,
	   "dtmail: getMessageSummary: Couldn't get summary for msg # %d\n", m);

	if (msg == NULL)
	{
		// Error
		;
	}
	else
	{
    		ms = get_message_struct(m + 1);
		newList [m] =
		  formatHeader(
			info,
			ms->indexNumber,
			show_with_attachments(msg),
			msg->flagIsSet(mail_error, DtMailMessageNew));
	}

	// Free the space allocated for info
	// delete []info.header_values;
	mbox->clearMessageSummary(info);
    }

    XmListReplaceItemsPos(_w, newList, session_message_number, 1);
    for (int fr = 0; fr < nmsgs; fr++)
      XmStringFree(newList[fr]);

    // Update current message
    _selected_item_position = current;
    _displayed_item_position = current;
    scroll_to_position(_displayed_item_position);

    if (selected_messages)
    {
        XmListDeselectAllItems(_w);
        for (int m=0, nselected=selected_messages->length(); m<nmsgs; m++)
        {
            MsgStruct *ms = get_message_struct(m+1);
	    for (int s=0; s<nselected; s++)
	    {
		MsgStruct *sms = selected_messages->at(s);
		if (ms == sms)
		  XmListSelectPos(_w, m+1, FALSE);
	    }
        }
        XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
    }
    else
      XmListSelectPos(_w, _displayed_item_position, FALSE);

    XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
    XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
    delete newList;
}

XmString
MsgScrollingList::formatHeader(DtMailHeaderLine & info,
			       int sess_num,
			       DtMailBoolean has_attachments,
			       DtMailBoolean new_msg)
{
    char *buf = new char[BUFSIZ];
    memset(buf, 0, BUFSIZ);
    const char *from=NULL;
    char *subject;
    int contentLength;
    char contentStr[20];
    char *date = new char[BUFSIZ];
    int msg_num = sess_num + 1;
    static XmString attachment_glyph = NULL;
    static XmString no_attachment_glyph = NULL;
    static XmString new_status = NULL;
    static XmString read_status = NULL;
    static unsigned char attach_symbol[16];
    Boolean showto = FALSE;

    if (!attachment_glyph) {
	attach_symbol[0] = 168;
	attach_symbol[1] = 0;
	attachment_glyph = XmStringCreate((char *)attach_symbol, "attach");
	no_attachment_glyph = XmStringCreateLocalized(" ");
	new_status = XmStringCreateLocalized(CATGETS(DT_catd, 1, 114, "N"));
	read_status = XmStringCreateLocalized(" ");
    }
    
    // strip out the Name of sender and retain only the address.
    //Later, we will have separate  entries for name and address
    //and this stripping will not need to be done.
    
    // IMAP is incapable of handling a message header that begins
    // with From (space) (NOTE: not a From:).
    // Thus, if a message has only a From but does not have a From:
    // or a Reply-To: or Received-From:, IMAP doesn't tell us who
    // it is from.  In such cases, we set it to "???".
    
    DtMailAddressSeq * addr_seq = NULL;
    DtMailValueAddress * addr = NULL;
    
    if (info.header_values[0].length() != 0) {
	addr_seq = ((info.header_values[0])[0])->toAddress();
        addr = (*addr_seq)[0];
        if (_header_info.number_of_names == 5 && addr 
		&& addr->dtm_address && info.header_values[4].length() != 0) {
        	// Check if mail is from me
		const char *ptr;
        	passwd pw;
		GetPasswordEntry(pw);
       		if ((ptr = strchr(addr->dtm_address, '@')) != NULL) {
			if (strncmp(pw.pw_name, addr->dtm_address, 
				ptr-addr->dtm_address) == 0) {
				from = *((info.header_values[4])[0]);
				showto = TRUE;
			}
  		}
		else 
			if (strcmp(pw.pw_name, addr->dtm_address) == 0) {
				from = *((info.header_values[4])[0]);
				showto = TRUE;
			}
	}
        if (from == NULL) {
                if (addr && addr->dtm_person)
                        from = addr->dtm_person;
                else if (addr && addr->dtm_address)
                        from = addr->dtm_address;
                else from = "???";
        }
    }
    else
    	from = "???";
    
    // If the Subject is nil
    
    if (info.header_values[3].length() == 0) {
	subject = new char[1];
	subject[0] = '\0';
    } else {
	// Get the BE store of header.  It may contain newlines or 
	// tab chars which can munge the scrolling list's display!
	// 
	const char *real_subj_header = *((info.header_values[3])[0]);
	int fc;
	int subj_len;
	char *tmp_subj;

	// Check if BE store contains the funky chars.

	for (fc = 0, subj_len = strlen(real_subj_header), 
	      tmp_subj = (char *)real_subj_header; 
	     fc < subj_len; fc++, tmp_subj++) {

	    char c = *tmp_subj;
	    if ( (c == '\n') 
	      || (c == '\t') 
	      || (c == '\r')) {

		break;
	    }
	}
	subject = new char[fc+1];
	strncpy((char *)subject, real_subj_header, fc);
	subject[fc] = '\0';
    }

    
    // Skip the first (beginning) space in from; search for the next
    // occurring space.  
    

    const char *dateformat;
    if (info.header_values[1].length() > 0)
    {
	DtMailValueDate ds = ((info.header_values[1])[0])->toDate();

#define USE_MAX_TZ_SECONDS	(60 * 60 * 12)
	if (ds.dtm_date && ds.dtm_tz_offset_secs >= -USE_MAX_TZ_SECONDS
			&& ds.dtm_tz_offset_secs <=  USE_MAX_TZ_SECONDS)
	{
#define USE_YEAR_FORMAT_SECONDS	(60 * 60 * 24 * 180)
	    time_t now;
	    tm tm_struct;

	    SafeLocaltime(&ds.dtm_date, tm_struct);

            // Refer to strftime man page for explanation of the date format.
	    now = time(NULL);
	    if (USE_YEAR_FORMAT_SECONDS < now - ds.dtm_date)
	      dateformat = CATGETS(DT_catd, 1, 259, "%a %b %d  %Y");
	    else
	    {

#ifdef sun
	        dateformat = CATGETS(DT_catd, 1, 260, "%a %b %d %k:%M");
#else
	        dateformat = CATGETS(DT_catd, 1, 261, "%a %b %d %H:%M");
#endif
	    }

	    SafeStrftime(date, BUFSIZ, dateformat, &tm_struct);
	}
	else
	  // Couldn't get Date string from Message. Make it empty.
	  sprintf(date, "%s", " ");
    }
    else
    {
	tm epoch;
	memset(&epoch, 0, sizeof(tm));

        /* Refer to strftime man page for explanation of the date format.  */
	dateformat = CATGETS(DT_catd, 1, 259, "%a %b %d  %Y");
	SafeStrftime(date, BUFSIZ, dateformat, &epoch);
    }
    
    if (info.header_values[2].length() > 0) {
	contentLength = (int) strtol(*((info.header_values[2])[0]), NULL, 10);
    }
    else {
	contentLength = 0;
    }
    
    if (contentLength < 1000) {
	sprintf(contentStr, "%d", contentLength);
    }
    else if (contentLength < 1000000) {
	sprintf(contentStr, "%dK", contentLength / 1000);
    }
    else {
	sprintf(contentStr, "%dM", contentLength / 1000000);
    }
    
    // If we are to print the message_number in the header_list,
    // use msg_num as the first element in the sprintf.  
    // Introduce a %d at the beginning though.

    DtMail::MailBox * mbox;
    DtMail::MailRc * mailrc;
    DtMailEnv mail_error;
  
    mbox = parent()->mailbox();
    mailrc = mbox->session()->mailRc(mail_error);

    const char * value = NULL;
    mailrc->getValue(mail_error, "showmsgnum", &value);
    if (mail_error.isSet()) {
        // No message numbers ... keep usual "35" col. "Subject".
      if (showto)
              sprintf(buf, " To %-15.15s %-35.35s %-17.17s %-5.5s",
                      from,
                      subject,
                      date,
                      contentStr);
      else
              sprintf(buf, " %-18.18s %-35.35s %-17.17s %-5.5s",
                      from,
                      subject,
                      date,
                      contentStr);
    }
    else {
        //  To keep 80 column format use 5 less columns of
        //  subject , when msg numbers are on.
      if (showto)
              sprintf(buf, " To %-15.15s %-30.30s %-17.17s %-5.5s",
                      from,
                      subject,
                      date,
                      contentStr);
      else
              sprintf(buf, " %-18.18s %-30.30s %-17.17s %-5.5s",
                      from,
                      subject,
                      date,
                      contentStr);
    }
    if (NULL != value)
      free((void*) value);

    XmString header_text = XmStringCreateLocalized(buf);
    XmString item, item2, complete_header;

    if (has_attachments == DTM_TRUE) {
	item = XmStringConcat(attachment_glyph, header_text);
	XmStringFree(header_text);
    }
    else {
	item = XmStringConcat(no_attachment_glyph, header_text);
	XmStringFree(header_text);
    }

    if (new_msg == DTM_FALSE) {
	item2 = XmStringConcat(read_status, item);
	XmStringFree(item);
    }
    else {
	item2 = XmStringConcat(new_status, item);
	XmStringFree(item);
    }

    value = NULL;
    mailrc->getValue(mail_error, "showmsgnum", &value);
    if (mail_error.isSet()) {
//	complete_header = XmStringCopy(item2);
	complete_header = item2;
	_numbered = DTM_FALSE;
    }
    else {
	char num_buf[64];

        if (NULL != value)
          free((void*) value);

	mailrc->getValue(mail_error, "nerdmode", &value);
	if (mail_error.isSet()) {
	    sprintf(num_buf, "%4d ", msg_num);
	}
	else {
	    sprintf(num_buf, "%4x ", msg_num - 1);
	}

	XmString num_str = XmStringCreateLocalized(num_buf);
        complete_header = XmStringConcat(num_str, item2);
	XmStringFree(item2);
	XmStringFree(num_str);
	_numbered = DTM_TRUE;
    }
    if (NULL != value)
      free((void*) value);

    delete addr_seq;
    delete subject;
    
    delete [] buf;
    delete [] date;
    return(complete_header);
}

void
MsgScrollingList::shutdown()
{
    int num_entries, i;
    FORCE_SEGV_DECL(MsgStruct, tmpMS);
    DtMailMessageHandle tmpMH;
    DtMail::MailBox	*mbox=parent()->mailbox();
    DtMailEnv mail_error;

    // Initialize the mail_error.
    mail_error.clear();

    if (num_deleted_messages == 0) return;
    
    num_entries = _deleted_messages->length();

    for (i = 0; i < num_entries; i++) {
	tmpMS = _deleted_messages->at(i);
	tmpMH = tmpMS->message_handle;

//	mbox->deleteMsg(mail_error, tmpMH);
	
    }
}

#ifdef DEAD_WOOD
DtMailMessageHandle
MsgScrollingList::lastMsg()
{
    if (_msgs->length() > 0) {
	return((_msgs->at(_msgs->length() - 1))->message_handle);
    }
    else {  // Currently an empty folder
	return(NULL);
    }
}
#endif /* DEAD_WOOD */

void
MsgScrollingList::clearMsgs()
{
    if ( _msgs->length() > 0 )
	_msgs->clear();
}


void
MsgScrollingList::display_message_summary()
{
    parent()->message_summary(
			selected_item_position(),
			get_num_messages(), 
			get_num_new_messages(),
			get_num_deleted_messages());
}

void
MsgScrollingList::display_message(
    DtMailEnv &mail_error,
    int pos
)
{
    if ((pos > 0) && (pos <= session_message_number)) {
	// When loading mail headers, we need to make sure that
	// the XmList resources are all set at the same time to
	// avoid painting multiple times.  So here we collect
	// these resources.
	if (_xtarg_collector && _xmstr_collector)
	{
	    XmString *items = _xmstr_collector->GetItems();
            
	    // Keep a handle to the malloced string copy so that
            // we can free it after the XtSetValues
	    _selected_items = XmStringCopy (items[pos-1]);

	    _xtarg_collector->AddItemToList (XmNselectedItems,
	        (XtArgVal) &_selected_items);
	    _xtarg_collector->AddItemToList (
	        XmNselectedItemCount, 1);
	}
	else
	{
	    XmListSelectPos(_w, pos, FALSE);
        }

	this->extended_selection(mail_error, pos);
	if (mail_error.isSet()) {
	    // Return whatever error mailbox->get_next_msg() returned.
	    return;
	}
    }
    else return;
}

DtMailMessageHandle
MsgScrollingList::current_msg_handle()
{
    DtMailMessageHandle	msg_number;

    if ( _displayed_item_position > 0 )
	msg_number=msgno( _displayed_item_position );
    else
	msg_number=NULL;

    return msg_number;
}

void
MsgScrollingList::expunge(void)
{
    for (int i = _deleted_messages->length() - 1; i >= 0; i--) {
	_deleted_messages->remove_entry(i);
    }
    resetSessionNums();
    resetIndexNums();
    num_deleted_messages = 0;
    display_message_summary();
}

int
MsgScrollingList::resetIndexNums(void)
{
    int	m;
    int	length = _msgs->length();
    MsgStruct *ms;
    
    for (m = 0; m < length; m++)
    {
        ms = _msgs->at(m);
        ms->indexNumber = m;
    }

    length = _deleted_messages->length();
    for (m = 0; m < length; m++)
    {
        ms = _deleted_messages->at(m);
        ms->indexNumber = m;
    }

    return m;
}

int
MsgScrollingList::resetSessionNums(void)
{
    int	m;
    int	length = _msgs->length();
    MsgStruct *ms;
    
    for (m = 0; m < length; m++)
    {
        ms = _msgs->at(m);
        ms->sessionNumber = m;
    }
    session_message_number = m;
    return session_message_number;
}

void
MsgScrollingList::sort_messages(void)
{
    DtMail::MailBox	*mbox;
    enum sortBy		sortby;
    int			current_msg;

    MsgHndArray *selected_messages = selected();

    mbox = _parent->mailbox();
    sortby = _parent->last_sorted_by();

    // Sort array of message handles
    current_msg = _sorter->sortMessages(this, mbox, sortby);

    // The array of message handles is sorted. Now we need to update
    // the display preserving the selected state of the messages.
    updateListItems(current_msg, FALSE, selected_messages);

    delete selected_messages;

    _parent->last_sorted_by(sortby);
    display_message_summary();
}

//
// Layout out the row of labels above the scrolling list
//
void
MsgScrollingList::layoutLabels(
	Widget sender,
	Widget subject,
	Widget date,
	Widget size)
{
    // Save the input values 
    _sender_lbl = sender; 
    _subject_lbl = subject;
    _date_lbl = date;
    _size_lbl = size;

    layoutLabels();

}
//
// Layout out the row of labels above the scrolling list
//
void
MsgScrollingList::layoutLabels()
{
    // Width of fields.  +1 for spaces
    int	num_width = 5,
	sender_width = 18 + 1,
	subject_width = 35 + 1,
        date_width = 3+1 + 3+1 + 2+1 + 5 + 2; // DDD mmm dd hh:mm 2spaces
    int	char_width;		// Width of a single character
    int	n = 0;
    XmString	xmstr;
    XmFontList	font_list;

    // Calculate the width of date format to allocate the label 
    // width dynamically
    struct tm *tm;
    time_t clock;
    char   buf[40];
    
    clock = time((time_t *) 0);
    tm = localtime(&clock);  
    #ifdef sun
	SafeStrftime(buf,
	     sizeof(buf), 
	     CATGETS(DT_catd, 1, 222, "%a %b %d %k:%M"),
	     tm);
    #else
        SafeStrftime(buf, 
	     sizeof(buf), 
	     CATGETS(DT_catd, 1, 223, "%a %b %d %H:%M"),
	     tm);
    #endif


    // List uses a fixed width font. Therefore all characters are the
    // same size.  So we use a space to determine the width of a char
    xmstr = XmStringCreateLocalized(" ");
    XtVaGetValues(_w, XmNfontList, &font_list, NULL);
    char_width = XmStringWidth(font_list, xmstr);

    if (_numbered) {
	// Numbering is on
	n = num_width;
	subject_width -= num_width;
    } else {
	n = 0;
    }

    // XXX dipol: Need to take into account if the scrollbar is on the
    // right or left
    n += 3;	// Margin.

    XtWidgetGeometry geom;
    geom.request_mode = CWX;
    XtQueryGeometry(_sender_lbl, NULL, &geom);
    if (geom.x != (n * char_width)) {
        geom.x = (n * char_width);
        XtMoveWidget(_sender_lbl, geom.x, geom.y);
    } // Move the X location of the sender title

    XtVaSetValues(_sender_lbl, XmNx, n * char_width, NULL);
    n += sender_width;

    XtQueryGeometry(_subject_lbl, NULL, &geom);
    if (geom.x != (n * char_width)) {
        geom.x = (n * char_width);
        XtMoveWidget(_subject_lbl, geom.x, geom.y);
    } // Move the X location of the subject title

    XtVaSetValues(_subject_lbl, XmNx, n * char_width, NULL);
    n += subject_width;
    XtVaSetValues(_date_lbl, XmNx, n * char_width, NULL);
    n += date_width;
    XtVaSetValues(_size_lbl, XmNx, n * char_width, NULL);

    XmStringFree(xmstr);

    return;
}
