Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtmail / dtmail / MsgScrollingList.C
1 /*
2  *+SNOTICE
3  *
4  *      $TOG: MsgScrollingList.C /main/38 1998/12/10 19:08:02 mgreess $
5  *
6  *      RESTRICTED CONFIDENTIAL INFORMATION:
7  *      
8  *      The information in this document is subject to special
9  *      restrictions in a confidential disclosure agreement between
10  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
11  *      document outside HP, IBM, Sun, USL, SCO, or Univel without
12  *      Sun's specific written approval.  This document and all copies
13  *      and derivative works thereof must be returned or destroyed at
14  *      Sun's request.
15  *
16  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
17  *
18  *+ENOTICE
19  */
20
21 #include <EUSCompat.h>
22 #include <ctype.h>
23 #include <assert.h>
24 #include <Xm/Text.h>
25 #include <Dt/Dts.h>
26
27 #include <DtMail/DtMailError.hh>
28 #include <DtMail/IO.hh>
29 #include "DtMailGenDialog.hh"
30 #include "DtMailHelp.hh"
31 #include "Help.hh"      // Remove after fixing problem with empty time headers
32 #include "MailMsg.h"
33 #include "MemUtils.hh"
34 #include "MsgHndArray.hh"
35 #include "MsgScrollingList.hh"
36 #ifdef DEAD_WOOD
37 #include "QueryDialogManager.hh"
38 #endif /* DEAD_WOOD */
39 #include "RoamApp.h"
40 #include "RoamMenuWindow.h"
41 #include "Sort.hh"
42
43
44 #include <X11/IntrinsicP.h> // Include for moving X location of titles
45
46 extern force( Widget );
47
48 MsgScrollingList::MsgScrollingList(
49     RoamMenuWindow *menuwindow,
50     Widget parent,
51     char *name
52 )
53     : ScrollingList(
54         parent,
55         name
56 )
57 {
58     _parent=menuwindow;
59     _numbered = DTM_FALSE;
60     _selected_item_position=-1;
61     _displayed_item_position=-1;
62     _selection_on = FALSE;
63     _xmstr_collector = NULL;
64     _xtarg_collector = NULL;
65     _selected_items = NULL;
66     _sorter = new Sort ();
67
68     XtAddCallback( _w,
69                    XmNextendedSelectionCallback,
70                    (XtCallbackProc)
71                    &MsgScrollingList::extendedSelectionCallback,
72                    this );
73
74     _msgs = new MsgHndArray(1024);
75     _deleted_messages = new MsgHndArray(1024);
76     num_new_messages = 0;
77     num_deleted_messages = 0;
78     session_message_number = 0;
79    
80     // Can later initialize these from the last use of the session.
81     // Each folder will have some idea of which message was last
82     // read.  We should select and display it at next load, no?
83
84     _selected_item_position = 0;
85     _displayed_item_position = 0;
86
87     DtMailEnv mail_error;
88     // Initialize the mail_error.
89     mail_error.clear();
90     DtMail::Session * d_session = theRoamApp.session()->session();
91     DtMail::MailRc * mailrc = d_session->mailRc(mail_error);
92
93     if (mailrc) {
94         const char * value = NULL;
95         mailrc->getValue(mail_error, "showto", &value);
96         if (mail_error.isNotSet())
97           _header_info.number_of_names = 5;
98         else 
99           _header_info.number_of_names = 4;
100         if (NULL != value)
101           free((void*) value);
102     }
103     else
104          _header_info.number_of_names = 4;
105
106     // Set up array for 5 items. The DtMailMessageTo is used only 
107     // if showto is set, but create placeholder for 5th item in case 
108     // they apply props in this same session. Then we just have to
109     // toggle between 4 or 5 number_of_nanes.
110
111     _header_info.header_name = new (char* [5]);
112
113     _header_info.header_name[0] = NULL;
114     _header_info.header_name[1] = NULL;
115     _header_info.header_name[2] = NULL;
116     _header_info.header_name[3] = NULL;
117     _header_info.header_name[4] = NULL;
118
119     _header_info.header_name[0] = strdup(DtMailMessageSender);
120     _header_info.header_name[1] = strdup(DtMailMessageReceivedTime);
121     _header_info.header_name[2] = strdup(DtMailMessageContentLength);
122     _header_info.header_name[3] = strdup(DtMailMessageSubject);
123     _header_info.header_name[4] = strdup(DtMailMessageTo);
124 }
125
126 MsgScrollingList::~MsgScrollingList()
127 {
128     MsgStruct   *ms;
129     int         i, length;
130
131     for (i=0; i<5; i++)
132     {
133         if (_header_info.header_name[i])
134         {
135             free(_header_info.header_name[i]);
136             _header_info.header_name[i] = NULL;
137         }
138     }
139     delete _header_info.header_name;
140
141     for (i=0, length=_deleted_messages->length(); i<length; i++)
142     {
143         ms = _deleted_messages->at(i);
144         delete ms;
145     }
146     _deleted_messages->clear();
147     delete _deleted_messages;
148
149     for (i=0, length=_msgs->length(); i<length; i++)
150     {
151         ms = _msgs->at(i);
152         delete ms;
153     }
154     _msgs->clear();
155     delete _msgs;
156
157     delete _sorter;
158 }
159
160 Widget
161 MsgScrollingList::get_scrolling_list()
162 {
163     return(_w);
164 }
165
166 void
167 MsgScrollingList::items(
168     XmString items[],
169     int count )
170 {
171     XtVaSetValues( _w,
172                    XmNitems, items,
173                    XmNitemCount, count,
174                    NULL );
175 }
176
177 #ifdef DEAD_WOOD
178 void 
179 MsgScrollingList::addChooseCommand(
180     ChooseCmd *cmd
181
182 {
183     _choose=cmd;
184 }
185
186 void 
187 MsgScrollingList::addDeleteCommand(
188     DeleteCmd *cmd
189
190 {
191     _delete=cmd;
192 }
193 #endif /* DEAD_WOOD */
194
195
196 void
197 MsgScrollingList::select_next_item()
198 {
199     INSERT_STACK_PROBE
200     int num_msgs = 0;
201     FORCE_SEGV_DECL(MsgStruct, tmpMS);
202     DtMailEnv mail_error;
203
204     // Initialize the mail_error.
205     mail_error.clear();
206
207     XtVaGetValues( _w,
208                    XmNitemCount, &num_msgs,
209                    NULL );
210     
211     _selected_item_position = _displayed_item_position + 1;
212
213     if (_selected_item_position <= num_msgs && 
214         _selected_item_position > 0 ) {
215
216         tmpMS = get_message_struct(_selected_item_position);
217         if (tmpMS == NULL) {
218             return;
219         }
220         else {
221             // Deselect all items currently selected.
222             // display_and_select_message() will select, highlight
223             // and display the "next" message.
224
225             XmListDeselectAllItems(_w);
226
227             this->display_and_select_message(mail_error,
228                                         tmpMS->message_handle);
229             if (mail_error.isSet()) {
230                 // Post an exception here...
231             }
232         }
233     }
234     else {
235         return;
236     }
237 }
238
239 void
240 MsgScrollingList::select_prev_item()
241 {
242     INSERT_STACK_PROBE
243     int num_msgs;
244     FORCE_SEGV_DECL(MsgStruct, tmpMS);
245     DtMailEnv mail_error;
246
247     // Initialize the mail_error.
248     mail_error.clear();
249
250     XtVaGetValues( _w,
251                    XmNitemCount, &num_msgs,
252                    NULL );
253     
254     if( _displayed_item_position != 1 )
255         _selected_item_position = _displayed_item_position - 1 ;
256     else
257         _selected_item_position = _displayed_item_position ;
258     
259     if (_selected_item_position >= 1) {
260         tmpMS = get_message_struct(_selected_item_position);
261         if (tmpMS == NULL) {
262             return;
263         }
264         else {
265             // Deselect all items currently selected.
266             // display_and_select_message() will select, highlight
267             // and display the "previous" message.
268
269             XmListDeselectAllItems(_w);
270
271             this->display_and_select_message(mail_error,
272                                         tmpMS->message_handle);
273             if (mail_error.isSet()) {
274                 // Post an exception here...
275             }
276         }
277     }
278     else {
279         return;
280     }
281 }
282
283 DtMailMessageHandle
284 MsgScrollingList::msgno( 
285                          int index 
286                      ) 
287
288     if (index <= 0) {
289         return(NULL);
290     }
291     else {
292         return _msgs->at(index-1)->message_handle; 
293     }
294 }
295
296 MsgStruct*
297 MsgScrollingList::get_message_struct(
298                                      int index 
299                                  ) 
300
301     if (index <= 0) {
302         return(NULL);
303     }
304     else {
305         return(_msgs->at(index-1));
306     }
307 }
308
309 int 
310 MsgScrollingList::position( 
311                             DtMailMessageHandle msgno 
312                         ) 
313
314     return (_msgs->indexof(msgno))+1; 
315 }
316
317 #ifdef DEAD_WOOD
318 int 
319 MsgScrollingList::position( 
320                             MsgStruct *a_msg_struct
321                         ) 
322
323     return (_msgs->indexof(a_msg_struct))+1; 
324 }
325
326 void
327 MsgScrollingList::appendMsg(
328     DtMailMessageHandle msg_hndl
329 )
330 {
331     MsgStruct *newMS;
332
333     // A new message has come in.
334     // Increase the session_message_number which keeps track of the
335     // number of messages in this session for this folder (scrolling list).
336     // 
337     
338     newMS = new MsgStruct();
339     newMS->message_handle = msg_hndl;
340     newMS->indexNumber = session_message_number-num_deleted_messages;
341     newMS->sessionNumber = session_message_number;
342     newMS->is_deleted = FALSE;
343     _msgs->append(newMS);
344     session_message_number++;
345
346 }
347 #endif /* DEAD_WOOD */
348
349     
350 void
351 MsgScrollingList::insertMsg(
352     DtMailMessageHandle msg_hndl
353 )
354 {
355     MsgStruct *newMS;
356
357     // A new message has come in.
358     // Increase the session_message_number which keeps track of the
359     // number of messages in this session for this folder (scrolling list).
360     // 
361
362     newMS = new MsgStruct();
363     newMS->message_handle = msg_hndl;
364     newMS->indexNumber = session_message_number-num_deleted_messages;
365     newMS->sessionNumber = session_message_number;
366     newMS->is_deleted = FALSE;
367     _msgs->append(newMS);
368
369     session_message_number++;
370 }
371
372 void
373 MsgScrollingList::insertDeletedMsg(DtMailMessageHandle msg_hndl)
374 {
375     MsgStruct *newMS;
376
377     // A new message has come in.
378     // Increase the session_message_number which keeps track of the
379     // number of messages in this session for this folder (scrolling list).
380     // 
381
382     newMS = new MsgStruct();
383     newMS->message_handle = msg_hndl;
384     newMS->indexNumber = num_deleted_messages;
385     newMS->sessionNumber = session_message_number;
386     newMS->is_deleted = FALSE;
387     _deleted_messages->append(newMS);
388
389     session_message_number++;
390     num_deleted_messages++;
391 }
392
393 int
394 MsgScrollingList::load_headers(
395     DtMailEnv &mail_error
396 )
397 {
398
399     DtMailMessageHandle tmpMH;
400 #ifdef undef
401     XmString new_status, read_status;
402 #endif
403     XmString complete_header; // text of header w/ glyphs
404     int num_items = 0;
405     int select_item;
406     DtMailHeaderLine info;
407     DtMail::MailBox * mbox = this->parent()->mailbox();
408     DtMailEnv error;
409     DtMailBoolean first_new = DTM_TRUE;
410
411     // Create a class to collect the mail header XmStrings
412     // then get all the items from the current list.
413     _xmstr_collector = new XmStrCollector();
414
415 #ifdef undef
416 /* NL_COMMENT
417  * In a mailer container window's message scrolling list, a "N" appears
418  * to the left of a mail message header indicating that the mail message
419  * is "new" (just arrived and not yet viewed by the user).
420  * There is only space to display 1 character.  If "N" needs to be translated,
421  * please make sure the translation is only 1 character.
422  */
423    new_status = XmStringCreateLocalized(GETMSG(DT_catd, 1, 110, "N"));
424    read_status = XmStringCreateLocalized(" ");
425 #endif
426
427     // Allocate memory for the XmString array and initialize it.
428
429     int visible;
430     XtVaGetValues(_w, XmNvisibleItemCount, &visible, NULL);
431
432     // Retrieve the message_handles, and from them their headers.
433     // Create an XmString and toss it into the XmStrCollector. 
434
435     select_item = 0;
436     int n_vis = 0;
437     MsgStruct *ms;
438
439     for (tmpMH = mbox->getFirstMessageSummary(error, _header_info, info);
440          tmpMH && !error.isSet();
441          tmpMH = mbox->getNextMessageSummary(error, tmpMH, _header_info, info),
442          num_items++) {
443
444         DtMail::Message * msg = mbox->getMessage(error, tmpMH);
445         if (msg->flagIsSet(error, DtMailMessageDeletePending) == DTM_TRUE) {
446             insertDeletedMsg(tmpMH);
447             continue;
448         }
449         else {
450             insertMsg(tmpMH);
451             n_vis += 1;
452         }
453
454         ms = get_message_struct(get_num_messages());
455         complete_header = formatHeader(
456                                 info,
457                                 ms->indexNumber,
458                                 show_with_attachments(msg),
459                                 msg->flagIsSet(error, DtMailMessageNew));
460
461  
462         if (msg->flagIsSet(error, DtMailMessageNew) == DTM_TRUE) {
463             num_new_messages++;
464
465             // We want to select the last read message before the
466             // first new message.  We will select the first new
467             // message if it is the first message.
468
469             if (first_new) {
470                 first_new = DTM_FALSE;
471                 if (num_items > 0)
472                     select_item = num_items - 1;
473                 else
474                     select_item = 0;
475             }
476         }
477
478         // Insert the XmString into the array.
479         _xmstr_collector->AddItemToList (complete_header);
480
481         // Free the space allocated for info
482         // delete []info.header_values;
483         mbox->clearMessageSummary(info);
484     }
485
486     // If there were no new messages, select and display the last message.
487     if (first_new) {
488         select_item = num_items - 1;
489      }
490
491     select_item += 1; // Message slots start at 1.
492
493     select_item -= num_deleted_messages; // List does not have deleted msgs.
494
495     // Add the items to the XmList.
496     // All XmStrings are freed in the XmStrCollector destructor.
497     _xtarg_collector = new XtArgCollector;
498
499     // The first time the headers are loaded, they should all be loaded
500     // at the same time.  XtVaSetValues is used for this rather than
501     // XmListAddItems so that all the other resource will be set at
502     // the same time.  This prevents multiple repaints.
503     //
504     // However, in the case where only an item or two are being added,
505     // XmListAddItems should be used.  This prevents an unnecessary
506     // repaint in this case.
507     XmListAddItems (_w, _xmstr_collector->GetItems(),
508             _xmstr_collector->GetNumItems(), 0);
509
510     display_message_summary();
511     display_message(mail_error, select_item);
512
513     _xtarg_collector->SetValues(_w);
514     if (_selected_items)
515     {
516         XmStringFree (_selected_items);
517         _selected_items = NULL;
518     }
519
520     delete _xtarg_collector;
521     delete _xmstr_collector;
522     _xtarg_collector = NULL;
523     _xmstr_collector = NULL;
524
525     return(num_items);
526 }
527
528 void
529 MsgScrollingList::load_headers(
530     DtMailEnv &mail_error,
531     DtMailMessageHandle last
532 )
533 {
534     DtMailMessageHandle tmpMH;
535 #ifdef undef
536     XmString read_status, new_status;
537 #endif
538     XmString complete_header; // read status + attach + header_text.
539     int num_items;
540     int num_new = 0, num_vis = 0;
541     DtMailHeaderLine info;
542     DtMailEnv error;
543     DtMail::MailBox * mbox = this->parent()->mailbox();
544
545
546     // Create a class to collect the mail header XmStrings
547     // then get all the items from the current list.
548     _xmstr_collector = new XmStrCollector();
549
550     mail_error.clear();
551
552 #ifdef undef
553 /* NL_COMMENT
554  * In a mailer container window's message scrolling list, a "N" appears
555  * to the left of a mail message header indicating that the mail message
556  * is "new" (just arrived and not yet viewed by the user).
557  * There is only space to display 1 character.  If "N" needs to be translated,
558  * please make sure the translation is only 1 character.
559  */
560     new_status = XmStringCreateLocalized(GETMSG(DT_catd, 1, 111, "N"));
561     read_status = XmStringCreateLocalized(" ");
562 #endif
563
564     // Allocate memory for the XmString array and initialize it.
565
566     XtVaGetValues(_w, XmNvisibleItemCount, &num_items, NULL);
567
568     MsgStruct *ms;
569
570     for (tmpMH = mbox->getNextMessageSummary(error, last, _header_info, info);
571          tmpMH && !error.isSet();
572          tmpMH = mbox->getNextMessageSummary(error, tmpMH, _header_info, info),
573          num_new++) {
574
575         DtMail::Message * msg = mbox->getMessage(error, tmpMH);
576         if (msg->flagIsSet(error, DtMailMessageDeletePending) == DTM_TRUE) {
577             insertDeletedMsg(tmpMH);
578             continue;
579         }
580         else {
581             insertMsg(tmpMH);
582             num_vis += 1;
583         }
584
585         ms = get_message_struct(get_num_messages());
586         complete_header = formatHeader(
587                                 info,
588                                 ms->indexNumber,
589                                 show_with_attachments(msg),
590                                 msg->flagIsSet(error, DtMailMessageNew));
591
592         if (msg->flagIsSet(error, DtMailMessageNew) == DTM_TRUE) {
593             num_new_messages++;
594         }
595
596         // Insert the XmString into the array.
597         _xmstr_collector->AddItemToList (complete_header);
598
599         // Free the space allocated for info
600         // delete []info.header_values;
601         mbox->clearMessageSummary(info);
602     }
603
604     // Add the items to the XmList.
605     // All XmStrings are freed in the XmStrCollector destructor.
606     _xtarg_collector = new XtArgCollector;
607
608     // The first time the headers are loaded, they should all be loaded
609     // at the same time.  XtVaSetValues is used for this rather than
610     // XmListAddItems so that all the other resource will be set at
611     // the same time.  This prevents multiple repaints.
612     //
613     // However, in the case where only an item or two are being added,
614     // XmListAddItems should be used.  This prevents an unnecessary
615     // repaint in this case.
616     XmListAddItems (_w, _xmstr_collector->GetItems(),
617             _xmstr_collector->GetNumItems(), 0);
618
619         do_list_vis_adjustment();
620
621     display_message_summary();
622
623     _xtarg_collector->SetValues(_w);
624     if (_selected_items)
625     {
626         XmStringFree (_selected_items);
627         _selected_items = NULL;
628     }
629
630     delete _xtarg_collector;
631     delete _xmstr_collector;
632     _xtarg_collector = NULL;
633     _xmstr_collector = NULL;
634 }
635
636 void MsgScrollingList::do_list_vis_adjustment()
637 {
638     Widget list = _w; 
639     int numNew = _xmstr_collector->GetNumItems();
640     int focItm = _selected_item_position;
641
642     int numItems;
643     int cItmCnt, pItmCnt, sItmCnt;
644     int cFocItm, cTopItm, cBotItm, cInvItm;
645  
646     XtVaGetValues(list, XmNvisibleItemCount, &numItems, NULL);
647     XtVaGetValues(list, XmNitemCount, &cItmCnt, NULL);
648     XtVaGetValues(list, XmNselectedItemCount, &sItmCnt, NULL);
649     XtVaGetValues(list, XmNtopItemPosition, &cTopItm, NULL);
650     pItmCnt = cItmCnt - numNew;
651  
652     cBotItm = cTopItm + numItems - 1; //[cTopItm...cBotItm is our window
653  
654     //[User has chosen to view some messages and that view needs to be
655     //[maintained.
656     if (cBotItm != pItmCnt)
657         return;
658  
659     //[If Brand New Mailbox
660     if (cItmCnt <= numItems)
661         return;
662  
663     //[If no items selected, simply synch right till bottom
664     if (sItmCnt == 0) {
665         XmListSetBottomPos(list, cItmCnt);
666         return;
667     }
668  
669     // assert(cFocItm != -1);
670
671     cFocItm = focItm;      
672     cInvItm = pItmCnt - cBotItm; //[Num below bottom-most, or hidden
673  
674     if ((cFocItm <= cBotItm) && (cFocItm >= cTopItm)) {
675         int winM = cFocItm - cTopItm - cInvItm;
676         if (winM <= 0) { //[There is no scope of adjustment
677             return;
678         }
679         //[All the new messages can be accomodated w/o scrolling curr selection
680         if (numNew <= winM) {
681             XmListSetBottomPos(list, cItmCnt);
682             return;
683         }
684         //[All the new messages cannot be accomodated, but we will do best fit
685         else {
686             int numNotShow = numNew - winM;
687             XmListSetBottomPos(list, cItmCnt - numNotShow);
688             return;
689         }
690     }
691     else {
692         XmListSetBottomPos(list, cItmCnt);
693         return;
694     }
695 }
696
697 void 
698 MsgScrollingList::deleteSelected(Boolean silent)
699 {
700     // SR - Added stuff below.  Made code more efficient also.
701     FORCE_SEGV_DECL(MsgStruct, a_del_msg_struct);
702     FORCE_SEGV_DECL(MsgStruct, tmpMS);
703     FORCE_SEGV_DECL(ViewMsgDialog, tmpView);
704     DtMailMessageHandle tmpMH;
705     int  position_in_list, i;
706     FORCE_SEGV_DECL(int, position_list);
707     int position_count;
708     Boolean any_selected;
709     int num_msgs;
710     UndelFromListDialog *undel_dialog;
711     int first_selected_pos;
712     DtMailEnv mail_error, error;
713     DtMailBoolean       cur_state;
714     char *status_message, *str;
715
716     // Initialize the mail_error.
717     mail_error.clear();
718
719     XtVaGetValues( _w, XmNitemCount, &num_msgs, NULL );
720     any_selected = XmListGetSelectedPos(_w, &position_list, &position_count);
721     if (!any_selected) return;
722
723
724     // Of the items in the list, potentially many could be selected
725     // for delete.  And those selected for deleted need not be 
726     // contiguous necessarily.  Note the position of the item that 
727     // appears first in the list.  When the items are deleted, other
728     // items if any exist will shift up to take the places of the 
729     // deleted items.  You want to display the corresponding item that
730     // takes the "first position".
731
732     // Say, if you have messages A, B, C, D, Evisible in that order.
733     // You select B and C for delete.
734     // The first selected pos is 2.
735     // When B and C are deleted, D (and E...) move up.
736     // The new order is A, D, E...
737     // You want to display the second message which is D.
738
739     first_selected_pos = *position_list;
740     undel_dialog = parent()->get_undel_dialog();
741
742     for (i=0; i < position_count; i++ )
743     {
744         position_in_list = *(position_list + i);
745         a_del_msg_struct = get_message_struct(position_in_list);
746         _msgs->mark_for_delete(position_in_list - 1);
747         _deleted_messages->append(a_del_msg_struct);
748         if (undel_dialog)
749           undel_dialog->insertMsg(mail_error, a_del_msg_struct);
750         if (mail_error.isSet()) parent()->postErrorDialog(mail_error);
751
752         // See if there is a standalone view of the message.
753         // If there is, quit it.
754
755         tmpMH = a_del_msg_struct->message_handle;
756         tmpView = parent()->ifViewExists(tmpMH);
757         if (tmpView) tmpView->quit();
758
759         // Update the status of the message in the persistent store.
760         //
761         DtMail::Message * msg = _parent->mailbox()->getMessage(
762                                         error, 
763                                         a_del_msg_struct->message_handle);
764         msg->setFlag(error, DtMailMessageDeletePending);
765
766         // Check if message is new.  If new and it is being deleted,
767         // reduce the new messages count.
768
769         cur_state = msg->flagIsSet(error, DtMailMessageNew);
770
771         if (cur_state == DTM_TRUE) num_new_messages--;
772     }
773
774     _msgs->compact(0);
775
776     // Delete the items from the scrolling list
777
778      XmListDeletePositions(_w, position_list, position_count);
779     num_deleted_messages += position_count;
780
781     // XnListGetSelectedPos allocated memory for the array and
782     // wants us to free it.  See documentation.
783
784     num_msgs = _msgs->length();
785
786     // Always remove the current attachments from the attachment area
787     // regardless of whether this is the last message in the list or not
788     // We call clearAttachArea if there are any other messages in the folder
789     // as parseAttachments will be called which will clean up the attachment
790     // status window. In the case of deleting the last message we must call
791     // removeCurrentAttachments which cleans up the status window as well.
792     //
793     if (num_msgs == 0)
794       parent()->get_editor()->attachArea()->removeCurrentAttachments();
795     else
796       parent()->get_editor()->attachArea()->clearAttachArea();
797
798     if (num_msgs == 0)
799     {
800         //No more messages left; clear the editor.
801         parent()->get_editor()->textEditor()->set_contents("", 1);
802
803         // NOTE: Need to obtain the attachArea and clear it up too
804
805         _displayed_item_position = 0;   // Not displaying any.
806         _selected_item_position = 0;    // Reset...
807         free(position_list);
808         display_message_summary();
809         return;
810     }
811
812     // If there are messages 1, 2 and 3 and 2 is selected and deleted
813     // first_selected_pos will be 2;
814     // after removing it, will need to display the 2nd message
815     // (which will now be 3)
816     // If 2 and 3 have been deleted, and there is no 2nd message,
817     // display the nth message (in our example, n = 1)
818     
819     _selected_item_position = first_selected_pos;
820     if (_selected_item_position > num_msgs)
821       _selected_item_position = num_msgs; 
822
823     // Having determined which message to display, confirm that it is
824     // within the bounds.  Call display_message().
825     // FYI, display_message() sets the _displayed_item_position.
826
827     if ((_selected_item_position > 0) &&
828         (_selected_item_position <= num_msgs))
829     {
830         XmListSelectPos(_w, _selected_item_position, FALSE);
831         tmpMS = this->get_message_struct(_selected_item_position);
832         if (tmpMS == NULL)
833         {
834             free(position_list);
835             return;
836         }
837         else
838         {
839             this->display_message(mail_error, tmpMS->message_handle);
840             if (mail_error.isSet()) parent()->postErrorDialog(mail_error);
841         }
842     }
843
844     if (!silent)
845     {
846         if (position_count > 1)
847         {
848             /* NL_COMMENT
849             * The following sentence means %d number of mail messages have 
850             * been deleted from the mail folder.  This is the plural form 
851             * of the message that gets printed if more than one message 
852             * is moved.
853             */
854             str = GETMSG(DT_catd, 3, 84, "%d messages deleted"); 
855         }
856         else
857         {
858             /* NL_COMMENT
859             * The following sentence means %d number of mail messages have 
860             * been deleted from the mail folder.  This is the singular 
861             * form of the message that gets printed if only one message 
862             * is moved.
863             */
864             str = GETMSG(DT_catd, 3, 85, "%d message deleted"); 
865         }
866         status_message = new char[strlen(str) + 10];
867         sprintf(status_message, str, position_count);
868         parent()->message(status_message);
869         delete [] status_message;
870     }
871
872     updateListItems(-1, TRUE, NULL);
873     display_message_summary();
874
875     XtFree((char*) position_list);
876 }
877
878 DtMailBoolean
879 copyCallback(
880     DtMailCallbackOp,
881     const char *,
882     const char *,
883     void *,
884     ...
885 )
886 {
887     return(DTM_FALSE);
888 }
889
890 // copySelected() will either copy or move the selected messages
891 // into the container called destname.  If the delete_after_copy
892 // flag is set to TRUE, it is effectively a move; otherwise, it
893 // is a copy.  If the container named by destname is a relative
894 // path, but it isn't prefixed with a '+', then a '+' will be
895 // prepended to the name so that the open() call will "do the
896 // right thing."
897 // If silent is TRUE then no status messages are displayed and the
898 // destname is not added to the copy/move cache.
899
900 int
901 MsgScrollingList::copySelected(
902     DtMailEnv &mail_error,
903     char *destname, 
904     int delete_after_copy,
905     int silent
906 )
907 {
908     FORCE_SEGV_DECL(DtMail::MailBox, mbox);
909     FORCE_SEGV_DECL(int, position_list);
910     FORCE_SEGV_DECL(DtMail::MailBox, target);
911     DtMail::MailRc * mailrc;
912     DtMail::Session * d_session = theRoamApp.session()->session();
913     int position_count, position, i;
914     Boolean any_selected = FALSE;
915     DtMailMessageHandle msg;
916     char *status_message, *str;
917     char *newdestname;
918     RoamMenuWindow      *rmw;
919
920
921     any_selected = XmListGetSelectedPos(_w,
922                                         &position_list,
923                                         &position_count);
924     // If there aren't any selected messages, then there isn't
925     // anything for us to do.
926
927     if (!any_selected) {
928         display_no_message();
929         if (! silent)
930         {
931             char * helpId = NULL;
932             DtMailGenDialog *dialog = _parent->genDialog();
933
934             dialog->setToErrorDialog(
935                                 GETMSG(DT_catd, 3, 50, "Mailer"),
936                                 GETMSG(DT_catd, 2, 16, "No message selected."));
937             dialog->post_and_return(helpId);
938         }
939         return(1);
940     }
941
942     mbox = parent()->mailbox();
943     mailrc = mbox->session()->mailRc(mail_error);
944     if (!mailrc) {
945     // NL_COMMENT
946     // The following is an error message.  "mailrc" is the name of the
947     // mail resource file.  Translate as appropriate.
948     //
949     parent()->message(GETMSG(DT_catd, 2, 15,"Error - Unable to get mailrc."));
950     return(1);
951     }
952
953     // If the first character of destname is alphanumeric, we can
954     // safely assume that it is a relative path, so we prepend a
955     // '+' to it.
956     if (isalnum(destname[0])) {
957         // Make sure we allocate enough for the name + '+' + null terminator.
958         newdestname = (char *) malloc(strlen(destname) + 2);
959         memset(newdestname, 0, strlen(destname) + 2);
960         sprintf(newdestname, "+%s", destname);
961         char *path = d_session->expandPath(mail_error, newdestname);
962         target = theRoamApp.session()->open(mail_error,
963                                         path,
964                                         copyCallback,
965                                         NULL,
966                                         DTM_TRUE,  
967                                         DTM_FALSE,
968                                         DTM_FALSE);
969         free(newdestname);
970         free(path);
971         newdestname = NULL;
972     } else {
973         target = theRoamApp.session()->open(mail_error,
974                                         destname,
975                                         copyCallback,
976                                         NULL,
977                                         DTM_TRUE,  
978                                         DTM_FALSE,
979                                         DTM_FALSE);
980     }
981     if (mail_error.isSet()) {
982         // if the error is DTME_AlreadyOpened, we don't care.
983         // go ahead to clear the error
984         // otherwise, 
985         // We couldn't open the container, so we want to post an
986         // error dialog.
987         if ((DTMailError_t)mail_error == DTME_AlreadyOpened)
988          {
989            mail_error.clear();
990          } else {
991           parent()->postErrorDialog(mail_error);
992           return(0);
993          }
994     }
995
996     // Go through the selected messages and copy them to the
997     // specified container.
998
999     if ( position_count > 0 )
1000         parent()->busyCursor() ;
1001
1002     for (i=0; i < position_count; i++) {
1003         position = *(position_list + i);
1004         msg = msgno(position);
1005         DtMail::Message * dtmsg = mbox->getMessage(mail_error, msg);
1006         if (mail_error.isSet()) {
1007             parent()->normalCursor() ;
1008             parent()->postErrorDialog(mail_error);
1009             theRoamApp.session()->close(mail_error, target);
1010             return(0);
1011         }
1012
1013         mail_error.clear();
1014         target->copyMessage(mail_error, dtmsg);
1015         if (mail_error.isSet()) {
1016             parent()->normalCursor() ;
1017             parent()->postErrorDialog(mail_error);
1018             theRoamApp.session()->close(mail_error, target);
1019             return(0);
1020         }
1021
1022         parent()->normalCursor() ;
1023         
1024     }
1025
1026     
1027     rmw = theRoamApp.session()->getRMW(target);
1028     mail_error.clear();
1029     if (rmw) rmw->checkForMail(mail_error);
1030
1031     if (delete_after_copy) {
1032         deleteSelected();
1033
1034         if (i > 1) {
1035             // NL_COMMENT
1036             // The following sentence means %d number of mail messages have 
1037             // been moved to the %s mail folder.  The %s is the name of a 
1038             // mail folder.  This is the plural form of the message that gets
1039             // printed if more than one message is moved.
1040             //
1041             str = GETMSG(DT_catd, 3, 65, "%d messages moved to %s"); 
1042         } else {
1043             // NL_COMMENT
1044             // The following sentence means %d number of mail messages have 
1045             // been moved to the %s mail folder.  The %s is the name of a 
1046             // mail folder.  This is the singular form of the message that
1047             // gets printed if only one message is moved.
1048             //
1049             str = GETMSG(DT_catd, 3, 66, "%d message moved to %s"); 
1050         }
1051
1052     } else {
1053         if (i > 1) {
1054             // NL_COMMENT
1055             // The following sentence means %d number of mail messages have been
1056             // copied to the %s mail folder.  This is the plural form of the
1057             // message that gets printed if more than one message is copied.
1058             //
1059             str = GETMSG(DT_catd, 3, 67, "%d messages copied to %s"); 
1060         } else {
1061             // NL_COMMENT
1062             // The following sentence means %d number of mail messages have been
1063             // copied to the %s mail folder.  This is the singular form of the
1064             // message that gets printed if only one message is copied.
1065             //
1066             str = GETMSG(DT_catd, 3, 68, "%d message copied to %s"); 
1067         }
1068
1069     }
1070     newdestname = d_session->getRelativePath(mail_error, destname);
1071     status_message = new char[strlen(str) + strlen(newdestname) + 10];
1072     sprintf(status_message, str, i, newdestname);
1073
1074     if (!silent) {
1075         theRoamApp.globalAddToCachedContainerList(newdestname);
1076         parent()->message(status_message);
1077     }
1078
1079     free(newdestname);
1080
1081     theRoamApp.session()->close(mail_error, target);
1082
1083     delete [] status_message;
1084     return(0);
1085 }
1086
1087
1088 //-----------------------------------------------------------------------------
1089 // This method returns a list of the currently selected messages.  This list
1090 // must be deleted by the calling method.
1091 //-----------------------------------------------------------------------------
1092
1093 MsgHndArray * 
1094 MsgScrollingList::selected()
1095 {
1096     FORCE_SEGV_DECL(MsgStruct, a_msg_struct);
1097     FORCE_SEGV_DECL(int, position_list);
1098     int  position_in_list, i;
1099     int position_count;
1100     Boolean any_selected;
1101
1102     // Find out first if any have been selected.
1103     // If i has been selected, how many?
1104     // We need the number selected so that we can allocate 
1105     // space for that many mesasgeStructs to be returned.
1106
1107     any_selected = XmListGetSelectedPos(_w,
1108                                         &position_list, 
1109                                         &position_count);
1110
1111     // If nothing selected, return
1112
1113     if (!any_selected) return NULL;
1114
1115     //  Allocate memory for position_count number of messageStructs
1116     // in MsgHndArray.
1117
1118     MsgHndArray *msgList = new MsgHndArray(position_count);
1119
1120     for (i=0; i < position_count; i++ ) {
1121         position_in_list = *(position_list + i);
1122         a_msg_struct = get_message_struct(position_in_list);
1123         msgList->append(a_msg_struct);
1124     }
1125     return msgList;
1126 }
1127
1128 DtMailBoolean
1129 MsgScrollingList::show_with_attachments(DtMailMessageHandle msg_num)
1130 {
1131     DtMailBoolean       has_attachments = DTM_FALSE;
1132     DtMailEnv           mail_error;
1133     DtMail::MailBox     *mbox=parent()->mailbox();
1134     DtMail::Message     *msg = mbox->getMessage(mail_error, msg_num);
1135
1136     has_attachments = show_with_attachments(msg);
1137     return has_attachments;
1138 }
1139
1140
1141 DtMailBoolean
1142 MsgScrollingList::show_with_attachments(DtMail::Message * msg)
1143 {
1144     DtMail::BodyPart    *bp;
1145     DtMailEnv           error;
1146     DtMailBoolean       has_attachments = DTM_FALSE;
1147     DtMailBoolean       is_multipart = DTM_FALSE;
1148     int                 num_bodyParts;
1149     char                *type;
1150
1151     is_multipart = msg->flagIsSet(error, DtMailMessageMultipart);
1152     if (! is_multipart)
1153       return DTM_FALSE;
1154
1155     num_bodyParts = msg->getBodyCount(error);
1156     if (num_bodyParts > 1)
1157       return DTM_TRUE;
1158
1159     bp = msg->getFirstBodyPart(error);
1160     bp->getContents(error, NULL, NULL, &type, NULL, NULL, NULL);
1161     if (NULL != type)
1162     {
1163         char *attr;
1164
1165         attr = DtDtsDataTypeToAttributeValue(type, DtDTS_DA_IS_TEXT, NULL);
1166         if (attr && strcasecmp(attr, "true") != 0)
1167           has_attachments = DTM_TRUE;
1168         
1169         if (type)
1170           free(type);
1171         if (attr)
1172           DtDtsFreeAttributeValue(attr);
1173     }
1174     return has_attachments;
1175 }
1176
1177
1178 void
1179 MsgScrollingList::display_message(
1180     DtMailEnv   &mail_error,
1181     DtMailMessageHandle   msg_num
1182 )
1183 {
1184     int                 item_index;
1185     DtMail::MailBox     *mbox=parent()->mailbox();
1186     Editor*             rmw_editor;
1187     DtMailBoolean       cur_state;
1188     int num_selected;
1189     int num_bodyParts;
1190
1191     // If there is a status message displayed, clear it first.
1192     parent()->clear_message();
1193
1194     
1195         // We could have called display_msg from anywhere.
1196         // Need to calculate what is the position of that item
1197         // in the scrolling list, given the DtMailMessageHandle.
1198         // Determine the index at the _msgs array; increment by 1
1199         // since array begins at 0 and XmList begins at 1.
1200
1201     item_index = _msgs->indexof(msg_num);
1202     if (item_index < 0) return;
1203         
1204     _displayed_item_position = item_index + 1;
1205
1206     // Make sure the header is visible in the scrolling list.
1207     this->scroll_to_position(_displayed_item_position);
1208
1209     // Retrieve the header text and insert it. We need to retrieve
1210     // message from the handle and inset the headers.
1211     //
1212
1213     DtMail::Message * msg = mbox->getMessage(mail_error, msg_num);
1214     DtMail::Envelope * env = msg->getEnvelope(mail_error);
1215
1216
1217     // There are multiple paths to this place:
1218     // 1) user has already read this message but is re-reading it;
1219     // 2) user has undeleted this message (implicitly, they have
1220     //    read this message);
1221     // 3) user has not read this message yet.
1222     //
1223     // For (1), we don't need to do anything fancy.
1224     // For (2), we have already reset the IsDeleted flag while 
1225     // undeleting.
1226     // For (3), we need to reset the flag in store to indicate
1227     // its read.
1228
1229     cur_state = msg->flagIsSet(mail_error, DtMailMessageNew);
1230
1231     // If the message was previously new, we need to reset the list
1232     // item to remove the "N" on the item.  We do this by reconstructing
1233     // the header line without the "N".
1234     //
1235     if (cur_state == DTM_TRUE) {
1236         DtMailHeaderLine  info;
1237
1238         msg->resetFlag(mail_error, DtMailMessageNew);
1239
1240         mbox->getMessageSummary(mail_error, msg_num, _header_info, info);
1241
1242         MsgStruct *ms;
1243         ms = get_message_struct(_displayed_item_position);
1244
1245         XmString complete_header;
1246         complete_header = formatHeader(
1247                             info,
1248                             ms->indexNumber,
1249                             show_with_attachments(msg),
1250                             msg->flagIsSet(mail_error, DtMailMessageNew));
1251
1252         mbox->clearMessageSummary(info);
1253
1254         XmListReplaceItemsPos(_w, &complete_header,1, _displayed_item_position);
1255         XmStringFree(complete_header);
1256
1257         // The default selection policy is extended_select.
1258         // Problem:
1259         // User extend selects multiple items
1260         // The last item selected is a "N" message
1261         // We display the "N" message and have to replace it without
1262         // the "N".
1263         // There is no way to replace with something else and select at 
1264         // the same time.  
1265         // Therefre, we need to explicitly select the replacement.
1266         // The problem is: selecting the replacement deselects the other
1267         // selected items.
1268         // To work around this, we switch temporarily to MULTIPLE_SELECT,
1269         // select the replacement, and switch back to extend_select.
1270         // This will select the replacement and *not*  drop the
1271         // selection on the other selected items.
1272         // Say "Amen" to  OSF for such convoluted thinking!
1273
1274         XtVaGetValues(_w,
1275             XmNselectedItemCount, &num_selected,
1276             NULL);
1277
1278         if (num_selected > 1) {
1279             // Change to MULTIPLE_SELECT.
1280             // Select item
1281             // Change back to EXTEND_SELECT
1282             XtVaSetValues (_w,
1283                 XmNselectionPolicy, XmMULTIPLE_SELECT,
1284                 XmNselectionMode,   XmNORMAL_MODE,
1285                 NULL);
1286           
1287            // When loading mail headers, we need to make sure that
1288            // the XmList resources are all set at the same time to
1289            // avoid painting multiple times.  So here we collect
1290            // these resources.
1291            if (_xtarg_collector && _xmstr_collector)
1292            {
1293                 XmString *items = _xmstr_collector->GetItems();
1294         
1295                 // Keep a handle to the malloced string copy so that
1296                 // we can free it after the XtSetValues
1297                 _selected_items 
1298                     = XmStringCopy (items[_displayed_item_position - 1]);
1299
1300                 _xtarg_collector->AddItemToList (XmNselectedItems,
1301                     (XtArgVal) &_selected_items);
1302                 _xtarg_collector->AddItemToList (
1303                     XmNselectedItemCount, 1);
1304                 _xtarg_collector->AddItemToList (
1305                     XmNselectionPolicy, XmEXTENDED_SELECT);
1306             }
1307             else
1308             {
1309                 XmListSelectPos(_w, _displayed_item_position, FALSE);
1310
1311                 XtVaSetValues (_w,
1312                     XmNselectionPolicy, XmEXTENDED_SELECT,
1313                     XmNselectionMode,   XmNORMAL_MODE,
1314                     NULL);
1315             }
1316         }
1317         else {
1318            if (_xtarg_collector && _xmstr_collector)
1319            {
1320                 XmString *items = _xmstr_collector->GetItems();
1321
1322                 _selected_items 
1323                     = XmStringCopy (items[_displayed_item_position - 1]);
1324
1325                 _xtarg_collector->AddItemToList (XmNselectedItems,
1326                     (XtArgVal) &_selected_items);
1327                 _xtarg_collector->AddItemToList (
1328                     XmNselectedItemCount, 1);
1329             }
1330             else
1331             {
1332                 XmListSelectPos(_w, _displayed_item_position, FALSE);
1333             }
1334         }
1335
1336         num_new_messages--;
1337     }
1338
1339     display_message_summary();
1340
1341     // Display the message now.
1342     rmw_editor = parent()->get_editor()->textEditor();
1343     rmw_editor->disable_redisplay();
1344     rmw_editor->auto_show_cursor_off();
1345     rmw_editor->clear_contents();
1346
1347     num_bodyParts = msg->getBodyCount(mail_error);
1348
1349     char * status_string;
1350     DtMailBoolean firstBPHandled;
1351
1352     // Turn on the busy Cursor
1353
1354     parent()->busyCursor();
1355
1356     if (parent()->fullHeader())
1357       firstBPHandled =
1358         rmw_editor->set_message(msg, &status_string, Editor::HF_FULL);
1359     else
1360       firstBPHandled =
1361         rmw_editor->set_message(msg, &status_string, Editor::HF_ABBREV);
1362
1363     if (status_string) parent()->message(status_string);
1364     if (mail_error.isSet()) {} // do something
1365
1366     if ((num_bodyParts > 1) || (!firstBPHandled))
1367     {
1368         // If the message has attachments, then let the attach pane
1369         // handle attachments but not the first bodyPart (which has
1370         // already been handled here).
1371
1372         if (firstBPHandled) {
1373
1374             //  The first bodyPart has already been handled.
1375             // The others, beginning from the second, need to be parsed 
1376             // and put into the attachPane.
1377
1378             parent()->get_editor()->attachArea()->parseAttachments(
1379                                         mail_error,
1380                                         msg, 
1381                                         TRUE,
1382                                         2);
1383         }
1384         else {
1385             // The first bodyPart was not handled.
1386             // It may not have been of type text.
1387             // The attachment pane needs to handle all the bodyParts
1388             // beginning with the first.
1389
1390             parent()->get_editor()->attachArea()->parseAttachments(
1391                                         mail_error,
1392                                         msg, 
1393                                         TRUE,
1394                                         1);
1395         }
1396
1397         // Check for errors.
1398         // Manage the attach area to display attachments.
1399         if (mail_error.isSet()) {} // do something
1400
1401         parent()->get_editor()->manageAttachArea();
1402         parent()->activate_default_attach_menu();
1403     }
1404     else
1405     {
1406         parent()->deactivate_default_attach_menu();
1407         parent()->get_editor()->unmanageAttachArea();
1408     }
1409
1410     parent()->sync_work_area_size();
1411     rmw_editor->auto_show_cursor_restore();
1412
1413     // Turn on text editor and manage attachPane
1414     rmw_editor->set_to_top();
1415     rmw_editor->enable_redisplay();
1416
1417     parent()->normalCursor();
1418 }
1419
1420 void
1421 MsgScrollingList::display_and_select_message(
1422     DtMailEnv &mail_error,
1423     DtMailMessageHandle msg_num
1424 )
1425 {
1426     int         item_index;
1427
1428     // Need to calculate what is the position of that item
1429     // in the scrolling list, given the DtMailMessageHandle.
1430     // Determine the index at the _msgs array; increment by 1
1431     // since array begins at 0 and XmList begins at 1.
1432
1433     mail_error.clear();
1434
1435     item_index = _msgs->indexof(msg_num);
1436     if (item_index < 0) return;
1437         
1438     _displayed_item_position = item_index + 1;
1439     _selected_item_position = _displayed_item_position;
1440     
1441     // Select this message in the scrolling list and display
1442     // the message.
1443
1444    // When loading mail headers, we need to make sure that
1445    // the XmList resources are all set at the same time to
1446    // avoid painting multiple times.  So here we collect
1447    // these resources.
1448    if (_xtarg_collector && _xmstr_collector)
1449    {
1450         XmString *items = _xmstr_collector->GetItems();
1451
1452         // Keep a handle to the malloced string copy so that
1453         // we can free it after the XtSetValues
1454         _selected_items 
1455             = XmStringCopy (items[_displayed_item_position - 1]);
1456
1457         _xtarg_collector->AddItemToList (XmNselectedItems,
1458             (XtArgVal) &_selected_items);
1459         _xtarg_collector->AddItemToList (
1460             XmNselectedItemCount, 1);
1461     }
1462     else
1463     {
1464         XmListSelectPos(_w, _displayed_item_position, FALSE);
1465     }
1466     this->display_message(mail_error, msg_num);
1467
1468     return;
1469 }
1470
1471 void
1472 MsgScrollingList::select_all_and_display_last(
1473     DtMailEnv         & error
1474 )
1475  
1476 {
1477     register int                item_pos;
1478     int                         num_items;
1479     MsgHndArray                 * msgHandles = get_messages();
1480  
1481     error.clear();
1482
1483     if (this->get_num_messages() == 0) return;
1484
1485     // If no message selected, return.
1486
1487     if (this->get_selected_item() == 0) return;
1488
1489     XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
1490     //
1491     // We have to go to the end of the list and go backwards.
1492     // We display the last one, and select the rest.
1493     //
1494     // A NULL terminated list.
1495     //
1496  
1497     XmListDeselectAllItems(baseWidget());
1498     XtVaGetValues(baseWidget(), XmNitemCount, &num_items, NULL);
1499  
1500     for (item_pos = 1; item_pos < num_items; item_pos++) {
1501         XmListSelectPos(baseWidget(), item_pos, FALSE);
1502     }
1503  
1504     // Invoke the selection callback on the last item.
1505     display_and_select_message(error, 
1506                                 msgHandles->at(item_pos-1)->message_handle);
1507     XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
1508     XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
1509 }
1510
1511 void
1512 MsgScrollingList::select_all_and_display_last(
1513     DtMailEnv   & error,
1514     DtMailMessageHandle *handleArray,
1515     unsigned int           elements
1516 )
1517 {
1518   register int          handleOffset = 0;
1519   register int          item_pos;
1520
1521   error.clear();
1522
1523   XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
1524   //
1525   // We have to go to the end of the list and go backwards.
1526   // We display the last one, and select the rest.
1527   //
1528   // A NULL terminated list.
1529   //
1530   handleOffset = elements;
1531
1532   XmListDeselectAllItems(baseWidget());
1533
1534   while (--handleOffset >= 0 && error.isNotSet()) {
1535
1536     item_pos = _msgs->indexof(handleArray[handleOffset]);       // Get position
1537     if (item_pos < 0) {
1538       continue;
1539     }
1540     
1541     //
1542     // Select this message in the scrolling list and IF
1543     // it is the last message, display it.
1544     //
1545     if (handleOffset == elements - 1) {
1546       display_and_select_message(error, handleArray[handleOffset]);
1547     } else {
1548       XmListSelectPos(_w, item_pos + 1, FALSE);
1549     }
1550   }
1551   XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
1552   XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
1553   return;
1554 }
1555
1556
1557 void
1558 MsgScrollingList::display_no_message()
1559 {
1560
1561         /* NL_COMMENT
1562          * No mail message has been selected by the user.
1563          */
1564
1565     parent()->message(GETMSG(DT_catd, 2, 16, "No message selected."));
1566     _displayed_item_position = 0;
1567     _selected_item_position = 0;
1568
1569     parent()->get_editor()->textEditor()->clear_contents();
1570     parent()->get_editor()->attachArea()->removeCurrentAttachments();
1571 }
1572
1573 // In need of a simple but useful optimization here.
1574 // By the time we get to this method, we already have displayed
1575 // the selected message in the lower section of the combo window.
1576 // Instead of having to parse the messageHandle and construct the
1577 // text buffer to display, we can just get the parsed-and-formatted
1578 // text from the lower section of the combo window and stick it into
1579 // the VMD's editor.
1580 // This will save us the effort of parsing and formatting a message
1581 // 
1582
1583 void 
1584 MsgScrollingList::viewInSeparateWindow(DtMailEnv &mail_error)
1585 {
1586     FORCE_SEGV_DECL(const char, title);
1587     FORCE_SEGV_DECL(char, header_txt);
1588     FORCE_SEGV_DECL(MsgStruct, tmpMS);
1589     DtMailMessageHandle  msgHandle;
1590     DtMail::MailBox     *mbox=parent()->mailbox();
1591     ViewMsgDialog       *newview;
1592     int                  num_bodyParts;
1593     Editor*              vmd_editor;
1594
1595     DtMailEnv            error;
1596     DtMail::Session     *d_session = theRoamApp.session()->session();
1597     DtMail::MailRc      *mail_rc = d_session->mailRc(error);
1598     const char          *value = NULL;  
1599  
1600     // If no message selected, return.
1601     if (this->get_selected_item() <= 0) return;
1602
1603     // Double-check.  If none selected, return
1604     MsgHndArray *selected_msgs = this->selected();
1605     if (!selected_msgs) return;
1606
1607     for (int a_msg_num = 0; a_msg_num < selected_msgs->length(); a_msg_num++)
1608     {
1609         tmpMS = selected_msgs->at(a_msg_num);
1610         if (tmpMS == NULL) return;
1611            
1612         msgHandle = tmpMS->message_handle;
1613         newview = parent()->ifViewExists(msgHandle);
1614            
1615         if (newview != NULL)
1616         {
1617             /* NL_COMMENT
1618              * The current mail message selected is already displayed in a
1619              * separate window.  Therefore this 'separate' window will be
1620              * raised in front of existing windows so the user can see it.
1621              */
1622             parent()->message(
1623                         GETMSG(
1624                                 DT_catd, 3, 69,
1625                                 "View already exists.  Raising it."));
1626             newview->displayInCurrentWorkspace();
1627             return;
1628         } 
1629
1630         // No view exists.  Display it.  For feedback, set busyCursor().
1631         parent()->busyCursor();
1632         mail_rc->getValue(error, "separatemessageviewer", &value);
1633         if (error.isSet())
1634           newview = new ViewMsgDialog(parent(), xmDialogShellWidgetClass);
1635         else
1636         {
1637             if (value) free((void*) value);
1638             newview = new ViewMsgDialog(parent(), topLevelShellWidgetClass);
1639         }
1640         parent()->registerDialog(newview);
1641         newview->initialize();
1642         vmd_editor = newview->get_editor()->textEditor();
1643            
1644         // Set the VMD's msgHandle. This unique handle is what is
1645         // used to raise the VMD when the same message is double
1646         // clicked on later.
1647         newview->msgno(msgHandle);
1648            
1649         DtMailEnv error;
1650         DtMail::Message * msg = mbox->getMessage(error, msgHandle);
1651         DtMail::Envelope * env = msg->getEnvelope(error);
1652            
1653         DtMailValueSeq title_value;
1654         env->getHeader(error, DtMailMessageSubject, DTM_TRUE, title_value);
1655         if (error.isSet())
1656           title = "NO SUBJECT!";
1657         else
1658           title = *(title_value[0]);
1659            
1660         newview->title((char *)title);
1661         newview->auto_show_cursor_off();
1662            
1663         // Unset the error produced when obtaining title...
1664         mail_error.clear();
1665         vmd_editor->disable_redisplay();
1666            
1667         char * status_string;
1668         DtMailBoolean firstBPHandled;
1669            
1670         if (parent()->fullHeader())
1671           firstBPHandled = newview->get_editor()->textEditor()->set_message(
1672                                         msg, &status_string, Editor::HF_FULL);
1673         else
1674           firstBPHandled = newview->get_editor()->textEditor()->set_message(
1675                                         msg, &status_string, Editor::HF_ABBREV);
1676            
1677         // If the message has attachments, then let the attach pane
1678         // handle attachments but not the first bodyPart (which has
1679         // already been handled here).
1680         num_bodyParts = msg->getBodyCount(mail_error);
1681         if (mail_error.isSet()) {} // do something
1682            
1683         if ((num_bodyParts == 1) && firstBPHandled)
1684         {
1685             newview->get_editor()->unmanageAttachArea();
1686             newview->deactivate_default_attach_menu();
1687         }
1688         else if ((num_bodyParts > 1) || (!firstBPHandled))
1689         {
1690             // If the message has attachments, then let the attach pane
1691             // handle attachments but not the first bodyPart (which has
1692             // already been handled here).
1693                
1694             if (firstBPHandled)
1695             {
1696                 //  The first bodyPart has already been handled.
1697                 // The others, beginning from the second, need to be parsed 
1698                 // and put into the attachPane.
1699                 newview->get_editor()->attachArea()->parseAttachments(
1700                                                 mail_error, msg, TRUE, 2);
1701             }
1702             else
1703             {
1704                 // The first bodyPart was not handled.
1705                 // It may not have been of type text.
1706                 // The attachment pane needs to handle all the bodyParts
1707                 // beginning with the first.
1708                 newview->get_editor()->attachArea()->parseAttachments(
1709                                                 mail_error, msg, TRUE, 1);
1710             }
1711                
1712             // Check for errors.
1713             // Manage the attach area to display attachments.
1714             if (mail_error.isSet()) {} // do something
1715             newview->get_editor()->manageAttachArea();
1716             newview->activate_default_attach_menu();
1717         }
1718            
1719         newview->set_to_top();
1720         newview->auto_show_cursor_restore();
1721         vmd_editor->enable_redisplay();
1722         newview->manage();
1723     }
1724
1725     parent()->normalCursor();
1726 }
1727
1728 // defaultAction() gets called *after* extendedSelectionCallback() is
1729 // called.  
1730 // Display the message in extendedSelectionCallback().
1731 // viewInSeparateWindow() in defaultAction().
1732
1733 void 
1734 MsgScrollingList::defaultAction(Widget, XtPointer, XmListCallbackStruct *cbs)
1735 {
1736     FORCE_SEGV_DECL(MsgStruct, tmpMS);
1737     DtMailEnv mail_error;
1738
1739     // Initialize the mail_error.
1740     mail_error.clear();
1741    _selected_item_position = cbs->item_position;
1742
1743    tmpMS = get_message_struct(_selected_item_position);
1744    if (tmpMS == NULL)
1745      return;
1746    else
1747    {
1748        this->viewInSeparateWindow(mail_error);
1749        if (mail_error.isSet()) {} // Post dialog indicating error
1750    }
1751 }
1752
1753     
1754 void
1755 MsgScrollingList::extendedSelectionCallback(
1756     Widget ,                    // w
1757     XtPointer clientData,
1758     XmListCallbackStruct *cbs
1759 )
1760 {
1761     int last_clicked_on_pos;
1762     int above_selected_pos;
1763     int tmp_selected_pos;
1764     int i, num_selected;
1765     DtMailEnv mail_error;
1766
1767     // Initialize the mail_error.
1768     mail_error.clear();
1769
1770     
1771     Boolean IS_SELECTION = FALSE;
1772     Boolean SELECTION_MADE = FALSE;
1773
1774     MsgScrollingList *obj=(MsgScrollingList *) clientData;
1775
1776     // If all items have been deselected
1777     if (cbs->selected_item_count == 0) {
1778         obj->extended_selection(mail_error, 0);
1779         if (mail_error.isSet()) {
1780             return;
1781         }
1782         SELECTION_MADE = TRUE;
1783     }
1784     else {
1785         
1786         last_clicked_on_pos = cbs->item_position;
1787
1788     // Check to see if this was a selection or deselection
1789     // We do that by seeing if last_clicked_on_pos is in the 
1790     // selected_item_positions array.  If it is, then it is a 
1791     // selection; if its not there, then its a deselection.
1792
1793         num_selected =  cbs->selected_item_count;
1794     
1795         for (i = 0; i < num_selected; i++) {
1796             if (last_clicked_on_pos == cbs->selected_item_positions[i]) {
1797                 // Yes, it was a selection
1798             
1799                 IS_SELECTION = TRUE;
1800                 obj->extended_selection(mail_error, last_clicked_on_pos);
1801                 if (mail_error.isSet()) {
1802                     return;
1803                 }
1804
1805                 SELECTION_MADE = TRUE;
1806             }
1807         }
1808
1809     // If it was not a selection, then we need to find the selected
1810     // item nearest to the deselected item.  We have a choice there -
1811     // we can find the nearest one above, or the nearest one below 
1812     // the deselected item.  Let's find the nearest one above.
1813     // We do that by cruising through the selected_item_positions array.
1814     // Motif arranges the array in ascending order, no matter what order
1815     // you selected the items!  If you deselect an item, the items on
1816     // either side of it are the ones above and below it.  We use that
1817     // ordering to now select and display the one above it.
1818     //
1819
1820         if (!IS_SELECTION) {
1821
1822         // If the first selected item is below the deselected item,
1823         // it follows that all selected items are below the deselected
1824         // item.  Display the first selected item and be done.
1825
1826             if (cbs->selected_item_positions[0] > last_clicked_on_pos) {
1827                 obj->extended_selection(mail_error,
1828                                         cbs->selected_item_positions[0]);
1829                 if (mail_error.isSet()) {
1830                     return;
1831                 }
1832                 SELECTION_MADE = TRUE;
1833             } 
1834             // If the last selected item is above the deselected item,
1835             // it follows that all selected items are above the deselected
1836             // item.  Display the last selected item and be done.
1837             else if (cbs->selected_item_positions[num_selected - 1] <
1838                                 last_clicked_on_pos) {
1839                 obj->extended_selection(mail_error,
1840                            cbs->selected_item_positions[num_selected - 1]);
1841                 if (mail_error.isSet()) {
1842                     return;
1843                 }
1844                 SELECTION_MADE = TRUE;
1845             }
1846             // Otherwise, the deselected item must lie in between other
1847             // selected items.  We choose to find the closest selected
1848             // item above the deselected item.
1849             else {
1850                 // There are selected items that are above the deselected 
1851                 // item.
1852                 // Need to find the one that is both above the deselected 
1853                 // item and closest to it.
1854                 // Iterate until you find the nearest above; select it and
1855                 // drop out of loop when after selection.
1856
1857                 for (i = 0; i < (num_selected - 1) && !SELECTION_MADE; i++) {
1858                     above_selected_pos = cbs->selected_item_positions[i];
1859                     tmp_selected_pos = cbs->selected_item_positions[i + 1];
1860                     if (tmp_selected_pos > last_clicked_on_pos) {
1861                         obj->extended_selection(mail_error,
1862                                         above_selected_pos);
1863                         if (mail_error.isSet()) {
1864                             return;
1865                         }
1866                         SELECTION_MADE = TRUE;
1867                     }
1868                 }
1869             }
1870         }
1871     }
1872 }
1873
1874
1875 void
1876 MsgScrollingList::extended_selection(
1877     DtMailEnv &mail_error,
1878     int position 
1879 )
1880 {
1881    FORCE_SEGV_DECL(MsgStruct, tmpMS);
1882
1883    // Disable the SaveAttachments menu item first.  
1884    // It gets enabled when an attachment is selected.
1885
1886    _parent->all_attachments_deselected();
1887
1888    if (position == 0) {  // all items deselected.
1889        _parent->hideAttachArea();
1890        this->display_no_message();
1891        _selection_on = FALSE;
1892        _parent->deactivate_default_message_menu();
1893        return;
1894    }
1895
1896    // if there were no selected item(s) before and now we
1897    // have one/some, we need to turn on the message menu at
1898    // the parent level.
1899
1900    if (!_selection_on) {
1901        _selection_on = TRUE;
1902        _parent->activate_default_message_menu();
1903    }
1904    
1905    // If selected message is the displayed message, return.
1906    // No need to redisplay the same message.
1907
1908    if (position == _selected_item_position) return;
1909
1910    // Retrieve message...
1911    // Display the selected message...
1912    _selected_item_position = position;
1913
1914    tmpMS = get_message_struct(_selected_item_position);
1915    if (tmpMS == NULL) {
1916        return;
1917     }
1918    else {
1919        display_message_summary();
1920        this->display_message(mail_error, tmpMS->message_handle);
1921        if (mail_error.isSet()) {
1922            return;
1923        }
1924    }
1925 }
1926
1927
1928 int
1929 MsgScrollingList::get_selected_item()
1930 {
1931     return(_selected_item_position);
1932 }
1933
1934 int
1935 MsgScrollingList::get_displayed_item()
1936 {
1937     return(_displayed_item_position);
1938 }
1939
1940 void
1941 MsgScrollingList::scroll_to_bottom()
1942 {
1943     XmListSetBottomPos( this->baseWidget() , 0 );
1944 }
1945
1946 // Scroll the list so that the item at position is in the
1947 // middle of the scrolling list.  If the number of items
1948 // that the scrolling list can display at one time is greater
1949 // than the total number of items, then display them all.
1950 // If the item at position is close to the bottom of the list
1951 // make sure we make as many items visible as possible.
1952 //
1953 // If the item at position is already visible, then don't
1954 // do anything.
1955
1956 void
1957 MsgScrollingList::scroll_to_position(
1958     int position
1959 )
1960 {
1961     int top, visible, total;
1962     int top_pos;
1963
1964     // Determine the position of the header that we want to select.
1965     // If the XtArgCollector exists, then add
1966     // the resources to the XtArgCollector so that we can prevent
1967     // multiple redisplays in the XmList widget.
1968     if (_xmstr_collector)
1969     {
1970         XtVaGetValues( _w,
1971                    XmNtopItemPosition, &top,
1972                    XmNvisibleItemCount, &visible,
1973                    NULL );
1974         total = _xmstr_collector->GetNumItems();
1975     }
1976     else
1977     {
1978         XtVaGetValues( _w,
1979                    XmNtopItemPosition, &top,
1980                    XmNvisibleItemCount, &visible,
1981                    XmNitemCount, &total,
1982                    NULL );
1983     }
1984
1985     if (( position < top ) || ( position >= top+visible )) {
1986
1987         if ((total <= visible) || (position <= visible/2)) {
1988             // If we can display them all, make the first item appear
1989             // at the top of the list.
1990             top_pos = 1;
1991         } else if (position > (total-(visible/2))) {
1992             top_pos = total - visible + 1;
1993         } else {
1994             top_pos = position - visible/2 + 1;
1995         }
1996
1997         // Determine the position of the header that we want to select.
1998         // If the XtArgCollector exists, then add
1999         // the resources to the XtArgCollector so that we can prevent
2000         // multiple redisplays in the XmList widget.
2001         if (_xtarg_collector)
2002             _xtarg_collector->AddItemToList (
2003                 XmNtopItemPosition, top_pos);
2004         else
2005             XmListSetPos (_w, top_pos);
2006     }
2007 }
2008
2009 void
2010 MsgScrollingList::undelete_messages(MsgHndArray *tmpMHlist)
2011 {
2012     FORCE_SEGV_DECL(MsgStruct, tmpMS);
2013     FORCE_SEGV_DECL(XmString, deleted_headers);
2014     int  i, num_entries, entry_position, del_pos;
2015     int whichToSelectDisplay = 0;
2016     DtMail::MailBox     *mbox=parent()->mailbox();
2017     DtMail::Message * tmpMsg;
2018     DtMailEnv mail_error;
2019     DtMailMessageHandle tmpMH;
2020 #ifdef undef
2021     XmString read_status, new_status;
2022 #endif
2023     XmString complete_header;   // read status + glyph + header_text.
2024
2025 #ifdef undef
2026 /* NL_COMMENT
2027  * In a mailer container window's message scrolling list, a "N" appears
2028  * to the left of a mail message header indicating that the mail message
2029  * is "new" (just arrived and not yet viewed by the user).
2030  * There is only space to display 1 character.  If "N" needs to be translated,
2031  * please make sure the translation is only 1 character.
2032  */
2033 //
2034 // gregl - new_status and read_status are not used in this function.
2035 //         either comment them out (like I'm doing) or free them.
2036 //
2037     new_status = XmStringCreateLocalized(GETMSG(DT_catd, 1, 112, "N"));
2038     read_status = XmStringCreateLocalized(" ");
2039 #endif
2040
2041     // Initialize the mail_error.
2042     mail_error.clear();
2043     num_entries = tmpMHlist->length();
2044     if (num_entries == 0) return;
2045
2046     // Deselect all items currently selected.
2047     // display_and_select_message() will select, highlight
2048     // and display the last "undeleted" message.
2049     
2050     XmListDeselectAllItems(_w);
2051
2052     XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
2053
2054     for (i = 0; i < num_entries; i++)
2055     {
2056         DtMailHeaderLine info;
2057
2058         tmpMS = tmpMHlist->at(i);
2059         tmpMS->is_deleted = FALSE;
2060
2061         // Reset the flag of the message in message store so that the  
2062         // message will not be expunged when the folder is quit.
2063         // 
2064
2065         tmpMsg = mbox->getMessage(mail_error, tmpMS->message_handle);
2066         tmpMsg->resetFlag(mail_error, DtMailMessageDeletePending);
2067         tmpMH = tmpMS->message_handle;
2068         
2069         // Remove chosen item from list of deleted messages;
2070         // insert it back into _msgs at the right place (which is 
2071         // determined by session_number of retrieved MsgStruct).
2072         // Insert back into scrolling list for visual display
2073         // at the position session_number.
2074
2075         entry_position = _msgs->insert(tmpMS);
2076
2077         // Increment by one, because the index into the scrolling
2078         // list is always one greater than the index into the
2079         // message handle array that we got the index from.
2080
2081         entry_position = entry_position + 1;
2082
2083         // Maintain the assumption that the item at entry_position
2084         // is the selected item
2085
2086         _selected_item_position = entry_position;
2087
2088         mbox->getMessageSummary(mail_error, tmpMS->message_handle,
2089                                 _header_info, info);
2090         DtMail::Message * msg = mbox->getMessage(mail_error, tmpMH);
2091         complete_header = formatHeader(
2092                            info,
2093                            tmpMS->indexNumber,
2094                            show_with_attachments(msg),
2095                            msg->flagIsSet(mail_error, DtMailMessageNew));
2096
2097         mbox->clearMessageSummary(info);
2098
2099         if (msg->flagIsSet(mail_error, DtMailMessageNew) == DTM_TRUE)
2100           num_new_messages++;
2101
2102         XmListAddItem(_w, complete_header, entry_position);
2103         XmListSelectItem(_w, complete_header, FALSE);
2104         XmStringFree(complete_header);
2105
2106         // Get position of undeleted message structure in _deleted_messages
2107         // and remove the entry from _deleted_messages
2108
2109         del_pos = _deleted_messages->indexof(tmpMS);
2110         _deleted_messages->remove_entry(del_pos);
2111     }
2112
2113     // Display this message and select it.
2114
2115     this->display_message(mail_error, tmpMS->message_handle);
2116     XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
2117     XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
2118
2119     if (mail_error.isSet()) return;
2120
2121     num_deleted_messages -= num_entries;
2122
2123     MsgHndArray *selected_messages = selected();
2124     updateListItems(-1, TRUE, selected_messages);
2125     delete selected_messages;
2126
2127     display_message_summary();
2128 }
2129
2130 void
2131 MsgScrollingList::undelete_last_deleted()
2132 {
2133     FORCE_SEGV_DECL(MsgStruct, tmpMS);
2134     int entry_position;
2135     int len;
2136     DtMail::MailBox     *mbox=parent()->mailbox();
2137     UndelFromListDialog *undel_dialog;
2138     DtMailEnv mail_error;
2139     DtMailHeaderLine info;
2140     DtMail::Message * tmpMsg;
2141     DtMailMessageHandle tmpMH;
2142 #ifdef undef
2143     XmString read_status, new_status;
2144 #endif
2145     XmString complete_header;   // read status + glyph + header_text.
2146
2147 #ifdef undef
2148 /* NL_COMMENT
2149  * In a mailer container window's message scrolling list, a "N" appears
2150  * to the left of a mail message header indicating that the mail message
2151  * is "new" (just arrived and not yet viewed by the user).
2152  * There is only space to display 1 character.  If "N" needs to be translated,
2153  * please make sure the translation is only 1 character.
2154  */
2155     new_status = XmStringCreateLocalized(GETMSG(DT_catd, 1, 113, "N"));
2156     read_status = XmStringCreateLocalized(" ");
2157 #endif
2158
2159
2160     if (num_deleted_messages == 0) return;
2161
2162     // Initialize the mail_error.
2163     mail_error.clear();
2164
2165
2166     // Delete the message from the Deleted Messages Dialog.
2167     undel_dialog = parent()->get_undel_dialog();
2168     if (undel_dialog)
2169         undel_dialog->undelLast();
2170     
2171     // Restore the message in RoamMenuWindow:MessageScrollingList.
2172     len = _deleted_messages->length();
2173
2174     tmpMS = _deleted_messages->at(len - 1);
2175     tmpMS->is_deleted = FALSE;
2176
2177     // Reset the flag of the message in message store so that the  
2178     // message will not be expunged when the folder is quit.
2179     // 
2180
2181     tmpMsg = mbox->getMessage(mail_error, tmpMS->message_handle);
2182     tmpMsg->resetFlag(mail_error, DtMailMessageDeletePending);
2183     tmpMH = tmpMS->message_handle;
2184
2185     // Remove chosen item from list of deleted messages;
2186     // insert it back into _msgs at the right place (which is 
2187     // determined by session_number of retrieved MsgStruct).
2188     // Insert back into scrolling list for visual display
2189     // at the position session_number.
2190
2191     entry_position = _msgs->insert(tmpMS);
2192
2193     // Increment by one, because the index into the scrolling
2194     // list is always greater than the index into the
2195     // message handle array that we got the index from.
2196
2197     entry_position = entry_position + 1;
2198
2199     mbox->getMessageSummary(mail_error, tmpMS->message_handle,
2200                             _header_info, info);
2201     DtMail::Message * msg = mbox->getMessage(mail_error, tmpMH);
2202
2203     complete_header = formatHeader(
2204                         info,
2205                         tmpMS->indexNumber,
2206                         show_with_attachments(msg),
2207                         msg->flagIsSet(mail_error, DtMailMessageNew));
2208
2209     mbox->clearMessageSummary(info);
2210
2211     if (msg->flagIsSet(mail_error, DtMailMessageNew) == DTM_TRUE) {
2212         num_new_messages++;
2213     }
2214
2215     _deleted_messages->remove_entry(len - 1);
2216     num_deleted_messages--;
2217     XmListAddItemUnselected(_w, complete_header, entry_position);
2218     XmStringFree(complete_header);
2219
2220     
2221     // If the undeleted message is before the currently viewed message,
2222     // then need to readjust our numbers by adding one to them -- there
2223     // is one more message above the currently-viewed message.
2224     // Don't need to do anything if the undeleted message is after the
2225     // currently-viewed message.
2226
2227     
2228     // Deselect all items currently selected.
2229     // display_and_select_message() will select, highlight
2230     // and display the last "undeleted" message.
2231     
2232     XmListDeselectAllItems(_w);
2233
2234     _selected_item_position = entry_position;
2235     this->display_and_select_message(mail_error, tmpMS->message_handle);
2236     if (mail_error.isSet()) return;
2237
2238     updateListItems(-1, TRUE, NULL);
2239     display_message_summary();
2240 }
2241
2242 DtMailBoolean
2243 MsgScrollingList::senderIsToHeaderWhenMailFromMe(void)
2244 {
2245     if (_header_info.number_of_names == 5)
2246       return(DTM_TRUE);
2247
2248     return(DTM_FALSE);
2249 }
2250
2251 void
2252 MsgScrollingList::checkDisplayProp(void)
2253 {
2254     DtMail::MailBox * mbox;
2255     DtMail::MailRc * mailrc;
2256     DtMailEnv mail_error;
2257     Boolean state_changed = FALSE;
2258
2259     mbox = parent()->mailbox();
2260     mailrc = mbox->session()->mailRc(mail_error);
2261
2262     const char * value = NULL;
2263     mailrc->getValue(mail_error, "showto", &value);
2264     if (mail_error.isNotSet()) {
2265         // showto is set...if number_of_names is 4 then they
2266         // just applied props and the showto value
2267         if (_header_info.number_of_names == 4) {
2268             _header_info.number_of_names = 5;
2269             state_changed = TRUE;
2270         }
2271     }
2272     else if (_header_info.number_of_names == 5) {
2273         // They just applied props and changed the showto value
2274         _header_info.number_of_names = 4;
2275         state_changed = TRUE;
2276     }
2277     if (NULL != value)
2278       free((void*) value);
2279
2280     // Here we need to adjust the header labels to maintain them left
2281     // justified. It looks better than centered. We only whant to change
2282     // the labels if message numbering has been enabled. Otherwise we will
2283     // leave them alone. Note: The _numbered stores the previous state for
2284     // message numbering. Check the error return when getting the value uses
2285     // reverse logic. ugly...
2286     DtMailBoolean use_msg_numbers;
2287
2288     value = NULL;
2289     mail_error.clear();
2290     mailrc->getValue(mail_error, "showmsgnum", &value);
2291
2292     use_msg_numbers = (mail_error.isNotSet()) ? DTM_TRUE : DTM_FALSE;
2293     if (NULL != value) free((void*) value);
2294
2295     if (use_msg_numbers != _numbered) 
2296     {
2297         _numbered = use_msg_numbers;
2298         layoutLabels(_sender_lbl, _subject_lbl, _date_lbl, _size_lbl);
2299     }
2300     else if (!state_changed) return;
2301
2302     // We have to build two lists from the current normal and deleted
2303     // lists. These will contain the new header lines.
2304     //
2305     DtMailHeaderLine info;
2306     XmString * normal_list = new XmString[_msgs->length()];
2307
2308     for (int m = 0; m < _msgs->length(); m++) {
2309         MsgStruct * ms = _msgs->at(m);
2310
2311         DtMail::Message * msg =
2312                         mbox->getMessage(mail_error, ms->message_handle);
2313         mbox->getMessageSummary(
2314                         mail_error, ms->message_handle,
2315                         _header_info, info);
2316         normal_list[m] = formatHeader(
2317                             info,
2318                             ms->indexNumber,
2319                             show_with_attachments(msg),
2320                             msg->flagIsSet(mail_error, DtMailMessageNew));
2321         mbox->clearMessageSummary(info);
2322     }
2323
2324     XmListReplaceItemsPos(_w, normal_list, _msgs->length(), 1);
2325     for (int fr = 0; fr < _msgs->length(); fr++) {
2326         XmStringFree(normal_list[fr]);
2327     }
2328     delete normal_list;
2329
2330     UndelFromListDialog * del_dialog = _parent->get_undel_dialog();
2331     if (del_dialog) {
2332         XmString * del_list = new XmString[_deleted_messages->length()];
2333         
2334         for (int m2 = 0; m2 < _deleted_messages->length(); m2++) {
2335             MsgStruct * ms = _deleted_messages->at(m2);
2336             
2337             DtMail::Message * msg =
2338                 mbox->getMessage(mail_error, ms->message_handle);
2339             mbox->getMessageSummary(
2340                                 mail_error, ms->message_handle,
2341                                 _header_info, info);
2342             del_list[m2] = formatHeader(
2343                              info,
2344                              ms->indexNumber,
2345                              show_with_attachments(msg),
2346                              msg->flagIsSet(mail_error, DtMailMessageNew));
2347             mbox->clearMessageSummary(info);
2348         }
2349         
2350         del_dialog->replaceItems(del_list, _deleted_messages->length());
2351         for (int fr2 = 0; fr2 < _deleted_messages->length(); fr2++) {
2352             XmStringFree(del_list[fr2]);
2353         }
2354         delete del_list;
2355     }
2356 }
2357
2358 //
2359 // Update the scrolling list. Current is the index of the message
2360 // to position the scrolling list to.  -1 to keep it as is.
2361 //
2362 void
2363 MsgScrollingList::updateListItems(int current,
2364                                   Boolean renumber_only,
2365                                   MsgHndArray *selected_messages)
2366 {
2367     DtMail::MailBox * mbox = NULL;
2368     DtMail::MailRc * mailrc;
2369     DtMailEnv mail_error;
2370     const char * value = NULL;
2371     int         nmsgs;
2372
2373     mbox = parent()->mailbox();
2374     mailrc = mbox->session()->mailRc(mail_error);
2375
2376     if (current < 0) current = _displayed_item_position;
2377     resetIndexNums();
2378
2379     mailrc->getValue(mail_error, "showmsgnum", &value);
2380     if (mail_error.isSet() && renumber_only) return;
2381
2382     if (selected_messages)
2383       XtVaSetValues (_w, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
2384
2385     //
2386     // We need to build a new list of strings to display
2387     // in the scrolling list.  Initialize that now.
2388     //
2389     DtMailHeaderLine info;
2390     XmString * newList;
2391     MsgStruct *ms;
2392
2393     nmsgs = _msgs->length();
2394     newList = new XmString[nmsgs];
2395     memset (newList, '0', nmsgs * sizeof (XmString *));
2396
2397     // Loop through _msgs and create new strings to display in the
2398     // scrolling list. This is inefficient and dominates the time
2399     // spent in sort.  It may be worth while finding a way to just
2400     // rearrange the existing strings.
2401     for (int m = 0; m < nmsgs; m++)
2402     {
2403         DtMail::Message * msg = mbox->getMessage(mail_error,
2404                         _msgs->at(m)->message_handle);
2405
2406         if (mail_error.isSet())
2407           fprintf(stderr, "dtmail: getMessage: Couldn't get message #%d\n", m);
2408         
2409         mbox->getMessageSummary(
2410                         mail_error, _msgs->at(m)->message_handle,
2411                         _header_info, info);
2412
2413         if (mail_error.isSet())
2414           fprintf(stderr,
2415            "dtmail: getMessageSummary: Couldn't get summary for msg # %d\n", m);
2416
2417         if ((msg == NULL) || (mbox == NULL))
2418         {
2419                 // Error
2420                 ;
2421         }
2422         else
2423         {
2424                 ms = get_message_struct(m + 1);
2425                 newList [m] =
2426                   formatHeader(
2427                         info,
2428                         ms->indexNumber,
2429                         show_with_attachments(msg),
2430                         msg->flagIsSet(mail_error, DtMailMessageNew));
2431         }
2432
2433         // Free the space allocated for info
2434         // delete []info.header_values;
2435         mbox->clearMessageSummary(info);
2436     }
2437
2438     XmListReplaceItemsPos(_w, newList, session_message_number, 1);
2439     for (int fr = 0; fr < nmsgs; fr++)
2440       XmStringFree(newList[fr]);
2441
2442     // Update current message
2443     _selected_item_position = current;
2444     _displayed_item_position = current;
2445     scroll_to_position(_displayed_item_position);
2446
2447     if (selected_messages)
2448     {
2449         XmListDeselectAllItems(_w);
2450         for (int m=0, nselected=selected_messages->length(); m<nmsgs; m++)
2451         {
2452             MsgStruct *ms = get_message_struct(m+1);
2453             for (int s=0; s<nselected; s++)
2454             {
2455                 MsgStruct *sms = selected_messages->at(s);
2456                 if (ms == sms)
2457                   XmListSelectPos(_w, m+1, FALSE);
2458             }
2459         }
2460         XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
2461     }
2462     else
2463       XmListSelectPos(_w, _displayed_item_position, FALSE);
2464
2465     XtVaSetValues (_w, XmNselectionPolicy, XmEXTENDED_SELECT, NULL);
2466     XtVaSetValues (_w, XmNselectionMode, XmNORMAL_MODE, NULL);
2467     delete newList;
2468 }
2469
2470 XmString
2471 MsgScrollingList::formatHeader(DtMailHeaderLine & info,
2472                                int sess_num,
2473                                DtMailBoolean has_attachments,
2474                                DtMailBoolean new_msg)
2475 {
2476     char *buf = new char[BUFSIZ];
2477     memset(buf, 0, BUFSIZ);
2478     const char *from=NULL;
2479     char *subject;
2480     int contentLength;
2481     char contentStr[20];
2482     char *date = new char[BUFSIZ];
2483     int msg_num = sess_num + 1;
2484     static XmString attachment_glyph = NULL;
2485     static XmString no_attachment_glyph = NULL;
2486     static XmString new_status = NULL;
2487     static XmString read_status = NULL;
2488     static unsigned char attach_symbol[16];
2489     Boolean showto = FALSE;
2490
2491     if (!attachment_glyph) {
2492         attach_symbol[0] = 168;
2493         attach_symbol[1] = 0;
2494         attachment_glyph = XmStringCreate((char *)attach_symbol, "attach");
2495         no_attachment_glyph = XmStringCreateLocalized(" ");
2496         new_status = XmStringCreateLocalized(GETMSG(DT_catd, 1, 114, "N"));
2497         read_status = XmStringCreateLocalized(" ");
2498     }
2499     
2500     // strip out the Name of sender and retain only the address.
2501     //Later, we will have separate  entries for name and address
2502     //and this stripping will not need to be done.
2503     
2504     // IMAP is incapable of handling a message header that begins
2505     // with From (space) (NOTE: not a From:).
2506     // Thus, if a message has only a From but does not have a From:
2507     // or a Reply-To: or Received-From:, IMAP doesn't tell us who
2508     // it is from.  In such cases, we set it to "???".
2509     
2510     DtMailAddressSeq * addr_seq = NULL;
2511     DtMailValueAddress * addr = NULL;
2512     
2513     if (info.header_values[0].length() != 0) {
2514         addr_seq = ((info.header_values[0])[0])->toAddress();
2515         addr = (*addr_seq)[0];
2516         if (_header_info.number_of_names == 5 && addr 
2517                 && addr->dtm_address && info.header_values[4].length() != 0) {
2518                 // Check if mail is from me
2519                 const char *ptr;
2520                 passwd pw;
2521                 GetPasswordEntry(pw);
2522                 if ((ptr = strchr(addr->dtm_address, '@')) != NULL) {
2523                         if (strncmp(pw.pw_name, addr->dtm_address, 
2524                                 ptr-addr->dtm_address) == 0) {
2525                                 from = *((info.header_values[4])[0]);
2526                                 showto = TRUE;
2527                         }
2528                 }
2529                 else 
2530                         if (strcmp(pw.pw_name, addr->dtm_address) == 0) {
2531                                 from = *((info.header_values[4])[0]);
2532                                 showto = TRUE;
2533                         }
2534         }
2535         if (from == NULL) {
2536                 if (addr && addr->dtm_person)
2537                         from = addr->dtm_person;
2538                 else if (addr && addr->dtm_address)
2539                         from = addr->dtm_address;
2540                 else from = "???";
2541         }
2542     }
2543     else
2544         from = "???";
2545     
2546     // If the Subject is nil
2547     
2548     if (info.header_values[3].length() == 0) {
2549         subject = new char[1];
2550         subject[0] = '\0';
2551     } else {
2552         // Get the BE store of header.  It may contain newlines or 
2553         // tab chars which can munge the scrolling list's display!
2554         // 
2555         const char *real_subj_header = *((info.header_values[3])[0]);
2556         int fc;
2557         int subj_len;
2558         char *tmp_subj;
2559
2560         // Check if BE store contains the funky chars.
2561
2562         for (fc = 0, subj_len = strlen(real_subj_header), 
2563               tmp_subj = (char *)real_subj_header; 
2564              fc < subj_len; fc++, tmp_subj++) {
2565
2566             char c = *tmp_subj;
2567             if ( (c == '\n') 
2568               || (c == '\t') 
2569               || (c == '\r')) {
2570
2571                 break;
2572             }
2573         }
2574         subject = new char[fc+1];
2575         strncpy((char *)subject, real_subj_header, fc);
2576         subject[fc] = '\0';
2577     }
2578
2579     
2580     // Skip the first (beginning) space in from; search for the next
2581     // occurring space.  
2582     
2583
2584     const char *dateformat;
2585     if (info.header_values[1].length() > 0)
2586     {
2587         DtMailValueDate ds = ((info.header_values[1])[0])->toDate();
2588
2589         if (ds.dtm_date && ds.dtm_tz_offset_secs)
2590         {
2591 #define USE_YEAR_FORMAT_SECONDS (60 * 60 * 24 * 180)
2592             time_t now;
2593             tm tm_struct;
2594
2595             SafeLocaltime(&ds.dtm_date, tm_struct);
2596
2597             // Refer to strftime man page for explanation of the date format.
2598             now = time(NULL);
2599             if (USE_YEAR_FORMAT_SECONDS < now - ds.dtm_date)
2600               dateformat = GETMSG(DT_catd, 1, 259, "%a %b %d  %Y");
2601             else
2602             {
2603
2604 #ifdef sun
2605                 dateformat = GETMSG(DT_catd, 1, 260, "%a %b %d %k:%M");
2606 #else
2607                 dateformat = GETMSG(DT_catd, 1, 261, "%a %b %d %H:%M");
2608 #endif
2609             }
2610
2611             SafeStrftime(date, BUFSIZ, dateformat, &tm_struct);
2612         }
2613         else
2614           // Couldn't get Date string from Message. Make it empty.
2615           sprintf(date, "%s", " ");
2616     }
2617     else
2618     {
2619         tm epoch;
2620         memset(&epoch, 0, sizeof(tm));
2621
2622         /* Refer to strftime man page for explanation of the date format.  */
2623         dateformat = GETMSG(DT_catd, 1, 259, "%a %b %d  %Y");
2624         SafeStrftime(date, BUFSIZ, dateformat, &epoch);
2625     }
2626     
2627     if (info.header_values[2].length() > 0) {
2628         contentLength = (int) strtol(*((info.header_values[2])[0]), NULL, 10);
2629     }
2630     else {
2631         contentLength = 0;
2632     }
2633     
2634     if (contentLength < 1000) {
2635         sprintf(contentStr, "%d", contentLength);
2636     }
2637     else if (contentLength < 1000000) {
2638         sprintf(contentStr, "%dK", contentLength / 1000);
2639     }
2640     else {
2641         sprintf(contentStr, "%dM", contentLength / 1000000);
2642     }
2643     
2644     // If we are to print the message_number in the header_list,
2645     // use msg_num as the first element in the sprintf.  
2646     // Introduce a %d at the beginning though.
2647
2648     DtMail::MailBox * mbox;
2649     DtMail::MailRc * mailrc;
2650     DtMailEnv mail_error;
2651   
2652     mbox = parent()->mailbox();
2653     mailrc = mbox->session()->mailRc(mail_error);
2654
2655     const char * value = NULL;
2656     mailrc->getValue(mail_error, "showmsgnum", &value);
2657     if (mail_error.isSet()) {
2658         // No message numbers ... keep usual "35" col. "Subject".
2659       if (showto)
2660               sprintf(buf, " To %-15.15s %-35.35s %-17.17s %-5.5s",
2661                       from,
2662                       subject,
2663                       date,
2664                       contentStr);
2665       else
2666               sprintf(buf, " %-18.18s %-35.35s %-17.17s %-5.5s",
2667                       from,
2668                       subject,
2669                       date,
2670                       contentStr);
2671     }
2672     else {
2673         //  To keep 80 column format use 5 less columns of
2674         //  subject , when msg numbers are on.
2675       if (showto)
2676               sprintf(buf, " To %-15.15s %-30.30s %-17.17s %-5.5s",
2677                       from,
2678                       subject,
2679                       date,
2680                       contentStr);
2681       else
2682               sprintf(buf, " %-18.18s %-30.30s %-17.17s %-5.5s",
2683                       from,
2684                       subject,
2685                       date,
2686                       contentStr);
2687     }
2688     if (NULL != value)
2689       free((void*) value);
2690
2691     XmString header_text = XmStringCreateLocalized(buf);
2692     XmString item, item2, complete_header;
2693
2694     if (has_attachments == DTM_TRUE) {
2695         item = XmStringConcat(attachment_glyph, header_text);
2696         XmStringFree(header_text);
2697     }
2698     else {
2699         item = XmStringConcat(no_attachment_glyph, header_text);
2700         XmStringFree(header_text);
2701     }
2702
2703     if (new_msg == DTM_FALSE) {
2704         item2 = XmStringConcat(read_status, item);
2705         XmStringFree(item);
2706     }
2707     else {
2708         item2 = XmStringConcat(new_status, item);
2709         XmStringFree(item);
2710     }
2711
2712     value = NULL;
2713     mailrc->getValue(mail_error, "showmsgnum", &value);
2714     if (mail_error.isSet()) {
2715 //      complete_header = XmStringCopy(item2);
2716         complete_header = item2;
2717         _numbered = DTM_FALSE;
2718     }
2719     else {
2720         char num_buf[64];
2721
2722         if (NULL != value)
2723           free((void*) value);
2724
2725         mailrc->getValue(mail_error, "nerdmode", &value);
2726         if (mail_error.isSet()) {
2727             sprintf(num_buf, "%4d ", msg_num);
2728         }
2729         else {
2730             sprintf(num_buf, "%4x ", msg_num - 1);
2731         }
2732
2733         XmString num_str = XmStringCreateLocalized(num_buf);
2734         complete_header = XmStringConcat(num_str, item2);
2735         XmStringFree(item2);
2736         XmStringFree(num_str);
2737         _numbered = DTM_TRUE;
2738     }
2739     if (NULL != value)
2740       free((void*) value);
2741
2742     delete addr_seq;
2743     delete subject;
2744     
2745     delete [] buf;
2746     delete [] date;
2747     return(complete_header);
2748 }
2749
2750 void
2751 MsgScrollingList::shutdown()
2752 {
2753     int num_entries, i;
2754     FORCE_SEGV_DECL(MsgStruct, tmpMS);
2755     DtMailMessageHandle tmpMH;
2756     DtMail::MailBox     *mbox=parent()->mailbox();
2757     DtMailEnv mail_error;
2758
2759     // Initialize the mail_error.
2760     mail_error.clear();
2761
2762     if (num_deleted_messages == 0) return;
2763     
2764     num_entries = _deleted_messages->length();
2765
2766     for (i = 0; i < num_entries; i++) {
2767         tmpMS = _deleted_messages->at(i);
2768         tmpMH = tmpMS->message_handle;
2769
2770 //      mbox->deleteMsg(mail_error, tmpMH);
2771         
2772     }
2773 }
2774
2775 #ifdef DEAD_WOOD
2776 DtMailMessageHandle
2777 MsgScrollingList::lastMsg()
2778 {
2779     if (_msgs->length() > 0) {
2780         return((_msgs->at(_msgs->length() - 1))->message_handle);
2781     }
2782     else {  // Currently an empty folder
2783         return(NULL);
2784     }
2785 }
2786 #endif /* DEAD_WOOD */
2787
2788 void
2789 MsgScrollingList::clearMsgs()
2790 {
2791     if ( _msgs->length() > 0 )
2792         _msgs->clear();
2793 }
2794
2795
2796 void
2797 MsgScrollingList::display_message_summary()
2798 {
2799     parent()->message_summary(
2800                         selected_item_position(),
2801                         get_num_messages(), 
2802                         get_num_new_messages(),
2803                         get_num_deleted_messages());
2804 }
2805
2806 void
2807 MsgScrollingList::display_message(
2808     DtMailEnv &mail_error,
2809     int pos
2810 )
2811 {
2812     if ((pos > 0) && (pos <= session_message_number)) {
2813         // When loading mail headers, we need to make sure that
2814         // the XmList resources are all set at the same time to
2815         // avoid painting multiple times.  So here we collect
2816         // these resources.
2817         if (_xtarg_collector && _xmstr_collector)
2818         {
2819             XmString *items = _xmstr_collector->GetItems();
2820             
2821             // Keep a handle to the malloced string copy so that
2822             // we can free it after the XtSetValues
2823             _selected_items = XmStringCopy (items[pos-1]);
2824
2825             _xtarg_collector->AddItemToList (XmNselectedItems,
2826                 (XtArgVal) &_selected_items);
2827             _xtarg_collector->AddItemToList (
2828                 XmNselectedItemCount, 1);
2829         }
2830         else
2831         {
2832             XmListSelectPos(_w, pos, FALSE);
2833         }
2834
2835         this->extended_selection(mail_error, pos);
2836         if (mail_error.isSet()) {
2837             // Return whatever error mailbox->get_next_msg() returned.
2838             return;
2839         }
2840     }
2841     else return;
2842 }
2843
2844 DtMailMessageHandle
2845 MsgScrollingList::current_msg_handle()
2846 {
2847     DtMailMessageHandle msg_number;
2848
2849     if ( _displayed_item_position > 0 )
2850         msg_number=msgno( _displayed_item_position );
2851     else
2852         msg_number=NULL;
2853
2854     return msg_number;
2855 }
2856
2857 void
2858 MsgScrollingList::expunge(void)
2859 {
2860     for (int i = _deleted_messages->length() - 1; i >= 0; i--) {
2861         _deleted_messages->remove_entry(i);
2862     }
2863     resetSessionNums();
2864     resetIndexNums();
2865     num_deleted_messages = 0;
2866     display_message_summary();
2867 }
2868
2869 int
2870 MsgScrollingList::resetIndexNums(void)
2871 {
2872     int m;
2873     int length = _msgs->length();
2874     MsgStruct *ms;
2875     
2876     for (m = 0; m < length; m++)
2877     {
2878         ms = _msgs->at(m);
2879         ms->indexNumber = m;
2880     }
2881
2882     length = _deleted_messages->length();
2883     for (m = 0; m < length; m++)
2884     {
2885         ms = _deleted_messages->at(m);
2886         ms->indexNumber = m;
2887     }
2888
2889     return m;
2890 }
2891
2892 int
2893 MsgScrollingList::resetSessionNums(void)
2894 {
2895     int m;
2896     int length = _msgs->length();
2897     MsgStruct *ms;
2898     
2899     for (m = 0; m < length; m++)
2900     {
2901         ms = _msgs->at(m);
2902         ms->sessionNumber = m;
2903     }
2904     session_message_number = m;
2905     return session_message_number;
2906 }
2907
2908 void
2909 MsgScrollingList::sort_messages(void)
2910 {
2911     DtMail::MailBox     *mbox;
2912     enum sortBy         sortby;
2913     int                 current_msg;
2914
2915     MsgHndArray *selected_messages = selected();
2916
2917     mbox = _parent->mailbox();
2918     sortby = _parent->last_sorted_by();
2919
2920     // Sort array of message handles
2921     current_msg = _sorter->sortMessages(this, mbox, sortby);
2922
2923     // The array of message handles is sorted. Now we need to update
2924     // the display preserving the selected state of the messages.
2925     updateListItems(current_msg, FALSE, selected_messages);
2926
2927     delete selected_messages;
2928
2929     _parent->last_sorted_by(sortby);
2930     display_message_summary();
2931 }
2932
2933 //
2934 // Layout out the row of labels above the scrolling list
2935 //
2936 void
2937 MsgScrollingList::layoutLabels(
2938         Widget sender,
2939         Widget subject,
2940         Widget date,
2941         Widget size)
2942 {
2943     // Save the input values 
2944     _sender_lbl = sender; 
2945     _subject_lbl = subject;
2946     _date_lbl = date;
2947     _size_lbl = size;
2948
2949     layoutLabels();
2950
2951 }
2952 //
2953 // Layout out the row of labels above the scrolling list
2954 //
2955 void
2956 MsgScrollingList::layoutLabels()
2957 {
2958     // Width of fields.  +1 for spaces
2959     int num_width = 5,
2960         sender_width = 18 + 1,
2961         subject_width = 35 + 1,
2962         date_width = 3+1 + 3+1 + 2+1 + 5 + 2; // DDD mmm dd hh:mm 2spaces
2963     int char_width;             // Width of a single character
2964     int n = 0;
2965     XmString    xmstr;
2966     XmFontList  font_list;
2967
2968     // Calculate the width of date format to allocate the label 
2969     // width dynamically
2970     struct tm *tm;
2971     time_t clock;
2972     char   buf[40];
2973     
2974     clock = time((time_t *) 0);
2975     tm = localtime(&clock);  
2976     #ifdef sun
2977         SafeStrftime(buf,
2978              sizeof(buf), 
2979              GETMSG(DT_catd, 1, 222, "%a %b %d %k:%M"), 
2980              tm);
2981     #else
2982         SafeStrftime(buf, 
2983              sizeof(buf), 
2984              GETMSG(DT_catd, 1, 223, "%a %b %d %H:%M"), 
2985              tm);
2986     #endif
2987
2988
2989     // List uses a fixed width font. Therefore all characters are the
2990     // same size.  So we use a space to determine the width of a char
2991     xmstr = XmStringCreateLocalized(" ");
2992     XtVaGetValues(_w, XmNfontList, &font_list, NULL);
2993     char_width = XmStringWidth(font_list, xmstr);
2994
2995     if (_numbered) {
2996         // Numbering is on
2997         n = num_width;
2998         subject_width -= num_width;
2999     } else {
3000         n = 0;
3001     }
3002
3003     // XXX dipol: Need to take into account if the scrollbar is on the
3004     // right or left
3005     n += 3;     // Margin.
3006
3007     XtWidgetGeometry geom;
3008     geom.request_mode = CWX;
3009     XtQueryGeometry(_sender_lbl, NULL, &geom);
3010     if (geom.x != (n * char_width)) {
3011         geom.x = (n * char_width);
3012         XtMoveWidget(_sender_lbl, geom.x, geom.y);
3013     } // Move the X location of the sender title
3014
3015     XtVaSetValues(_sender_lbl, XmNx, n * char_width, NULL);
3016     n += sender_width;
3017
3018     XtQueryGeometry(_subject_lbl, NULL, &geom);
3019     if (geom.x != (n * char_width)) {
3020         geom.x = (n * char_width);
3021         XtMoveWidget(_subject_lbl, geom.x, geom.y);
3022     } // Move the X location of the subject title
3023
3024     XtVaSetValues(_subject_lbl, XmNx, n * char_width, NULL);
3025     n += subject_width;
3026     XtVaSetValues(_date_lbl, XmNx, n * char_width, NULL);
3027     n += date_width;
3028     XtVaSetValues(_size_lbl, XmNx, n * char_width, NULL);
3029
3030     XmStringFree(xmstr);
3031
3032     return;
3033 }