1/* $XConsortium: toc.c,v 2.59 95/01/09 16:52:53 swick Exp $
2 * $XFree86: xc/programs/xmh/toc.c,v 3.4 2001/10/28 03:34:39 tsi Exp $
3 *
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
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
28/* toc.c -- handle things in the toc widget. */
29
30#include "xmh.h"
31#include "tocintrnl.h"
32#include "toc.h"
33#include "tocutil.h"
34#include "actions.h"
35
36#include <sys/stat.h>
37
38#ifndef S_ISDIR
39#define S_ISDIR(mode) ((mode & S_IFMT) == S_IFDIR)
40#endif
41
42static int IsDir(char *name)
43{
44    char str[500];
45    struct stat buf;
46    if (*name == '.')
47	return FALSE;
48    if (snprintf(str, sizeof(str), "%s/%s", app_resources.mail_path, name)
49        >= sizeof(str)) return False;
50    if (stat(str, &buf) /* failed */) return False;
51    return S_ISDIR(buf.st_mode);
52}
53
54
55static void MakeSureFolderExists(
56    char ***namelistptr,
57    int *numfoldersptr,
58    char *name)
59{
60    int i;
61    char str[200];
62    for (i=0 ; i<*numfoldersptr ; i++)
63	if (strcmp((*namelistptr)[i], name) == 0) return;
64    if (snprintf(str, sizeof(str), "%s/%s", app_resources.mail_path, name)
65        >= sizeof(str)) goto punt;
66    (void) mkdir(str, 0700);
67    *numfoldersptr = ScanDir(app_resources.mail_path, namelistptr, IsDir);
68    for (i=0 ; i<*numfoldersptr ; i++)
69	if (strcmp((*namelistptr)[i], name) == 0) return;
70  punt:
71    Punt("Can't create new mail folder!");
72}
73
74
75static void MakeSureSubfolderExists(
76    char ***		namelistptr,
77    int *		numfoldersptr,
78    char *		name)
79{
80    char folder[300];
81    char subfolder_path[300];
82    char *subfolder;
83    struct stat buf;
84
85    /* Make sure that the parent folder exists */
86
87    subfolder = strchr( strcpy(folder, name), '/');
88    *subfolder = '\0';
89    subfolder++;
90    MakeSureFolderExists(namelistptr, numfoldersptr, folder);
91
92    /* The parent folder exists.  Make sure the subfolder exists. */
93
94    if (snprintf(subfolder_path, sizeof(subfolder_path), "%s/%s",
95                 app_resources.mail_path, name) >= sizeof(subfolder_path))
96        goto punt;
97    if (stat(subfolder_path, &buf) /* failed */) {
98	(void) mkdir(subfolder_path, 0700);
99	if (stat(subfolder_path, &buf) /* failed */)
100	    Punt("Can't create new xmh subfolder!");
101    }
102    if (!S_ISDIR(buf.st_mode))
103      punt:
104	Punt("Can't create new xmh subfolder!");
105}
106
107int TocFolderExists(Toc toc)
108{
109    struct stat buf;
110    if (! toc->path) {
111	XtAsprintf(&toc->path, "%s/%s",
112                   app_resources.mail_path, toc->foldername);
113    }
114    return ((stat(toc->path, &buf) == 0) &&
115	    (S_ISDIR(buf.st_mode)));
116}
117
118static void LoadCheckFiles(void)
119{
120    FILE *fid;
121    char str[1024];
122
123    snprintf(str, sizeof(str), "%s/.xmhcheck", homeDir);
124    fid = myfopen(str, "r");
125    if (fid) {
126	int i;
127	char *ptr, *ptr2;
128
129	while ((ptr = ReadLine(fid))) {
130	    while (*ptr == ' ' || *ptr == '\t') ptr++;
131	    ptr2 = ptr;
132	    while (*ptr2 && *ptr2 != ' ' && *ptr2 != '\t') ptr2++;
133	    if (*ptr2 == 0) continue;
134	    *ptr2++ = 0;
135	    while (*ptr2 == ' ' || *ptr2 == '\t') ptr2++;
136	    if (*ptr2 == 0) continue;
137	    for (i=0 ; i<numFolders ; i++) {
138		if (strcmp(ptr, folderList[i]->foldername) == 0) {
139		    folderList[i]->incfile = XtNewString(ptr2);
140		    break;
141		}
142	    }
143	}
144	myfclose(fid);
145    } else if ( app_resources.initial_inc_file &&
146	       *app_resources.initial_inc_file)
147	InitialFolder->incfile = app_resources.initial_inc_file;
148}
149
150
151/*	PUBLIC ROUTINES 	*/
152
153
154/* Read in the list of folders. */
155
156void TocInit(void)
157{
158    Toc toc;
159    char **namelist;
160    int i;
161    numFolders = ScanDir(app_resources.mail_path, &namelist, IsDir);
162    if (numFolders < 0) {
163	(void) mkdir(app_resources.mail_path, 0700);
164	numFolders = ScanDir(app_resources.mail_path, &namelist, IsDir);
165	if (numFolders < 0)
166	    Punt("Can't create or read mail directory!");
167    }
168    if (IsSubfolder(app_resources.initial_folder_name))
169	MakeSureSubfolderExists(&namelist, &numFolders,
170				app_resources.initial_folder_name);
171    else
172	MakeSureFolderExists(&namelist, &numFolders,
173			     app_resources.initial_folder_name);
174
175    if (IsSubfolder(app_resources.drafts_folder_name))
176	MakeSureSubfolderExists(&namelist, &numFolders,
177				app_resources.drafts_folder_name);
178    else
179	MakeSureFolderExists(&namelist, &numFolders,
180			     app_resources.drafts_folder_name);
181    folderList = XtMallocArray((Cardinal)numFolders, sizeof(Toc));
182    for (i=0 ; i<numFolders ; i++) {
183	toc = folderList[i] = TUMalloc();
184	toc->foldername = XtNewString(namelist[i]);
185	free((char *)namelist[i]);
186    }
187    if (! (InitialFolder = TocGetNamed(app_resources.initial_folder_name)))
188	InitialFolder = TocCreate(app_resources.initial_folder_name);
189
190    if (! (DraftsFolder = TocGetNamed(app_resources.drafts_folder_name)))
191	DraftsFolder = TocCreate(app_resources.drafts_folder_name);
192    free((char *)namelist);
193    LoadCheckFiles();
194}
195
196
197
198/* Create a toc and add a folder to the folderList.  */
199
200Toc TocCreate(const char *foldername)
201{
202    Toc		toc = TUMalloc();
203
204    toc->foldername = XtNewString(foldername);
205    folderList = XtReallocArray(folderList, ++numFolders, sizeof(Toc));
206    folderList[numFolders - 1] = toc;
207    return toc;
208}
209
210
211/* Create a new folder with the given name. */
212
213Toc TocCreateFolder(const char *foldername)
214{
215    Toc toc;
216    char str[500];
217    if (TocGetNamed(foldername)) return NULL;
218    if (snprintf(str, sizeof(str), "%s/%s", app_resources.mail_path, foldername)
219        >= sizeof(str)) return NULL;
220    if (mkdir(str, 0700) < 0) return NULL;
221    toc = TocCreate(foldername);
222    return toc;
223}
224
225int TocHasMail(Toc toc)
226{
227    return toc->mailpending;
228}
229
230static int CheckForNewMail(Toc toc)
231{
232    if (toc->incfile)
233	return (GetFileLength(toc->incfile) > 0);
234    else if (toc == InitialFolder) {
235	char **argv;
236	char *result;
237	int hasmail;
238
239	argv = MakeArgv(4);
240	argv[0] = "msgchk";
241	argv[1] = "-nonotify";
242	argv[2] = "nomail";
243	argv[3] = "-nodate";
244	result = DoCommandToString(argv);
245	hasmail = (*result != '\0');
246	XtFree(result);
247	XtFree((char*)argv);
248	return hasmail;
249    }
250    return False;
251}
252
253/*ARGSUSED*/
254void TocCheckForNewMail(
255    Boolean update)	/* if True, actually make the check */
256{
257    Toc toc;
258    Scrn scrn;
259    int i, j, hasmail;
260    Boolean mail_waiting = False;
261
262    if (update) {
263	for (i=0 ; i<numFolders ; i++) {
264	    toc = folderList[i];
265	    if (TocCanIncorporate(toc)) {
266		toc->mailpending = hasmail = CheckForNewMail(toc);
267		if (hasmail) mail_waiting = True;
268		for (j=0 ; j<numScrns ; j++) {
269		    scrn = scrnList[j];
270		    if (scrn->kind == STtocAndView)
271			/* give visual indication of new mail waiting */
272			BBoxMailFlag(scrn->folderbuttons, TocName(toc),
273				     hasmail);
274		}
275	    }
276	}
277    } else {
278	for (i=0; i < numFolders; i++) {
279	    toc = folderList[i];
280	    if (toc->mailpending) {
281		mail_waiting = True;
282		break;
283	    }
284	}
285    }
286
287    if (app_resources.mail_waiting_flag) {
288	Arg args[1];
289	static Boolean icon_state = -1;
290
291	if (icon_state != mail_waiting) {
292	    icon_state = mail_waiting;
293	    for (i=0; i < numScrns; i++) {
294		scrn = scrnList[i];
295		if (scrn->kind == STtocAndView) {
296		    XtSetArg(args[0], XtNiconPixmap,
297			     (mail_waiting ? app_resources.new_mail_icon
298			                   : app_resources.no_mail_icon));
299		    XtSetValues(scrn->parent, args, (Cardinal)1);
300		}
301	    }
302	}
303    }
304}
305
306/* Intended to support mutual exclusion on deleting folders, so that you
307 * cannot have two confirm popups at the same time on the same folder.
308 *
309 * You can have confirm popups on different folders simultaneously.
310 * However, I did not protect the user from popping up a delete confirm
311 * popup on folder A, then popping up a delete confirm popup on folder
312 * A/subA, then deleting A, then deleting A/subA -- which of course is
313 * already gone, and will cause xmh to Punt.
314 *
315 * TocClearDeletePending is a callback from the No confirmation button
316 * of the confirm popup.
317 */
318
319Boolean TocTestAndSetDeletePending(Toc toc)
320{
321    Boolean flag;
322
323    flag = toc->delete_pending;
324    toc->delete_pending = True;
325    return flag;
326}
327
328void TocClearDeletePending(Toc toc)
329{
330    toc->delete_pending = False;
331}
332
333
334/* Recursively delete an entire directory.  Nasty. */
335
336static void NukeDirectory(char *path)
337{
338    struct stat buf;
339
340#ifdef S_IFLNK
341    /* POSIX.1 does not discuss symbolic links. */
342    if (lstat(path, &buf) /* failed */)
343	return;
344    if ((buf.st_mode & S_IFMT) == S_IFLNK) {
345	(void) unlink(path);
346	return;
347    }
348#endif
349    if (stat(path, &buf) /* failed */)
350	return;
351    if (buf.st_mode & S_IWRITE) {
352	char **argv = MakeArgv(3);
353	argv[0] = "/bin/rm";
354	argv[1] = "-rf";
355	argv[2] = path;
356	(void) DoCommand(argv, (char*)NULL, (char*)NULL);
357	XtFree((char*)argv);
358    }
359}
360
361
362/* Destroy the given folder. */
363
364void TocDeleteFolder(Toc toc)
365{
366    Toc toc2;
367    int i, j, w;
368    if (toc == NULL) return;
369    TUGetFullFolderInfo(toc);
370
371    w = -1;
372    for (i=0 ; i<numFolders ; i++) {
373	toc2 = folderList[i];
374	if (toc2 == toc)
375	    w = i;
376	else if (toc2->validity == valid)
377	    for (j=0 ; j<toc2->nummsgs ; j++)
378		if (toc2->msgs[j]->desttoc == toc)
379		    MsgSetFate(toc2->msgs[j], Fignore, (Toc) NULL);
380    }
381    if (w < 0) Punt("Couldn't find it in TocDeleteFolder!");
382    NukeDirectory(toc->path);
383    if (toc->validity == valid) {
384	for (i=0 ; i<toc->nummsgs ; i++) {
385	    MsgSetScrnForce(toc->msgs[i], (Scrn) NULL);
386	    MsgFree(toc->msgs[i]);
387	}
388	XtFree((char *) toc->msgs);
389    }
390    XtFree((char *)toc);
391    numFolders--;
392    for (i=w ; i<numFolders ; i++) folderList[i] = folderList[i+1];
393}
394
395
396/*
397 * Display the given toc in the given scrn.  If scrn is NULL, then remove the
398 * toc from all scrns displaying it.
399 */
400
401void TocSetScrn(Toc toc, Scrn scrn)
402{
403    Cardinal i;
404
405    if (toc == NULL && scrn == NULL) return;
406    if (scrn == NULL) {
407	for (i=0 ; i<toc->num_scrns ; i++)
408	    TocSetScrn((Toc) NULL, toc->scrn[i]);
409	return;
410    }
411    if (scrn->toc == toc) return;
412    if (scrn->toc != NULL) {
413	for (i=0 ; i<scrn->toc->num_scrns ; i++)
414	    if (scrn->toc->scrn[i] == scrn) break;
415	if (i >= scrn->toc->num_scrns)
416	    Punt("Couldn't find scrn in TocSetScrn!");
417	scrn->toc->scrn[i] = scrn->toc->scrn[--scrn->toc->num_scrns];
418    }
419    scrn->toc = toc;
420    if (toc == NULL) {
421	TUResetTocLabel(scrn);
422	TURedisplayToc(scrn);
423	StoreWindowName(scrn, progName);
424    } else {
425	toc->num_scrns++;
426	toc->scrn = XtReallocArray(toc->scrn, toc->num_scrns, sizeof(Scrn));
427	toc->scrn[toc->num_scrns - 1] = scrn;
428	TUEnsureScanIsValidAndOpen(toc, True);
429	TUResetTocLabel(scrn);
430	if (app_resources.prefix_wm_and_icon_name) {
431	    char wm_name[64];
432	    snprintf(wm_name, sizeof(wm_name), "%s: %s",
433		     progName, toc->foldername);
434	    StoreWindowName(scrn, wm_name);
435	}
436	else
437	    StoreWindowName(scrn, toc->foldername);
438	TURedisplayToc(scrn);
439	SetCurrentFolderName(scrn, toc->foldername);
440    }
441    EnableProperButtons(scrn);
442}
443
444
445
446/* Remove the given message from the toc.  Doesn't actually touch the file.
447   Also note that it does not free the storage for the msg. */
448
449void TocRemoveMsg(Toc toc, Msg msg)
450{
451    Msg newcurmsg;
452    MsgList mlist;
453    int i;
454    if (toc->validity == unknown)
455	TUGetFullFolderInfo(toc);
456    if (toc->validity != valid)
457	return;
458    newcurmsg = TocMsgAfter(toc, msg);
459    if (newcurmsg) newcurmsg->changed = TRUE;
460    newcurmsg = toc->curmsg;
461    if (msg == toc->curmsg) {
462	newcurmsg = TocMsgAfter(toc, msg);
463	if (newcurmsg == NULL) newcurmsg = TocMsgBefore(toc, msg);
464	toc->curmsg = NULL;
465    }
466    toc->length -= msg->length;
467    if (msg->visible) toc->lastPos -= msg->length;
468    for(i = TUGetMsgPosition(toc, msg), toc->nummsgs--; i<toc->nummsgs ; i++) {
469	toc->msgs[i] = toc->msgs[i+1];
470	if (msg->visible) toc->msgs[i]->position -= msg->length;
471    }
472    for (i=0 ; i<toc->numsequences ; i++) {
473	mlist = toc->seqlist[i]->mlist;
474	if (mlist) DeleteMsgFromMsgList(mlist, msg);
475    }
476
477    if (msg->visible && toc->num_scrns > 0 && !toc->needsrepaint)
478	TSourceInvalid(toc, msg->position, -msg->length);
479    TocSetCurMsg(toc, newcurmsg);
480    TUSaveTocFile(toc);
481}
482
483
484
485void TocRecheckValidity(Toc toc)
486{
487    Cardinal i;
488
489    if (toc && toc->validity == valid && TUScanFileOutOfDate(toc)) {
490	if (app_resources.block_events_on_busy) ShowBusyCursor();
491
492	TUScanFileForToc(toc);
493	if (toc->source)
494	    TULoadTocFile(toc);
495	for (i=0 ; i<toc->num_scrns ; i++)
496	    TURedisplayToc(toc->scrn[i]);
497
498	if (app_resources.block_events_on_busy) UnshowBusyCursor();
499    }
500}
501
502
503/* Set the current message. */
504
505void TocSetCurMsg(Toc toc, Msg msg)
506{
507    Msg msg2;
508    Cardinal i;
509
510    if (toc->validity != valid) return;
511    if (msg != toc->curmsg) {
512	msg2 = toc->curmsg;
513	toc->curmsg = msg;
514	if (msg2)
515	    MsgSetFate(msg2, msg2->fate, msg2->desttoc);
516    }
517    if (msg) {
518	MsgSetFate(msg, msg->fate, msg->desttoc);
519	if (toc->num_scrns) {
520	    if (toc->stopupdate)
521		toc->needsrepaint = TRUE;
522	    else {
523		for (i=0 ; i<toc->num_scrns ; i++)
524		    XawTextSetInsertionPoint(toc->scrn[i]->tocwidget,
525						msg->position);
526	    }
527	}
528    }
529}
530
531
532/* Return the current message. */
533
534Msg TocGetCurMsg(Toc toc)
535{
536    return toc->curmsg;
537}
538
539
540
541
542/* Return the message after the given one.  (If none, return NULL.) */
543
544Msg TocMsgAfter(Toc toc, Msg msg)
545{
546    int i;
547    i = TUGetMsgPosition(toc, msg);
548    do {
549	i++;
550	if (i >= toc->nummsgs)
551	    return NULL;
552    } while (!(toc->msgs[i]->visible));
553    return toc->msgs[i];
554}
555
556
557
558/* Return the message before the given one.  (If none, return NULL.) */
559
560Msg TocMsgBefore(Toc toc, Msg msg)
561{
562    int i;
563    i = TUGetMsgPosition(toc, msg);
564    do {
565	i--;
566	if (i < 0)
567	    return NULL;
568    } while (!(toc->msgs[i]->visible));
569    return toc->msgs[i];
570}
571
572
573
574/* The caller KNOWS the toc's information is out of date; rescan it. */
575
576void TocForceRescan(Toc toc)
577{
578    register Cardinal i;
579
580    if (toc->num_scrns) {
581	toc->viewedseq = toc->seqlist[0];
582	for (i=0 ; i<toc->num_scrns ; i++)
583	    TUResetTocLabel(toc->scrn[i]);
584	TUScanFileForToc(toc);
585	TULoadTocFile(toc);
586	for (i=0 ; i<toc->num_scrns ; i++)
587	    TURedisplayToc(toc->scrn[i]);
588    } else {
589	TUGetFullFolderInfo(toc);
590	(void) unlink(toc->scanfile);
591	toc->validity = invalid;
592    }
593}
594
595
596
597/* The caller has just changed a sequence list.  Reread them from mh. */
598
599void TocReloadSeqLists(Toc toc)
600{
601    Cardinal i;
602
603    TocSetCacheValid(toc);
604    TULoadSeqLists(toc);
605    TURefigureWhatsVisible(toc);
606    for (i=0 ; i<toc->num_scrns ; i++) {
607	TUResetTocLabel(toc->scrn[i]);
608	EnableProperButtons(toc->scrn[i]);
609    }
610}
611
612
613/*ARGSUSED*/
614void XmhReloadSeqLists(
615    Widget	w,
616    XEvent	*event,
617    String	*params,
618    Cardinal	*num_params)
619{
620    Scrn scrn = ScrnFromWidget(w);
621    TocReloadSeqLists(scrn->toc);
622    TUCheckSequenceMenu(scrn->toc);
623}
624
625
626
627/* Return TRUE if the toc has an interesting sequence. */
628
629int TocHasSequences(Toc toc)
630{
631    return toc && toc->numsequences > 1;
632}
633
634
635/* Change which sequence is being viewed. */
636
637void TocChangeViewedSeq(Toc toc, Sequence seq)
638{
639    if (seq == NULL) seq = toc->viewedseq;
640    toc->viewedseq = seq;
641    toc->force_reset = True; /* %%% force Text source to be reset */
642    TURefigureWhatsVisible(toc);
643}
644
645
646/* Return the sequence with the given name in the given toc. */
647
648Sequence TocGetSeqNamed(Toc toc, const char *name)
649{
650    register int i;
651    if (name == NULL)
652	return (Sequence) NULL;
653
654    for (i=0 ; i<toc->numsequences ; i++)
655	if (strcmp(toc->seqlist[i]->name, name) == 0)
656	    return toc->seqlist[i];
657    return (Sequence) NULL;
658}
659
660
661/* Return the sequence currently being viewed in the toc. */
662
663Sequence TocViewedSequence(Toc toc)
664{
665    return toc->viewedseq;
666}
667
668
669/* Set the selected sequence in the toc */
670
671void TocSetSelectedSequence(
672    Toc		toc,
673    Sequence	sequence)
674{
675    if (toc)
676	toc->selectseq = sequence;
677}
678
679
680/* Return the sequence currently selected */
681
682Sequence TocSelectedSequence(Toc toc)
683{
684    if (toc) return (toc->selectseq);
685    else return (Sequence) NULL;
686}
687
688
689/* Return the list of messages currently selected. */
690
691#define SrcScan XawTextSourceScan
692
693MsgList TocCurMsgList(Toc toc)
694{
695    MsgList result;
696    XawTextPosition pos1, pos2;
697
698    if (toc->num_scrns == 0) return NULL;
699    result = MakeNullMsgList();
700    XawTextGetSelectionPos( toc->scrn[0]->tocwidget, &pos1, &pos2); /* %%% */
701    if (pos1 < pos2) {
702	pos1 = SrcScan(toc->source, pos1, XawstEOL, XawsdLeft, 1, FALSE);
703	pos2 = SrcScan(toc->source, pos2, XawstPositions, XawsdLeft, 1, TRUE);
704	pos2 = SrcScan(toc->source, pos2, XawstEOL, XawsdRight, 1, FALSE);
705	while (pos1 < pos2) {
706	    AppendMsgList(result, MsgFromPosition(toc, pos1, XawsdRight));
707	    pos1 = SrcScan(toc->source, pos1, XawstEOL, XawsdRight, 1, TRUE);
708	}
709    }
710    return result;
711}
712
713
714
715/* Unset the current selection. */
716
717void TocUnsetSelection(Toc toc)
718{
719    if (toc->source)
720        XawTextUnsetSelection(toc->scrn[0]->tocwidget);
721}
722
723
724
725/* Create a brand new, blank message. */
726
727Msg TocMakeNewMsg(Toc toc)
728{
729    Msg msg;
730    static int looping = False;
731    TUEnsureScanIsValidAndOpen(toc, False);
732    msg = TUAppendToc(toc, "####  empty\n");
733    if (FileExists(MsgFileName(msg))) {
734	if (looping++) Punt( "Cannot correct scan file" );
735        DEBUG2("**** FOLDER %s WAS INVALID; msg %d already existed!\n",
736	       toc->foldername, msg->msgid);
737	TocForceRescan(toc);
738	return TocMakeNewMsg(toc); /* Try again.  Using recursion here is ugly,
739				      but what the hack ... */
740    }
741    CopyFileAndCheck("/dev/null", MsgFileName(msg));
742    looping = False;
743    return msg;
744}
745
746
747/* Set things to not update cache or display until further notice. */
748
749void TocStopUpdate(Toc toc)
750{
751    Cardinal i;
752
753    for (i=0 ; i<toc->num_scrns ; i++)
754	XawTextDisableRedisplay(toc->scrn[i]->tocwidget);
755    toc->stopupdate++;
756}
757
758
759/* Start updating again, and do whatever updating has been queued. */
760
761void TocStartUpdate(Toc toc)
762{
763    Cardinal i;
764
765    if (toc->stopupdate && --(toc->stopupdate) == 0) {
766	for (i=0 ; i<toc->num_scrns ; i++) {
767	    if (toc->needsrepaint)
768		TURedisplayToc(toc->scrn[i]);
769	    if (toc->needslabelupdate)
770		TUResetTocLabel(toc->scrn[i]);
771	}
772	if (toc->needscachesave)
773	    TUSaveTocFile(toc);
774    }
775    for (i=0 ; i<toc->num_scrns ; i++)
776	XawTextEnableRedisplay(toc->scrn[i]->tocwidget);
777}
778
779
780
781/* Something has happened that could later convince us that our cache is out
782   of date.  Make this not happen; our cache really *is* up-to-date. */
783
784void TocSetCacheValid(Toc toc)
785{
786    TUSaveTocFile(toc);
787}
788
789
790/* Return the full folder pathname of the given toc, prefixed w/'+' */
791
792char *TocMakeFolderName(Toc toc)
793{
794    char* name;
795    XtAsprintf(&name, "+%s", toc->path);
796    return name;
797}
798
799char *TocName(Toc toc)
800{
801    return toc->foldername;
802}
803
804
805
806/* Given a foldername, return the corresponding toc. */
807
808Toc TocGetNamed(const char *name)
809{
810    int i;
811    for (i=0; i<numFolders ; i++)
812	if (strcmp(folderList[i]->foldername, name) == 0) return folderList[i];
813    return NULL;
814}
815
816
817Boolean TocHasChanges(Toc toc)
818{
819    int i;
820    for (i=0 ; i<toc->nummsgs ; i++)
821	if (toc->msgs[i]->fate != Fignore) return True;
822
823    return False;
824}
825
826
827
828/* Throw out all changes to this toc, and close all views of msgs in it.
829   Requires confirmation by the user. */
830
831/*ARGSUSED*/
832static void TocCataclysmOkay(
833    Widget	widget,		/* unused */
834    XtPointer	client_data,
835    XtPointer	call_data)	/* unused */
836{
837    Toc			toc = (Toc) client_data;
838    register int	i;
839
840    for (i=0; i < toc->nummsgs; i++)
841	MsgSetFate(toc->msgs[i], Fignore, (Toc)NULL);
842
843/* Doesn't make sense to have this MsgSetScrn for loop here. dmc. %%% */
844    for (i=0; i < toc->nummsgs; i++)
845	MsgSetScrn(toc->msgs[i], (Scrn) NULL, (XtCallbackList) NULL,
846		   (XtCallbackList) NULL);
847}
848
849int TocConfirmCataclysm(
850    Toc			toc,
851    XtCallbackList	confirms,
852    XtCallbackList	cancels)
853{
854    register int	i;
855
856    static XtCallbackRec yes_callbacks[] = {
857	{TocCataclysmOkay,	(XtPointer) NULL},
858	{(XtCallbackProc) NULL,	(XtPointer) NULL},
859	{(XtCallbackProc) NULL,	(XtPointer) NULL}
860    };
861
862    if (! toc)
863	return 0;
864
865    if (TocHasChanges(toc)) {
866	char		str[300];
867	Widget		tocwidget;
868
869	snprintf(str, sizeof(str),
870                 "Are you sure you want to remove all changes to %s?",
871                 toc->foldername);
872	yes_callbacks[0].closure = (XtPointer) toc;
873	yes_callbacks[1].callback = confirms[0].callback;
874	yes_callbacks[1].closure = confirms[0].closure;
875
876	tocwidget = NULL;
877	for (i=0; i < toc->num_scrns; i++)
878	    if (toc->scrn[i]->mapped) {
879		tocwidget = toc->scrn[i]->tocwidget;
880		break;
881	    }
882
883	PopupConfirm(tocwidget, str, yes_callbacks, cancels);
884	return NEEDS_CONFIRMATION;
885    }
886    else {
887/* Doesn't make sense to have this MsgSetFate for loop here. dmc. %%% */
888	for (i=0 ; i<toc->nummsgs ; i++)
889	    MsgSetFate(toc->msgs[i], Fignore, (Toc)NULL);
890
891	for (i=0 ; i<toc->nummsgs ; i++)
892	    if (MsgSetScrn(toc->msgs[i], (Scrn) NULL, confirms, cancels))
893		return NEEDS_CONFIRMATION;
894	return 0;
895    }
896}
897
898
899/* Commit all the changes in this toc; all messages will meet their 'fate'. */
900
901/*ARGSUSED*/
902void TocCommitChanges(
903    Widget	widget,		/* unused */
904    XtPointer	client_data,
905    XtPointer	call_data)	/* unused */
906{
907    Toc toc = (Toc) client_data;
908    Msg msg;
909    int i, cur = 0;
910    char str[100], **argv = NULL;
911    FateType curfate, fate;
912    Toc desttoc;
913    Toc curdesttoc = NULL;
914    XtCallbackRec	confirms[2];
915
916    confirms[0].callback = TocCommitChanges;
917    confirms[0].closure = (XtPointer) toc;
918    confirms[1].callback = (XtCallbackProc) NULL;
919    confirms[1].closure = (XtPointer) NULL;
920
921    if (toc == NULL) return;
922    for (i=0 ; i<toc->nummsgs ; i++) {
923	msg = toc->msgs[i];
924	fate = MsgGetFate(msg, (Toc *)NULL);
925	if (fate != Fignore && fate != Fcopy)
926	    if (MsgSetScrn(msg, (Scrn) NULL, confirms, (XtCallbackList) NULL)
927		== NEEDS_CONFIRMATION)
928	        return;
929    }
930    XFlush(XtDisplay(toc->scrn[0]->parent));
931    for (i=0 ; i<numFolders ; i++)
932	TocStopUpdate(folderList[i]);
933    toc->haschanged = TRUE;
934    if (app_resources.block_events_on_busy) ShowBusyCursor();
935
936    do {
937	curfate = Fignore;
938	i = 0;
939	while (i < toc->nummsgs) {
940	    msg = toc->msgs[i];
941	    fate = MsgGetFate(msg, &desttoc);
942	    if (curfate == Fignore && fate != Fignore) {
943		curfate = fate;
944		argv = MakeArgv(2);
945		switch (curfate) {
946		  case Fdelete:
947		    argv[0] = XtNewString("rmm");
948		    argv[1] = TocMakeFolderName(toc);
949		    cur = 2;
950		    curdesttoc = NULL;
951		    break;
952		  case Fmove:
953		  case Fcopy:
954		    argv[0] = XtNewString("refile");
955		    cur = 1;
956		    curdesttoc = desttoc;
957		    break;
958		  default:
959		    break;
960		}
961	    }
962	    if (curfate != Fignore &&
963		  curfate == fate && desttoc == curdesttoc) {
964		argv = ResizeArgv(argv, cur + 1);
965		snprintf(str, sizeof(str), "%d", MsgGetId(msg));
966		argv[cur++] = XtNewString(str);
967		MsgSetFate(msg, Fignore, (Toc)NULL);
968		if (curdesttoc) {
969		    (void) TUAppendToc(curdesttoc, MsgGetScanLine(msg));
970		    curdesttoc->haschanged = TRUE;
971		}
972		if (curfate != Fcopy) {
973		    TocRemoveMsg(toc, msg);
974		    MsgFree(msg);
975		    i--;
976		}
977		if (cur > 40)
978		    break;	/* Do only 40 at a time, just to be safe. */
979	    }
980	    i++;
981	}
982	if (curfate != Fignore) {
983	    switch (curfate) {
984	      case Fmove:
985	      case Fcopy:
986		argv = ResizeArgv(argv, cur + 4);
987		argv[cur++] = XtNewString(curfate == Fmove ? "-nolink"
988				       			   : "-link");
989		argv[cur++] = XtNewString("-src");
990		argv[cur++] = TocMakeFolderName(toc);
991		argv[cur++] = TocMakeFolderName(curdesttoc);
992		break;
993	      default:
994		break;
995	    }
996	    if (app_resources.debug) {
997		for (i = 0; i < cur; i++)
998		    (void) fprintf(stderr, "%s ", argv[i]);
999		(void) fprintf(stderr, "\n");
1000		(void) fflush(stderr);
1001	    }
1002	    DoCommand(argv, (char *) NULL, (char *) NULL);
1003	    for (i = 0; argv[i]; i++)
1004		XtFree((char *) argv[i]);
1005	    XtFree((char *) argv);
1006	}
1007    } while (curfate != Fignore);
1008    for (i=0 ; i<numFolders ; i++) {
1009	if (folderList[i]->haschanged) {
1010	    TocReloadSeqLists(folderList[i]);
1011	    folderList[i]->haschanged = FALSE;
1012	}
1013	TocStartUpdate(folderList[i]);
1014    }
1015
1016    if (app_resources.block_events_on_busy) UnshowBusyCursor();
1017}
1018
1019
1020
1021/* Return whether the given toc can incorporate mail. */
1022
1023int TocCanIncorporate(Toc toc)
1024{
1025    return (toc && (toc == InitialFolder || toc->incfile));
1026}
1027
1028
1029/* Incorporate new messages into the given toc. */
1030
1031int TocIncorporate(Toc toc)
1032{
1033    char **argv;
1034    char str[100], *file, *ptr;
1035    Msg msg, firstmessage = NULL;
1036    FILEPTR fid;
1037
1038    argv = MakeArgv(toc->incfile ? 7 : 4);
1039    argv[0] = "inc";
1040    argv[1] = TocMakeFolderName(toc);
1041    argv[2] = "-width";
1042    snprintf(str, sizeof(str), "%d", app_resources.toc_width);
1043    argv[3] = str;
1044    if (toc->incfile) {
1045	argv[4] = "-file";
1046	argv[5] = toc->incfile;
1047	argv[6] = "-truncate";
1048    }
1049    if (app_resources.block_events_on_busy) ShowBusyCursor();
1050
1051    file = DoCommandToFile(argv);
1052    XtFree(argv[1]);
1053    XtFree((char *)argv);
1054    TUGetFullFolderInfo(toc);
1055    if (toc->validity == valid) {
1056	fid = FOpenAndCheck(file, "r");
1057	TocStopUpdate(toc);
1058	while ((ptr = ReadLineWithCR(fid))) {
1059	    if (atoi(ptr) > 0) {
1060		msg = TUAppendToc(toc, ptr);
1061		if (firstmessage == NULL) firstmessage = msg;
1062	    }
1063	}
1064	if (firstmessage && firstmessage->visible) {
1065	    TocSetCurMsg(toc, firstmessage);
1066	}
1067	TocStartUpdate(toc);
1068	myfclose(fid);
1069    }
1070    DeleteFileAndCheck(file);
1071
1072    if (app_resources.block_events_on_busy) UnshowBusyCursor();
1073
1074    toc->mailpending = False;
1075    return (firstmessage != NULL);
1076}
1077
1078
1079/* The given message has changed.  Rescan it and change the scanfile. */
1080
1081void TocMsgChanged(Toc toc, Msg msg)
1082{
1083    char **argv, str[100], str2[10], *ptr;
1084    int length, delta;
1085    int i;
1086    FateType fate;
1087    Toc desttoc;
1088
1089    if (toc->validity != valid) return;
1090    fate = MsgGetFate(msg, &desttoc);
1091    MsgSetFate(msg, Fignore, (Toc) NULL);
1092    argv = MakeArgv(6);
1093    argv[0] = "scan";
1094    argv[1] = TocMakeFolderName(toc);
1095    snprintf(str, sizeof(str), "%d", msg->msgid);
1096    argv[2] = str;
1097    argv[3] = "-width";
1098    snprintf(str2, sizeof(str2), "%d", app_resources.toc_width);
1099    argv[4] = str2;
1100    argv[5] = "-noheader";
1101    ptr = DoCommandToString(argv);
1102    XtFree(argv[1]);
1103    XtFree((char *) argv);
1104    if (strcmp(ptr, msg->buf) != 0) {
1105	length = strlen(ptr);
1106	delta = length - msg->length;
1107	XtFree(msg->buf);
1108	msg->buf = ptr;
1109	msg->length = length;
1110	toc->length += delta;
1111	if (msg->visible) {
1112	    if (delta != 0) {
1113		for (i=TUGetMsgPosition(toc, msg)+1; i<toc->nummsgs ; i++)
1114		    toc->msgs[i]->position += delta;
1115		toc->lastPos += delta;
1116	    }
1117	    for (i=0 ; i<toc->num_scrns ; i++)
1118		TURedisplayToc(toc->scrn[i]);
1119	}
1120	MsgSetFate(msg, fate, desttoc);
1121	TUSaveTocFile(toc);
1122    } else XtFree(ptr);
1123}
1124
1125
1126
1127Msg TocMsgFromId(Toc toc, int msgid)
1128{
1129    int h, l, m;
1130    l = 0;
1131    h = toc->nummsgs - 1;
1132    if (h < 0) {
1133	DEBUG1("Toc is empty! folder=%s\n", toc->foldername)
1134	return NULL;
1135    }
1136    while (l < h - 1) {
1137	m = (l + h) / 2;
1138	if (toc->msgs[m]->msgid > msgid)
1139	    h = m;
1140	else
1141	    l = m;
1142    }
1143    if (toc->msgs[l]->msgid == msgid) return toc->msgs[l];
1144    if (toc->msgs[h]->msgid == msgid) return toc->msgs[h];
1145    if (app_resources.debug) {
1146	char str[100];
1147	snprintf(str, sizeof(str),
1148                 "TocMsgFromId search failed! hi=%d, lo=%d, msgid=%d\n",
1149                 h, l, msgid);
1150	DEBUG( str )
1151    }
1152    return NULL;
1153}
1154
1155/* Sequence names are put on a stack which is specific to the folder.
1156 * Sequence names are very volatile, so we make our own copies of the strings.
1157 */
1158
1159/*ARGSUSED*/
1160void XmhPushSequence(
1161    Widget	w,
1162    XEvent	*event,
1163    String	*params,
1164    Cardinal	*count)
1165{
1166    Scrn	scrn = ScrnFromWidget(w);
1167    Toc		toc;
1168    Cardinal	i;
1169
1170    if (! (toc = scrn->toc)) return;
1171
1172    if (*count == 0) {
1173	if (toc->selectseq)
1174	    Push(&toc->sequence_stack, XtNewString(toc->selectseq->name));
1175    }
1176    else
1177	for (i=0; i < *count; i++)
1178	    Push(&toc->sequence_stack, XtNewString(params[i]));
1179}
1180
1181
1182/*ARGSUSED*/
1183void XmhPopSequence(
1184    Widget	w,		/* any widget on the screen of interest */
1185    XEvent	*event,
1186    String	*params,
1187    Cardinal	*count)
1188{
1189    Scrn	scrn = ScrnFromWidget(w);
1190    const char	*seqname;
1191    Widget	sequenceMenu, selected, original;
1192    Button	button;
1193    Sequence	sequence;
1194
1195    if ((seqname = Pop(&scrn->toc->sequence_stack)) != NULL) {
1196
1197	button = BBoxFindButtonNamed(scrn->mainbuttons,
1198				     MenuBoxButtons[XMH_SEQUENCE].button_name);
1199	sequenceMenu = BBoxMenuOfButton(button);
1200
1201	if ((selected = XawSimpleMenuGetActiveEntry(sequenceMenu)))
1202	    ToggleMenuItem(selected, False);
1203
1204	if ((original = XtNameToWidget(sequenceMenu, seqname))) {
1205	    ToggleMenuItem(original, True);
1206	    sequence = TocGetSeqNamed(scrn->toc, seqname);
1207	    TocSetSelectedSequence(scrn->toc, sequence);
1208	}
1209	XtFree((char *)seqname);
1210    }
1211}
1212