Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtmail / dtmail / SendMsgDialog.C
1 /*
2  *+SNOTICE
3  *
4  * $TOG: SendMsgDialog.C /main/43 1999/03/25 13:42:29 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, 1994, 1995 Sun Microsystems, Inc.  All rights reserved.
17  *
18  *+ENOTICE
19  */
20
21 #include <EUSCompat.h>
22 #include "AttachArea.h"
23 #include "Attachment.h"
24 #include "RoamApp.h"
25 #include "SendMsgDialog.h"
26 #include "NoOpCmd.h"
27 #include "RoamCmds.h"
28 #include "ComposeCmds.hh"
29 #include "CmdList.h"
30 #include "MenuBar.h"
31 #include "ButtonInterface.h"
32 #include "MemUtils.hh"
33 #include "Help.hh"
34 #include "DtMailHelp.hh"
35 #include "SelectFileCmd.h"
36 #include "DtMailGenDialog.hh"
37 #include "Application.h"
38 #include <X11/Intrinsic.h>
39 #include <X11/IntrinsicP.h>
40 #include <Xm/RepType.h>
41 #include <Xm/ScrolledW.h>
42 #include <Xm/Text.h>
43 #include <Xm/TextF.h>
44 #include <Xm/Form.h>
45 #include <Xm/Label.h>
46 #include <Xm/RowColumn.h>
47 #include <Xm/PushB.h>
48 #include <Xm/CascadeB.h>
49 #include <Xm/SeparatoG.h>
50 #include <DtMail/IO.hh>
51 #include <DtMail/DtMailP.hh>
52 #include <stdio.h>
53 #include <errno.h>
54 #include <sys/param.h>
55 #include <stdlib.h>
56 #include <ctype.h>
57 #include <unistd.h>
58
59 #include "MailMsg.h"
60 #include "str_utils.h"
61
62 #define OFFSET 10    // Number of spaces from margin
63
64 #ifdef DTMAIL_TOOLTALK
65 // Time to self destruct
66 #define DESTRUCT_TIMEOUT   60000    // 1 minutes
67 #endif
68
69 // Pipe used between RFCTransport::childHandler and XtAppAddInput
70 static int _transfds[2];
71
72 static const char *ComposeIcon = "IcMcomp";
73
74 struct DefaultHeaders {
75     int                         msg_set;
76     int                         msg_number;
77     char *                      dflt_label;
78     char *                      label;
79     const char *                header;
80     SendMsgDialog::ShowState    show;
81 };
82
83 static int              isInitializedDefaultHeaderList = 0;
84 static DefaultHeaders   DefaultHeaderList[] = {
85 { 1, 241, "To",         NULL, DtMailMessageTo,  SendMsgDialog::SMD_ALWAYS }, 
86 { 1, 242, "Subject",    NULL, DtMailMessageSubject, SendMsgDialog::SMD_ALWAYS },
87 { 1, 243, "Cc",         NULL, DtMailMessageCc,  SendMsgDialog::SMD_ALWAYS },
88 { 1, 244, "Bcc",        NULL, DtMailMessageBcc, SendMsgDialog::SMD_HIDDEN },
89 { 0, 0,   NULL,         NULL, NULL,             SendMsgDialog::SMD_NEVER  }
90 };
91
92 // These headers can never be controlled by the user. They are generated
93 // by dtmail and the user is not allowed to override the values generated
94 // by the software. Besides, most users would not have a clue as to what
95 // a correct value would be.
96 //
97 static const char * BlockedHeaders[] = {
98     "Content-Length",
99     "Content-Type",
100     "Content-MD5",
101     "Content-Transfer-Encoding",
102     "Mime-Version",
103     "X-Sun-Charset",
104     NULL
105 };
106
107
108 static DtMailBoolean
109 block(const char * header)
110 {
111     for (const char ** test = BlockedHeaders; *test; test++) {
112         if (strcasecmp(header, *test) == 0) {
113             return(DTM_TRUE);
114         }
115     }
116
117     return(DTM_FALSE);
118 }
119
120 class CmdList;
121
122 Compose theCompose;   // Manages all compose windows.
123
124 SendMsgDialog::HeaderList::HeaderList(void)
125 {
126     label = NULL;
127     header = NULL;
128     value = NULL;
129     show = SMD_NEVER;
130     form_widget = NULL;
131     label_widget = NULL;
132     field_widget = NULL;
133 }
134
135 SendMsgDialog::HeaderList::HeaderList(const HeaderList & other)
136 {
137     label = NULL;
138     header = NULL;
139     show = other.show;
140     form_widget = other.form_widget;
141     label_widget = other.label_widget;
142     field_widget = other.field_widget;
143
144     if (other.label) {
145         label = strdup(other.label);
146     }
147
148     if (other.header) {
149         header = strdup(other.header);
150     }
151
152     if (other.value) {
153         value = strdup(other.value);
154     }
155 }
156
157 SendMsgDialog::HeaderList::~HeaderList(void)
158 {
159     if (label) {
160         free(label);
161     }
162
163     if (header) {
164         free(header);
165     }
166
167     if (value) {
168         free(value);
169     }
170 }
171 Boolean SendMsgDialog::reservedHeader(const char *label)
172 {
173     for (DefaultHeaders * hl = DefaultHeaderList; hl->dflt_label; hl++)
174       if (strcmp(label, hl->label) == 0)
175         return TRUE;
176     return FALSE;
177 }
178
179
180 SendMsgDialog::SendMsgDialog() 
181 : MenuWindow ( "ComposeDialog", True ),
182 _header_list(16)
183 {
184     _main_form = NULL;
185     _num_sendAs = 0;
186     _genDialog = NULL;
187
188     _show_attach_area = FALSE;
189     _takeDown = FALSE;
190     _first_time = TRUE;
191     _already_sending = FALSE;
192
193     _separator = NULL;
194
195     // Alias Popup menus
196     _aliasList = NULL;
197     _bccPopupCmdlist = NULL;
198     _bccPopupMenu = NULL;
199     _bccPopupMenuBar = NULL;
200     _ccPopupCmdlist = NULL;
201     _ccPopupMenu = NULL;
202     _ccPopupMenuBar = NULL;
203     _toPopupCmdlist = NULL;
204     _toPopupMenu = NULL;
205     _toPopupMenuBar = NULL;
206
207     // Attachment menu
208     _attachmentActionsList = NULL;
209     _attachmentMenu = NULL;
210     _attachmentMenuList = NULL;
211     _attachmentPopupMenuList = NULL;
212     _textPopupMenuList = NULL;
213     
214     _att_show_pane = NULL;
215     _att_select_all = NULL;
216     _att_save = NULL;
217     _att_add = NULL;
218     _att_delete = NULL;
219     _att_undelete = NULL;
220     _att_rename = NULL;
221     _att_select_all = NULL;
222     _auto_save_interval = 0;
223     _auto_save_path = NULL;
224     _auto_save_file = NULL;
225     _dead_letter_buf = NULL;
226     
227     _file_include = NULL;
228     _file_save_as = NULL;
229     _file_send = NULL;
230     _file_log = NULL;
231     _file_close = NULL;
232
233     _format_word_wrap = NULL;
234     _format_settings = NULL;
235     _format_find_change = NULL;
236     _format_spell = NULL;
237     _format_menu = NULL;
238     _format_cmds = NULL;
239     _format_cascade = NULL;
240     _format_separator = NULL;
241     _templateList = NULL;
242
243     // Now we need to get the additional headers from the property system.
244     //   
245     DtMailEnv error;
246     DtMail::Session * d_session = theRoamApp.session()->session();
247     DtMail::MailRc * mail_rc = d_session->mailRc(error);
248  
249     const char * value = NULL;  
250     mail_rc->getValue(error, "additionalfields", &value);
251  
252     DtVirtArray<PropStringPair *> results(8);
253     if (error.isNotSet()) {
254         _additionalfields = strdup(value);
255         parsePropString(value, results);
256     }
257  
258     if (NULL != value)
259       free((void*) value);
260
261     // Load the header list with the predefined/fixed headers.
262     //
263     if (! isInitializedDefaultHeaderList)
264       for (DefaultHeaders * hl = DefaultHeaderList; hl->dflt_label; hl++) {
265           hl->label =
266             GETMSG(DT_catd, hl->msg_set, hl->msg_number, hl->dflt_label);
267
268           isInitializedDefaultHeaderList = TRUE;
269       }
270
271     for (DefaultHeaders * hl = DefaultHeaderList; hl->dflt_label; hl++) {
272         HeaderList * copy_hl = new HeaderList;
273         copy_hl->label = strdup(hl->label);
274         copy_hl->value = getPropStringValue(results, hl->label);
275         copy_hl->header = strdup(hl->header);
276         copy_hl->show = hl->show;
277         _header_list.append(copy_hl);
278     }
279
280     if (error.isNotSet()) {
281
282         for (int mrc = 0; mrc < results.length(); mrc++) {
283             PropStringPair * psp = results[mrc];
284
285             if (!reservedHeader(psp->label)) {
286                 HeaderList * copy_hl = new HeaderList;
287                 copy_hl->label = strdup(psp->label);
288                 copy_hl->header = strdup(psp->label);
289                 if (psp->value) 
290                         copy_hl->value = strdup(psp->value);
291                 copy_hl->show = SMD_HIDDEN;
292                 _header_list.append(copy_hl);
293             }
294         }
295
296         while(results.length()) {
297             PropStringPair * psp = results[0];
298             delete psp;
299             results.remove(0);
300         }
301     }
302     else 
303         _additionalfields = NULL;
304
305 }
306
307 SendMsgDialog::~SendMsgDialog()
308 {
309     
310     delete _genDialog;
311     delete _separator;
312     
313     // File
314     
315     delete _file_include;
316     delete _file_save_as;
317     delete _file_send;
318     delete _file_log;
319     delete _file_close;
320     
321     // Edit
322     
323     delete _edit_undo;
324     delete _edit_cut;
325     delete _edit_copy;
326     delete _edit_paste;
327     delete _edit_clear;
328     delete _edit_delete;
329     delete _edit_select_all;
330     
331     // Alias Popup menus
332
333     delete _aliasList;
334
335     XtDestroyWidget(_ccPopupMenu);
336     delete _ccPopupMenuBar;
337     delete _ccPopupCmdlist;
338
339     XtDestroyWidget(_toPopupMenu);
340     delete _toPopupMenuBar;
341     delete _toPopupCmdlist;
342
343     XtDestroyWidget(_bccPopupMenu);
344     delete _bccPopupMenuBar;
345     delete _bccPopupCmdlist;
346
347     // Attachment
348     
349     delete _att_save;
350     delete _att_add;
351     delete _att_delete;
352     delete _att_undelete;
353     delete _att_rename;
354     delete _att_select_all;
355     
356     delete _attachmentActionsList;
357     
358     // Format
359     
360     delete _format_word_wrap;
361     delete _format_settings;
362     delete _format_find_change;
363     delete _format_spell;
364     delete _format_separator;
365     
366     // Things created by createWorkArea()
367     delete _my_editor;
368     delete _send_button;
369     delete _close_button;
370     
371     //
372     // Allocated using 'malloc'.
373     // Purify requires us to free it using 'free'.
374     //
375     free(_auto_save_path);
376     if (_auto_save_file) {
377         delete _auto_save_file;
378     }
379     if (_dead_letter_buf) {
380         delete _dead_letter_buf;
381     }
382
383     delete _attachmentMenuList;
384     delete _attachmentPopupMenuList;
385     delete _textPopupMenuList;
386
387 }
388
389 // Callback for each header row
390 void
391 header_form_traverse(Widget w, XtPointer, XtPointer)
392 {
393     (void) XmProcessTraversal(w, XmTRAVERSE_NEXT_TAB_GROUP);
394 }
395
396 #ifdef DEAD_WOOD
397 void
398 send_pushb_callback(Widget, XtPointer, XtPointer)
399 {
400     
401 }
402 #endif /* DEAD_WOOD */
403
404 // Create Message Handle with empty first body part.
405 DtMail::Message *
406 SendMsgDialog::makeMessage(void)
407 {
408     DtMailEnv error;
409     DtMail::Session * d_session = theRoamApp.session()->session();
410     DtMailBuffer mbuf;
411     
412     mbuf.buffer = NULL;
413     mbuf.size = 0;
414     
415     DtMail::Message * msg = d_session->messageConstruct(error,
416                                                         DtMailBufferObject,
417                                                         &mbuf,
418                                                         NULL,
419                                                         NULL,
420                                                         NULL);
421     if (error.isSet() || !msg) {
422         return(NULL);
423     }
424     
425     DtMail::BodyPart * bp = msg->newBodyPart(error, NULL);
426     
427     // For now, reserve the first body part for text.
428     setFirstBPHandled(TRUE);
429     
430     return(msg);
431 }
432
433 // Before Submitting, call this routine.
434 // This routine does not save attachment(s).
435 // Check point routine should also call this routine.
436 void
437 SendMsgDialog::updateMsgHnd()
438 {
439     DtMailEnv error;
440     DtMail::Envelope * env;
441     int textLen;
442
443     env = _msgHandle->getEnvelope(error);
444     storeHeaders();
445
446     char * widget_text = this->text();
447     if (widget_text && *widget_text == '\0') {
448         textLen = 0;
449         XtFree(widget_text);
450         widget_text = NULL;
451     }
452     else
453         textLen = strlen(widget_text);
454     
455     // Even if textlen is 0 because user has cleared all previous text,
456     // need to setContents again to clear first BP.  Otherwise, deleted
457     // text will show up. 
458
459     // Get FirstBodyPart and fill it up.
460     DtMail::BodyPart *bp = _msgHandle->getFirstBodyPart(error);
461     bp->setContents(error, widget_text, textLen, NULL, NULL, 0, NULL);
462     setFirstBPHandled(TRUE);
463     
464     if (NULL != widget_text)
465       XtFree(widget_text);
466 }
467
468 // Before Submitting, also call this routine.
469 // This routine fills the message handle with attachment(s).
470 // The reason why we get attachment(s) from the Back End before submission
471 // is in case the attachment(s) are updated during the compose session.
472 // If changes are saved back, then the AttachArea class would register the
473 // update with Back End.  So BE always has the latest attachments.
474 void
475 SendMsgDialog::updateMsgHndAtt()
476 {
477     if ( _inclMsgHandle == NULL )
478         return;    // No attachments
479     
480     DtMailEnv error;
481     DtMail::BodyPart *msgBP;
482     DtMail::BodyPart *inclBP = _inclMsgHandle->getFirstBodyPart(error);
483     const void *contents;
484     unsigned long len;
485     char *type;
486     char *name;
487     int mode;
488     char *desc;
489     
490     // If message handle to be copied, _inclMsgHandle, does not have its first
491     // body part as text, then get its first body part content and copy it.
492     // If it contains text, then skip it.
493     if ( !_inclMsgHasText ) {
494         inclBP->getContents(error, &contents, &len, &type, &name, &mode, &desc);
495         if ( _firstBPHandled ) {
496             msgBP = _msgHandle->newBodyPart(error, _lastAttBP); 
497         } else {
498             msgBP = _msgHandle->getFirstBodyPart(error);
499         }
500         msgBP->setContents(error, contents, len, type, name, mode, desc);
501         setLastAttBP(msgBP);
502         free(name);
503         free(type);
504     }
505     
506     // Continue to get the next body part and start copy.
507     while ((inclBP = _inclMsgHandle->getNextBodyPart(error, inclBP)) != NULL) {
508         inclBP->getContents(error, &contents, &len, &type, &name, &mode, &desc);
509         if ( _firstBPHandled ) {
510             msgBP = _msgHandle->newBodyPart(error, _lastAttBP); 
511         } else {
512             msgBP = _msgHandle->getFirstBodyPart(error);
513         }
514         msgBP->setContents(error, contents, len, type, name, mode, desc);
515         setLastAttBP(msgBP);
516         free(name);
517         free(type);
518     }  // end of while loop
519 }
520
521 // Update the _lastAttBP pointer so that subsequent newBodyPart() calls can
522 // return a message body part following the last body part.
523 void
524 SendMsgDialog::setLastAttBP()
525 {
526     DtMailEnv error;
527     _lastAttBP = _msgHandle->getNextBodyPart(error, _lastAttBP);
528 }
529
530 // This sister routine is needed when _lastAttBP has not been initialized.
531 void
532 SendMsgDialog::setLastAttBP(DtMail::BodyPart *bp)
533 {
534     _lastAttBP = bp;
535 }
536
537 // Initialize _msgHandle
538 void
539 SendMsgDialog::setMsgHnd()
540 {
541     _msgHandle = makeMessage();
542     _inclMsgHandle = NULL;
543     _inclMsgHasText = FALSE;
544     _lastAttBP = NULL;
545 }
546
547 // Set timeout to ten minutes (as milliseconds)
548 //
549 void
550 SendMsgDialog::startAutoSave(void)
551 {
552     mkAutoSavePath();
553     if(!_auto_save_path) return;
554     _auto_save_interval = XtAppAddTimeOut(theApplication->appContext(),
555                                           getAutoSaveInterval(),
556                                           autoSaveCallback,
557                                           this);
558 }
559
560 void
561 SendMsgDialog::stopAutoSave(void)
562 {
563     if (!_auto_save_interval) {
564         return;
565     }
566     
567     XtRemoveTimeOut(_auto_save_interval);
568     _auto_save_interval = 0;
569     
570     unlink(_auto_save_file);
571     free(_auto_save_file);
572     _auto_save_file = NULL;
573 }
574
575 static const char * DEAD_LETTER_DIR = "~/dead_letter";
576 static const char * BACKUP_DEAD_LETTER = "./dead_letter";
577 static const char * PREFIX = "mail.dead.letter";
578
579 void
580 SendMsgDialog::mkAutoSavePath(void)
581 {
582     // First, see if we need to set up the path.
583     //
584
585     if (!_auto_save_path) {
586         DtMail::Session * d_session = theRoamApp.session()->session();
587         DtMailEnv error;
588         const char *save_path = NULL;
589
590         d_session->mailRc(error)->getValue(error, "deaddir", &save_path);
591         if (error.isNotSet() && save_path != NULL && *save_path != '\0')
592             _auto_save_path = d_session->expandPath(error, save_path);
593         else {
594         
595                 _auto_save_path = d_session->expandPath(error, DEAD_LETTER_DIR);
596                 if (!_auto_save_path)
597                 _auto_save_path = d_session->expandPath(error, BACKUP_DEAD_LETTER);
598         }
599
600         if (NULL != save_path)
601           free((void*) save_path);
602     }
603     
604     // If we still have a path, punt.
605     // 
606     if (!_auto_save_path) {
607         return;
608     }
609     
610     if (SafeAccess(_auto_save_path, W_OK) != 0) {
611         if (errno != ENOENT) {
612             // Not an error we can overcome here.
613             //
614             free(_auto_save_path);
615             _auto_save_path = NULL;
616             return;
617         }
618         
619         if (mkdir(_auto_save_path, 0700) < 0) {
620             free(_auto_save_path);
621             _auto_save_path = NULL;
622             return;
623         }
624     }
625     
626     // Now we run through the possible file names until we hit pay dirt.
627     //
628     _auto_save_file = (char*) malloc((size_t) strlen(_auto_save_path) + 100);
629     for (int suffix = 1; ; suffix++) {
630         sprintf(_auto_save_file, "%s/%s.%d", _auto_save_path, PREFIX, suffix);
631         if (SafeAccess(_auto_save_file, F_OK) != 0) {
632             break;
633         }
634     }
635 }
636
637 void
638 SendMsgDialog::loadDeadLetter(const char * path)
639 {
640     _auto_save_file = strdup(path);
641     parseNplace(path);
642     
643     _auto_save_interval = XtAppAddTimeOut(theApplication->appContext(),
644                                           getAutoSaveInterval(),
645                                           autoSaveCallback,
646                                           this);
647 }
648
649 void
650 SendMsgDialog::autoSaveCallback(XtPointer client_data, XtIntervalId * id)
651 {
652     SendMsgDialog * self = (SendMsgDialog *)client_data;
653     
654     if (self->_auto_save_interval != *id) {
655         // Random noise. Ignore it.
656         return;
657     }
658     
659     self->doAutoSave();
660     
661     self->_auto_save_interval = XtAppAddTimeOut(theApplication->appContext(),
662                                                 self->getAutoSaveInterval(),
663                                                 autoSaveCallback,
664                                                 self);
665 }
666
667 void
668 SendMsgDialog::doAutoSave(char *filename)
669 {
670     DtMail::Session * d_session = theRoamApp.session()->session();
671     DtMailEnv error;
672
673     busyCursor();
674     setStatus(GETMSG(DT_catd, 3, 70, "Writing dead letter..."));
675
676     updateMsgHnd();
677     
678     assert((NULL != filename));
679     RFCWriteMessage(error, d_session, filename, _msgHandle);
680     if((DTMailError_t) error == DTME_OutOfSpace )
681     {
682        RoamMenuWindow::ShowErrMsg((char *)error.getClient(),TRUE,this );
683     }
684     normalCursor();
685     clearStatus();
686 }
687
688 void
689 SendMsgDialog::doAutoSave(void)
690 {
691     doAutoSave(_auto_save_file);
692 }
693
694 int
695 SendMsgDialog::getAutoSaveInterval(void)
696 {
697     DtMailEnv error;
698     // Initialize the mail_error.
699     
700     error.clear();
701     
702     DtMail::Session * d_session = theRoamApp.session()->session();
703     DtMail::MailRc * mail_rc = d_session->mailRc(error);
704     
705     int save_interval;
706     const char * value = NULL;
707     mail_rc->getValue(error, "composeinterval", &value);
708     if (error.isSet() || value == NULL) {
709         save_interval = 10 * 60 * 1000; // 10 minutes
710     }
711     else {
712         save_interval = (int) strtol(value, NULL, 10) * 60 * 1000;
713         save_interval = (save_interval <= 0) ? 10 * 60 * 1000 : save_interval;
714     }
715     if (NULL != value)
716       free((void*) value);
717     
718     return(save_interval);
719 }
720
721 void
722 SendMsgDialog::setInclMsgHnd(DtMail::Message *msg, Boolean text)
723 {
724     _inclMsgHandle = msg;
725     _inclMsgHasText = text;
726 }
727
728 void
729 SendMsgDialog::setFirstBPHandled(Boolean handle)
730 {
731     _firstBPHandled = handle;
732 }
733
734 void
735 SendMsgDialog::setHeader(const char * name, const char * value)
736 {
737     // See if this header is in the list. If so, set the widget for
738     // it.
739     //
740     int slot = lookupHeader(name);
741     if (slot < 0) {
742         return;
743     }
744
745     HeaderList * hl = _header_list[slot];
746     if (hl->show == SMD_NEVER) {
747         // The user removed this header via props.
748         //
749         return;
750     }
751
752     if (hl->field_widget) {
753         XtVaSetValues(hl->field_widget,
754                       XmNvalue, value,
755                       NULL);
756         if (hl->show == SMD_HIDDEN) {
757             changeHeaderState(name);
758         }
759     }
760 }
761
762 void
763 SendMsgDialog::setHeader(const char * name, DtMailValueSeq & value)
764 {
765     if (value.length() == 0) {
766         return;
767     }
768
769     if (value.length() == 1) {
770         setHeader(name, *(value[0]));
771     }
772     else {
773         int max_len = 0;
774         for (int slen = 0; slen < value.length(); slen++) {
775             max_len += strlen(*(value[slen]));
776         }
777
778         char * new_str = new char[max_len + (value.length() * 3)];
779
780         strcpy(new_str, "");
781         for (int copy = 0; copy < value.length(); copy++) {
782             if (copy != 0) {
783                 strcat(new_str, " ");
784             }
785
786             strcat(new_str, *(value[copy]));
787         }
788
789         setHeader(name, new_str);
790         delete [] new_str;
791     }
792 }
793
794 void
795 SendMsgDialog::loadHeaders(DtMail::Message * input,
796                            DtMailBoolean load_all)
797 {
798     // We are going to go through every header in the message.
799     // If it is not one of the headers we block, then we will
800     // load it into the header pane, depending on the setting of
801     // load_all. If true, then we load every header we allow and
802     // create new dynamic headers as necessary. If load_all is false,
803     // then we only load headers that already are available in the
804     // memory pane.
805     //
806     DtMailEnv error;
807     DtMail::Message * msg = (input ? input : _msgHandle);
808     DtMail::Envelope * env = msg->getEnvelope(error);
809     DtMailHeaderHandle hnd;
810
811     int hcount = 0;
812     char * name;
813     DtMailValueSeq value;
814
815     for (hnd = env->getFirstHeader(error, &name, value);
816          error.isNotSet() && hnd;
817          hnd = env->getNextHeader(error, hnd, &name, value)) {
818
819         // Always ignore the Unix from line.
820         //
821         if (hcount == 0 &&
822             strcmp(name, "From") == 0) {
823             free(name);
824             value.clear();
825             hcount += 1;
826             continue;
827         }
828
829         hcount += 1;
830
831         // See if the name is one we always block.
832         //
833         if (block(name)) {
834             free(name);
835             value.clear();
836             continue;
837         }
838
839         int slot = lookupHeader(name);
840         if (slot < 0) {
841             // We dont have a place for this information. We may need
842             // to create a new header.
843             //
844             if (load_all) {
845                 HeaderList *hl = new HeaderList;
846                 hl->label = strdup(name);
847                 hl->header = strdup(name);
848                 hl->show = SMD_HIDDEN;
849                 _header_list.append(hl);
850                 createHeaders(_header_form);
851                 doDynamicHeaderMenus();
852             }
853             else {
854                 free(name);
855                 value.clear();
856                 continue;
857             }
858         }
859
860         setHeader(name, value);
861         free(name);
862         value.clear();
863     }
864 }
865
866 void
867 SendMsgDialog::storeHeaders(DtMail::Message * input)
868 {
869     DtMail::Message * msg = (input ? input : _msgHandle);
870     DtMailEnv error;
871     DtMail::Envelope * env = msg->getEnvelope(error);
872
873     // Walk through the headers. Fetch the strings from the ones
874     // that are visible to the user and stuff them into the
875     // message.
876     //
877     for (int scan = 0; scan < _header_list.length(); scan++) {
878         HeaderList * hl = _header_list[scan];
879         if (hl->show != SMD_ALWAYS && hl->show != SMD_SHOWN) {
880             continue;
881         }
882
883         char * value;
884         XtVaGetValues(hl->field_widget,
885                       XmNvalue, &value,
886                       NULL);
887         // If the header has a value, we want to set the value
888         // in the back end.  Otherwise, we don't want to send
889         // out a blank header, so we remove it.
890         if (strlen(value) > 0) {
891             env->setHeader(error, hl->header, DTM_TRUE, value);
892         }
893         else {
894             env->removeHeader(error, hl->header);
895         }
896         if (value) {
897                 XtFree(value);
898         }
899     }
900 }
901
902 void
903 SendMsgDialog::changeHeaderState(const char * name)
904 {
905     int slot = lookupHeader(name);
906     if (slot < 0) {
907         return;
908     }
909
910     HeaderList * hl = _header_list[slot];
911     if (hl->show == SMD_ALWAYS || hl->show == SMD_NEVER) {
912         return;
913     }
914
915     // If the user is trying to remove a header with a value other than
916     // the default, we should at least ask.
917     //
918     if (hl->show == SMD_SHOWN) {
919         char * value;
920         XtVaGetValues(hl->field_widget,
921                       XmNvalue, &value,
922                       NULL);
923         if (strlen(value) > 0) {
924             if (!hl->value || strcmp(value, hl->value) != 0) {
925                 char *buf = new char[256];
926                 sprintf(buf, 
927                         GETMSG(DT_catd, 2, 17, 
928                                 "You have edited \"%s\". Delete anyway?"), 
929                         hl->label);
930                 _genDialog->setToWarningDialog(GETMSG(DT_catd, 3, 71,
931                                                        "Mailer"),
932                                                buf);
933                 char * helpId = DTMAILHELPERROR;
934                 int answer = _genDialog->post_and_return(
935                                             GETMSG(DT_catd, 3, 72, "OK"), 
936                                             GETMSG(DT_catd, 3, 73, "Cancel"), 
937                                             helpId);
938                 delete [] buf;
939                 if (answer == 2) {
940                     free(value);
941                     return;
942                 }
943             }
944         }
945
946         free(value);
947     }
948
949     // Now we need to toggle the current state of the header.
950     //
951     char *label = new char[100];
952     if (hl->show == SMD_SHOWN) {
953         XtUnmanageChild(hl->form_widget);
954         hl->show = SMD_HIDDEN;
955         sprintf(label, "%s ", GETMSG(DT_catd, 1, 228, "Add"));
956
957     }
958     else {
959         XtManageChild(hl->form_widget);
960         hl->show = SMD_SHOWN;
961         sprintf(label, "%s ", GETMSG(DT_catd, 1, 229, "Delete"));
962     }
963
964     justifyHeaders();
965     reattachHeaders();
966
967     // Change the label on the menu item.
968     //
969     strcat(label, hl->label);
970     strcat(label, ":");
971     char *button_name = new char[100];
972     sprintf(button_name, "%s ", GETMSG(DT_catd, 1, 228, "Add"));
973     strcat(button_name, hl->label);
974     strcat(button_name, ":");
975
976     _menuBar->changeLabel(_format_menu, button_name, label);
977     delete [] label;
978     delete [] button_name;
979 }
980
981 void
982 SendMsgDialog::setStatus(const char * str)
983 {
984     char *tmpstr = strdup(str);
985     XmString label = XmStringCreateLocalized(tmpstr);
986
987     XtVaSetValues(_status_text,
988                   XmNlabelString, label,
989                   NULL);
990
991     XmUpdateDisplay(baseWidget());
992     XmStringFree(label);
993     free(tmpstr);
994 }
995
996 void
997 SendMsgDialog::clearStatus(void)
998 {
999     setStatus(" ");
1000 }
1001
1002 Boolean
1003 SendMsgDialog::isMsgValid(void)
1004 {
1005     if (!_msgHandle)
1006         return FALSE;
1007     else
1008         return TRUE;
1009 }
1010
1011
1012 // Sendmail is exed'd and this parent process returns immediately.  When
1013 // the sendmail child exits, this function is called with the pid of the 
1014 // child and its status.
1015 void
1016 SendMsgDialog::sendmailErrorProc (int, int status, void *data)
1017 {
1018     SendMsgDialog *smd = (SendMsgDialog *)data;
1019     char *helpId = NULL;
1020     char *buf = new char[2048];
1021
1022     smd->_first_time = TRUE;
1023     smd->_takeDown = FALSE;
1024
1025     // pid is the child process (sendmail) id
1026     // status is the exit status of the child process
1027     // data is any extra data associated with the child process
1028  
1029     switch (status) {
1030         case DTME_NoError:
1031             // The mail was successfully sent so return the compose
1032             // window to the cache and then return.
1033             smd->_send_button->activate();
1034             smd->_close_button->activate();
1035             smd->goAway(FALSE);
1036             return;
1037
1038         case DTME_BadMailAddress:
1039             /* NL_COMMENT
1040              * There was an error in one or more of the email addresses.  
1041              * Ask the user to type in a valid address and try again.
1042              */
1043             sprintf(buf, GETMSG(DT_catd, 5, 5, 
1044 "Some of the addresses in the message are incorrect,\n\
1045 and do not refer to any known users in the system.\n\
1046 Please make sure all of the addresses are valid and try again."));
1047             helpId = DTMAILHELPBADADDRESS;
1048
1049             break;
1050  
1051         case DTME_NoMemory:
1052             /* NL_COMMENT
1053              * Mailer ran out of memory.  Ask the user to quit some other
1054              * applications so there will be more memory available.
1055              */
1056  
1057             sprintf(buf, GETMSG(DT_catd, 5, 6,
1058 "Mailer does not have enough memory\n\
1059 available to send this message.\n\
1060 Try quitting other applications and\n\
1061 resend this message."));
1062             helpId = DTMAILHELPNOMEMORY;
1063                 
1064             break;
1065
1066         case DTME_TransportFailed:
1067         default:
1068             /* NL_COMMENT
1069              * There was an error from the mail transport (sendmail).
1070              */
1071
1072             sprintf(buf, GETMSG(DT_catd, 5, 7,
1073 "An error occurred while trying to send your message.\n\
1074 Check to make sure the message was received.  If not,\n\
1075 you may have to resend this message."));
1076             helpId = DTMAILHELPTRANSPORTFAILED;
1077     }
1078
1079     // popup the compose window
1080     smd->manage();
1081     smd->_send_button->activate();
1082     smd->_close_button->activate();
1083
1084     // popup the error dialog
1085     smd->_genDialog->setToErrorDialog(GETMSG(DT_catd, 2, 21, "Mailer"),
1086                              (char *) buf);
1087     smd->_genDialog->post_and_return(GETMSG(DT_catd, 3, 76, "OK"), helpId);
1088     delete [] buf;
1089 }
1090
1091
1092 void
1093 SendMsgDialog::send_message(const char * trans_impl, int trans_type)
1094 {
1095     DtMailEnv mail_exec_error;
1096     DtMailOperationId id;
1097     DtMail::Transport * mail_transport;
1098     DtMail::Session * d_session = theRoamApp.session()->session();
1099     DtMailEditor *editor = this->get_editor();
1100     AttachArea *attachArea = editor->attachArea();
1101     int numPendingActions, answer;
1102     char *helpId = NULL;
1103     char *buf = new char[2048];
1104     static int first_time = 1;
1105
1106     //
1107     // Check to see if we are already trying to send from this
1108     // SendMsgDialog.  If we are, we don't want to do it again.
1109     if (_already_sending) {
1110         delete [] buf;
1111         return;
1112     } else {
1113         _already_sending = TRUE;
1114     }
1115
1116     // First remove (unmap) the window;
1117     // send the message.
1118     // If you do it in the reverse order, users get confused coz
1119     // the window remains behind for a couple seconds after hitting 
1120     // "Send" and it ...
1121     this->unmanage();
1122     _send_button->deactivate();
1123     _close_button->deactivate();
1124
1125     mail_exec_error.clear();
1126     
1127
1128     // Check if message has addressees.  If it doesn't, what sense does
1129     // it make to Send it? 
1130
1131     if (!this->hasAddressee()) {
1132         // Message has no valid addressee.  Pop up error dialog.
1133
1134         sprintf(buf, GETMSG(DT_catd, 5, 8,
1135                  "Try Send after specifying  recipient(s) of the message in \nthe To:, Cc:, or Bcc: fields."));
1136
1137         helpId = DTMAILHELPNEEDADDRESSEE;
1138
1139         // Need help tag for above HelpID.
1140         
1141         // popup the compose window
1142         this->manage();
1143         _send_button->activate();
1144         _close_button->activate();
1145
1146         _genDialog->setToErrorDialog(GETMSG(DT_catd, 2, 21, "Mailer"),
1147                                      (char *) buf);
1148         _genDialog->post_and_return(GETMSG(DT_catd, 3, 76, "OK"), helpId);
1149
1150         // Reset the flag before we return.
1151         _already_sending = FALSE;
1152
1153         delete [] buf;
1154         return;
1155     }
1156
1157     // Since we are Send-ing, the SMD can be taken down later without
1158     // checking for dirty...
1159
1160     _first_time = TRUE;
1161     _takeDown = TRUE;
1162
1163     stopAutoSave();
1164     
1165     // Just get text from text widget; attachment BPs are filled
1166     // already when they are included/forwarded/added.
1167     
1168     updateMsgHnd();
1169     
1170     // Check if there are any pending attachments (open, print....)
1171     // If there are, pop up the dialog.
1172     // If the user wants to Send the message as is, continue with  the
1173     // submission process. 
1174     // If the user opted to Cancel, then return.
1175     
1176     
1177     numPendingActions = attachArea->getNumPendingActions();
1178     sprintf(buf, GETMSG(
1179                         DT_catd, 
1180                         3, 
1181                         77, 
1182                         "You have an attachment open that may have unsaved changes.\nSending this message will break the connection to the open\n attachment. Any unsaved changes will not be part of the\n message. You can use Save As to save Changes after the\n connection is broken, but the changes will not be part of\n the attachment." ));
1183     
1184     while (numPendingActions != 0) {
1185         // popup the compose window
1186         this->manage();
1187         _send_button->activate();
1188         _close_button->activate();
1189
1190         /* NL_COMMENT
1191          * The user tried to send a messages without saving changes in
1192          * some open attachments.  This warning makes sure that is what
1193          * the user intended.
1194          */
1195         
1196         _genDialog->setToQuestionDialog(
1197                         GETMSG(DT_catd, 5, 1, "Mailer"),
1198                         buf);
1199         helpId = DTMAILHELPPENDINGACTIONS;
1200         
1201         answer = _genDialog->post_and_return(helpId);
1202         
1203         if (answer == 1) {
1204             // OK selected
1205             numPendingActions = 0;
1206             this->unmanage();
1207             _send_button->deactivate();
1208             _close_button->deactivate();
1209         }
1210         else if (answer == 2) {
1211             // Cancel selected
1212             // Reset the flag before we return.
1213             _already_sending = FALSE;
1214             delete [] buf;
1215             return;
1216         }
1217     }
1218    
1219     // Determine which transport mechanism will be used.
1220     if ( trans_type ) {   // Default
1221         // Only register XtAppAddInput once
1222         if (first_time)
1223         {
1224             // Create the pipe between the RFCTransport::childHandler
1225             // and XtAppAddInput
1226             if (pipe(_transfds) < 0) {
1227                 // If this failed, make sure we try to initialize again later.
1228                 mail_exec_error.setError(DTME_NoMemory);        
1229                 popupMemoryError (mail_exec_error);
1230
1231                 // Reset the flag before we return.
1232                 _already_sending = FALSE;
1233                 delete [] buf;
1234                 return;
1235             }
1236
1237             // Call ourproc when input is available on _transfds[0]
1238             XtAppAddInput(XtWidgetToApplicationContext(this->_main_form),
1239                 _transfds[0], (XtPointer)XtInputReadMask,
1240                 (XtInputCallbackProc)
1241                     (theRoamApp.default_transport()->getSendmailReturnProc()),
1242                 NULL);
1243             first_time = 0;
1244         }
1245
1246         // Tell the transport where the callback is
1247         theRoamApp.default_transport()->initTransportData( _transfds,
1248                 &(SendMsgDialog::sendmailErrorProc), this);
1249         id = theRoamApp.default_transport()->submit(mail_exec_error,
1250                 _msgHandle, _log_msg);
1251
1252     } else {
1253         // Construct transport
1254         mail_transport = d_session->transportConstruct(mail_exec_error,
1255                 trans_impl, RoamApp::statusCallback, this);
1256
1257         // Only register XtAppAddInput once
1258         if (first_time)
1259         {
1260             // Create the pipe between the RFCTransport::childHandler
1261             // and XtAppAddInput
1262             if (pipe(_transfds) < 0) {
1263                 // If this failed, make sure we try to initialize again later.
1264                 mail_exec_error.setError(DTME_NoMemory);        
1265                 popupMemoryError (mail_exec_error);
1266
1267                 // Reset the flag before we return.
1268                 _already_sending = FALSE;
1269                 delete [] buf;
1270                 return;
1271             }
1272
1273             // Call ourproc when input is available on _transfds[0]
1274             XtAppAddInput(XtWidgetToApplicationContext(this->_main_form),
1275                 _transfds[0], (XtPointer)XtInputReadMask,
1276                 (XtInputCallbackProc)(mail_transport->getSendmailReturnProc()),
1277                 NULL);
1278             first_time = 0;
1279         }
1280
1281         // Tell the transport where the callback is
1282         mail_transport->initTransportData(_transfds,
1283                 &(SendMsgDialog::sendmailErrorProc), this);
1284         id = mail_transport->submit(mail_exec_error, _msgHandle, _log_msg);
1285     }
1286   
1287     popupMemoryError (mail_exec_error);
1288
1289     // Reset the flag before we return.
1290     _already_sending = FALSE;
1291
1292     delete [] buf;
1293 }
1294
1295 void
1296 SendMsgDialog::popupMemoryError(DtMailEnv &error)
1297 {
1298     char *helpId = NULL;
1299     char *buf = new char[2048];
1300
1301     _takeDown = FALSE;
1302
1303     // Popup an error dialog if necessary. 
1304     if (error.isSet()) {
1305         if ((DTMailError_t)error == DTME_NoMemory) {
1306
1307             /* NL_COMMENT
1308              * Mailer ran out of memory.  Ask the user to quit some other
1309              * applications so there will be more memory available.
1310              */
1311             sprintf(buf, GETMSG(DT_catd, 5, 6,
1312 "Mailer does not have enough memory\n\
1313 available to send this message.\n\
1314 Try quitting other applications and\n\
1315 resend this message."));
1316             helpId = DTMAILHELPNOMEMORY;
1317          }
1318          else {
1319                 /* NL_COMMENT
1320              * An unidentifiable error happened during mail transport
1321              * Pop it up *as is* (need to update this function if so)
1322              */
1323             sprintf(buf, "%s", (const char *)error);
1324             helpId = DTMAILHELPERROR;
1325         }
1326
1327         // popup the compose window
1328         this->manage();
1329         _send_button->activate();
1330         _close_button->activate();
1331
1332         // popup the error dialog
1333         this->_genDialog->setToErrorDialog(GETMSG(DT_catd, 2, 21, "Mailer"),
1334                 (char *) buf);
1335         this->_genDialog->post_and_return(GETMSG(DT_catd, 3, 76, "OK"),
1336                 helpId);
1337     }   
1338     delete [] buf;
1339 }
1340
1341
1342 Widget 
1343 SendMsgDialog::createWorkArea ( Widget parent )
1344 {
1345     FORCE_SEGV_DECL(CmdInterface, ci);
1346     Widget send_form;
1347     
1348     // Create the parent form
1349     
1350     _main_form = XmCreateForm( parent, "Work_Area", NULL, 0 );
1351     XtVaSetValues(_main_form, XmNresizePolicy, XmRESIZE_NONE, NULL);
1352     
1353     printHelpId("form", _main_form);
1354     /* add help callback */
1355     XtAddCallback(_main_form, XmNhelpCallback, HelpCB, DTMAILCOMPOSEWINDOW);
1356     XtVaSetValues(_main_form, XmNallowResize, True, NULL);
1357     
1358     
1359     // Create the area for status messages.
1360     //
1361     _status_form = XtVaCreateManagedWidget("StatusForm",
1362                                            xmFormWidgetClass, _main_form,
1363                                            XmNtopAttachment, XmATTACH_FORM,
1364                                            XmNrightAttachment, XmATTACH_FORM,
1365                                            XmNrightOffset, 2,
1366                                            XmNleftAttachment, XmATTACH_FORM,
1367                                            XmNleftOffset, 2,
1368                                            NULL);
1369
1370     _status_text = XtVaCreateManagedWidget("StatusLabel",
1371                                            xmLabelWidgetClass, _status_form,
1372                                            XmNtopAttachment, XmATTACH_FORM,
1373                                            XmNbottomAttachment, XmATTACH_FORM,
1374                                            XmNrightAttachment, XmATTACH_FORM,
1375                                            XmNleftAttachment, XmATTACH_FORM,
1376                                            XmNalignment, XmALIGNMENT_BEGINNING,
1377                                            NULL);
1378
1379     clearStatus();
1380
1381     Widget s_sep = XtVaCreateManagedWidget("StatusSep",
1382                                            xmSeparatorGadgetClass,
1383                                            _main_form,
1384                                            XmNtopAttachment, XmATTACH_WIDGET,
1385                                            XmNtopWidget, _status_form,
1386                                            XmNleftAttachment, XmATTACH_FORM,
1387                                            XmNrightAttachment, XmATTACH_FORM,
1388                                            NULL);
1389     
1390     _header_form = XtVaCreateManagedWidget("HeaderArea",
1391                                            xmFormWidgetClass, _main_form,
1392                                            XmNtopAttachment, XmATTACH_WIDGET,
1393                                            XmNtopWidget, s_sep,
1394                                            XmNleftAttachment, XmATTACH_FORM,
1395                                            XmNrightAttachment, XmATTACH_FORM,
1396                                            NULL);
1397     printHelpId("header_form", _header_form);
1398
1399     createHeaders(_header_form);
1400     
1401     Widget sep1 = XtVaCreateManagedWidget("Sep1",
1402                                           xmSeparatorGadgetClass,
1403                                           _main_form,
1404                                           XmNtopAttachment, XmATTACH_WIDGET,
1405                                           XmNtopWidget, _header_form,
1406                                           XmNtopOffset, 1,
1407                                           XmNrightAttachment, XmATTACH_FORM,
1408                                           XmNleftAttachment, XmATTACH_FORM,
1409                                           NULL);
1410
1411     // Create the editor and attach it to the header_form
1412     
1413     _my_editor = new DtMailEditor(_main_form, this);
1414     
1415     _my_editor->initialize();
1416     _my_editor->attachArea()->setOwnerShell(this);
1417     _my_editor->setEditable(TRUE);
1418     _my_editor->manageAttachArea();
1419     
1420     // Create a RowCol widget that contains buttons
1421     
1422     send_form =  XtCreateManagedWidget("SendForm", 
1423                                        xmFormWidgetClass, _main_form, NULL, 0);
1424     
1425     
1426     // Create the Send and Close buttons as children of rowCol
1427     
1428     _send_button = new SendCmd ( "Send", 
1429                                  GETMSG(DT_catd, 1, 230, "Send"), 
1430                                  TRUE, 
1431                                  this, 
1432                                  1);
1433     ci  = new ButtonInterface (send_form, _send_button);
1434     
1435     XtVaSetValues(ci->baseWidget(),
1436                   XmNleftAttachment, XmATTACH_FORM,
1437                   XmNleftOffset, OFFSET,
1438                   XmNbottomAttachment, XmATTACH_FORM,
1439                   XmNbottomOffset, 5,
1440                   NULL);
1441     
1442     Widget send_bw = ci->baseWidget();
1443     XtManageChild(send_bw);
1444     ci->manage();
1445     
1446     _close_button = new CloseCmd (
1447                                   "Close",
1448                                   GETMSG(DT_catd, 1, 118, "Close"),
1449                                   TRUE, 
1450                                   this->baseWidget(), 
1451                                   this );
1452     ci = new ButtonInterface (send_form, _close_button);
1453     XtVaSetValues(ci->baseWidget(),
1454                   XmNleftOffset, 20,
1455                   XmNleftAttachment, XmATTACH_WIDGET,
1456                   XmNleftWidget, send_bw,
1457                   XmNbottomAttachment, XmATTACH_FORM,
1458                   XmNbottomOffset, 5,
1459                   NULL);
1460     
1461     XtManageChild(ci->baseWidget());
1462     ci->manage();
1463     
1464     // Now attach the editor to the form and to the rowCol
1465     // And the rowCol to the bottom of the form.
1466     // We need this attachment ordering so that resizes always
1467     // get transferred to the editor.
1468     
1469     Widget wid = _my_editor->container();
1470     XtVaSetValues(wid,
1471                   XmNleftAttachment, XmATTACH_FORM,
1472                   XmNrightAttachment, XmATTACH_FORM,
1473                   XmNtopAttachment, XmATTACH_WIDGET,
1474                   XmNtopWidget, sep1,
1475                   XmNtopOffset, 1,
1476                   XmNbottomAttachment, XmATTACH_WIDGET,
1477                   XmNbottomWidget, send_form,
1478                   NULL);
1479     
1480     XtVaSetValues(send_form,
1481                   XmNbottomAttachment, XmATTACH_FORM,
1482                   NULL);
1483     
1484     
1485     // Set focus 
1486     HeaderList * hl = _header_list[0];
1487     (void) XmProcessTraversal(hl->field_widget, XmTRAVERSE_CURRENT);
1488     
1489     // Set the title to be New Message
1490     //char *ttl = GETMSG(DT_catd, 1, 119, "New Message");
1491     //this->setTitle(ttl);
1492     //this->setIconTitle(ttl);
1493     
1494     XtManageChild(_main_form);
1495     return _main_form;
1496 }
1497
1498
1499 void
1500 SendMsgDialog::createHeaders(Widget header_form)
1501 {
1502     Widget      previous_form = NULL;
1503     char        *field_name = new char[50];
1504
1505     for (int header = 0; header < _header_list.length(); header++) {
1506         HeaderList * hl = _header_list[header];
1507
1508         // We use SMD_NEVER to indicate the header has disappeared from
1509         // the list.
1510         //
1511         if (hl->show == SMD_NEVER) {
1512             continue;
1513         }
1514
1515         // If the widgets already exist, then simply manage them.
1516         if (hl->form_widget) {
1517             previous_form = hl->form_widget;
1518             if (hl->value)
1519                 XtVaSetValues(hl->field_widget,
1520                           XmNvalue, hl->value,
1521                           NULL);
1522             else
1523                 XtVaSetValues(hl->field_widget,
1524                           XmNvalue, "",
1525                           NULL);
1526             continue;
1527         }
1528
1529         if (previous_form == NULL) {
1530             // Create a form, attaching it to the top. This is a special
1531             // case. Other lines are created attached to the form above
1532             // them.
1533             strcpy(field_name, "form_");
1534             strncat(field_name, hl->label, 45);
1535             field_name[strlen(hl->label) + 5] = 0;
1536             hl->form_widget =
1537                 XtVaCreateWidget(field_name,
1538                                  xmFormWidgetClass,
1539                                  header_form,
1540                                  XmNtopAttachment, XmATTACH_FORM,
1541                                  XmNtopOffset, 2,
1542                                  XmNleftAttachment, XmATTACH_FORM,
1543                                  XmNleftOffset, 3,
1544                                  XmNrightAttachment, XmATTACH_FORM,
1545                                  XmNrightOffset, 3,
1546                                  NULL);
1547
1548         }
1549         else {
1550             strcpy(field_name, "form_");
1551             strncat(field_name, hl->label, 45);
1552             field_name[strlen(hl->label) + 5] = 0;
1553             hl->form_widget =
1554                 XtVaCreateWidget(field_name,
1555                                  xmFormWidgetClass,
1556                                  header_form,
1557                                  XmNtopAttachment, XmATTACH_WIDGET,
1558                                  XmNtopWidget, previous_form,
1559                                  XmNleftAttachment, XmATTACH_FORM,
1560                                  XmNleftOffset, 3,
1561                                  XmNrightAttachment, XmATTACH_FORM,
1562                                  XmNrightOffset, 3,
1563                                  NULL);
1564
1565         }
1566
1567         // The label will be to the left of the form.
1568         //
1569         strcpy(field_name, hl->label);
1570         strcat(field_name, ":");
1571         XmString label = XmStringCreateLocalized(field_name);
1572         hl->label_widget =
1573             XtVaCreateManagedWidget(hl->label,
1574                                     xmLabelWidgetClass,
1575                                     hl->form_widget,
1576                                     XmNtopAttachment, XmATTACH_FORM,
1577                                     XmNbottomAttachment, XmATTACH_FORM,
1578                                     XmNleftAttachment, XmATTACH_FORM,
1579                                     XmNlabelString, label,
1580                                     NULL);
1581         XmStringFree(label);
1582         
1583         strcpy(field_name, "field_");
1584         strncat(field_name, hl->label, 43);
1585         field_name[strlen(hl->label) + 6] = 0;
1586         
1587         hl->field_widget =
1588             XtVaCreateManagedWidget(field_name,
1589                                     xmTextFieldWidgetClass,
1590                                     hl->form_widget,
1591                                     XmNtraversalOn,     True,
1592                                     XmNtopAttachment, XmATTACH_FORM,
1593                                     XmNrightAttachment, XmATTACH_FORM,
1594                                     XmNleftAttachment, XmATTACH_WIDGET,
1595                                     XmNleftWidget, hl->label_widget,
1596                                     NULL);
1597
1598         if (hl->show != SMD_HIDDEN) {
1599             XtManageChild(hl->form_widget);
1600         }
1601         else {
1602             XtVaSetValues(hl->form_widget,
1603                           XmNtopAttachment, XmATTACH_NONE,
1604                           NULL);
1605         }
1606
1607         XtAddCallback(hl->field_widget,
1608                       XmNactivateCallback,
1609                       header_form_traverse,
1610                       NULL);
1611
1612         XtAddCallback(hl->field_widget,
1613                       XmNvalueChangedCallback,
1614                       headerValueChanged,
1615                       this);
1616
1617         if (hl->value) {
1618             XtVaSetValues(hl->field_widget,
1619                           XmNvalue, hl->value,
1620                           NULL);
1621         }
1622
1623         previous_form = hl->form_widget;
1624     }
1625
1626     justifyHeaders();
1627     delete [] field_name;
1628 }
1629
1630 void
1631 SendMsgDialog::doDynamicHeaderMenus(void)
1632 {
1633     // This is really a pain, but we have to blow away the list to
1634     // build another one. This could probably be done more efficiently,
1635     // but we wont try to figure out how right now.
1636     //
1637     if (_format_cmds) {
1638         _menuBar->removeOnlyCommands(_format_menu, _format_cmds);
1639     }
1640
1641     _format_cmds = new CmdList("DynamicFormatCommands", "DynamicFormatCommands");
1642
1643     // Only put on commands that are shown or hidden. The items that
1644     // are always are never should not be presented to the user as
1645     // an option to change.
1646     //
1647     char *label = new char[100];
1648
1649     for (int h = 0; h < _header_list.length(); h++) {
1650         HeaderList * hl = _header_list[h];
1651
1652         switch(hl->show) {
1653           case SMD_SHOWN:
1654             sprintf(label, "%s ", GETMSG(DT_catd, 1, 229, "Delete"));
1655             break;
1656
1657           case SMD_HIDDEN:
1658             sprintf(label, "%s ", GETMSG(DT_catd, 1, 228, "Add"));
1659             break;
1660
1661           default:
1662             continue;
1663         }
1664
1665         strcat(label, hl->label);
1666         strcat(label, ":");
1667
1668         char * priv_label = strdup(label);
1669
1670         Cmd * new_cmd = new HideShowCmd(priv_label, priv_label,
1671                                         1, this, hl->label);
1672
1673         // Add the commands one at a time with addCommand() vs. all
1674         // at once with addCommands(). That way new commands will
1675         // be created instead of reusing old ones.
1676         _menuBar->addCommand(_format_menu, new_cmd);
1677         _format_cmds->add(new_cmd);
1678     }
1679     delete [] label;
1680 }
1681
1682 void
1683 bogus_cb(void *)
1684 {
1685 // Should theInfoDialogManager be destroyed here ???
1686 }
1687
1688 #ifdef DEAD_WOOD
1689 void
1690 SendMsgDialog::open_att_cb( void *clientData, char *selection )
1691 {
1692     SendMsgDialog *obj = (SendMsgDialog *)clientData;
1693     
1694     obj->open_att(selection);
1695 }
1696
1697 void
1698 SendMsgDialog::open_att( char *)  // arg is char *selection
1699 {
1700 }
1701 #endif /* DEAD_WOOD */
1702
1703 void
1704 SendMsgDialog::include_file_cb( void *client_data, char *selection )
1705 {
1706     SendMsgDialog *obj = (SendMsgDialog *)client_data;
1707     obj->include_file(selection);
1708     if (NULL != selection)
1709       XtFree(selection);
1710     
1711 }
1712
1713 void
1714 SendMsgDialog::include_file(
1715                             char *selection
1716                             )
1717 {
1718     FILE *fp;
1719     char *buf = new char[MAXPATHLEN];
1720     
1721     // I don't need to open the file to see if it's readable if loadFile()
1722     // returns error status.
1723     if ( (fp = fopen(selection, "r")) == NULL ) {
1724         sprintf(buf, GETMSG(DT_catd, 2, 18, "Error: Cannot include file %s"), 
1725                 selection);
1726         theInfoDialogManager->post(
1727                                    "Mailer", 
1728                                    buf, 
1729                                    (void *)this->_file_include, 
1730                                    bogus_cb);
1731     } else {
1732         fclose(fp);
1733         this->_my_editor->textEditor()->append_to_contents("\n", 2);
1734         this->_my_editor->textEditor()->append_at_cursor(selection);
1735         this->_my_editor->textEditor()->append_to_contents("\n", 2);
1736     }
1737     delete [] buf;
1738 }
1739
1740 int
1741 SendMsgDialog::get_confirm_attachment_threshold()
1742 {
1743     DtMailEnv        error;
1744     DtMail::Session *m_session = theRoamApp.session()->session();
1745     const char      *value = NULL;
1746     int              threshold;
1747
1748     m_session->mailRc(error)->getValue(error, "confirmattachments", &value);
1749     if (error.isSet()) return 0;
1750
1751     m_session->mailRc(error)->getValue(error, "confirmattachmentthreshold",
1752                                         &value);
1753     if (error.isNotSet() && NULL!=value)
1754       threshold = 1024 * atoi(value);
1755     else
1756       threshold = 1024 * 64;
1757     
1758     return threshold;
1759 }
1760
1761 int
1762 SendMsgDialog::confirm_add_attachment(char *file, int size)
1763 {
1764     char *buf = new char[BUFSIZ];
1765     char *format;
1766     int   answer;
1767         
1768     format =
1769       GETMSG(DT_catd, 1, 263,
1770              "The attachment '%s' is %d kilobytes.\nAdd as attachment?");
1771     sprintf(buf, format, file, size/1024);
1772     _genDialog->setToQuestionDialog(GETMSG(DT_catd, 5, 2, "Mailer"), buf);
1773     answer = _genDialog->post_and_return(NULL);
1774     return (answer==1);
1775 }
1776
1777 void
1778 SendMsgDialog::add_att_cb( void *client_data, char *selection )
1779 {
1780     SendMsgDialog *obj = (SendMsgDialog *)client_data;
1781     obj->add_att(selection);
1782     if (NULL != selection)
1783       free(selection);
1784 }
1785
1786 void
1787 SendMsgDialog::add_att(char *file)
1788 {
1789     struct stat statbuf;
1790
1791     if (-1 != stat(file, &statbuf) && _confirm_attachment_threshold &&
1792         _confirm_attachment_threshold < statbuf.st_size)
1793     {
1794         if (! confirm_add_attachment(file, statbuf.st_size)) return;
1795     }
1796
1797     // Activate Attachment menu???
1798     this->get_editor()->attachArea()->
1799       addAttachment(_msgHandle, _lastAttBP, file, NULL);
1800     this->setLastAttBP();
1801     this->activate_default_attach_menu();
1802
1803     // This will manage the attach pane too.
1804     ((ToggleButtonCmd *)_att_show_pane)->setButtonState(TRUE, TRUE);
1805 }
1806
1807 void
1808 SendMsgDialog::add_att(char *name, DtMailBuffer buf)
1809 {
1810     if (_confirm_attachment_threshold &&
1811         _confirm_attachment_threshold < buf.size)
1812     {
1813         if (! confirm_add_attachment("", buf.size)) return;
1814     }
1815
1816     this->get_editor()->attachArea()->
1817       addAttachment(_msgHandle, _lastAttBP, name, buf);
1818     this->setLastAttBP();
1819     this->activate_default_attach_menu();
1820     
1821     // This will manage the attach pane too.
1822     ((ToggleButtonCmd *)_att_show_pane)->setButtonState(TRUE, TRUE);
1823 }
1824
1825 void
1826 SendMsgDialog::add_att(DtMailBuffer buf)
1827 {
1828     char *name = NULL;
1829     add_att(name, buf);
1830 }
1831
1832 void
1833 SendMsgDialog::save_att_cb( void *client_data, char *selection )
1834 {
1835     SendMsgDialog *obj = (SendMsgDialog *)client_data;
1836     
1837     obj->save_selected_attachment(selection);
1838     
1839 }
1840
1841 void
1842 SendMsgDialog::save_selected_attachment(
1843                                         char *selection
1844                                         )
1845 {
1846     DtMailEnv mail_error;
1847     
1848     mail_error.clear();
1849     
1850     AttachArea *attarea = this->get_editor()->attachArea();
1851     Attachment *attachment = attarea->getSelectedAttachment(); 
1852     
1853     // Get selected attachment, if none selected, then return.
1854     if ( attachment == NULL ) {
1855         // Let User know that no attachment has been selected???
1856         int answer = 0;
1857         char *helpId = NULL;
1858         
1859         
1860         _genDialog->setToErrorDialog(
1861                                      GETMSG(DT_catd, 1, 120, "Mailer"),    
1862                                      GETMSG(DT_catd, 2, 19, "An attachment needs to be selected before issuing the\n\"Save As\" command to save to a file.") );
1863         helpId = DTMAILHELPSELECTATTACH;
1864         answer = _genDialog->post_and_return(
1865                                              GETMSG(DT_catd, 3, 74, "OK"), helpId );
1866         return;
1867     }
1868     
1869     // Save selected attachments.
1870     attachment->saveToFile(mail_error, selection);
1871     if ( mail_error.isSet() ) {
1872         // Let User know error condition???
1873         return;
1874     }
1875 }
1876
1877 void
1878 SendMsgDialog::propsChanged(void)
1879 {
1880     DtMail::Session *m_session = theRoamApp.session()->session();
1881     const char * value = NULL;
1882     DtMailEnv error;
1883
1884     enableWorkAreaResize();
1885
1886     m_session->mailRc(error)->getValue(error, "hideattachments", &value);
1887     if (value) {
1888         if (_show_attach_area) {
1889                 _show_attach_area = FALSE;
1890                 this->hideAttachArea();
1891         }
1892     }
1893     else 
1894         if (!_show_attach_area) {
1895                 _show_attach_area = TRUE;
1896                 this->showAttachArea();
1897         }
1898     if (NULL != value)
1899       free((void*) value);
1900
1901     _confirm_attachment_threshold = get_confirm_attachment_threshold();
1902
1903     // Reset Log State
1904     const char * logfile = NULL;
1905     m_session->mailRc(error)->getValue(error, "record", &logfile);
1906     if (logfile == NULL)
1907         _file_log->deactivate();
1908     else
1909         _file_log->activate();
1910
1911     _my_editor->textEditor()->update_display_from_props();
1912
1913     m_session->mailRc(error)->getValue(error, "dontlogmessages", &value);
1914     if (logfile == NULL || error.isNotSet()) {
1915         // logfile is not specified or "dontlogmessages" is TRUE
1916         setLogState(DTM_FALSE);
1917         ((ToggleButtonCmd *)_file_log)->setButtonState(FALSE, TRUE);
1918     } else {
1919         // logfile is specified and "dontlogmessages" is FALSE
1920         setLogState(DTM_TRUE);
1921         ((ToggleButtonCmd *)_file_log)->setButtonState(TRUE, TRUE);
1922     }
1923
1924     if (NULL != logfile)
1925       free((void*) logfile);
1926     if (NULL != value)
1927       free((void*) value);
1928
1929     value = NULL;
1930     m_session->mailRc(error)->getValue(error, "templates", &value);
1931     if  ( (value == NULL && _templateList != NULL) ||
1932           (value != NULL &&
1933           (_templateList == NULL || strcmp(value, _templateList)) != 0) ) {
1934         // Template list has changed
1935         if (_templateList != NULL)
1936                 free (_templateList);
1937         if (value != NULL && *value != '\0')
1938                 _templateList = strdup(value);
1939         else
1940                 _templateList = NULL;
1941         createFormatMenu();
1942     }
1943     if (NULL != value)
1944       free((void*) value);
1945
1946     // Alias Popup Menus
1947     DtVirtArray<PropStringPair*>        *newAliases;
1948     Boolean                             aliasesChanged = FALSE;
1949     
1950     newAliases = new DtVirtArray<PropStringPair*> (10);
1951     createAliasList(newAliases);
1952     if (newAliases->length() == _aliasList->length())
1953     {
1954         int     length = newAliases->length();
1955         int     i = 0;
1956
1957         while (i<length && aliasesChanged==FALSE)
1958         {
1959             PropStringPair      *p1 = (*newAliases)[i];
1960             PropStringPair      *p2 = (*_aliasList)[i];
1961
1962             if ( strncmp(p1->label, p2->label, strlen(p2->label)) ||
1963                  strncmp(p1->value, p2->value, strlen(p2->value)) )
1964               aliasesChanged = TRUE;
1965
1966             i++;
1967         }
1968     }
1969     else
1970       aliasesChanged = TRUE;
1971     
1972     if (aliasesChanged == TRUE)
1973     {
1974         destroyAliasPopupMenus();
1975         destroyAliasList(_aliasList);
1976         _aliasList = newAliases;
1977         createAliasPopupMenus();
1978     }
1979     else
1980       destroyAliasList(newAliases);
1981
1982     disableWorkAreaResize();
1983 }
1984
1985 void
1986 SendMsgDialog::createMenuPanes()
1987 {
1988     CmdList *cmdList;
1989     Cardinal n = 0;
1990     DtMailEnv error;
1991     const char * value = NULL;
1992
1993     
1994     _separator = new SeparatorCmd( "Separator","Separator", TRUE );
1995     
1996     // File
1997     cmdList = new CmdList( "File", GETMSG(DT_catd, 1, 121, "File") );
1998     
1999     // Default directory is set below at the same time as the default
2000     // directory for att_add.
2001     _file_include   = new UnifiedSelectFileCmd (
2002                                  "Include...",
2003                                  GETMSG(DT_catd, 1, 122, "Include..."),
2004                                  GETMSG(DT_catd, 1, 123, "Mailer - Include"),
2005                                  GETMSG(DT_catd, 1, 124, "Include"),
2006                                  TRUE, 
2007                                  SendMsgDialog::include_file_cb, 
2008                                  this,
2009                                  this->baseWidget());
2010     
2011
2012     // Remap OK button to Include
2013     // XtVaSetValues(_file_include->fileBrowser, 
2014     //    XmNokLabelString, GETMSG(DT_catd,
2015     //    1, 77, "Include"), NULL);
2016     _file_save_as = new SaveAsTextCmd(
2017                               "Save As Text...",
2018                               GETMSG(DT_catd, 1, 125, "Save As Text..."),
2019                               GETMSG(DT_catd, 1, 126, "Mailer - Save As Text"),
2020                               TRUE,
2021                               get_editor()->textEditor(),
2022                               this,
2023                               baseWidget());
2024     
2025     _file_log = new LogMsgCmd ( 
2026                         "Log Message",
2027                         GETMSG(DT_catd, 1, 127, "Log Message"), TRUE, this);
2028     
2029     // 1 for default transport.
2030     
2031     _file_send = new SendCmd (
2032                  "Send",
2033                  GETMSG(DT_catd, 1, 117, "Send"), 
2034                  TRUE, 
2035                  this, 
2036                  1 );
2037     
2038     // Find out how many transports there are and build sub menu dynamically.
2039     DtMail::Session *d_session;
2040
2041     if ( theRoamApp.session() == NULL ) {
2042          MailSession *new_session = new MailSession(
2043                                                 error, 
2044                                                 theApplication->appContext());
2045          theRoamApp.setSession(new_session);
2046      }
2047
2048     CmdList *subcmdList1 = new CmdList (
2049                                         "Send As",
2050                                         GETMSG(DT_catd, 1, 128, "Send As") );
2051     
2052     d_session = theRoamApp.session()->session();
2053     const char **impls = d_session->enumerateImpls(error);
2054     
2055     for ( int impl = 0;  impls[impl];  impl++ ) {
2056         DtMailBoolean trans;
2057         d_session->queryImpl(error, impls[impl],
2058                              DtMailCapabilityTransport, &trans);
2059         if (!error.isSet() && trans == DTM_TRUE ) {
2060             _file_sendAs[_num_sendAs] = new SendCmd( strdup(impls[impl]), 
2061                                                      (char *)impls[impl],
2062                                                      TRUE, 
2063                                                      this, 
2064                                                      0 );
2065             subcmdList1->add( _file_sendAs[_num_sendAs] );
2066             _num_sendAs++;
2067         }
2068         // Assume an error means this query failed.  But keep going and
2069         // get the next transport.
2070     }
2071     
2072     _file_close = new CloseCmd ( 
2073                                  "Close",
2074                                  GETMSG(DT_catd, 1, 129, "Close"), 
2075                                  TRUE, 
2076                                  _menuBar->baseWidget(), 
2077                                  this );
2078     
2079     // Now build the menu
2080     
2081     cmdList->add( _file_include );
2082     cmdList->add( _file_save_as );
2083     cmdList->add( _file_log );
2084     cmdList->add( _separator );
2085     
2086     cmdList->add( _file_send );
2087 #if defined(USE_SEND_AS_MENU)
2088     cmdList->add( subcmdList1 );
2089 #endif
2090     cmdList->add( _separator );
2091     
2092     cmdList->add( _file_close );
2093     
2094     _menuBar->addCommands ( cmdList );
2095     delete subcmdList1;
2096     delete cmdList;
2097     
2098     // Edit
2099     
2100     cmdList = new CmdList( "Edit", GETMSG(DT_catd, 1, 130, "Edit") );
2101     
2102     _edit_undo = new EditUndoCmd ( "Undo",
2103                                         GETMSG(DT_catd, 1, 131, "Undo"),
2104                                         TRUE, this );
2105     _edit_cut = new EditCutCmd ( "Cut",
2106                                         GETMSG(DT_catd, 1, 132, "Cut"),
2107                                         TRUE, this );
2108     _edit_copy = new EditCopyCmd ( "Copy",
2109                                         GETMSG(DT_catd, 1, 133, "Copy"),
2110                                         TRUE, this );
2111     _edit_paste = new EditPasteCmd ( "Paste",
2112                                         GETMSG(DT_catd, 1, 134 , "Paste"), 
2113                                         TRUE, 
2114                                         this );
2115     
2116     // Begin Paste Special submenu
2117     subcmdList1 = new CmdList ( "Paste Special", GETMSG(DT_catd, 1, 135 , "Paste Special") );
2118     _edit_paste_special[0] = new EditPasteSpecialCmd (
2119                                                       "Bracketed",
2120                                                       GETMSG(DT_catd, 1, 136 , "Bracketed"),
2121                                                       TRUE, this, Editor::IF_BRACKETED
2122                                                       );
2123     subcmdList1->add(_edit_paste_special[0]);
2124     _edit_paste_special[1] = new EditPasteSpecialCmd (
2125                                                       "Indented",
2126                                                       GETMSG(DT_catd, 1, 137 , "Indented"), 
2127                                                       TRUE, this, Editor::IF_INDENTED );
2128     subcmdList1->add(_edit_paste_special[1]);
2129     // End Paste Special submenu
2130     
2131     _edit_clear = new EditClearCmd ( "Clear", GETMSG(DT_catd, 1, 138, "Clear"), 
2132                                      TRUE, 
2133                                      this );
2134     _edit_delete = new EditDeleteCmd ( "Delete", GETMSG(DT_catd, 1, 139, "Delete"), 
2135                                        TRUE, 
2136                                        this );
2137     _edit_select_all = new EditSelectAllCmd ( 
2138                                               "Select All",
2139                                               GETMSG(DT_catd, 1, 140, "Select All"), 
2140                                               TRUE, 
2141                                               this );
2142     _format_find_change = new FindChangeCmd ( 
2143                                               "Find/Change...",
2144                                               GETMSG(DT_catd, 1, 155, "Find/Change..."), 
2145                                               TRUE, 
2146                                               this );
2147     _format_spell = new SpellCmd (
2148                                   "Check Spelling...",
2149                                   GETMSG(DT_catd, 1, 156, "Check Spelling..."), 
2150                                   TRUE, 
2151                                   this );
2152
2153     cmdList->add( _edit_undo );
2154     cmdList->add( _separator );
2155     cmdList->add( _edit_cut );
2156     cmdList->add( _edit_copy );
2157     cmdList->add( _edit_paste );
2158     cmdList->add( subcmdList1 ); // Add Paste Special submenu
2159     cmdList->add( _separator );
2160     cmdList->add( _edit_clear );
2161     cmdList->add( _edit_delete );
2162     cmdList->add( _separator );
2163     cmdList->add( _edit_select_all );
2164     cmdList->add( _separator );
2165     cmdList->add( _format_find_change );
2166     /*
2167      *
2168      * SpellCheck is not supported by Base System for the multibyte language
2169      * currently. ( See dtpad's source ) So that this should be disabled.
2170      * See Defect 174873. (I should think this solution is not good one, but..)
2171      * What is the best way to check if I'm in MB or SB.....???
2172      *
2173      */
2174     if ( MB_CUR_MAX == 1 )
2175       cmdList->add( _format_spell );
2176
2177     _menuBar->addCommands ( cmdList );
2178     delete subcmdList1;
2179     delete cmdList;
2180
2181     // Alias Popup Menus
2182     if (NULL != _aliasList) delete _aliasList;
2183     _aliasList = new DtVirtArray<PropStringPair*> (10);
2184     createAliasList(_aliasList);
2185     createAliasPopupMenus();
2186
2187     // Compose Popup CmdList
2188     construct_text_popup();
2189     
2190     // Attachment
2191     
2192     cmdList = new CmdList(
2193                         "Attachments",
2194                         GETMSG(DT_catd, 1, 141, "Attachments"));
2195     
2196     _att_add   = new UnifiedSelectFileCmd (
2197                                 "Add File...",
2198                                 GETMSG(DT_catd, 1, 142, "Add File..."),
2199                                 GETMSG(DT_catd, 1, 143, "Mailer - Add"), 
2200                                 GETMSG(DT_catd, 1, 144, "Add"),
2201                                 TRUE, 
2202                                 SendMsgDialog::add_att_cb,
2203                                 this,
2204                                 this->baseWidget());
2205
2206     _att_save  = new SaveAttachCmd (
2207                                 "Save As...",
2208                                 GETMSG(DT_catd, 1, 145, "Save As..."),
2209                                 GETMSG(DT_catd, 1, 146,
2210                                         "Mailer - Attachments - Save As"),
2211                                 FALSE, 
2212                                 SendMsgDialog::save_att_cb,
2213                                 this,
2214                                 this->baseWidget());
2215     _att_delete = new DeleteAttachCmd (
2216                                 "Delete",
2217                                 GETMSG(DT_catd, 1, 147, "Delete"),
2218                                 FALSE, 
2219                                 this);
2220     _att_undelete = new UndeleteAttachCmd (
2221                                 "Undelete",
2222                                 GETMSG(DT_catd, 1, 148, "Undelete"),
2223                                 FALSE, 
2224                                 this);
2225     _att_rename = new RenameAttachCmd(
2226                                 "Rename",
2227                                 GETMSG(DT_catd, 1, 149, "Rename"),
2228                                 FALSE,
2229                                 this);
2230     
2231     _att_select_all = new SelectAllAttachsCmd( 
2232                                 "Select All",
2233                                 GETMSG(DT_catd, 1, 150, "Select All"), 
2234                                 this);
2235     
2236         /* NL_COMMENT
2237          * This is the label for a toggle item in a menu.  When the item
2238          * is set to "Show List", the Attachment List is mapped in the
2239          * Compose Window.  This message replaces message 151 in set 1.
2240          */
2241     _att_show_pane = new ShowAttachPaneCmd(
2242                                            "Show List",
2243                                            GETMSG(DT_catd, 1, 226, "Show List"),
2244                                            this
2245                                            );
2246     cmdList->add( _att_add );
2247     cmdList->add( _att_save );
2248     cmdList->add( _separator );
2249     
2250 //   subcmdList1 = new CmdList ( "Create", "Create" );
2251 //   // subcmdList1->add( att_audio );
2252 //   // subcmdList1->add( att_appt );
2253 //   cmdList->add( subcmdList1 );
2254 //   cmdList->add( _separator );
2255     
2256     cmdList->add( _att_delete );
2257     cmdList->add( _att_undelete );
2258     cmdList->add( _att_rename );
2259     cmdList->add( _att_select_all );
2260     cmdList->add(_att_show_pane);
2261     
2262     // Create a pulldown from the items in the list.  Retain a handle
2263     // to that pulldown since we need to dynamically add/delete entries 
2264     // to this menu based on the selection of attachments.
2265     
2266     _attachmentMenu = _menuBar->addCommands ( cmdList );
2267     construct_attachment_popup();
2268  
2269 //  delete subcmdList1;
2270     delete cmdList;
2271     
2272     value = NULL;
2273     d_session->mailRc(error)->getValue(error, "templates", &value);
2274     if (value != NULL && *value != '\0')
2275         _templateList = strdup(value);
2276     if (NULL != value)
2277       free((void*) value);
2278
2279     createFormatMenu();
2280     
2281     _overview = new OnAppCmd("Overview",
2282                                 GETMSG(DT_catd, 1, 71, "Overview"),
2283                                 TRUE, this);
2284     _tasks = new TasksCmd("Tasks", GETMSG(DT_catd, 1, 72, "Tasks"), 
2285                                 TRUE, this);
2286     _reference = new ReferenceCmd("Reference",
2287                                 GETMSG(DT_catd, 1, 73, "Reference"), 
2288                                 TRUE, this);
2289     _on_item = new OnItemCmd("On Item", GETMSG(DT_catd, 1, 74, "On Item"),
2290                                 TRUE, this);
2291     _using_help = new UsingHelpCmd("Using Help",
2292                                 GETMSG(DT_catd, 1, 75, "Using Help"), 
2293                                 TRUE, this);
2294     _about_mailer = new RelNoteCmd("About Mailer...",
2295                                 GETMSG(DT_catd, 1, 77, "About Mailer..."),
2296                                 TRUE, this);
2297     cmdList = new CmdList("Help", GETMSG(DT_catd, 1, 76, "Help"));
2298     cmdList->add(_overview);
2299     cmdList->add(_separator);
2300     cmdList->add(_tasks);
2301     cmdList->add(_reference);
2302     cmdList->add(_separator);
2303     cmdList->add(_on_item);
2304     cmdList->add(_separator);
2305     cmdList->add(_using_help);
2306     cmdList->add(_separator);
2307     cmdList->add(_about_mailer);
2308     _menuBar->addCommands(cmdList, TRUE);
2309     delete cmdList;
2310 }
2311
2312 void
2313 SendMsgDialog::construct_attachment_popup(void)
2314 {
2315    _attachmentPopupMenuList = new CmdList( "AttachmentsPopup", "AttachmentsPopup");
2316
2317     LabelCmd *title     = new LabelCmd (
2318                         "Mailer - Attachments",
2319                         GETMSG(DT_catd, 1, 158, "Mailer - Attachments"), TRUE);
2320     SeparatorCmd *separator = new SeparatorCmd( "Separator","Separator", TRUE );
2321
2322     _attachmentPopupMenuList->add(title);
2323     _attachmentPopupMenuList->add(separator);
2324     _attachmentPopupMenuList->add( _att_add );
2325     _attachmentPopupMenuList->add( _att_save );
2326     _attachmentPopupMenuList->add( _att_delete );
2327     _attachmentPopupMenuList->add( _att_undelete );
2328     _attachmentPopupMenuList->add( _att_select_all );
2329
2330     _menuPopupAtt = new MenuBar(_my_editor->attachArea()->getClipWindow(), 
2331                                         "RoamAttachmentPopup", XmMENU_POPUP);
2332     _attachmentPopupMenu = _menuPopupAtt->addCommands(_attachmentPopupMenuList, 
2333                                 FALSE, XmMENU_POPUP);
2334 }
2335
2336 void
2337 SendMsgDialog::construct_text_popup(void)
2338 {
2339    if (theApplication->bMenuButton() != Button3)
2340         return;
2341
2342    _textPopupMenuList = new CmdList( "TextPopup", "TextPopup");
2343
2344     LabelCmd *title     = new LabelCmd (
2345                         "Mailer - Compose",
2346                         GETMSG(DT_catd, 1, 159, "Mailer - Compose"), TRUE);
2347     SeparatorCmd *separator = new SeparatorCmd("Separator", "Separator", TRUE );
2348
2349     _textPopupMenuList->add(title);
2350     _textPopupMenuList->add(separator);
2351     _textPopupMenuList->add(_file_send);
2352     _textPopupMenuList->add( _edit_undo );
2353     _textPopupMenuList->add( _edit_cut );
2354     _textPopupMenuList->add( _edit_copy );
2355     _textPopupMenuList->add( _edit_paste );
2356 #ifdef __osf__
2357     // Work in progress from Mike. This adds the Paste Special to the
2358     // third mouse button in the compose area of a compose window.
2359     // Begin Paste Special submenu
2360     CmdList * subcmdList1 = new CmdList ( "Paste Special", GETMSG(DT_catd, 1, 135 , "Paste Special") );
2361     subcmdList1->add(_edit_paste_special[0]);
2362     subcmdList1->add(_edit_paste_special[1]);
2363     // End Paste Special submenu
2364     _textPopupMenuList->add( subcmdList1 ); // Add Paste Special submenu
2365     // (Either way) _textPopupMenuList->add( separator );
2366     _textPopupMenuList->add( _edit_clear );
2367 #endif
2368     _textPopupMenuList->add( _edit_delete );
2369     _textPopupMenuList->add( _edit_select_all );
2370
2371     Widget parent = _my_editor->textEditor()->get_editor();
2372     _menuPopupText = new MenuBar(parent, "SendMsgTextPopup", XmMENU_POPUP);
2373     _textPopupMenu = _menuPopupText->addCommands(_textPopupMenuList, 
2374                                 FALSE, XmMENU_POPUP);
2375 }
2376
2377 static int cmp_prop_pair(const void *v1, const void *v2)
2378 {
2379     PropStringPair      *p1 = *((PropStringPair **) v1);
2380     PropStringPair      *p2 = *((PropStringPair **) v2);
2381     int                 ret;
2382
2383     ret =  strcmp((const char *) p1->label, (const char *) p2->label);
2384     return ret;
2385 }
2386
2387 static void alias_stuffing_func(char * key, void * data, void * client_data)
2388 {
2389     DtVirtArray<PropStringPair *>       *alias_list;
2390     PropStringPair                      *new_pair;
2391
2392     alias_list = (DtVirtArray<PropStringPair*> *) client_data;
2393     new_pair = new PropStringPair;
2394     new_pair->label = strdup(key);
2395     new_pair->value = strdup((char *)data);
2396     alias_list->append(new_pair);
2397 }
2398
2399 void
2400 SendMsgDialog::createAliasList(DtVirtArray<PropStringPair*> *aliases)
2401 {
2402     DtMailEnv           error;
2403     DtMail::Session     *d_session = theRoamApp.session()->session();
2404     DtMail::MailRc      *mail_rc = d_session->mailRc(error);
2405     int                 nalias = 0;
2406
2407     mail_rc->getAliasList(alias_stuffing_func, aliases);
2408
2409     if (nalias = aliases->length())
2410     {
2411         PropStringPair  **prop_pairs = NULL;
2412
2413         prop_pairs = (PropStringPair**) malloc(nalias*sizeof(PropStringPair*));
2414         int i;
2415         for (i=0; i<nalias; i++)
2416         {
2417             prop_pairs[i] = (*aliases)[0];
2418             aliases->remove(0);
2419         }
2420         qsort(prop_pairs, nalias, sizeof(PropStringPair*), cmp_prop_pair);
2421         for (i=0; i<nalias; i++)
2422           aliases->append(prop_pairs[i]);
2423         
2424         free((void*) prop_pairs);
2425     }
2426 }
2427
2428 void
2429 SendMsgDialog::destroyAliasList(DtVirtArray<PropStringPair*> *aliases)
2430 {
2431     while (aliases->length() >= 0)
2432     {
2433         PropStringPair  *prop_pair = (*aliases)[0];
2434         aliases->remove(0);
2435         delete(prop_pair);
2436     }
2437 }
2438
2439 // map_menu is used to figure out how many columns to split the menu
2440 // into.  It is a callback that is called when the menu is mapped.
2441 // If the menu is over half the height of the screen, it figures out
2442 // how many columns to make the menu, and sets its XmNnumColumns
2443 // attribute to that value.  It calculates the maximum number of columns
2444 // that would fit and never goes beyond that number.
2445
2446 static void map_alias_menu(Widget menu, XtPointer, XtPointer)
2447 {
2448     Position y;
2449     Dimension h, w;
2450     Dimension maxcols, newcols, columns;
2451     Dimension screenheight = (Dimension) HeightOfScreen(XtScreen(menu));
2452     Dimension fudgefact = 20; /* to allow for decorations on menu */
2453
2454     XtVaGetValues(
2455                 menu,
2456                 XmNheight, &h,
2457                 XmNwidth, &w,
2458                 XmNy, &y,
2459                 XmNnumColumns, &columns,
2460                 NULL);
2461
2462     if ((h + fudgefact) > (screenheight / 2))
2463     {
2464         // The menu is taller than half the screen. 
2465         // We need to find out how many more columns
2466         // to specify for the menu to make it fit.
2467
2468         newcols = (columns * ((h+fudgefact)/(screenheight/2))) + 1;
2469         maxcols = WidthOfScreen(XtScreen(menu))/(w/columns);
2470
2471         if (newcols > maxcols)
2472           newcols = maxcols;
2473
2474         XtVaSetValues(menu, XmNnumColumns, newcols, NULL);
2475     }
2476 }
2477
2478 void
2479 SendMsgDialog::aliasMenuButtonHandler(
2480                                 Widget,
2481                                 XtPointer client_data,
2482                                 XEvent *event,
2483                                 Boolean *)
2484 {
2485     Widget              menu = (Widget) client_data;
2486     XButtonEvent        *be = (XButtonEvent *) event;
2487
2488     if (event->xany.type != ButtonPress) return;
2489     if(be->button == theApplication->bMenuButton())
2490     {
2491         XmMenuPosition(menu, (XButtonEvent *)event);
2492         XtManageChild(menu);
2493     }
2494 }
2495
2496 Widget
2497 SendMsgDialog::createAliasPopupMenu(
2498                                 Widget parent,
2499                                 MenuBar **menubar,
2500                                 CmdList **cmdlist,
2501                                 DtVirtArray<PropStringPair*> *aliases)
2502 {
2503     Widget              menu = NULL;
2504     OtherAliasesCmd     *otherAliases =
2505                           new OtherAliasesCmd(
2506                                 "Other Aliases...",
2507                                 GETMSG(DT_catd, 1, 247, "Other Aliases..."),
2508                                 TRUE);
2509 #if defined(USE_TITLED_ALIAS_POPUPS)
2510     LabelCmd            *title =
2511                           new LabelCmd(
2512                                 "Mailer - Aliases",
2513                                 GETMSG(DT_catd, 1, 248, "Mailer - Aliases"),
2514                                 TRUE);
2515 #endif
2516     SeparatorCmd        *separator =
2517                           new SeparatorCmd("Separator","Separator", TRUE);
2518
2519     (*cmdlist) = new CmdList("AliasCommands", "AliasCommands");
2520 #if defined(USE_TITLED_ALIAS_POPUPS)
2521     (*cmdlist)->add(title);
2522     (*cmdlist)->add(separator);
2523 #endif
2524     for (int i=0, length=aliases->length(); i<length; i++)
2525     {
2526         PropStringPair  *prop_pair = (*aliases)[i];
2527
2528         AliasCmd *alias = new AliasCmd(
2529                                 strdup(prop_pair->label),
2530                                 strdup(prop_pair->label),
2531                                 TRUE,
2532                                 parent);
2533         (*cmdlist)->add(alias);
2534     }
2535     if (0 < aliases->length())
2536       (*cmdlist)->add(separator);
2537     (*cmdlist)->add(otherAliases);
2538
2539     *menubar = new MenuBar(parent, "AliasesPopup", XmMENU_POPUP);
2540     menu = (*menubar)->addCommands((*cmdlist), FALSE, XmMENU_POPUP);
2541
2542     XtAddEventHandler(
2543                 parent,
2544                 ButtonPressMask,
2545                 FALSE,
2546                 aliasMenuButtonHandler,
2547                 (XtPointer) menu);
2548
2549     XtVaSetValues(
2550                 menu,
2551                 XmNpacking, XmPACK_COLUMN,
2552                 XmNorientation, XmVERTICAL,
2553                 NULL);
2554
2555     XtAddCallback(
2556                 menu,
2557                 XmNmapCallback, &map_alias_menu,
2558                 NULL);
2559
2560     return menu;
2561 }
2562
2563 void
2564 SendMsgDialog::destroyAliasPopupMenu(
2565                                 Widget  parent,
2566                                 MenuBar *menubar,
2567                                 CmdList *cmdlist,
2568                                 Widget  menu)
2569 {
2570     XtRemoveEventHandler(
2571                 parent,
2572                 ButtonPressMask,
2573                 FALSE,
2574                 aliasMenuButtonHandler,
2575                 (XtPointer) menu);
2576
2577     XtRemoveCallback(
2578                 menu,
2579                 XmNmapCallback, &map_alias_menu,
2580                 NULL);
2581
2582     XtDestroyWidget(menu);
2583
2584     delete menubar;
2585     delete cmdlist;
2586 }
2587
2588 Widget
2589 SendMsgDialog::getHeaderWidget(const char *hdrname)
2590 {
2591     for (int i=0, length=_header_list.length(); i<length; i++)
2592     {
2593         HeaderList      *hdritem = _header_list[i];
2594
2595         if (0 == strncmp(hdrname, hdritem->header, strlen(hdrname)))
2596           return hdritem->field_widget;
2597     }
2598
2599     return NULL;
2600 }
2601
2602 void
2603 SendMsgDialog::createAliasPopupMenus(void)
2604 {
2605     Widget      w;
2606
2607     w = getHeaderWidget(DtMailMessageTo);
2608     if (NULL != w)
2609       _toPopupMenu = createAliasPopupMenu(
2610                                         w,
2611                                         &_toPopupMenuBar,
2612                                         &_toPopupCmdlist,
2613                                         _aliasList);
2614
2615     w = getHeaderWidget(DtMailMessageCc);
2616     if (NULL != w)
2617       _ccPopupMenu = createAliasPopupMenu(
2618                                         w,
2619                                         &_ccPopupMenuBar,
2620                                         &_ccPopupCmdlist,
2621                                         _aliasList);
2622
2623     w = getHeaderWidget(DtMailMessageBcc);
2624     if (NULL != w)
2625       _bccPopupMenu = createAliasPopupMenu(
2626                                         w,
2627                                         &_bccPopupMenuBar,
2628                                         &_bccPopupCmdlist,
2629                                         _aliasList);
2630 }
2631
2632 void
2633 SendMsgDialog::destroyAliasPopupMenus(void)
2634 {
2635     Widget      w;
2636
2637     w = getHeaderWidget(DtMailMessageTo);
2638     destroyAliasPopupMenu(w, _toPopupMenuBar, _toPopupCmdlist, _toPopupMenu);
2639     _toPopupMenuBar = NULL;
2640     _toPopupCmdlist = NULL;
2641     _toPopupMenu = NULL;
2642
2643     w = getHeaderWidget(DtMailMessageCc);
2644     destroyAliasPopupMenu(w, _ccPopupMenuBar, _ccPopupCmdlist, _ccPopupMenu);
2645     _ccPopupMenuBar = NULL;
2646     _ccPopupCmdlist = NULL;
2647     _ccPopupMenu = NULL;
2648
2649     w = getHeaderWidget(DtMailMessageBcc);
2650     destroyAliasPopupMenu(w, _bccPopupMenuBar, _bccPopupCmdlist, _bccPopupMenu);
2651     _bccPopupMenuBar = NULL;
2652     _bccPopupCmdlist = NULL;
2653     _bccPopupMenu = NULL;
2654 }
2655
2656 void
2657 SendMsgDialog::createFormatMenu()
2658 {
2659     CmdList *cmdList;
2660     _format_separator = new SeparatorCmd( "Separator","Separator", TRUE );
2661
2662     cmdList = new CmdList( "Format", GETMSG(DT_catd, 1, 152,"Format") );
2663     
2664     _format_word_wrap = new WordWrapCmd ( 
2665                                           "Word Wrap",
2666                                           GETMSG(DT_catd, 1, 153, "Word Wrap"), 
2667                                           TRUE, 
2668                                           this );
2669     _format_settings = new FormatCmd ( "Settings...",
2670                                        GETMSG(DT_catd, 1, 154, "Settings..."), 
2671                                        TRUE, 
2672                                        this );
2673     
2674     cmdList->add( _format_word_wrap );
2675     cmdList->add( _format_settings );
2676     cmdList->add( _format_separator);
2677     
2678     _templates = new CmdList ( "Templates", GETMSG(DT_catd, 1, 157, "Templates") );
2679     addTemplates(_templates);
2680     
2681     cmdList->add(_templates);
2682     
2683     cmdList->add( _format_separator );
2684
2685     _format_menu = _menuBar->addCommands ( &_format_cascade, cmdList, 
2686                         FALSE, XmMENU_BAR);
2687
2688     delete cmdList;
2689
2690     doDynamicHeaderMenus();
2691
2692     if (_template_count == 0 && _templates->getPaneWidget())
2693     {
2694         XtSetSensitive(_templates->getPaneWidget(), FALSE);
2695     }
2696     
2697 }
2698
2699 void
2700 SendMsgDialog::addTemplates(CmdList * subCmd)
2701 {
2702     DtMailEnv error;
2703
2704     _template_count = 0;
2705     
2706     if (_templateList == NULL)
2707         return;
2708
2709     DtMail::Session *m_session = theRoamApp.session()->session();
2710     char * expanded_list = m_session->expandPath(error, _templateList);
2711
2712     DtVirtArray<PropStringPair *> templates(8);
2713     parsePropString(expanded_list, templates);
2714     free(expanded_list);
2715
2716     _template_count = templates.length();
2717
2718     for (int tmp = 0; tmp < _template_count; tmp++) {
2719         PropStringPair * psp = templates[tmp];
2720         if (psp->label && psp->value) {
2721             Cmd * button = new TemplateCmd(strdup(psp->label), 
2722                                            strdup(psp->label), 
2723                                            1, 
2724                                            this, 
2725                                            psp->value);
2726             subCmd->add(button);
2727         }
2728     }
2729
2730     while (templates.length()) {
2731         PropStringPair * psp = templates[0];
2732         delete psp;
2733         templates.remove(0);
2734     }
2735 }
2736
2737 void
2738 SendMsgDialog::initialize()
2739 {
2740     Cardinal n = 0;
2741     Arg args[1];
2742     const char * hideAttachPane = NULL;
2743     DtMailEnv error;
2744     
2745     // Without the TearOffModelConverter call, there will be warning messages:
2746     // Warning: No type converter registered for 'String' to 'TearOffModel' 
2747     // conversion.
2748     
2749     XmRepTypeInstallTearOffModelConverter();
2750     MenuWindow::initialize();
2751
2752     char *ttl = GETMSG(DT_catd, 1, 160, "New Message");
2753     setTitle(ttl);
2754
2755     XtSetArg(args[n], XmNdeleteResponse, XmDO_NOTHING); n++;
2756     XtSetValues( _w, args, n);
2757     
2758     _genDialog = new DtMailGenDialog("Dialog", _main);
2759     
2760     // See if the .mailrc specifies if attachPane is to be shown or hid 
2761     // at SMD startup time.
2762     
2763     DtMail::Session *m_session = theRoamApp.session()->session();
2764     m_session->mailRc(error)->getValue(error, "hideattachments", 
2765                                 &hideAttachPane);
2766     
2767     if (!hideAttachPane) {
2768         _show_attach_area = TRUE;
2769     }
2770     else {
2771         _show_attach_area = FALSE;
2772         // The user wants to hide attachments
2773
2774         this->hideAttachArea();
2775     }
2776     if (NULL != hideAttachPane)
2777       free((void*) hideAttachPane);
2778
2779     _confirm_attachment_threshold = get_confirm_attachment_threshold();
2780     
2781     // Log Message Toggle button.  A LogMsgCmd is a ToggleButtonCmd....
2782     const char * logfile = NULL;
2783     const char * value = NULL;
2784     m_session->mailRc(error)->getValue(error, "record", &logfile);
2785     if(logfile == NULL)
2786         _file_log->deactivate();
2787     else
2788         _file_log->activate();
2789
2790     m_session->mailRc(error)->getValue(error, "dontlogmessages", &value);
2791     if (logfile == NULL || error.isNotSet()) {
2792         // logfile is not specified or "dontlogmessages" is TRUE
2793         setLogState(DTM_FALSE);
2794         ((ToggleButtonCmd *)_file_log)->setButtonState(FALSE, TRUE);
2795     } else {
2796         // logfile is specified and "dontlogmessages" is FALSE
2797         setLogState(DTM_TRUE);
2798         ((ToggleButtonCmd *)_file_log)->setButtonState(TRUE, TRUE);
2799     }
2800     
2801     if (NULL != logfile)
2802       free((void*) logfile);
2803     if (NULL != value)
2804       free((void*) value);
2805
2806     // Word Wrap Toggle button.  A WordWrapCmd is a ToggleButtonCmd...
2807     ((ToggleButtonCmd *)_format_word_wrap)->setButtonState(
2808                            ((WordWrapCmd *)_format_word_wrap)->wordWrap(), 
2809                            FALSE
2810                            );
2811
2812     // Initialize the Edit menu
2813
2814     this->text_unselected();
2815         
2816     setIconName(ComposeIcon);
2817 }
2818
2819 static void
2820 Self_destruct(XtPointer, XtIntervalId *)
2821 {
2822 #ifdef WM_TT
2823     fprintf(stderr, "DEBUG: Self_destruct(): invoked!\n");
2824 #endif
2825     
2826     
2827     XtRemoveAllCallbacks(
2828                          theApplication->baseWidget(), 
2829                          XmNdestroyCallback);
2830     delete theApplication;
2831 }
2832
2833 // Clears Compose window title, header fields, text, and attachment areas.
2834 void
2835 SendMsgDialog::reset()
2836 {
2837     _my_editor->textEditor()->clear_contents();
2838     _my_editor->attachArea()->resetPendingAction();
2839
2840     _takeDown = FALSE;
2841     // This will deselect any Attachment action, if any available now.
2842     // Also deselect text menu items....
2843
2844     this->deactivate_default_attach_menu();
2845     this->text_unselected();
2846     this->all_attachments_deselected();
2847     _att_undelete->deactivate();  // This needs to be done in addition
2848
2849     this->get_editor()->attachArea()->removeCurrentAttachments();
2850
2851     // Unmanage the dialog
2852     this->unmanage();
2853
2854     if (_show_attach_area) { // .mailrc wants default attach area invisible
2855
2856     // Unmanage the attach Area.  Set the show_pane button.
2857     // This is done because if we are caching this window (after 
2858     // unmanaging), we don't want the window to pop back up, on uncaching,
2859     // with the attachment pane visible, etc..
2860
2861         this->showAttachArea();
2862     }
2863     else {
2864         this->hideAttachArea();
2865     }
2866
2867     // Need to destroy current Message handle.
2868     delete _msgHandle;    // All its body parts are deleted.
2869     _msgHandle = NULL;
2870     _lastAttBP = NULL;    // So just set this to NULL.
2871     // Delete or set to NULL ???
2872     _inclMsgHandle = NULL; 
2873     _inclMsgHasText = NULL;
2874
2875     for (int clear = 0; clear < _header_list.length(); clear++) {
2876         HeaderList * hl = _header_list[clear];
2877
2878         // Bugfix: Old selection area remained selected, after text cleared
2879         // and parent widget unmanged, and then managed again for next
2880         // Compose.  (So new text in old select area was still being selected).
2881         // Perhaps, this is a Motif bug ... but this fixes the problem.
2882         XmTextFieldClearSelection( hl->field_widget, CurrentTime );
2883
2884         if (hl->value) {
2885             XtVaSetValues(hl->field_widget,
2886                           XmNvalue, hl->value,
2887                           NULL);
2888         }
2889         else {
2890             XtVaSetValues(hl->field_widget,
2891                           XmNvalue, "",
2892                           NULL);
2893         }
2894     }
2895     // Reset the Log state in case the user happened to change it.
2896     DtMail::Session *m_session = theRoamApp.session()->session();
2897     const char * logfile = NULL;
2898     const char * value = NULL;
2899     DtMailEnv error;
2900
2901     m_session->mailRc(error)->getValue(error, "record", &logfile);
2902     if(logfile == NULL)
2903         _file_log->deactivate();
2904     else
2905         _file_log->activate();
2906
2907     m_session->mailRc(error)->getValue(error, "dontlogmessages", &value);
2908     if (logfile == NULL || error.isNotSet()) {
2909         // logfile is not specified or "dontlogmessages" is TRUE
2910         setLogState(DTM_FALSE);
2911         ((ToggleButtonCmd *)_file_log)->setButtonState(FALSE, TRUE);
2912     } else {
2913         // logfile is specified and "dontlogmessages" is FALSE
2914         setLogState(DTM_TRUE);
2915         ((ToggleButtonCmd *)_file_log)->setButtonState(TRUE, TRUE);
2916     }
2917         
2918     if (NULL != logfile)
2919       free((void*) logfile);
2920     if (NULL != value)
2921       free((void*) value);
2922 }
2923
2924 // Recycles Compose window.
2925 void
2926 SendMsgDialog::quit(Boolean delete_win)
2927 {
2928
2929     // There are several ways we could have reached here.
2930     // 1) From the user choosing Send.
2931     // 2) From the user clicking on the Close button or Close menu item
2932     // 3) The user choosing Close from window manager menu.
2933     // For (1), we just forge ahead.  For that, the _takeDown boolean
2934     // is set in send_message() method.
2935     // For (2), the boolean is set in goAway().
2936     // For (3), we call goAway() which sets the _takeDown depending on
2937     // a dialog negotiation if SMD has contents.
2938
2939      if (_file_include->fileBrowser() != NULL) 
2940          XtPopdown(XtParent(_file_include->fileBrowser())); 
2941      if (_att_add->fileBrowser() != NULL) 
2942          XtPopdown(XtParent(_att_add->fileBrowser())); 
2943
2944      if (_file_save_as->fileBrowser() != NULL) 
2945          XtPopdown(XtParent(_file_save_as->fileBrowser())); 
2946      if (_att_save->fileBrowser() != NULL) 
2947          XtPopdown(XtParent(_att_save->fileBrowser())); 
2948
2949     if (!_takeDown) {
2950         // Check to see if it's the first time through the quit()
2951         // method.  Set _first_time to FALSE so that we don't come
2952         // down this path again until we're done quitting or bad
2953         // things will happen.
2954         if (_first_time == TRUE) {
2955             _first_time = FALSE;
2956             this->goAway(TRUE);
2957             // We're done quitting, so we can set _first_time to TRUE again.
2958             _first_time = TRUE;
2959         }
2960         return;
2961     }  
2962
2963     stopAutoSave();
2964     
2965 #ifdef DTMAIL_TOOLTALK
2966     //  For explanation of dtmail_mapped, look at RoamApp.h.
2967     if ( started_by_tt && (0 == theCompose.getTimeOutId()) &&
2968          (theCompose.numUnusedWindows() == theCompose.numCreatedWindows()) &&
2969          !dtmail_mapped )
2970     {
2971         int id;
2972         id = XtAppAddTimeOut(
2973                         theApplication->appContext(), 
2974                         (unsigned long)DESTRUCT_TIMEOUT, 
2975                         Self_destruct, NULL);
2976         theCompose.putTimeOutId(id);
2977     }
2978 #endif
2979
2980     if (delete_win)
2981      delete this;
2982     else
2983     {
2984         this->reset();
2985         theCompose.putWin(this, FALSE);
2986     }
2987     
2988     //
2989     // If there are no composer timeouts, check if its time to shutdown.
2990     //
2991     if (0 == theCompose.getTimeOutId()) theRoamApp.checkForShutdown();
2992 }
2993
2994 void
2995 SendMsgDialog::panicQuit()
2996 {
2997     //
2998     // Need to make sure the message is still valid before proceeding.
2999     // ::reset may have been called so the message may no longer be valid.
3000     //
3001     if (!isMsgValid())
3002       return;
3003
3004     doAutoSave();
3005 }
3006
3007
3008 // Given a file name, include the file as attachment.
3009 void
3010 SendMsgDialog::inclAsAttmt(char *file, char *name)
3011 {
3012     this->get_editor()->attachArea()->addAttachment(_msgHandle, _lastAttBP,
3013                                                     file, name);
3014     this->setLastAttBP();
3015     
3016 }
3017
3018 // Given a buffer, include its content as an attachment.
3019 void
3020 SendMsgDialog::inclAsAttmt(unsigned char *contents, int len, char *name)
3021 {
3022     DtMailBuffer mbuf;
3023
3024     mbuf.buffer = (void *)contents;
3025     mbuf.size = (unsigned long)len;
3026     this->get_editor()->attachArea()->addAttachment(_msgHandle, _lastAttBP,
3027                                                     (String)name, mbuf);
3028     this->setLastAttBP();
3029 }
3030
3031 // Given a RFC_822_Message formatted buffer, parse it and fill the Compose Window.
3032 void
3033 SendMsgDialog::parseNplace(char *contents, int len)
3034 {
3035     // 1. Create message handle for contents
3036
3037     DtMailEnv error;
3038     DtMail::Session * d_session = theRoamApp.session()->session();
3039     DtMailBuffer mbuf;
3040     
3041     mbuf.buffer = (void *)contents;
3042     mbuf.size = (unsigned long)len;
3043     
3044     DtMail::Message * msg = d_session->messageConstruct(error,
3045                                                         DtMailBufferObject,
3046                                                         &mbuf,
3047                                                         NULL,
3048                                                         NULL,
3049                                                         NULL);
3050     if ( !msg ) {
3051         return;
3052     } else if ( error.isSet() ) {
3053         if  ( (DTMailError_t) error == DTME_UnknownFormat ) {
3054             // The content does not have header info.  Therefore, store
3055             // everything as text.
3056             _my_editor->textEditor()->set_contents((const char *)mbuf.buffer,
3057                                                    mbuf.size);
3058             return;
3059         }
3060     }
3061
3062     char * status_string;
3063     DtMailBoolean first_bp_handled;
3064     first_bp_handled = _my_editor->textEditor()->set_message(
3065                                                              msg,
3066                                                              &status_string,
3067                                                              Editor::HF_NONE
3068                                                              );
3069     
3070     int num_bodyParts = msg->getBodyCount(error);
3071
3072     // Don't use setInclMsgHnd() because it causes the SMD's attachments
3073     // to get out of sink with the BE.  Just assign the newly created message
3074     // to _msgHandle.
3075     //
3076     if ((num_bodyParts > 1) || (!first_bp_handled)) {
3077         int start;
3078         if (first_bp_handled) {
3079             start = 2;
3080 //          setInclMsgHnd(msg, TRUE);
3081         }
3082         else {
3083             start = 1;
3084 //          setInclMsgHnd(msg, FALSE);
3085         }
3086
3087         if (_msgHandle) {
3088             delete _msgHandle;
3089         }
3090         _msgHandle = msg;
3091         _my_editor->attachArea()->parseAttachments(error,
3092                                                    msg,
3093                                                    TRUE,
3094 //                                                 FALSE,
3095                                                    start);
3096         // Need to call this after calling parseAttachments() so attachments
3097         // will be displayed in the attachment pane.
3098         _my_editor->manageAttachArea();
3099
3100         // Need to update this compose window's internal message handle.
3101         //
3102         // GL - calling updateMsgHndAtt is no longer necessary because we
3103         // just assigning the newly created msg to _msgHandle.
3104 //      updateMsgHndAtt();
3105     }
3106
3107     loadHeaders(msg, DTM_TRUE);
3108 }
3109
3110 // Given a RFC_822_Message formatted file, parse it and fill the Compose Window.
3111 void
3112 SendMsgDialog::parseNplace(const char * path)
3113 {
3114     // 1. Get file content into buffer.
3115     int fd = SafeOpen(path, O_RDONLY);
3116     if (fd < 0) {
3117         return;
3118     }
3119     
3120     struct stat buf;
3121     if (SafeFStat(fd, &buf) < 0) {
3122         close(fd);
3123         return;
3124     }
3125     
3126     _dead_letter_buf = new char[buf.st_size];
3127     if (!_dead_letter_buf) {
3128         close(fd);
3129         return;
3130     }
3131     
3132     if (SafeRead(fd, _dead_letter_buf, 
3133                  (unsigned int) buf.st_size) != buf.st_size) {
3134         delete _dead_letter_buf;
3135         close(fd);
3136         return;
3137     }
3138     
3139     parseNplace(_dead_letter_buf, (int) buf.st_size);
3140 }
3141
3142 void
3143 SendMsgDialog::text( const char *text )
3144 {
3145     _my_editor->textEditor()->set_contents( text, strlen(text) );
3146 }
3147
3148 void
3149 SendMsgDialog::append( const char *text )
3150 {
3151     _my_editor->textEditor()->append_to_contents( text, strlen(text) );
3152 }
3153
3154 char *
3155 SendMsgDialog::text()
3156 {
3157     // Potential memory leak here.  Because XmTextGetString returns 
3158     // pointer to space containing all the text in the widget.  Need 
3159     // to call XtFree after we use this space
3160     // Also DtEditor widget requires application to free data.
3161     
3162     return (_my_editor->textEditor()->get_contents());
3163     
3164 }
3165
3166
3167 void
3168 SendMsgDialog::text_selected()
3169 {
3170     // turn on sensitivity for Cut/Clear/Copy/Delete
3171     _edit_cut->activate();
3172     _edit_copy->activate();
3173     _edit_clear->activate();
3174     _edit_delete->activate();
3175     _edit_select_all->activate();
3176 }
3177
3178 void
3179 SendMsgDialog::text_unselected()
3180 {
3181     // turn off sensitivity for those items
3182     _edit_cut->deactivate();
3183     _edit_copy->deactivate();
3184     _edit_clear->deactivate();
3185     _edit_delete->deactivate();
3186 }
3187
3188 // Attachments
3189
3190 void
3191 SendMsgDialog::attachment_selected()
3192 {
3193     _att_save->activate();
3194     _att_delete->activate();
3195     _att_rename->activate();
3196     
3197 }
3198
3199 void
3200 SendMsgDialog::all_attachments_selected()
3201 {
3202     _att_delete->activate();
3203     _att_save->deactivate();
3204     _att_rename->deactivate();
3205
3206     if (_attachmentActionsList != NULL) {
3207         _menuBar->removeCommands(_attachmentMenu, _attachmentActionsList);
3208         _menuPopupAtt->removeCommands(_attachmentPopupMenu, 
3209                                 _attachmentActionsList);
3210         delete _attachmentActionsList;
3211         _attachmentActionsList = NULL;
3212     }
3213
3214 }
3215
3216
3217 void
3218 SendMsgDialog::all_attachments_deselected()
3219 {
3220     _att_save->deactivate();
3221     _att_delete->deactivate();
3222     _att_rename->deactivate();
3223     
3224     if (_attachmentActionsList != NULL) {
3225         _menuBar->removeCommands(_attachmentMenu, _attachmentActionsList);
3226         _menuPopupAtt->removeCommands(_attachmentPopupMenu, 
3227                                 _attachmentActionsList);
3228         delete _attachmentActionsList;
3229         _attachmentActionsList = NULL;
3230     }
3231     
3232 }
3233
3234
3235 void
3236 SendMsgDialog::addAttachmentActions(
3237                                     char **actions,
3238                                     int indx
3239                                     )
3240 {
3241     int i;
3242     char *anAction;
3243     AttachmentActionCmd *attachActionCmd;
3244     
3245     if (_attachmentActionsList == NULL) { 
3246         _attachmentActionsList = new CmdList("AttachmentActions", "AttachmentActions");
3247     }
3248     else {
3249         _menuBar->removeCommands(_attachmentMenu, _attachmentActionsList);
3250         _menuPopupAtt->removeCommands(_attachmentPopupMenu, 
3251                                         _attachmentActionsList);
3252         delete _attachmentActionsList;
3253         _attachmentActionsList = new CmdList("AttachmentActions", "AttachmentActions");
3254     }
3255     
3256     char *actionLabel;         
3257     for (i = 0; i < indx; i++) {
3258         anAction = actions[i];
3259         actionLabel = DtActionLabel(anAction);   // get the localized action label
3260         attachActionCmd = new AttachmentActionCmd(
3261                                                   anAction,
3262                                                   actionLabel,
3263                                                   this,
3264                                                   i);
3265         _attachmentActionsList->add(attachActionCmd);
3266         
3267     }
3268     _attachmentMenu = _menuBar->addCommands(
3269                                             _attachmentMenu, 
3270                                             _attachmentActionsList
3271                                             );
3272     _attachmentPopupMenu = _menuPopupAtt->addCommands(
3273                                             _attachmentPopupMenu, 
3274                                             _attachmentActionsList
3275                                             );
3276 }
3277
3278 void
3279 SendMsgDialog::removeAttachmentActions()
3280 {
3281     
3282     // Stubbed out for now
3283 }
3284
3285 void
3286 SendMsgDialog::invokeAttachmentAction(
3287                                       int index
3288                                       )
3289 {
3290     DtMailEditor *editor = this->get_editor();
3291     AttachArea *attacharea = editor->attachArea();
3292     Attachment *attachment = attacharea->getSelectedAttachment();
3293     
3294     attachment->invokeAction(index);
3295 }
3296
3297 void
3298 SendMsgDialog::selectAllAttachments()
3299 {
3300     
3301     DtMailEditor *editor = this->get_editor();
3302     AttachArea *attachArea = editor->attachArea();
3303     
3304     attachArea->selectAllAttachments();
3305     
3306 }
3307
3308
3309 void
3310 SendMsgDialog::activate_default_attach_menu()
3311 {
3312     _att_select_all->activate();
3313 }
3314
3315 void
3316 SendMsgDialog::deactivate_default_attach_menu()
3317 {
3318     _att_select_all->deactivate();
3319 }
3320
3321
3322 void
3323 SendMsgDialog::delete_selected_attachments()
3324 {
3325     DtMailEnv mail_error;
3326     
3327     // Initialize the mail_error.
3328
3329     mail_error.clear();
3330     
3331     AttachArea *attachArea = _my_editor->attachArea();
3332     attachArea->deleteSelectedAttachments(mail_error);
3333     
3334     if (mail_error.isSet()) {
3335         // do something
3336     }
3337     
3338     // Activate this button to permit the user to undelete.
3339     
3340     _att_undelete->activate();
3341     
3342     // Deactivate buttons that will be activated when another
3343     // selection applies.
3344     
3345     _att_save->deactivate();
3346     _att_delete->deactivate();
3347     _att_rename->deactivate();
3348
3349     if (_attachmentActionsList) {
3350         _menuBar->removeCommands(_attachmentMenu, _attachmentActionsList);
3351         _menuPopupAtt->removeCommands(_attachmentPopupMenu, 
3352                                       _attachmentActionsList);
3353         delete _attachmentActionsList;
3354         _attachmentActionsList = NULL;
3355     }
3356 }
3357
3358 void
3359 SendMsgDialog::undelete_last_deleted_attachment()
3360 {
3361     DtMailEnv mail_error;
3362     
3363     // Initialize the mail_error.
3364     
3365     mail_error.clear();
3366     
3367     AttachArea *attachArea = _my_editor->attachArea();
3368     attachArea->undeleteLastDeletedAttachment(mail_error);
3369     
3370     if (mail_error.isSet()) {
3371         // do something
3372     }
3373     
3374     if(_my_editor->attachArea()->getIconSelectedCount()) 
3375         _att_delete->activate();
3376
3377     if (attachArea->getDeleteCount() == 0) {
3378         _att_undelete->deactivate();
3379     }
3380 }
3381
3382 Boolean
3383 SendMsgDialog::renameAttachmentOK()
3384 {
3385     AttachArea *attachArea = _my_editor->attachArea();
3386     
3387     if (attachArea->getIconSelectedCount() > 1) {
3388        char *buf = new char[512];
3389         
3390        sprintf(buf, GETMSG(DT_catd, 5, 4, "Select only one attachment\n\
3391 and then choose rename"));
3392         
3393         _genDialog->setToQuestionDialog(
3394                                         GETMSG(DT_catd, 5, 2, "Mailer"),
3395                                         buf);
3396         
3397         char * helpId = DTMAILHELPSELECTONEATTACH;
3398         
3399         int answer = _genDialog->post_and_return(helpId);
3400         
3401         delete [] buf;
3402         return(FALSE);
3403     }
3404     else {
3405         return(TRUE);
3406     }
3407 }
3408
3409 void
3410 SendMsgDialog::showAttachArea()
3411 {
3412     DtMailEditor *editor = this->get_editor();
3413     editor->showAttachArea();
3414     ((ToggleButtonCmd *)_att_show_pane)->setButtonState(TRUE, FALSE);
3415 }
3416
3417 void
3418 SendMsgDialog::hideAttachArea()
3419 {
3420     DtMailEditor *editor = this->get_editor();
3421     editor->hideAttachArea();
3422     ((ToggleButtonCmd *)_att_show_pane)->setButtonState(FALSE, FALSE);
3423 }
3424
3425 int
3426 SendMsgDialog::lookupHeader(const char * name)
3427 {
3428     for (int h = 0; h < _header_list.length(); h++) {
3429         HeaderList * hl = _header_list[h];
3430         if (hl->show != SMD_NEVER && 
3431                 strcmp(hl->label, name) == 0) {
3432             return(h);
3433         }
3434     }
3435
3436     return(-1);
3437 }
3438
3439 void
3440 SendMsgDialog::headerValueChanged(Widget,
3441                                   XtPointer client_data,
3442                                   XtPointer)
3443 {
3444     SendMsgDialog * self = (SendMsgDialog *)client_data;
3445     self->_headers_changed = DTM_TRUE;
3446 }
3447
3448 void
3449 SendMsgDialog::reattachHeaders(void)
3450 {
3451     // We have to walk through the entire list of headers, attaching
3452     // the shown headers to the ones above them.
3453     //
3454     HeaderList * hl = _header_list[0];
3455     Widget previous_form = hl->form_widget;
3456
3457     for (int h = 1; h < _header_list.length(); h++) {
3458         hl = _header_list[h];
3459
3460         switch(hl->show) {
3461           case SMD_ALWAYS:
3462             previous_form = hl->form_widget;
3463             break;
3464
3465           case SMD_SHOWN:
3466             XtVaSetValues(hl->form_widget,
3467                           XmNtopAttachment, XmATTACH_WIDGET,
3468                           XmNtopWidget, previous_form,
3469                           NULL);
3470             previous_form = hl->form_widget;
3471             break;
3472
3473           default:
3474             break;
3475         }
3476     }
3477
3478     forceFormResize(_main_form);
3479     forceFormResize(_header_form);
3480 }
3481
3482 void
3483 SendMsgDialog::justifyHeaders(void)
3484 {
3485     // Find out which header label has the longest display width to right
3486     // justify all labels.
3487     //
3488     Dimension longest = 0;
3489     Dimension w = 0;
3490     Dimension margin;
3491     for (int count = 0;  count < _header_list.length(); count++) {
3492         HeaderList * hl = _header_list[count];
3493         if (hl->show == SMD_HIDDEN || hl->show == SMD_NEVER) {
3494             continue;
3495         }
3496
3497         XtVaGetValues(hl->label_widget,
3498                       XmNwidth, &w,
3499                       XmNmarginLeft, &margin,
3500                       NULL);
3501         w -= margin;
3502         if ( w > longest ) {
3503             longest = w;
3504         }
3505     }
3506
3507     for (int adjust = 0;  adjust < _header_list.length();  adjust++) {
3508         HeaderList * hl = _header_list[adjust];
3509         if (hl->show == SMD_HIDDEN || hl->show == SMD_NEVER) {
3510             continue;
3511         }
3512
3513         XtVaGetValues(hl->label_widget,
3514                       XmNwidth, &w,
3515                       XmNmarginLeft, &margin,
3516                       NULL);
3517         w -= margin;
3518         XtVaSetValues(hl->label_widget, XmNmarginLeft, (longest-w) > 0 ? longest-w : 1, NULL );
3519     }
3520 }
3521
3522 void
3523 SendMsgDialog::forceFormResize(Widget form)
3524 {
3525     // The Motif Form widget is at least a little bit brain damaged.
3526     // We need to convince it to do the right thing after we make
3527     // minor adjustments in the children.
3528     //
3529     Dimension width, height, border;
3530     XtVaGetValues(form,
3531                   XmNwidth, &width,
3532                   XmNheight, &height,
3533                   XmNborderWidth, &border,
3534                   NULL);
3535
3536     XtVaSetValues(form,
3537                   XmNwidth, width + 1,
3538                   XmNheight, height + 1,
3539                   NULL);
3540
3541     XtVaSetValues(form,
3542                   XmNwidth, width,
3543                   XmNheight, height,
3544                   NULL);
3545 }
3546
3547
3548 Boolean
3549 SendMsgDialog::unfilled_headers()
3550 {
3551     // Walk through the headers. See if any of them have a value.
3552     //
3553     for (int scan = 0; scan < _header_list.length(); scan++) {
3554         HeaderList * hl = _header_list[scan];
3555         if (hl->show != SMD_ALWAYS && hl->show != SMD_SHOWN) {
3556             continue;
3557         }
3558
3559         char * value = NULL;
3560         XtVaGetValues(hl->field_widget,
3561                       XmNvalue, &value,
3562                       NULL);
3563         if (value) {
3564           if (strlen(value) > 0) {
3565               XtFree(value);
3566               return(FALSE);
3567           }
3568           XtFree(value);
3569         }
3570     }
3571     return(TRUE);
3572 }
3573
3574 // Method checks if self has text in it.  
3575
3576 Boolean
3577 SendMsgDialog::checkDirty()
3578 {
3579     if (this->unfilled_headers() && 
3580        (this->get_editor()->textEditor()->no_text()) && 
3581        (this->get_editor()->attachArea()->getIconCount() == 0) ) {
3582
3583         // return FALSE so quit() can go ahead.
3584
3585         return(FALSE);
3586
3587     } 
3588     else {
3589         return(TRUE);
3590     }
3591 }
3592
3593 Boolean
3594 SendMsgDialog::handleQuitDialog()
3595 {
3596     char *helpId;
3597
3598     DtMail::Session *m_session = theRoamApp.session()->session();
3599     const char * value = NULL;
3600     DtMailEnv error;
3601
3602     m_session->mailRc(error)->getValue(error, "expert", &value);
3603     if (error.isNotSet() && value != NULL)
3604     {
3605         if (NULL != value)
3606           free((void*) value);
3607
3608         return (TRUE); 
3609     }
3610
3611     DtMailGenDialog *dialog = this->genDialog();
3612
3613     dialog->setToQuestionDialog(
3614                         GETMSG(
3615                                 DT_catd, 
3616                                 1, 
3617                                 99, 
3618                                 "Mailer - Close"),
3619                         GETMSG(
3620                                 DT_catd, 
3621                                 3, 
3622                                 58, 
3623                                 "The Compose window contains text or\n\
3624 attachments that will be lost if\n\
3625 the window is closed.\n\
3626 Close the Compose window?")
3627                 );
3628     helpId = DTMAILHELPCLOSECOMPOSEWINDOW;
3629     if ( dialog->post_and_return(
3630                         GETMSG(
3631                                DT_catd, 
3632                                1, 
3633                                100, 
3634                                "OK"),
3635                         GETMSG(
3636                                DT_catd, 
3637                                1, 
3638                                101, 
3639                                "Cancel"),
3640                         helpId) == 1 ) {    // Close selected
3641             
3642         return(TRUE);
3643     }
3644     else {
3645         return(FALSE);  // Cancel selected
3646     }
3647 }
3648
3649 void
3650 SendMsgDialog::goAway(
3651     Boolean checkForDirty
3652 )
3653 {
3654     
3655     if (!checkForDirty) {
3656         _takeDown = TRUE;
3657         this->quit();
3658     }
3659     else {
3660         // Check to see if self has contents (ie., is dirty)
3661
3662         Boolean is_dirty = this->checkDirty();
3663
3664         if (is_dirty) {
3665             if (isIconified()) {
3666                 MainWindow::manage();
3667             }
3668
3669             // Enquire if user really wants this window to go away
3670
3671             Boolean really_quit = this->handleQuitDialog();
3672             if (!really_quit) {
3673                 return;
3674             }
3675         }
3676         _takeDown = TRUE;
3677         this->quit();
3678     }
3679 }
3680
3681 void
3682 SendMsgDialog::manage()
3683 {
3684     MenuWindow::manage();
3685     // Set focus 
3686     HeaderList * hl = _header_list[0];
3687     (void) XmProcessTraversal(hl->field_widget, XmTRAVERSE_CURRENT);
3688    
3689 }
3690
3691 void
3692 SendMsgDialog::unmanage()
3693 {
3694     MenuWindow::unmanage();
3695     XFlush(XtDisplay(this->_main_form));
3696     XSync(XtDisplay(this->_main_form), False);
3697 }
3698     
3699
3700 Compose::Compose()
3701 {
3702     _compose_head = NULL;
3703     _not_in_use = 0;
3704     _num_created = 0;
3705     _timeout_id = 0;
3706 }
3707
3708 Compose::~Compose()
3709 {
3710     Compose::Compose_Win *a_node;
3711     Compose::Compose_Win *next_node;
3712
3713     theRoamApp.registerPendingTask();
3714
3715     a_node = _compose_head;
3716     while (a_node)
3717     {
3718         next_node = a_node->next;
3719         if (!a_node->in_use)
3720         {
3721             delete a_node->win;
3722             free((void*) a_node);
3723         }
3724         a_node = next_node;
3725     }
3726
3727     theRoamApp.unregisterPendingTask();
3728 }
3729
3730 void
3731 Compose::putWin(SendMsgDialog *smd, Boolean in_use)
3732 {
3733     Compose::Compose_Win* a_node = NULL; 
3734     Compose::Compose_Win *tmp = NULL;
3735
3736     //
3737     // Update the _not_in_use count.
3738     //
3739     if (! in_use)
3740       _not_in_use++;
3741
3742     //
3743     // Check to see if compose window is already in the list.
3744     //
3745     for (a_node = _compose_head; a_node; a_node=a_node->next)
3746     {
3747         if (a_node->win == smd)
3748         {
3749             a_node->in_use = in_use;
3750             return;
3751         }
3752     }
3753
3754     // Need new node with smd.
3755     tmp = (Compose::Compose_Win *)malloc(sizeof(Compose::Compose_Win));
3756     tmp->win = smd;
3757     tmp->next = NULL;
3758     tmp->in_use = in_use;
3759
3760     // If nothing is cached so far, add this Compose window to the head.
3761     if (NULL == _compose_head)
3762     {
3763         _compose_head = tmp;
3764         return;
3765     }
3766     
3767     // There exists a cache.  Add this compose window to the tail.
3768     for (a_node=_compose_head; a_node; a_node=a_node->next)
3769     {
3770         if (NULL == a_node->next)
3771         {
3772             a_node->next = tmp;
3773             return;
3774         }
3775     }
3776 }
3777
3778 SendMsgDialog*
3779 Compose::getUnusedWin()
3780 {
3781     if (NULL == _compose_head) return NULL;
3782    
3783     Compose::Compose_Win* a_node = NULL; 
3784     Compose::Compose_Win* the_node = NULL; 
3785     
3786     // Find a node with unused smd.  Return smd
3787     for (a_node=_compose_head; a_node; a_node=a_node->next)
3788     {
3789         if (!a_node->in_use)
3790         {
3791             a_node->in_use = TRUE;
3792             _not_in_use--;
3793             return a_node->win;
3794         }
3795     }
3796
3797    return NULL;
3798 }
3799
3800 // Get a compose window either by creating a new SendMsgDialog or
3801 // from the recycle list.
3802 SendMsgDialog *
3803 Compose::getWin()
3804 {
3805     SendMsgDialog *newsend = NULL;
3806     
3807 #ifdef DTMAIL_TOOLTALK
3808     if (_timeout_id)
3809     {
3810         XtRemoveTimeOut(_timeout_id);
3811         _timeout_id = 0;
3812     }
3813 #endif
3814     
3815     newsend = getUnusedWin();
3816
3817     if (!newsend)
3818     {
3819         // We have no unused SMDs around; therefore, create new window.
3820         theRoamApp.busyAllWindows();
3821         newsend = new SendMsgDialog();
3822         newsend->initialize();
3823         _num_created++;
3824         putWin(newsend, TRUE);
3825         theRoamApp.unbusyAllWindows();
3826     }
3827     else
3828     {
3829         newsend->resetHeaders();
3830         newsend->displayInCurrentWorkspace();
3831     }
3832
3833     newsend->text_unselected();
3834     newsend->manage();
3835     newsend->startAutoSave();
3836     
3837     // Get new Message Handle
3838     newsend->setMsgHnd();
3839     char *ttl = GETMSG(DT_catd, 1, 160, "New Message");
3840     newsend->setTitle(ttl);
3841     newsend->setIconTitle(ttl);
3842     return newsend;
3843 }
3844
3845
3846 void
3847 SendMsgDialog::setTitle(char *subject)
3848 {
3849     char *format = "%s - %s";
3850     char *prefix = GETMSG(DT_catd, 1, 6, "Mailer");
3851     char *new_title;
3852     int   len;
3853
3854     len = strlen(format) + strlen(prefix) + strlen(subject) + 1;
3855     new_title = new char[len];
3856     sprintf(new_title, format, prefix, subject);
3857
3858     title(new_title);
3859     delete [] new_title; 
3860 }
3861
3862 void
3863 SendMsgDialog::resetHeaders(void)
3864 {
3865     DtMail::Session *m_session = theRoamApp.session()->session();
3866     const char * value = NULL;
3867     DtMailEnv error;
3868     int i, j;
3869
3870     m_session->mailRc(error)->getValue(error, "additionalfields", &value);
3871
3872     // Return if no props were applied and headers did not change.
3873     if ((_additionalfields == NULL && value == NULL) ||
3874         ( _additionalfields != NULL && value != NULL &&
3875           strcmp (_additionalfields, value) == 0))
3876     {
3877         if (NULL != value)
3878           free((void*) value);
3879
3880         return;
3881     }
3882
3883     // User changed the Header list via props. Recreate list
3884     // and menus...
3885
3886     // First hide all shown headers 
3887     for (i=0; i < _header_list.length(); i++) {
3888         HeaderList * hl = _header_list[i];
3889         if (hl->show == SMD_SHOWN) {
3890                 hl->show = SMD_HIDDEN;
3891                 XtUnmanageChild(hl->form_widget);
3892         }
3893     }
3894
3895     // Now remove the old list.
3896     DtVirtArray<PropStringPair *> results(8);
3897     parsePropString(_additionalfields, results);
3898     for (i=0, j=results.length(); i < j; i++) {
3899         PropStringPair * psp = results[i];
3900         int slot = lookupHeader(psp->label);
3901         // dont allow removal of default headers.
3902         HeaderList * hl = _header_list[slot];
3903         if (!reservedHeader(hl->label)) {
3904                 if (slot != -1) 
3905                         hl->show = SMD_NEVER;
3906         }
3907         else if (hl->value != NULL) {
3908                 free(hl->value);
3909                 hl->value = NULL;
3910         }
3911     }
3912     while(results.length()) {
3913         PropStringPair * psp = results[0];
3914         delete psp;
3915         results.remove(0);
3916     }
3917
3918     if (_additionalfields != NULL) 
3919         free(_additionalfields);
3920     if (value != NULL && *value != '\0')
3921         _additionalfields = strdup(value);
3922     else
3923         _additionalfields = NULL;
3924
3925     parsePropString(value, results);
3926
3927     // New List...
3928     for (j=results.length(), i=0; i < j; i++) {
3929         PropStringPair * psp = results[i];
3930         int slot = lookupHeader(psp->label);
3931         if (slot != -1) {
3932                 // Already in list
3933                 HeaderList * hl = _header_list[slot];
3934                 if (!reservedHeader(hl->label)) 
3935                         hl->show = SMD_HIDDEN;
3936                 if (hl->value != NULL) {
3937                         free(hl->value);
3938                         hl->value = NULL;
3939                 }
3940                 if (psp->value != NULL)
3941                         hl->value = strdup(psp->value);
3942                 continue;
3943         }       
3944         HeaderList * copy_hl = new HeaderList;
3945         copy_hl->label = strdup(psp->label);
3946         copy_hl->header = strdup(psp->label);
3947         if (psp->value)
3948                 copy_hl->value = strdup(psp->value);
3949         copy_hl->show = SMD_HIDDEN;
3950         _header_list.append(copy_hl);
3951     }
3952     while(results.length()) {
3953         PropStringPair * psp = results[0];
3954         delete psp;
3955         results.remove(0);
3956     }
3957
3958     createHeaders(_header_form);
3959     doDynamicHeaderMenus();
3960
3961     if (NULL != value)
3962       free((void*) value);
3963 }
3964
3965 void
3966 SendMsgDialog::setInputFocus(const int mode)
3967 {
3968     if (mode == 0) {
3969         // Set focus
3970         HeaderList * hl = _header_list[0];
3971         (void) XmProcessTraversal(hl->field_widget, XmTRAVERSE_CURRENT);
3972     }
3973     else if (mode == 1) {
3974         Widget edWid = _my_editor->textEditor()->get_editor();
3975         (void) XmProcessTraversal(edWid, XmTRAVERSE_CURRENT);
3976     }
3977 }
3978  
3979 void
3980 SendMsgDialog::attachmentFeedback(
3981     Boolean bval
3982 )
3983 {
3984     if (bval) {
3985         this->busyCursor();
3986     }
3987     else {
3988         this->normalCursor();
3989     }
3990 }
3991
3992 Boolean
3993 SendMsgDialog::hasAddressee()
3994 {
3995   DtMailEnv error;
3996   
3997   DtMail::Envelope * env = _msgHandle->getEnvelope(error);
3998   
3999   // Walk through the headers. 
4000   // Return TRUE if the message has a value for either of the
4001   // following headers: To:, Cc:, or Bcc:.
4002   // Return FALSE if none of the three headers have any value.
4003   
4004   for (int scan = 0; scan < _header_list.length(); scan++) {
4005     HeaderList * hl = _header_list[scan];
4006     if (hl->show != SMD_ALWAYS && hl->show != SMD_SHOWN) {
4007       continue;
4008     }
4009     if ((strcmp(hl->label, "To") == 0) ||
4010         (strcmp(hl->label, "Cc") == 0) || 
4011         (strcmp(hl->label, "Bcc") == 0)) {
4012       char * value = NULL;
4013       XtVaGetValues(hl->field_widget,
4014                     XmNvalue, &value,
4015                     NULL);
4016       if (value) {
4017         for (char *cv = value; *cv; cv++) {
4018           if (!isspace(*cv)) {
4019             XtFree(value);
4020             return(TRUE);       // text value contains contents
4021           }
4022         }
4023         XtFree(value);          // text value is "content free" - try the next one
4024       }
4025     }
4026   }
4027   return(FALSE);                // no field has contents 
4028 }