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