1/*
2 * $XConsortium: msg.c /main/2 1996/01/14 16:51:45 kaleb $
3 *
4 *
5 *		       COPYRIGHT 1987, 1989
6 *		   DIGITAL EQUIPMENT CORPORATION
7 *		       MAYNARD, MASSACHUSETTS
8 *			ALL RIGHTS RESERVED.
9 *
10 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
11 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
12 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
13 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
14 *
15 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
16 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
17 * ADDITION TO THAT SET FORTH ABOVE.
18 *
19 * Permission to use, copy, modify, and distribute this software and its
20 * documentation for any purpose and without fee is hereby granted, provided
21 * that the above copyright notice appear in all copies and that both that
22 * copyright notice and this permission notice appear in supporting
23 * documentation, and that the name of Digital Equipment Corporation not be
24 * used in advertising or publicity pertaining to distribution of the software
25 * without specific, written prior permission.
26 */
27/* $XFree86: xc/programs/xmh/msg.c,v 1.4 2002/04/05 21:06:28 dickey Exp $ */
28
29/* msgs.c -- handle operations on messages. */
30
31#include "xmh.h"
32#include "tocintrnl.h"
33#include "actions.h"
34
35#include <X11/Xaw/Cardinals.h>
36
37static int SetScrn(Msg, Scrn, Boolean, XtCallbackList, XtCallbackList);
38
39/*	Function Name: SetEditable
40 *	Description: Sets the editable flag for this message.
41 *	Arguments: msg - the message.
42 *                 edit - set editable to this.
43 *	Returns: none
44 */
45
46static void
47SetEditable(Msg msg, Boolean edit)
48{
49  Arg args[1];
50
51  if (edit)
52    XtSetArg(args[0], XtNeditType, XawtextEdit);
53  else
54    XtSetArg(args[0], XtNeditType, XawtextRead);
55
56  XtSetValues(msg->source, args, ONE);
57}
58
59/*	Function Name: IsEditable
60 *	Description: Returns true if this is an editable message.
61 *	Arguments: msg - the message to edit.
62 *	Returns: TRUE if editable.
63 */
64
65static Boolean
66IsEditable(Msg msg)
67{
68  Arg args[1];
69  XawTextEditType type;
70
71  XtSetArg(args[0], XtNeditType, &type);
72  XtGetValues(msg->source, args, ONE);
73
74  return(type == XawtextEdit);
75}
76
77/* Return the user-viewable name of the given message. */
78
79char *MsgName(Msg msg)
80{
81    static char result[100];
82    snprintf(result, sizeof(result), "%s:%d", msg->toc->foldername, msg->msgid);
83    return result;
84}
85
86
87/* Update the message titlebar in the given scrn. */
88
89static void ResetMsgLabel(Scrn scrn)
90{
91    Msg msg;
92    char str[200];
93    if (scrn) {
94 	msg = scrn->msg;
95	if (msg == NULL) (void) strcpy(str, app_resources.banner);
96	else {
97	    (void) strcpy(str, MsgName(msg));
98	    switch (msg->fate) {
99	      case Fdelete:
100		(void) strcat(str, " -> *Delete*");
101 		break;
102	      case Fcopy:
103	      case Fmove:
104		(void) strcat(str, " -> ");
105		(void) strcat(str, msg->desttoc->foldername);
106		if (msg->fate == Fcopy)
107		    (void) strcat(str, " (Copy)");
108	      default:
109		break;
110	    }
111	    if (msg->temporary) (void)strcat(str, " [Temporary]");
112	}
113	ChangeLabel((Widget) scrn->viewlabel, str);
114    }
115}
116
117
118/* A major msg change has occurred; redisplay it.  (This also should
119work even if we now have a new source to display stuff from.)  This
120routine arranges to hide boring headers, and also will set the text
121insertion point to the proper place if this is a composition and we're
122viewing it for the first time. */
123
124static void RedisplayMsg(Scrn scrn)
125{
126    Msg msg;
127    XawTextPosition startPos, lastPos, nextPos;
128    int length; char str[100];
129    XawTextBlock block;
130    if (scrn) {
131	msg = scrn->msg;
132	if (msg) {
133	    startPos = 0;
134	    if (app_resources.hide_boring_headers && scrn->kind != STcomp) {
135		lastPos = XawTextSourceScan(msg->source, (XawTextPosition) 0,
136					    XawstAll, XawsdRight, 1, FALSE);
137 		while (startPos < lastPos) {
138		    nextPos = startPos;
139		    length = 0;
140		    while (length < 8 && nextPos < lastPos) {
141		        nextPos = XawTextSourceRead(msg->source, nextPos,
142						    &block, 8 - length);
143			(void) strncpy(str + length, block.ptr, block.length);
144 			length += block.length;
145		    }
146		    if (length == 8) {
147			if (strncmp(str, "From:", 5) == 0 ||
148			    strncmp(str, "To:", 3) == 0 ||
149			    strncmp(str, "Date:", 5) == 0 ||
150			    strncmp(str, "Subject:", 8) == 0) break;
151		    }
152		    startPos = XawTextSourceScan(msg->source, startPos,
153					        XawstEOL, XawsdRight, 1, TRUE);
154 		}
155		if (startPos >= lastPos) startPos = 0;
156	    }
157	    XawTextSetSource(scrn->viewwidget, msg->source, startPos);
158	    if (msg->startPos > 0) {
159		XawTextSetInsertionPoint(scrn->viewwidget, msg->startPos);
160		msg->startPos = 0; /* Start in magic place only once. */
161	    }
162	} else {
163	    XawTextSetSource(scrn->viewwidget, PNullSource,
164			     (XawTextPosition)0);
165 	}
166    }
167}
168
169
170
171static char tempDraftFile[100] = "";
172
173/* Temporarily move the draftfile somewhere else, so we can exec an mh
174   command that affects it. */
175
176static void TempMoveDraft(void)
177{
178    char *ptr;
179    if (FileExists(draftFile)) {
180	do {
181	    ptr = MakeNewTempFileName();
182	    (void) strcpy(tempDraftFile, draftFile);
183	    (void) strcpy(strrchr(tempDraftFile, '/'), strrchr(ptr, '/'));
184	} while (FileExists(tempDraftFile));
185	RenameAndCheck(draftFile, tempDraftFile);
186    }
187}
188
189
190
191/* Restore the draftfile from its temporary hiding place. */
192
193static void RestoreDraft(void)
194{
195    if (*tempDraftFile) {
196	RenameAndCheck(tempDraftFile, draftFile);
197	*tempDraftFile = 0;
198    }
199}
200
201
202
203/* Public routines */
204
205
206/* Given a message, return the corresponding filename. */
207
208char *MsgFileName(Msg msg)
209{
210    static char result[500];
211    snprintf(result, sizeof(result), "%s/%d", msg->toc->path, msg->msgid);
212    return result;
213}
214
215
216
217/* Save any changes to a message.  Also calls the toc routine to update the
218   scanline for this msg.  Returns True if saved, false otherwise. */
219
220int MsgSaveChanges(Msg msg)
221{
222    int i;
223    Window w;
224    if (msg->source) {
225	if (XawAsciiSave(msg->source)) {
226	    for (i=0; i < (int) msg->num_scrns; i++)
227		EnableProperButtons(msg->scrn[i]);
228	    if (!msg->temporary)
229		TocMsgChanged(msg->toc, msg);
230	    return True;
231	}
232	else {
233	    char str[256];
234	    snprintf(str, sizeof(str), "Cannot save changes to \"%s/%d\"!",
235			   msg->toc->foldername, msg->msgid);
236	    PopupError((Widget)NULL, str);
237	    return False;
238	}
239    }
240    w= (msg->source?XtWindow(msg->source):None);
241    Feep(XkbBI_Failure,0,w);
242    return False;
243}
244
245
246/*
247 * Show the given message in the given scrn.  If a message is changed, and we
248 * are removing it from any scrn, then ask for confirmation first.  If the
249 * scrn was showing a temporary msg that is not being shown in any other scrn,
250 * it is deleted.  If scrn is NULL, then remove the message from every scrn
251 * that's showing it.
252 */
253
254
255/*ARGSUSED*/
256static void ConfirmedNoScrn(
257    Widget	widget,		/* unused */
258    XtPointer	client_data,
259    XtPointer	call_data)	/* unused */
260{
261    Msg		msg = (Msg) client_data;
262    register int i;
263
264    for (i=msg->num_scrns - 1 ; i >= 0 ; i--)
265	SetScrn((Msg)NULL, msg->scrn[i], TRUE, (XtCallbackList) NULL,
266		(XtCallbackList) NULL);
267}
268
269
270static void RemoveMsgConfirmed(Scrn scrn)
271{
272    if (scrn->kind == STtocAndView && MsgChanged(scrn->msg)) {
273	Arg	args[1];
274	XtSetArg(args[0], XtNtranslations, scrn->read_translations);
275	XtSetValues(scrn->viewwidget, args, (Cardinal) 1);
276    }
277    scrn->msg->scrn[0] = NULL;
278    scrn->msg->num_scrns = 0;
279    XawTextSetSource(scrn->viewwidget, PNullSource, (XawTextPosition) 0);
280    XtDestroyWidget(scrn->msg->source);
281    scrn->msg->source = NULL;
282    if (scrn->msg->temporary) {
283	(void) unlink(MsgFileName(scrn->msg));
284	TocRemoveMsg(scrn->msg->toc, scrn->msg);
285	MsgFree(scrn->msg);
286    }
287}
288
289
290static void SetScrnNewMsg(
291    Msg		msg,
292    Scrn	scrn)
293{
294   scrn->msg = msg;
295   if (msg == NULL) {
296	XawTextSetSource(scrn->viewwidget, PNullSource, (XawTextPosition) 0);
297	ResetMsgLabel(scrn);
298	EnableProperButtons(scrn);
299	if (scrn->kind != STtocAndView && scrn->kind != STcomp) {
300	    StoreWindowName(scrn, progName);
301	    DestroyScrn(scrn);
302	}
303    } else {
304	msg->num_scrns++;
305	msg->scrn = XtReallocArray(msg->scrn, msg->num_scrns, sizeof(Scrn));
306	msg->scrn[msg->num_scrns - 1] = scrn;
307	if (msg->source == NULL)
308	    msg->source = CreateFileSource(scrn->viewwidget, MsgFileName(msg),
309					   scrn->kind == STcomp);
310	ResetMsgLabel(scrn);
311	RedisplayMsg(scrn);
312	EnableProperButtons(scrn);
313	if (scrn->kind != STtocAndView)
314	    StoreWindowName(scrn, MsgName(msg));
315    }
316}
317
318typedef struct _MsgAndScrn {
319    Msg		msg;
320    Scrn	scrn;
321} MsgAndScrnRec, *MsgAndScrn;
322
323/*ARGSUSED*/
324static void ConfirmedWithScrn(
325    Widget	widget,		/* unused */
326    XtPointer	client_data,
327    XtPointer	call_data)	/* unused */
328{
329    MsgAndScrn	mas = (MsgAndScrn) client_data;
330    RemoveMsgConfirmed(mas->scrn);
331    SetScrnNewMsg(mas->msg, mas->scrn);
332    XtFree((char *) mas);
333}
334
335
336static int SetScrn(
337    Msg		msg,
338    Scrn	scrn,
339    Boolean	force,			/* if true, force msg set scrn */
340    XtCallbackList	confirms,	/* callbacks upon confirmation */
341    XtCallbackList	cancels)	/* callbacks upon cancellation */
342{
343    register int i, num_scrns;
344    static XtCallbackRec yes_callbacks[] = {
345	{(XtCallbackProc) NULL,	(XtPointer) NULL},
346	{(XtCallbackProc) NULL,	(XtPointer) NULL},
347	{(XtCallbackProc) NULL,	(XtPointer) NULL}
348    };
349
350    if (scrn == NULL) {
351	if (msg == NULL || msg->num_scrns == 0) return 0;
352	if (!force && XawAsciiSourceChanged(msg->source)) {
353	    char str[150];
354	    snprintf(str, sizeof(str),
355                     "Are you sure you want to remove changes to %s?",
356                     MsgName(msg));
357
358	    yes_callbacks[0].callback = ConfirmedNoScrn;
359	    yes_callbacks[0].closure = (XtPointer) msg;
360	    yes_callbacks[1].callback = confirms[0].callback;
361	    yes_callbacks[1].closure = confirms[0].closure;
362
363	    PopupConfirm((Widget) NULL, str, yes_callbacks, cancels);
364	    return NEEDS_CONFIRMATION;
365	}
366	ConfirmedNoScrn((Widget)NULL, (XtPointer) msg, (XtPointer) NULL);
367	return 0;
368    }
369
370    if (scrn->msg == msg) return 0;
371
372    if (scrn->msg) {
373	num_scrns = scrn->msg->num_scrns;
374	for (i=0 ; i<num_scrns ; i++)
375	    if (scrn->msg->scrn[i] == scrn) break;
376	if (i >= num_scrns) Punt("Couldn't find scrn in SetScrn!");
377	if (num_scrns > 1)
378	    scrn->msg->scrn[i] = scrn->msg->scrn[--(scrn->msg->num_scrns)];
379	else {
380	    if (!force && XawAsciiSourceChanged(scrn->msg->source)) {
381		char		str[100];
382		MsgAndScrn	cb_data;
383
384		cb_data = XtNew(MsgAndScrnRec);
385		cb_data->msg = msg;
386		cb_data->scrn = scrn;
387		snprintf(str, sizeof(str),
388                         "Are you sure you want to remove changes to %s?",
389                         MsgName(scrn->msg));
390		yes_callbacks[0].callback = ConfirmedWithScrn;
391		yes_callbacks[0].closure = (XtPointer) cb_data;
392		yes_callbacks[1].callback = confirms[0].callback;
393		yes_callbacks[1].closure = confirms[0].closure;
394		PopupConfirm(scrn->viewwidget, str, yes_callbacks, cancels);
395		return NEEDS_CONFIRMATION;
396	    }
397	    RemoveMsgConfirmed(scrn);
398	}
399    }
400    SetScrnNewMsg(msg, scrn);
401    return 0;
402}
403
404
405
406/* Associate the given msg and scrn, asking for confirmation if necessary. */
407
408int MsgSetScrn(
409    Msg msg,
410    Scrn scrn,
411    XtCallbackList confirms,
412    XtCallbackList cancels)
413{
414    return SetScrn(msg, scrn, FALSE, confirms, cancels);
415}
416
417
418/* Same as above, but with the extra information that the message is actually
419   a composition.  (Nothing currently takes advantage of that extra fact.) */
420
421void MsgSetScrnForComp(Msg msg, Scrn scrn)
422{
423    (void) SetScrn(msg, scrn, FALSE, (XtCallbackList) NULL,
424		   (XtCallbackList) NULL);
425}
426
427
428/* Associate the given msg and scrn, even if it means losing some unsaved
429   changes. */
430
431void MsgSetScrnForce(Msg msg, Scrn scrn)
432{
433    (void) SetScrn(msg, scrn, TRUE, (XtCallbackList) NULL,
434		   (XtCallbackList) NULL);
435}
436
437
438
439/* Set the fate of the given message. */
440
441void MsgSetFate(Msg msg, FateType fate, Toc desttoc)
442{
443    Toc toc = msg->toc;
444    XawTextBlock block;
445    int i;
446    msg->fate = fate;
447    msg->desttoc = desttoc;
448    if (fate == Fignore && msg == msg->toc->curmsg)
449	block.ptr = "+";
450    else {
451	switch (fate) {
452	    case Fignore:	block.ptr = " "; break;
453	    case Fcopy:		block.ptr = "C"; break;
454	    case Fmove:		block.ptr = "^"; break;
455	    case Fdelete:	block.ptr = "D"; break;
456	}
457    }
458    block.firstPos = 0;
459    block.format = FMT8BIT;
460    block.length = 1;
461    if (toc->stopupdate)
462	toc->needsrepaint = TRUE;
463    if (toc->num_scrns && msg->visible && !toc->needsrepaint &&
464	    *block.ptr != msg->buf[MARKPOS])
465	(void)XawTextReplace(msg->toc->scrn[0]->tocwidget, /*%%%SourceReplace*/
466			    msg->position + MARKPOS,
467			    msg->position + MARKPOS + 1, &block);
468    else
469	msg->buf[MARKPOS] = *block.ptr;
470    for (i=0; i < (int) msg->num_scrns; i++)
471	ResetMsgLabel(msg->scrn[i]);
472}
473
474
475
476/* Get the fate of this message. */
477
478FateType MsgGetFate(Msg msg, Toc *toc)
479{
480    if (toc) *toc = msg->desttoc;
481    return msg->fate;
482}
483
484
485/* Make this a temporary message. */
486
487void MsgSetTemporary(Msg msg)
488{
489    int i;
490    msg->temporary = TRUE;
491    for (i=0; i < (int) msg->num_scrns; i++)
492	ResetMsgLabel(msg->scrn[i]);
493}
494
495
496/* Make this a permanent message. */
497
498void MsgSetPermanent(Msg msg)
499{
500    int i;
501    msg->temporary = FALSE;
502    for (i=0; i < (int) msg->num_scrns; i++)
503	ResetMsgLabel(msg->scrn[i]);
504}
505
506
507
508/* Return the id# of this message. */
509
510int MsgGetId(Msg msg)
511{
512    return msg->msgid;
513}
514
515
516/* Return the scanline for this message. */
517
518char *MsgGetScanLine(Msg msg)
519{
520    return msg->buf;
521}
522
523
524
525/* Return the toc this message is in. */
526
527Toc MsgGetToc(Msg msg)
528{
529    return msg->toc;
530}
531
532
533/* Set the reapable flag for this msg. */
534
535void MsgSetReapable(Msg msg)
536{
537    int i;
538    msg->reapable = TRUE;
539    for (i=0; i < (int) msg->num_scrns; i++)
540	EnableProperButtons(msg->scrn[i]);
541}
542
543
544
545/* Clear the reapable flag for this msg. */
546
547void MsgClearReapable(Msg msg)
548{
549    int i;
550    msg->reapable = FALSE;
551    for (i=0; i < (int) msg->num_scrns; i++)
552	EnableProperButtons(msg->scrn[i]);
553}
554
555
556/* Get the reapable value for this msg.  Returns TRUE iff the reapable flag
557   is set AND no changes have been made. */
558
559int MsgGetReapable(Msg msg)
560{
561    return msg == NULL || (msg->reapable &&
562			   (msg->source == NULL ||
563			    !XawAsciiSourceChanged(msg->source)));
564}
565
566
567/* Make it possible to edit the given msg. */
568void MsgSetEditable(Msg msg)
569{
570    int i;
571    if (msg && msg->source) {
572	SetEditable(msg, TRUE);
573	for (i=0; i < (int) msg->num_scrns; i++)
574	    EnableProperButtons(msg->scrn[i]);
575    }
576}
577
578
579
580/* Turn off editing for the given msg. */
581
582void MsgClearEditable(Msg msg)
583{
584    int i;
585    if (msg && msg->source) {
586	SetEditable(msg, FALSE);
587	for (i=0; i < (int) msg->num_scrns; i++)
588	    EnableProperButtons(msg->scrn[i]);
589    }
590}
591
592
593
594/* Get whether the msg is editable. */
595
596int MsgGetEditable(Msg msg)
597{
598    return msg && msg->source && IsEditable(msg);
599}
600
601
602/* Get whether the msg has changed since last saved. */
603
604int MsgChanged(Msg msg)
605{
606    return msg && msg->source && XawAsciiSourceChanged(msg->source);
607}
608
609/* Call the given function when the msg changes. */
610
611void
612MsgSetCallOnChange(Msg msg, void (*func)(XMH_CB_ARGS), XtPointer param)
613{
614  Arg args[1];
615  static XtCallbackRec cb[] = { {NULL, NULL}, {NULL, NULL} };
616
617  if (func != NULL) {
618    cb[0].callback = func;
619    cb[0].closure = param;
620    XtSetArg(args[0], XtNcallback, cb);
621  }
622  else
623    XtSetArg(args[0], XtNcallback, NULL);
624
625  XtSetValues(msg->source, args, (Cardinal) 1);
626
627}
628
629/* Send (i.e., mail) the given message as is.  First break it up into lines,
630   and copy it to a new file in the process.  The new file is one of 10
631   possible draft files; we rotate among the 10 so that the user can have up
632   to 10 messages being sent at once.  (Using a file in /tmp is a bad idea
633   because these files never actually get deleted, but renamed with some
634   prefix.  Also, these should stay in an area private to the user for
635   security.) */
636
637void MsgSend(Msg msg)
638{
639    FILEPTR from;
640    FILEPTR to;
641    int     p, c, l, inheader, sendwidth, sendbreakwidth;
642    char   *ptr, *ptr2, **argv, str[100];
643    static int sendcount = -1;
644    (void) MsgSaveChanges(msg);
645    from = FOpenAndCheck(MsgFileName(msg), "r");
646    sendcount = (sendcount + 1) % 10;
647    snprintf(str, sizeof(str), "%s%d", xmhDraftFile, sendcount);
648    to = FOpenAndCheck(str, "w");
649    sendwidth = app_resources.send_line_width;
650    sendbreakwidth = app_resources.break_send_line_width;
651    inheader = TRUE;
652    while ((ptr = ReadLine(from))) {
653	if (inheader) {
654	    if (strncmpIgnoringCase(ptr, "sendwidth:", 10) == 0) {
655		if (atoi(ptr+10) > 0) sendwidth = atoi(ptr+10);
656		continue;
657	    }
658	    if (strncmpIgnoringCase(ptr, "sendbreakwidth:", 15) == 0) {
659		if (atoi(ptr+15) > 0) sendbreakwidth = atoi(ptr+15);
660		continue;
661	    }
662	    for (l = 0, ptr2 = ptr ; *ptr2 && !l ; ptr2++)
663		l = (*ptr2 != ' ' && *ptr2 != '\t' && *ptr != '-');
664	    if (l) {
665		(void) fprintf(to, "%s\n", ptr);
666		continue;
667	    }
668	    inheader = FALSE;
669	    if (sendbreakwidth < sendwidth) sendbreakwidth = sendwidth;
670	}
671	do {
672	    for (p = c = l = 0, ptr2 = ptr;
673		 *ptr2 && c < sendbreakwidth;
674		 p++, ptr2++) {
675		 if (*ptr2 == ' ' && c < sendwidth)
676		     l = p;
677		 if (*ptr2 == '\t') {
678		     if (c < sendwidth) l = p;
679		     c += 8 - (c % 8);
680		 }
681		 else
682		 c++;
683	     }
684	    if (c < sendbreakwidth) {
685		(void) fprintf(to, "%s\n", ptr);
686		*ptr = 0;
687	    }
688	    else
689		if (l) {
690		    ptr[l] = 0;
691		    (void) fprintf(to, "%s\n", ptr);
692		    ptr += l + 1;
693		}
694		else {
695		    for (c = 0; c < sendwidth; ) {
696			if (*ptr == '\t') c += 8 - (c % 8);
697			else c++;
698			(void) fputc(*ptr++, to);
699		    }
700		    (void) fputc('\n', to);
701		}
702	} while (*ptr);
703    }
704    myfclose(from);
705    myfclose(to);
706    argv = MakeArgv(3);
707    argv[0] = "send";
708    argv[1] = "-push";
709    argv[2] = str;
710    DoCommand(argv, (char *) NULL, (char *) NULL);
711    XtFree((char *) argv);
712}
713
714
715/* Make the msg into the form for a generic composition.  Set msg->startPos
716   so that the text insertion point will be placed at the end of the first
717   line (which is usually the "To:" field). */
718
719void MsgLoadComposition(Msg msg)
720{
721    static const char *blankcomp = NULL; /* Array containing comp template */
722    static int compsize = 0;
723    static XawTextPosition startPos;
724    char *file, **argv;
725    int fid;
726    if (blankcomp == NULL) {
727	file = MakeNewTempFileName();
728	argv = MakeArgv(5);
729	argv[0] = "comp";
730	argv[1] = "-file";
731	argv[2] = file;
732	argv[3] = "-nowhatnowproc";
733	argv[4] = "-nodraftfolder";
734	DoCommand(argv, (char *) NULL, (char *) NULL);
735	XtFree((char *) argv);
736	compsize = GetFileLength(file);
737	if (compsize > 0) {
738	    char *readcomp = XtMalloc((Cardinal) compsize);
739	    blankcomp = readcomp;
740	    fid = myopen(file, O_RDONLY, 0666);
741	    if (compsize != read(fid, readcomp, compsize))
742		Punt("Error reading in MsgLoadComposition!");
743	    myclose(fid);
744	    DeleteFileAndCheck(file);
745	} else {
746 	    blankcomp = "To: \n--------\n";
747 	    compsize = strlen(blankcomp);
748 	}
749	startPos = strchr(blankcomp, '\n') - blankcomp;
750    }
751    fid = myopen(MsgFileName(msg), O_WRONLY | O_TRUNC | O_CREAT, 0666);
752    if (compsize != write(fid, blankcomp, compsize))
753	Punt("Error writing in MsgLoadComposition!");
754    myclose(fid);
755    TocSetCacheValid(msg->toc);
756    msg->startPos = startPos;
757}
758
759
760
761/* Load a msg with a template of a reply to frommsg.  Set msg->startPos so
762   that the text insertion point will be placed at the beginning of the
763   message body. */
764
765void MsgLoadReply(
766    Msg msg,
767    Msg frommsg,
768    String *params,
769    Cardinal num_params)
770{
771    char **argv;
772    char str[100];
773    int status;
774
775    TempMoveDraft();
776    argv = MakeArgv(5 + num_params);
777    argv[0] = "repl";
778    argv[1] = TocMakeFolderName(frommsg->toc);
779    snprintf(str, sizeof(str), "%d", frommsg->msgid);
780    argv[2] = str;
781    argv[3] = "-nowhatnowproc";
782    argv[4] = "-nodraftfolder";
783    memmove( (char *)(argv + 5), (char *)params, num_params * sizeof(String *));
784    status = DoCommand(argv, (char *) NULL, (char *) NULL);
785    XtFree(argv[1]);
786    XtFree((char*)argv);
787    if (!status) {
788	RenameAndCheck(draftFile, MsgFileName(msg));
789	RestoreDraft();
790	TocSetCacheValid(frommsg->toc); /* If -anno is set, this keeps us from
791					   rescanning folder. */
792	TocSetCacheValid(msg->toc);
793	msg->startPos = GetFileLength(MsgFileName(msg));
794    }
795}
796
797
798
799/* Load a msg with a template of forwarding a list of messages.  Set
800   msg->startPos so that the text insertion point will be placed at the end
801   of the first line (which is usually a "To:" field). */
802
803void MsgLoadForward(
804  Scrn scrn,
805  Msg msg,
806  MsgList mlist,
807  String *params,
808  Cardinal num_params)
809{
810    char  **argv, str[100];
811    int     i;
812    TempMoveDraft();
813    argv = MakeArgv(4 + mlist->nummsgs + num_params);
814    argv[0] = "forw";
815    argv[1] = TocMakeFolderName(mlist->msglist[0]->toc);
816    for (i = 0; i < mlist->nummsgs; i++) {
817        snprintf(str, sizeof(str), "%d", mlist->msglist[i]->msgid);
818        argv[2 + i] = XtNewString(str);
819    }
820    argv[2 + i] = "-nowhatnowproc";
821    argv[3 + i] = "-nodraftfolder";
822    memmove( (char *)(argv + 4 + i), (char *)params,
823	  num_params * sizeof(String *));
824    DoCommand(argv, (char *) NULL, (char *) NULL);
825    for (i = 1; i < 2 + mlist->nummsgs; i++)
826        XtFree((char *) argv[i]);
827    XtFree((char *) argv);
828    RenameAndCheck(draftFile, MsgFileName(msg));
829    RestoreDraft();
830    TocSetCacheValid(msg->toc);
831    msg->source = CreateFileSource(scrn->viewlabel, MsgFileName(msg), True);
832    msg->startPos = XawTextSourceScan(msg->source, (XawTextPosition) 0,
833				      XawstEOL, XawsdRight, 1, False);
834}
835
836
837/* Load msg with a copy of frommsg. */
838
839void MsgLoadCopy(Msg msg, Msg frommsg)
840{
841    char str[500];
842    (void)strcpy(str, MsgFileName(msg));
843    CopyFileAndCheck(MsgFileName(frommsg), str);
844    TocSetCacheValid(msg->toc);
845}
846
847/* Checkpoint the given message if it contains unsaved edits. */
848
849void MsgCheckPoint(Msg msg)
850{
851    int len;
852    char file[500];
853
854    if (!msg || !msg->source || !IsEditable(msg) ||
855	!XawAsciiSourceChanged(msg->source))
856	return;
857
858    if (*app_resources.checkpoint_name_format == '/') {
859	snprintf(file, sizeof(file),
860                 app_resources.checkpoint_name_format, msg->msgid);
861    } else {
862	snprintf(file, sizeof(file), "%s/", msg->toc->path);
863	len = strlen(file);
864	snprintf(file + len, sizeof(file) - len,
865                 app_resources.checkpoint_name_format, msg->msgid);
866    }
867    if (!XawAsciiSaveAsFile(msg->source, file)) {
868	char str[256];
869	snprintf(str, sizeof(str),
870                 "Unsaved edits cannot be checkpointed to %s.",file);
871	PopupError((Widget)NULL, str);
872    }
873    TocSetCacheValid(msg->toc);
874}
875
876/* Free the storage being used by the given msg. */
877
878void MsgFree(Msg msg)
879{
880    XtFree(msg->buf);
881    XtFree((char *)msg);
882}
883
884/* Insert the associated message, if any, filtering it first */
885
886/*ARGSUSED*/
887void XmhInsert(
888    Widget	w,
889    XEvent	*event,
890    String	*params,
891    Cardinal	*num_params)
892{
893    Scrn scrn = ScrnFromWidget(w);
894    Msg msg = scrn->msg;
895    XawTextPosition pos;
896    XawTextBlock block;
897
898    if (msg == NULL || scrn->assocmsg == NULL) return;
899
900    if (app_resources.insert_filter && *app_resources.insert_filter) {
901	char command[1024];
902	char *argv[4];
903	argv[0] = "/bin/sh";
904	argv[1] = "-c";
905	snprintf(command, sizeof(command), "%s %s", app_resources.insert_filter,
906		MsgFileName(scrn->assocmsg));
907	argv[2] = command;
908	argv[3] = NULL;
909	block.ptr = DoCommandToString(argv);
910        block.length = strlen(block.ptr);
911    }
912    else {
913	/* default filter is equivalent to 'echo "<filename>"' */
914	block.ptr = XtNewString(MsgFileName(scrn->assocmsg));
915	block.length = strlen(block.ptr);
916    }
917    block.firstPos = 0;
918    block.format = FMT8BIT;
919    pos = XawTextGetInsertionPoint(scrn->viewwidget);
920    if (XawTextReplace(scrn->viewwidget, pos, pos, &block) != XawEditDone)
921	PopupError(scrn->parent, "Insertion failed!");
922    XtFree(block.ptr);
923}
924
925/*	Function Name: CreateFileSource
926 *	Description: Creates an AsciiSource for a file.
927 *	Arguments: w - the widget to create the source for.
928 *                 filename - the file to assign to this source.
929 *                 edit - if TRUE then this disk source is editable.
930 *	Returns: the source.
931 */
932
933Widget
934CreateFileSource(Widget w, String filename, Boolean edit)
935{
936  Arg arglist[10];
937  Cardinal num_args = 0;
938
939  XtSetArg(arglist[num_args], XtNtype, XawAsciiFile);  num_args++;
940  XtSetArg(arglist[num_args], XtNstring, filename);    num_args++;
941  if (edit)
942      XtSetArg(arglist[num_args], XtNeditType, XawtextEdit);
943  else
944      XtSetArg(arglist[num_args], XtNeditType, XawtextRead);
945  num_args++;
946
947  return(XtCreateWidget("textSource", asciiSrcObjectClass, w,
948			arglist, num_args));
949}
950