|
|
1.1 root 1: #ifndef lint
2: static char rcs_id[] = "$Header: msg.c,v 1.18 87/08/20 11:14:28 swick Exp $";
3: #endif lint
4: /*
5: * COPYRIGHT 1987
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 RIGHTS,
16: * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
17: * SET FORTH ABOVE.
18: *
19: *
20: * Permission to use, copy, modify, and distribute this software and its
21: * documentation for any purpose and without fee is hereby granted, provided
22: * that the above copyright notice appear in all copies and that both that
23: * copyright notice and this permission notice appear in supporting documentation,
24: * and that the name of Digital Equipment Corporation not be used in advertising
25: * or publicity pertaining to distribution of the software without specific,
26: * written prior permission.
27: */
28:
29: /* msgs.c -- handle operations on messages. */
30:
31: #include "xmh.h"
32: #include "tocintrnl.h"
33: #include <sys/file.h>
34:
35: #ifdef NOGRABFOCUS
36: /* Overload this Xlib routine, as the toolkit widgets use it directly. */
37:
38: XSetInputFocus(d, w, r, t)
39: Display d;
40: Window w;
41: int r;
42: Time t;
43: {
44: }
45: #endif
46:
47:
48: /* Return the user-viewable name of the given message. */
49:
50: static char *NameOfMsg(msg)
51: Msg msg;
52: {
53: static char result[100];
54: (void) sprintf(result, "%s:%d", msg->toc->foldername, msg->msgid);
55: return result;
56: }
57:
58:
59: /* Update the message titlebar in the given scrn. */
60:
61: static void ResetMsgLabel(scrn)
62: Scrn scrn;
63: {
64: Msg msg;
65: char str[200];
66: if (scrn) {
67: msg = scrn->msg;
68: if (msg == NULL) (void) strcpy(str, Version());
69: else {
70: (void) strcpy(str, NameOfMsg(msg));
71: switch (msg->fate) {
72: case Fdelete:
73: (void) strcat(str, " -> *Delete*");
74: break;
75: case Fcopy:
76: case Fmove:
77: (void) strcat(str, " -> ");
78: (void) strcat(str, msg->desttoc->foldername);
79: if (msg->fate == Fcopy)
80: (void) strcat(str, " (Copy)");
81: break;
82: }
83: if (msg->temporary) (void)strcat(str, " [Temporary]");
84: }
85: ChangeLabel(scrn->viewlabel, str);
86: }
87: }
88:
89:
90: /* A major msg change has occured; redisplay it. (This also should
91: work even if we now have a new source to display stuff from.) This
92: routine arranges to hide boring headers, and also will set the text
93: insertion point to the proper place if this is a composition and we're
94: viewing it for the first time. */
95:
96: static void RedisplayMsg(scrn)
97: Scrn scrn;
98: {
99: Msg msg;
100: XtTextPosition startPos, lastPos, nextPos;
101: int length; char str[100];
102: XtTextBlock text;
103: if (scrn) {
104: msg = scrn->msg;
105: if (msg) {
106: startPos = 0;
107: if (defHideBoringHeaders && scrn->kind != STcomp) {
108: lastPos = msg->source->getLastPos(msg->source);
109: while (startPos < lastPos) {
110: nextPos = startPos;
111: length = 0;
112: while (length < 8 && nextPos < lastPos) {
113: nextPos = (*msg->source->read)(msg->source, nextPos,
114: &text, 8 - length);
115: (void) strncpy(str + length, text.ptr, text.length);
116: length += text.length;
117: }
118: if (length == 8) {
119: if (strncmp(str, "From:", 5) == 0 ||
120: strncmp(str, "To:", 3) == 0 ||
121: strncmp(str, "Date:", 5) == 0 ||
122: strncmp(str, "Subject:", 8) == 0) break;
123: }
124: startPos = (*msg->source->scan)
125: (msg->source, startPos, XtstEOL, XtsdRight, 1,TRUE);
126: }
127: }
128: if (startPos >= lastPos) startPos = 0;
129: XtTextNewSource(DISPLAY scrn->viewwindow, msg->source, startPos);
130: if (msg->startPos > 0) {
131: #ifdef X10
132: /* Do an incredibly disgusting hack to make things display right.
133: The X10 toolkit stinks. */
134: MapScrn(scrn);
135: EmptyEventQueue();
136: #endif X10
137: XtTextSetInsertionPoint(DISPLAY scrn->viewwindow,
138: msg->startPos);
139: msg->startPos = 0; /* Start in magic place only once. */
140: }
141: } else {
142: XtTextNewSource(DISPLAY scrn->viewwindow,
143: NullSource, (XtTextPosition) 0);
144: }
145: }
146: }
147:
148:
149:
150: static char tempDraftFile[100] = "";
151:
152: /* Temporarily move the draftfile somewhere else, so we can exec an mh
153: command that affects it. */
154:
155: static void TempMoveDraft()
156: {
157: char *ptr;
158: if (FileExists(draftFile)) {
159: do {
160: ptr = MakeNewTempFileName();
161: (void) strcpy(tempDraftFile, draftFile);
162: (void) strcpy(rindex(tempDraftFile, '/'), rindex(ptr, '/'));
163: } while (FileExists(tempDraftFile));
164: RenameAndCheck(draftFile, tempDraftFile);
165: }
166: }
167:
168:
169:
170: /* Restore the draftfile from its temporary hiding place. */
171:
172: static void RestoreDraft()
173: {
174: if (*tempDraftFile) {
175: RenameAndCheck(tempDraftFile, draftFile);
176: *tempDraftFile = 0;
177: }
178: }
179:
180:
181:
182: /* Public routines */
183:
184:
185: /* Given a message, return the corresponding filename. */
186:
187: char *MsgFileName(msg)
188: Msg msg;
189: {
190: static char result[500];
191: (void) sprintf(result, "%s/%d", msg->toc->path, msg->msgid);
192: return result;
193: }
194:
195:
196:
197: /* Save any changes to a message. Also calls the toc routine to update the
198: scanline for this msg. */
199:
200: void MsgSaveChanges(msg)
201: Msg msg;
202: {
203: if (msg->source) {
204: XtEDiskSaveFile(msg->source);
205: EnableProperButtons(msg->scrn);
206: if (!msg->temporary)
207: TocMsgChanged(msg->toc, msg);
208: }
209: }
210:
211: /*ARGSUSED*/
212: static Boolean IfMapWindow(dpy, event, scrn)
213: Display *dpy;
214: XEvent *event;
215: Scrn scrn;
216: {
217: return (event->type == MapNotify && event->xany.window == scrn->window);
218: }
219:
220: /* Associate the given message with the given scrn. If a message is
221: changed, and we are removing it from any scrn, then ask for confirmation
222: first. If the message is a temporary one and it is removed from any scrn,
223: it is deleted. */
224:
225: static int SetScrn(msg, scrn, force)
226: Msg msg;
227: Scrn scrn;
228: Boolean force; /* If TRUE, don't ask for confirm; just do it */
229: {
230: char str[100];
231: if (msg == NULL && scrn == NULL) return 0;
232: if (scrn && scrn->msg != msg)
233: if (SetScrn(scrn->msg, (Scrn) NULL, force))
234: return DELETEABORTED;
235: if (msg == NULL)
236: return 0;
237: if (msg->scrn == scrn) return 0;
238: if (msg->scrn) {
239: if (msg->source && scrn == NULL) {
240: if (XtEDiskChanged(msg->source) && !force) {
241: (void)sprintf(str,
242: "Are you sure you want to remove changes to %s?",
243: NameOfMsg(msg));
244: if (!Confirm(msg->scrn, str)) return DELETEABORTED;
245: }
246: XtDestroyEDiskSource(msg->source);
247: msg->source = NULL;
248: }
249: msg->scrn->msg = NULL;
250: ResetMsgLabel(msg->scrn);
251: RedisplayMsg(msg->scrn);
252: EnableProperButtons(msg->scrn);
253: if (msg->scrn->kind != STtocAndView)
254: QXStoreName(theDisplay, msg->scrn->window, progName);
255: msg->scrn = NULL;
256: if (scrn == NULL) {
257: if (msg->temporary) {
258: (void) unlink(MsgFileName(msg));
259: TocRemoveMsg(msg->toc, msg);
260: MsgFree(msg);
261: }
262: return 0;
263: }
264: }
265: if (msg->source == NULL)
266: msg->source = XtCreateEDiskSource(MsgFileName(msg), XttextRead);
267: if (scrn->kind == STcomp) {
268: XtEDiskChangeEditMode(msg->source, XttextEdit);
269: if (defGrabFocus) {
270: XEvent event;
271: QXMapWindow(theDisplay, scrn->window);
272: XPeekIfEvent(theDisplay, &event, IfMapWindow, (char *)scrn);
273: QXSetInputFocus(theDisplay, scrn->viewwindow,
274: RevertToParent, CurrentTime);
275: }
276: }
277:
278: msg->scrn = scrn;
279: scrn->msg = msg;
280: ResetMsgLabel(msg->scrn);
281: RedisplayMsg(msg->scrn);
282: EnableProperButtons(msg->scrn);
283: if (msg->scrn->kind != STtocAndView)
284: QXStoreName(theDisplay, msg->scrn->window, NameOfMsg(msg));
285: return 0;
286: }
287:
288:
289: /* Associate the given msg and scrn, asking for confirmation if necessary. */
290:
291: int MsgSetScrn(msg, scrn)
292: Msg msg;
293: Scrn scrn;
294: {
295: return SetScrn(msg, scrn, FALSE);
296: }
297:
298:
299: /* Same as above, but with the extra information that the message is actually
300: a composition. (Nothing currently takes advantage of that extra fact.) */
301:
302: int MsgSetScrnForComp(msg, scrn)
303: Msg msg;
304: Scrn scrn;
305: {
306: return SetScrn(msg, scrn, FALSE);
307: }
308:
309:
310:
311: /* Associate the given msg and scrn, even if it means losing some unsaved
312: changes. */
313:
314: void MsgSetScrnForce(msg, scrn)
315: Msg msg;
316: Scrn scrn;
317: {
318: (void) SetScrn(msg, scrn, TRUE);
319: }
320:
321:
322:
323: /* Return what screen (if any) is displaying the given msg. */
324:
325: Scrn MsgGetScrn(msg)
326: Msg msg;
327: {
328: return msg->scrn;
329: }
330:
331:
332:
333: /* Set the fate of the given message. */
334:
335: void MsgSetFate(msg, fate, desttoc)
336: Msg msg;
337: FateType fate;
338: Toc desttoc;
339: {
340: Toc toc = msg->toc;
341: XtTextBlock text;
342: msg->fate = fate;
343: msg->desttoc = desttoc;
344: if (fate == Fignore && msg == msg->toc->curmsg)
345: text.ptr = "+";
346: else {
347: switch (fate) {
348: case Fignore: text.ptr = " "; break;
349: case Fcopy: text.ptr = "C"; break;
350: case Fmove: text.ptr = "^"; break;
351: case Fdelete: text.ptr = "D"; break;
352: }
353: }
354: text.length = 1;
355: text.firstPos = msg->position + MARKPOS;
356: if (toc->stopupdate)
357: toc->needsrepaint = TRUE;
358: if (toc->scrn && msg->visible && !toc->needsrepaint &&
359: *text.ptr != msg->buf[MARKPOS])
360: (void) XtTextReplace(DISPLAY toc->scrn->tocwindow,
361: msg->position + MARKPOS,
362: msg->position + MARKPOS + 1, &text);
363: else
364: msg->buf[MARKPOS] = *text.ptr;
365: if (msg->scrn)
366: ResetMsgLabel(msg->scrn);
367: }
368:
369:
370:
371: /* Get the fate of this message. */
372:
373: FateType MsgGetFate(msg, toc)
374: Msg msg;
375: Toc *toc; /* RETURN */
376: {
377: if (toc) *toc = msg->desttoc;
378: return msg->fate;
379: }
380:
381:
382: /* Make this a temporary message. */
383:
384: void MsgSetTemporary(msg)
385: Msg msg;
386: {
387: msg->temporary = TRUE;
388: ResetMsgLabel(msg->scrn);
389: }
390:
391:
392: /* Make this a permanent message. */
393:
394: void MsgSetPermanent(msg)
395: Msg msg;
396: {
397: msg->temporary = FALSE;
398: ResetMsgLabel(msg->scrn);
399: }
400:
401:
402:
403: /* Return the id# of this message. */
404:
405: int MsgGetId(msg)
406: Msg msg;
407: {
408: return msg->msgid;
409: }
410:
411:
412: /* Return the scanline for this message. */
413:
414: char *MsgGetScanLine(msg)
415: Msg msg;
416: {
417: return msg->buf;
418: }
419:
420:
421:
422: /* Return the toc this message is in. */
423:
424: Toc MsgGetToc(msg)
425: Msg msg;
426: {
427: return msg->toc;
428: }
429:
430:
431: /* Set the reapable flag for this msg. */
432:
433: void MsgSetReapable(msg)
434: Msg msg;
435: {
436: msg->reapable = TRUE;
437: EnableProperButtons(msg->scrn);
438: }
439:
440:
441:
442: /* Clear the reapable flag for this msg. */
443:
444: void MsgClearReapable(msg)
445: Msg msg;
446: {
447: msg->reapable = FALSE;
448: EnableProperButtons(msg->scrn);
449: }
450:
451:
452: /* Get the reapable value for this msg. Returns TRUE iff the reapable flag
453: is set AND no changes have been made. */
454:
455: int MsgGetReapable(msg)
456: Msg msg;
457: {
458: return msg == NULL || (msg->reapable &&
459: (msg->source == NULL ||
460: !XtEDiskChanged(msg->source)));
461: }
462:
463:
464: /* Make it possible to edit the given msg. */
465: void MsgSetEditable(msg)
466: Msg msg;
467: {
468: if (msg && msg->source) {
469: XtEDiskChangeEditMode(msg->source, XttextEdit);
470: if (defGrabFocus && msg->scrn)
471: QXSetInputFocus(theDisplay, msg->scrn->viewwindow,
472: RevertToParent, CurrentTime);
473: EnableProperButtons(msg->scrn);
474: }
475: }
476:
477:
478:
479: /* Turn off editing for the given msg. */
480:
481: void MsgClearEditable(msg)
482: Msg msg;
483: {
484: if (msg && msg->source) {
485: XtEDiskChangeEditMode(msg->source, XttextRead);
486: EnableProperButtons(msg->scrn);
487: }
488: }
489:
490:
491:
492: /* Get whether the msg is editable. */
493:
494: int MsgGetEditable(msg)
495: Msg msg;
496: {
497: return msg && msg->source &&
498: (*msg->source->editType)(msg->source) == XttextEdit;
499: }
500:
501:
502: /* Get whether the msg has changed since last saved. */
503:
504: int MsgChanged(msg)
505: Msg msg;
506: {
507: return msg && msg->source && XtEDiskChanged(msg->source);
508: }
509:
510:
511:
512: /* Call the given function when the msg changes. */
513:
514: void MsgSetCallOnChange(msg, func, param)
515: Msg msg;
516: void (*func)();
517: caddr_t param;
518: {
519: XtEDiskSetCallbackWhenChanged(msg->source, func, param);
520: }
521:
522:
523:
524: /* Call no function when the msg changes. */
525:
526: void MsgClearCallOnChange(msg)
527: Msg msg;
528: {
529: XtEDiskSetCallbackWhenChanged(msg->source, NULL, (caddr_t) NULL);
530: }
531:
532:
533: /* Send (i.e., mail) the given message as is. First break it up into lines,
534: and copy it to a new file in the process. The new file is one of 10
535: possible draft files; we rotate amoung the 10 so that the user can have up
536: to 10 messages being sent at once. (Using a file in /tmp is a bad idea
537: because these files never actually get deleted, but renamed with some
538: prefix. Also, these should stay in an area private to the user for
539: security.) */
540:
541: void MsgSend(msg)
542: Msg msg;
543: {
544: FILEPTR from;
545: FILEPTR to;
546: int p, c, l, inheader, sendwidth, sendbreakwidth;
547: char *ptr, *ptr2, **argv, str[100];
548: static sendcount = -1;
549: MsgSaveChanges(msg);
550: from = FOpenAndCheck(MsgFileName(msg), "r");
551: sendcount = (sendcount + 1) % 10;
552: (void) sprintf(str, "%s%d", xmhDraftFile, sendcount);
553: to = FOpenAndCheck(str, "w");
554: sendwidth = defSendLineWidth;
555: sendbreakwidth = defBreakSendLineWidth;
556: inheader = TRUE;
557: while (ptr = ReadLine(from)) {
558: if (inheader) {
559: if (strncmpIgnoringCase(ptr, "sendwidth:", 10) == 0) {
560: if (atoi(ptr+10) > 0) sendwidth = atoi(ptr+10);
561: continue;
562: }
563: if (strncmpIgnoringCase(ptr, "sendbreakwidth:", 15) == 0) {
564: if (atoi(ptr+15) > 0) sendbreakwidth = atoi(ptr+15);
565: continue;
566: }
567: for (l = 0, ptr2 = ptr ; *ptr2 && !l ; ptr2++)
568: l = (*ptr2 != ' ' && *ptr2 != '\t' && *ptr != '-');
569: if (l) {
570: (void) fprintf(to, "%s\n", ptr);
571: continue;
572: }
573: inheader = FALSE;
574: if (sendbreakwidth < sendwidth) sendbreakwidth = sendwidth;
575: }
576: do {
577: for (p = c = l = 0, ptr2 = ptr;
578: *ptr2 && c < sendbreakwidth;
579: p++, ptr2++) {
580: if (*ptr2 == ' ' && c < sendwidth)
581: l = p;
582: if (*ptr2 == '\t') {
583: if (c < sendwidth) l = p;
584: c += 8 - (c % 8);
585: }
586: else
587: c++;
588: }
589: if (c < sendbreakwidth) {
590: (void) fprintf(to, "%s\n", ptr);
591: *ptr = 0;
592: }
593: else
594: if (l) {
595: ptr[l] = 0;
596: (void) fprintf(to, "%s\n", ptr);
597: ptr += l + 1;
598: }
599: else {
600: for (c = 0; c < sendwidth; ) {
601: if (*ptr == '\t') c += 8 - (c % 8);
602: else c++;
603: (void) fputc(*ptr++, to);
604: }
605: (void) fputc('\n', to);
606: }
607: } while (*ptr);
608: }
609: (void) myfclose(from);
610: (void) myfclose(to);
611: argv = MakeArgv(3);
612: argv[0] = "send";
613: argv[1] = "-push";
614: argv[2] = str;
615: DoCommand(argv, (char *) NULL, (char *) NULL);
616: XtFree((char *) argv);
617: }
618:
619:
620: /* Make the msg into the form for a generic composition. Set msg->startPos
621: so that the text insertion point will be placed at the end of the first
622: line (which is usually the "To:" field). */
623:
624: void MsgLoadComposition(msg)
625: Msg msg;
626: {
627: static char *blankcomp = NULL; /* Array containing comp template */
628: static int compsize = 0;
629: static XtTextPosition startPos;
630: char *file, **argv;
631: int fid;
632: if (blankcomp == NULL) {
633: file = MakeNewTempFileName();
634: argv = MakeArgv(4);
635: argv[0] = "comp";
636: argv[1] = "-file";
637: argv[2] = file;
638: argv[3] = "-nowhatnowproc";
639: DoCommand(argv, (char *) NULL, "/dev/null");
640: XtFree((char *) argv);
641: compsize = GetFileLength(file);
642: blankcomp = XtMalloc((unsigned) compsize);
643: fid = myopen(file, O_RDONLY, 0666);
644: if (compsize != read(fid, blankcomp, compsize))
645: Punt("Error reading in MsgLoadComposition!");
646: (void) myclose(fid);
647: DeleteFileAndCheck(file);
648: startPos = index(blankcomp, '\n') - blankcomp;
649: }
650: fid = myopen(MsgFileName(msg), O_WRONLY | O_TRUNC | O_CREAT, 0666);
651: if (compsize != write(fid, blankcomp, compsize))
652: Punt("Error writing in MsgLoadComposition!");
653: (void) myclose(fid);
654: TocSetCacheValid(msg->toc);
655: msg->startPos = startPos;
656: }
657:
658:
659:
660: /* Load a msg with a template of a reply to frommsg. Set msg->startPos so
661: that the text insertion point will be placed at the beginning of the
662: message body. */
663:
664: void MsgLoadReply(msg, frommsg)
665: Msg msg, frommsg;
666: {
667: char **argv;
668: char str1[100], str2[10];
669: TempMoveDraft();
670: argv = MakeArgv(4);
671: argv[0] = "repl";
672: (void) sprintf(str1, "+%s", frommsg->toc->foldername);
673: argv[1] = str1;
674: (void) sprintf(str2, "%d", frommsg->msgid);
675: argv[2] = str2;
676: argv[3] = "-nowhatnowproc";
677: DoCommand(argv, (char *) NULL, "/dev/null");
678: RenameAndCheck(draftFile, MsgFileName(msg));
679: RestoreDraft();
680: TocSetCacheValid(frommsg->toc); /* If -anno is set, this keeps us from
681: rescanning folder. */
682: TocSetCacheValid(msg->toc);
683: msg->startPos = GetFileLength(MsgFileName(msg));
684: }
685:
686:
687:
688: /* Load a msg with a template of forwarding a list of messages. Set
689: msg->startPos so that the text insertion point will be placed at the end
690: of the first line (which is usually a "To:" field). */
691:
692: void MsgLoadForward(msg, mlist)
693: Msg msg;
694: MsgList mlist;
695: {
696: char **argv, str[100];
697: int i;
698: TempMoveDraft();
699: argv = MakeArgv(3 + mlist->nummsgs);
700: argv[0] = "forw";
701: (void) sprintf(str, "+%s", mlist->msglist[0]->toc->foldername);
702: argv[1] = MallocACopy(str);
703: for (i = 0; i < mlist->nummsgs; i++) {
704: (void) sprintf(str, "%d", mlist->msglist[i]->msgid);
705: argv[2 + i] = MallocACopy(str);
706: }
707: argv[2 + i] = "-nowhatnowproc";
708: DoCommand(argv, (char *) NULL, "/dev/null");
709: for (i = 1; i < 2 + mlist->nummsgs; i++)
710: XtFree((char *) argv[i]);
711: XtFree((char *) argv);
712: RenameAndCheck(draftFile, MsgFileName(msg));
713: RestoreDraft();
714: TocSetCacheValid(msg->toc);
715: msg->source = XtCreateEDiskSource(MsgFileName(msg), XttextEdit);
716: msg->startPos = (*msg->source->scan)(msg->source, 0, XtstEOL, XtsdRight,
717: 1, FALSE);
718: }
719:
720:
721: /* Load msg with a copy of frommsg. */
722:
723: void MsgLoadCopy(msg, frommsg)
724: Msg msg, frommsg;
725: {
726: char str[500];
727: (void)strcpy(str, MsgFileName(msg));
728: CopyFileAndCheck(MsgFileName(frommsg), str);
729: TocSetCacheValid(msg->toc);
730: }
731:
732:
733:
734: /* Checkpoint the given message. */
735:
736: void MsgCheckPoint(msg)
737: Msg msg;
738: {
739: if (msg && msg->source) {
740: XtEDiskMakeCheckpoint(msg->source);
741: TocSetCacheValid(msg->toc);
742: }
743: }
744:
745:
746: /* Free the storage being used by the given msg. */
747:
748: void MsgFree(msg)
749: Msg msg;
750: {
751: XtFree(msg->buf);
752: XtFree((char *)msg);
753: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.