msg.c revision e2264b6d
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 <X11/Xaw/Cardinals.h>
32
33#include "xmh.h"
34#include "tocintrnl.h"
35#include "actions.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    (void) sprintf(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 occured; 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    (void) sprintf(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	    (void) sprintf(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 = (Scrn *) XtRealloc((char *)msg->scrn,
306				       (unsigned) sizeof(Scrn)*msg->num_scrns);
307	msg->scrn[msg->num_scrns - 1] = scrn;
308	if (msg->source == NULL)
309	    msg->source = CreateFileSource(scrn->viewwidget, MsgFileName(msg),
310					   scrn->kind == STcomp);
311	ResetMsgLabel(scrn);
312	RedisplayMsg(scrn);
313	EnableProperButtons(scrn);
314	if (scrn->kind != STtocAndView)
315	    StoreWindowName(scrn, MsgName(msg));
316    }
317}
318
319typedef struct _MsgAndScrn {
320    Msg		msg;
321    Scrn	scrn;
322} MsgAndScrnRec, *MsgAndScrn;
323
324/*ARGSUSED*/
325static void ConfirmedWithScrn(
326    Widget	widget,		/* unused */
327    XtPointer	client_data,
328    XtPointer	call_data)	/* unused */
329{
330    MsgAndScrn	mas = (MsgAndScrn) client_data;
331    RemoveMsgConfirmed(mas->scrn);
332    SetScrnNewMsg(mas->msg, mas->scrn);
333    XtFree((char *) mas);
334}
335
336
337static int SetScrn(
338    Msg		msg,
339    Scrn	scrn,
340    Boolean	force,			/* if true, force msg set scrn */
341    XtCallbackList	confirms,	/* callbacks upon confirmation */
342    XtCallbackList	cancels)	/* callbacks upon cancellation */
343{
344    register int i, num_scrns;
345    static XtCallbackRec yes_callbacks[] = {
346	{(XtCallbackProc) NULL,	(XtPointer) NULL},
347	{(XtCallbackProc) NULL,	(XtPointer) NULL},
348	{(XtCallbackProc) NULL,	(XtPointer) NULL}
349    };
350
351    if (scrn == NULL) {
352	if (msg == NULL || msg->num_scrns == 0) return 0;
353	if (!force && XawAsciiSourceChanged(msg->source)) {
354	    char str[100];
355	    (void) sprintf(str,
356			   "Are you sure you want to remove changes to %s?",
357			   MsgName(msg));
358
359	    yes_callbacks[0].callback = ConfirmedNoScrn;
360	    yes_callbacks[0].closure = (XtPointer) msg;
361	    yes_callbacks[1].callback = confirms[0].callback;
362	    yes_callbacks[1].closure = confirms[0].closure;
363
364	    PopupConfirm((Widget) NULL, str, yes_callbacks, cancels);
365	    return NEEDS_CONFIRMATION;
366	}
367	ConfirmedNoScrn((Widget)NULL, (XtPointer) msg, (XtPointer) NULL);
368	return 0;
369    }
370
371    if (scrn->msg == msg) return 0;
372
373    if (scrn->msg) {
374	num_scrns = scrn->msg->num_scrns;
375	for (i=0 ; i<num_scrns ; i++)
376	    if (scrn->msg->scrn[i] == scrn) break;
377	if (i >= num_scrns) Punt("Couldn't find scrn in SetScrn!");
378	if (num_scrns > 1)
379	    scrn->msg->scrn[i] = scrn->msg->scrn[--(scrn->msg->num_scrns)];
380	else {
381	    if (!force && XawAsciiSourceChanged(scrn->msg->source)) {
382		char		str[100];
383		MsgAndScrn	cb_data;
384
385		cb_data = XtNew(MsgAndScrnRec);
386		cb_data->msg = msg;
387		cb_data->scrn = scrn;
388		(void)sprintf(str,
389			      "Are you sure you want to remove changes to %s?",
390			      MsgName(scrn->msg));
391		yes_callbacks[0].callback = ConfirmedWithScrn;
392		yes_callbacks[0].closure = (XtPointer) cb_data;
393		yes_callbacks[1].callback = confirms[0].callback;
394		yes_callbacks[1].closure = confirms[0].closure;
395		PopupConfirm(scrn->viewwidget, str, yes_callbacks, cancels);
396		return NEEDS_CONFIRMATION;
397	    }
398	    RemoveMsgConfirmed(scrn);
399	}
400    }
401    SetScrnNewMsg(msg, scrn);
402    return 0;
403}
404
405
406
407/* Associate the given msg and scrn, asking for confirmation if necessary. */
408
409int MsgSetScrn(
410    Msg msg,
411    Scrn scrn,
412    XtCallbackList confirms,
413    XtCallbackList cancels)
414{
415    return SetScrn(msg, scrn, FALSE, confirms, cancels);
416}
417
418
419/* Same as above, but with the extra information that the message is actually
420   a composition.  (Nothing currently takes advantage of that extra fact.) */
421
422void MsgSetScrnForComp(Msg msg, Scrn scrn)
423{
424    (void) SetScrn(msg, scrn, FALSE, (XtCallbackList) NULL,
425		   (XtCallbackList) NULL);
426}
427
428
429/* Associate the given msg and scrn, even if it means losing some unsaved
430   changes. */
431
432void MsgSetScrnForce(Msg msg, Scrn scrn)
433{
434    (void) SetScrn(msg, scrn, TRUE, (XtCallbackList) NULL,
435		   (XtCallbackList) NULL);
436}
437
438
439
440/* Set the fate of the given message. */
441
442void MsgSetFate(Msg msg, FateType fate, Toc desttoc)
443{
444    Toc toc = msg->toc;
445    XawTextBlock block;
446    int i;
447    msg->fate = fate;
448    msg->desttoc = desttoc;
449    if (fate == Fignore && msg == msg->toc->curmsg)
450	block.ptr = "+";
451    else {
452	switch (fate) {
453	    case Fignore:	block.ptr = " "; break;
454	    case Fcopy:		block.ptr = "C"; break;
455	    case Fmove:		block.ptr = "^"; break;
456	    case Fdelete:	block.ptr = "D"; break;
457	}
458    }
459    block.firstPos = 0;
460    block.format = FMT8BIT;
461    block.length = 1;
462    if (toc->stopupdate)
463	toc->needsrepaint = TRUE;
464    if (toc->num_scrns && msg->visible && !toc->needsrepaint &&
465	    *block.ptr != msg->buf[MARKPOS])
466	(void)XawTextReplace(msg->toc->scrn[0]->tocwidget, /*%%%SourceReplace*/
467			    msg->position + MARKPOS,
468			    msg->position + MARKPOS + 1, &block);
469    else
470	msg->buf[MARKPOS] = *block.ptr;
471    for (i=0; i < (int) msg->num_scrns; i++)
472	ResetMsgLabel(msg->scrn[i]);
473}
474
475
476
477/* Get the fate of this message. */
478
479FateType MsgGetFate(Msg msg, Toc *toc)
480{
481    if (toc) *toc = msg->desttoc;
482    return msg->fate;
483}
484
485
486/* Make this a temporary message. */
487
488void MsgSetTemporary(Msg msg)
489{
490    int i;
491    msg->temporary = TRUE;
492    for (i=0; i < (int) msg->num_scrns; i++)
493	ResetMsgLabel(msg->scrn[i]);
494}
495
496
497/* Make this a permanent message. */
498
499void MsgSetPermanent(Msg msg)
500{
501    int i;
502    msg->temporary = FALSE;
503    for (i=0; i < (int) msg->num_scrns; i++)
504	ResetMsgLabel(msg->scrn[i]);
505}
506
507
508
509/* Return the id# of this message. */
510
511int MsgGetId(Msg msg)
512{
513    return msg->msgid;
514}
515
516
517/* Return the scanline for this message. */
518
519char *MsgGetScanLine(Msg msg)
520{
521    return msg->buf;
522}
523
524
525
526/* Return the toc this message is in. */
527
528Toc MsgGetToc(Msg msg)
529{
530    return msg->toc;
531}
532
533
534/* Set the reapable flag for this msg. */
535
536void MsgSetReapable(Msg msg)
537{
538    int i;
539    msg->reapable = TRUE;
540    for (i=0; i < (int) msg->num_scrns; i++)
541	EnableProperButtons(msg->scrn[i]);
542}
543
544
545
546/* Clear the reapable flag for this msg. */
547
548void MsgClearReapable(Msg msg)
549{
550    int i;
551    msg->reapable = FALSE;
552    for (i=0; i < (int) msg->num_scrns; i++)
553	EnableProperButtons(msg->scrn[i]);
554}
555
556
557/* Get the reapable value for this msg.  Returns TRUE iff the reapable flag
558   is set AND no changes have been made. */
559
560int MsgGetReapable(Msg msg)
561{
562    return msg == NULL || (msg->reapable &&
563			   (msg->source == NULL ||
564			    !XawAsciiSourceChanged(msg->source)));
565}
566
567
568/* Make it possible to edit the given msg. */
569void MsgSetEditable(Msg msg)
570{
571    int i;
572    if (msg && msg->source) {
573	SetEditable(msg, TRUE);
574	for (i=0; i < (int) msg->num_scrns; i++)
575	    EnableProperButtons(msg->scrn[i]);
576    }
577}
578
579
580
581/* Turn off editing for the given msg. */
582
583void MsgClearEditable(Msg msg)
584{
585    int i;
586    if (msg && msg->source) {
587	SetEditable(msg, FALSE);
588	for (i=0; i < (int) msg->num_scrns; i++)
589	    EnableProperButtons(msg->scrn[i]);
590    }
591}
592
593
594
595/* Get whether the msg is editable. */
596
597int MsgGetEditable(Msg msg)
598{
599    return msg && msg->source && IsEditable(msg);
600}
601
602
603/* Get whether the msg has changed since last saved. */
604
605int MsgChanged(Msg msg)
606{
607    return msg && msg->source && XawAsciiSourceChanged(msg->source);
608}
609
610/* Call the given function when the msg changes. */
611
612void
613MsgSetCallOnChange(Msg msg, void (*func)(XMH_CB_ARGS), XtPointer param)
614{
615  Arg args[1];
616  static XtCallbackRec cb[] = { {NULL, NULL}, {NULL, NULL} };
617
618  if (func != NULL) {
619    cb[0].callback = func;
620    cb[0].closure = param;
621    XtSetArg(args[0], XtNcallback, cb);
622  }
623  else
624    XtSetArg(args[0], XtNcallback, NULL);
625
626  XtSetValues(msg->source, args, (Cardinal) 1);
627
628}
629
630/* Send (i.e., mail) the given message as is.  First break it up into lines,
631   and copy it to a new file in the process.  The new file is one of 10
632   possible draft files; we rotate amoung the 10 so that the user can have up
633   to 10 messages being sent at once.  (Using a file in /tmp is a bad idea
634   because these files never actually get deleted, but renamed with some
635   prefix.  Also, these should stay in an area private to the user for
636   security.) */
637
638void MsgSend(Msg msg)
639{
640    FILEPTR from;
641    FILEPTR to;
642    int     p, c, l, inheader, sendwidth, sendbreakwidth;
643    char   *ptr, *ptr2, **argv, str[100];
644    static int sendcount = -1;
645    (void) MsgSaveChanges(msg);
646    from = FOpenAndCheck(MsgFileName(msg), "r");
647    sendcount = (sendcount + 1) % 10;
648    (void) sprintf(str, "%s%d", xmhDraftFile, sendcount);
649    to = FOpenAndCheck(str, "w");
650    sendwidth = app_resources.send_line_width;
651    sendbreakwidth = app_resources.break_send_line_width;
652    inheader = TRUE;
653    while ((ptr = ReadLine(from))) {
654	if (inheader) {
655	    if (strncmpIgnoringCase(ptr, "sendwidth:", 10) == 0) {
656		if (atoi(ptr+10) > 0) sendwidth = atoi(ptr+10);
657		continue;
658	    }
659	    if (strncmpIgnoringCase(ptr, "sendbreakwidth:", 15) == 0) {
660		if (atoi(ptr+15) > 0) sendbreakwidth = atoi(ptr+15);
661		continue;
662	    }
663	    for (l = 0, ptr2 = ptr ; *ptr2 && !l ; ptr2++)
664		l = (*ptr2 != ' ' && *ptr2 != '\t' && *ptr != '-');
665	    if (l) {
666		(void) fprintf(to, "%s\n", ptr);
667		continue;
668	    }
669	    inheader = FALSE;
670	    if (sendbreakwidth < sendwidth) sendbreakwidth = sendwidth;
671	}
672	do {
673	    for (p = c = l = 0, ptr2 = ptr;
674		 *ptr2 && c < sendbreakwidth;
675		 p++, ptr2++) {
676		 if (*ptr2 == ' ' && c < sendwidth)
677		     l = p;
678		 if (*ptr2 == '\t') {
679		     if (c < sendwidth) l = p;
680		     c += 8 - (c % 8);
681		 }
682		 else
683		 c++;
684	     }
685	    if (c < sendbreakwidth) {
686		(void) fprintf(to, "%s\n", ptr);
687		*ptr = 0;
688	    }
689	    else
690		if (l) {
691		    ptr[l] = 0;
692		    (void) fprintf(to, "%s\n", ptr);
693		    ptr += l + 1;
694		}
695		else {
696		    for (c = 0; c < sendwidth; ) {
697			if (*ptr == '\t') c += 8 - (c % 8);
698			else c++;
699			(void) fputc(*ptr++, to);
700		    }
701		    (void) fputc('\n', to);
702		}
703	} while (*ptr);
704    }
705    myfclose(from);
706    myfclose(to);
707    argv = MakeArgv(3);
708    argv[0] = "send";
709    argv[1] = "-push";
710    argv[2] = str;
711    DoCommand(argv, (char *) NULL, (char *) NULL);
712    XtFree((char *) argv);
713}
714
715
716/* Make the msg into the form for a generic composition.  Set msg->startPos
717   so that the text insertion point will be placed at the end of the first
718   line (which is usually the "To:" field). */
719
720void MsgLoadComposition(Msg msg)
721{
722    static char *blankcomp = NULL; /* Array containing comp template */
723    static int compsize = 0;
724    static XawTextPosition startPos;
725    char *file, **argv;
726    int fid;
727    if (blankcomp == NULL) {
728	file = MakeNewTempFileName();
729	argv = MakeArgv(5);
730	argv[0] = "comp";
731	argv[1] = "-file";
732	argv[2] = file;
733	argv[3] = "-nowhatnowproc";
734	argv[4] = "-nodraftfolder";
735	DoCommand(argv, (char *) NULL, (char *) NULL);
736	XtFree((char *) argv);
737	compsize = GetFileLength(file);
738	if (compsize > 0) {
739	    blankcomp = XtMalloc((Cardinal) compsize);
740	    fid = myopen(file, O_RDONLY, 0666);
741	    if (compsize != read(fid, blankcomp, 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    (void) sprintf(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        (void) sprintf(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	(void) sprintf(file, app_resources.checkpoint_name_format, msg->msgid);
860    } else {
861	(void) sprintf(file, "%s/", msg->toc->path);
862	len = strlen(file);
863	(void) sprintf(file + len, app_resources.checkpoint_name_format,
864		       msg->msgid);
865    }
866    if (!XawAsciiSaveAsFile(msg->source, file)) {
867	char str[256];
868	(void) sprintf(str, "Unsaved edits cannot be checkpointed to %s.",
869		       file);
870	PopupError((Widget)NULL, str);
871    }
872    TocSetCacheValid(msg->toc);
873}
874
875/* Free the storage being used by the given msg. */
876
877void MsgFree(Msg msg)
878{
879    XtFree(msg->buf);
880    XtFree((char *)msg);
881}
882
883/* Insert the associated message, if any, filtering it first */
884
885/*ARGSUSED*/
886void XmhInsert(
887    Widget	w,
888    XEvent	*event,
889    String	*params,
890    Cardinal	*num_params)
891{
892    Scrn scrn = ScrnFromWidget(w);
893    Msg msg = scrn->msg;
894    XawTextPosition pos;
895    XawTextBlock block;
896
897    if (msg == NULL || scrn->assocmsg == NULL) return;
898
899    if (app_resources.insert_filter && *app_resources.insert_filter) {
900	char command[1024];
901	char *argv[4];
902	argv[0] = "/bin/sh";
903	argv[1] = "-c";
904	sprintf(command, "%s %s", app_resources.insert_filter,
905		MsgFileName(scrn->assocmsg));
906	argv[2] = command;
907	argv[3] = NULL;
908	block.ptr = DoCommandToString(argv);
909        block.length = strlen(block.ptr);
910    }
911    else {
912	/* default filter is equivalent to 'echo "<filename>"' */
913	block.ptr = XtNewString(MsgFileName(scrn->assocmsg));
914	block.length = strlen(block.ptr);
915    }
916    block.firstPos = 0;
917    block.format = FMT8BIT;
918    pos = XawTextGetInsertionPoint(scrn->viewwidget);
919    if (XawTextReplace(scrn->viewwidget, pos, pos, &block) != XawEditDone)
920	PopupError(scrn->parent, "Insertion failed!");
921    XtFree(block.ptr);
922}
923
924/*	Function Name: CreateFileSource
925 *	Description: Creates an AsciiSource for a file.
926 *	Arguments: w - the widget to create the source for.
927 *                 filename - the file to assign to this source.
928 *                 edit - if TRUE then this disk source is editable.
929 *	Returns: the source.
930 */
931
932Widget
933CreateFileSource(Widget w, String filename, Boolean edit)
934{
935  Arg arglist[10];
936  Cardinal num_args = 0;
937
938  XtSetArg(arglist[num_args], XtNtype, XawAsciiFile);  num_args++;
939  XtSetArg(arglist[num_args], XtNstring, filename);    num_args++;
940  if (edit)
941      XtSetArg(arglist[num_args], XtNeditType, XawtextEdit);
942  else
943      XtSetArg(arglist[num_args], XtNeditType, XawtextRead);
944  num_args++;
945
946  return(XtCreateWidget("textSource", asciiSrcObjectClass, w,
947			arglist, num_args));
948}
949