/* $XConsortium: toc.c,v 2.59 95/01/09 16:52:53 swick Exp $
 * $XFree86: xc/programs/xmh/toc.c,v 3.4 2001/10/28 03:34:39 tsi Exp $
 *
 *
 *			  COPYRIGHT 1987
 *		   DIGITAL EQUIPMENT CORPORATION
 *		       MAYNARD, MASSACHUSETTS
 *			ALL RIGHTS RESERVED.
 *
 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
 * ADDITION TO THAT SET FORTH ABOVE.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Digital Equipment Corporation not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 */

/* toc.c -- handle things in the toc widget. */

#include "xmh.h"
#include "tocintrnl.h"
#include "toc.h"
#include "tocutil.h"
#include "actions.h"

#include <sys/stat.h>

static int IsDir(char *name)
{
    char str[500];
    struct stat buf;
    if (*name == '.')
	return FALSE;
    (void) sprintf(str, "%s/%s", app_resources.mail_path, name);
    if (stat(str, &buf) /* failed */) return False;
#ifdef S_ISDIR
    return S_ISDIR(buf.st_mode);
#else
    return (buf.st_mode & S_IFMT) == S_IFDIR;
#endif
}


static void MakeSureFolderExists(
    char ***namelistptr,
    int *numfoldersptr,
    char *name)
{
    int i;
    char str[200];
    for (i=0 ; i<*numfoldersptr ; i++)
	if (strcmp((*namelistptr)[i], name) == 0) return;
    (void) sprintf(str, "%s/%s", app_resources.mail_path, name);
    (void) mkdir(str, 0700);
    *numfoldersptr = ScanDir(app_resources.mail_path, namelistptr, IsDir);
    for (i=0 ; i<*numfoldersptr ; i++)
	if (strcmp((*namelistptr)[i], name) == 0) return;
    Punt("Can't create new mail folder!");
}


static void MakeSureSubfolderExists(
    char ***		namelistptr,
    int *		numfoldersptr,
    char *		name)
{
    char folder[300];
    char subfolder_path[300];
    char *subfolder;
    struct stat buf;

    /* Make sure that the parent folder exists */

    subfolder = strchr( strcpy(folder, name), '/');
    *subfolder = '\0';
    subfolder++;
    MakeSureFolderExists(namelistptr, numfoldersptr, folder);
	
    /* The parent folder exists.  Make sure the subfolder exists. */

    (void) sprintf(subfolder_path, "%s/%s", app_resources.mail_path, name);
    if (stat(subfolder_path, &buf) /* failed */) {
	(void) mkdir(subfolder_path, 0700);
	if (stat(subfolder_path, &buf) /* failed */)
	    Punt("Can't create new xmh subfolder!");
    }
#ifdef S_ISDIR
    if (!S_ISDIR(buf.st_mode))
#else
    if ((buf.st_mode & S_IFMT) != S_IFDIR)
#endif
	Punt("Can't create new xmh subfolder!");
}

int TocFolderExists(Toc toc)
{
    struct stat buf;
    if (! toc->path) {
	char str[500];
	(void) sprintf(str, "%s/%s", app_resources.mail_path, toc->foldername);
	toc->path = XtNewString(str);
    }
    return ((stat(toc->path, &buf) == 0) &&
#ifdef S_ISDIR
	    (S_ISDIR(buf.st_mode)));
#else
	    ((buf.st_mode & S_IFMT) == S_IFDIR));
#endif
}

static void LoadCheckFiles(void)
{
    FILE *fid;
    char str[1024];

    (void) sprintf(str, "%s/.xmhcheck", homeDir);
    fid = myfopen(str, "r");
    if (fid) {
	int i;
	char *ptr, *ptr2;

	while ((ptr = ReadLine(fid))) {
	    while (*ptr == ' ' || *ptr == '\t') ptr++;
	    ptr2 = ptr;
	    while (*ptr2 && *ptr2 != ' ' && *ptr2 != '\t') ptr2++;
	    if (*ptr2 == 0) continue;
	    *ptr2++ = 0;
	    while (*ptr2 == ' ' || *ptr2 == '\t') ptr2++;
	    if (*ptr2 == 0) continue;
	    for (i=0 ; i<numFolders ; i++) {
		if (strcmp(ptr, folderList[i]->foldername) == 0) {
		    folderList[i]->incfile = XtNewString(ptr2);
		    break;
		}
	    }
	}
	myfclose(fid);
    } else if ( app_resources.initial_inc_file &&
	       *app_resources.initial_inc_file)
	InitialFolder->incfile = app_resources.initial_inc_file;
}
	    

/*	PUBLIC ROUTINES 	*/


/* Read in the list of folders. */

void TocInit(void)
{
    Toc toc;
    char **namelist;
    int i;
    numFolders = ScanDir(app_resources.mail_path, &namelist, IsDir);
    if (numFolders < 0) {
	(void) mkdir(app_resources.mail_path, 0700);
	numFolders = ScanDir(app_resources.mail_path, &namelist, IsDir);
	if (numFolders < 0)
	    Punt("Can't create or read mail directory!");
    }
    if (IsSubfolder(app_resources.initial_folder_name))
	MakeSureSubfolderExists(&namelist, &numFolders,
				app_resources.initial_folder_name);
    else
	MakeSureFolderExists(&namelist, &numFolders,
			     app_resources.initial_folder_name);

    if (IsSubfolder(app_resources.drafts_folder_name))
	MakeSureSubfolderExists(&namelist, &numFolders,
				app_resources.drafts_folder_name);
    else
	MakeSureFolderExists(&namelist, &numFolders,
			     app_resources.drafts_folder_name);
    folderList = (Toc *) XtMalloc((Cardinal)numFolders * sizeof(Toc));
    for (i=0 ; i<numFolders ; i++) {
	toc = folderList[i] = TUMalloc();
	toc->foldername = XtNewString(namelist[i]);
	free((char *)namelist[i]);
    }
    if (! (InitialFolder = TocGetNamed(app_resources.initial_folder_name)))
	InitialFolder = TocCreate(app_resources.initial_folder_name);

    if (! (DraftsFolder = TocGetNamed(app_resources.drafts_folder_name)))
	DraftsFolder = TocCreate(app_resources.drafts_folder_name);
    free((char *)namelist);
    LoadCheckFiles();
}



/* Create a toc and add a folder to the folderList.  */

Toc TocCreate(char *foldername)
{
    Toc		toc = TUMalloc();

    toc->foldername = XtNewString(foldername);
    folderList = (Toc *) XtRealloc((char *) folderList,
				   (unsigned) ++numFolders * sizeof(Toc));
    folderList[numFolders - 1] = toc;
    return toc;
}


/* Create a new folder with the given name. */

Toc TocCreateFolder(char *foldername)
{
    Toc toc;
    char str[500];
    if (TocGetNamed(foldername)) return NULL;
    (void) sprintf(str, "%s/%s", app_resources.mail_path, foldername);
    if (mkdir(str, 0700) < 0) return NULL;
    toc = TocCreate(foldername);
    return toc;
}

int TocHasMail(Toc toc)
{
    return toc->mailpending;
}

static int CheckForNewMail(Toc toc)
{
    if (toc->incfile)
	return (GetFileLength(toc->incfile) > 0);
    else if (toc == InitialFolder) {
	char **argv;
	char *result;
	int hasmail;

	argv = MakeArgv(4);
	argv[0] = "msgchk";
	argv[1] = "-nonotify";
	argv[2] = "nomail";
	argv[3] = "-nodate";
	result = DoCommandToString(argv);
	hasmail = (*result != '\0');
	XtFree(result);
	XtFree((char*)argv);
	return hasmail;
    }
    return False;
}

/*ARGSUSED*/
void TocCheckForNewMail(
    Boolean update)	/* if True, actually make the check */
{
    Toc toc;
    Scrn scrn;
    int i, j, hasmail;
    Boolean mail_waiting = False;

    if (update) {
	for (i=0 ; i<numFolders ; i++) {
	    toc = folderList[i];
	    if (TocCanIncorporate(toc)) {
		toc->mailpending = hasmail = CheckForNewMail(toc);
		if (hasmail) mail_waiting = True;
		for (j=0 ; j<numScrns ; j++) {
		    scrn = scrnList[j];
		    if (scrn->kind == STtocAndView)
			/* give visual indication of new mail waiting */
			BBoxMailFlag(scrn->folderbuttons, TocName(toc),
				     hasmail);
		}
	    }
	}
    } else {
	for (i=0; i < numFolders; i++) {
	    toc = folderList[i];
	    if (toc->mailpending) {
		mail_waiting = True;
		break;
	    }
	}
    }

    if (app_resources.mail_waiting_flag) {
	Arg args[1];
	static Boolean icon_state = -1;

	if (icon_state != mail_waiting) {
	    icon_state = mail_waiting;
	    for (i=0; i < numScrns; i++) {
		scrn = scrnList[i];
		if (scrn->kind == STtocAndView) {
		    XtSetArg(args[0], XtNiconPixmap,
			     (mail_waiting ? app_resources.new_mail_icon
			                   : app_resources.no_mail_icon));
		    XtSetValues(scrn->parent, args, (Cardinal)1);
		}
	    }
	}
    }
}

/* Intended to support mutual exclusion on deleting folders, so that you
 * cannot have two confirm popups at the same time on the same folder.
 *
 * You can have confirm popups on different folders simultaneously.
 * However, I did not protect the user from popping up a delete confirm
 * popup on folder A, then popping up a delete confirm popup on folder
 * A/subA, then deleting A, then deleting A/subA -- which of course is 
 * already gone, and will cause xmh to Punt.
 *
 * TocClearDeletePending is a callback from the No confirmation button
 * of the confirm popup.
 */

Boolean TocTestAndSetDeletePending(Toc toc)
{
    Boolean flag;

    flag = toc->delete_pending;
    toc->delete_pending = True;
    return flag;
}

void TocClearDeletePending(Toc toc)
{
    toc->delete_pending = False;
}


/* Recursively delete an entire directory.  Nasty. */

static void NukeDirectory(char *path)
{
    struct stat buf;

#ifdef S_IFLNK
    /* POSIX.1 does not discuss symbolic links. */
    if (lstat(path, &buf) /* failed */)
	return;
    if ((buf.st_mode & S_IFMT) == S_IFLNK) {
	(void) unlink(path);
	return;
    }
#endif
    if (stat(path, &buf) /* failed */)
	return;
    if (buf.st_mode & S_IWRITE) {
	char **argv = MakeArgv(3);
	argv[0] = "/bin/rm";
	argv[1] = "-rf";
	argv[2] = path;
	(void) DoCommand(argv, (char*)NULL, (char*)NULL);
	XtFree((char*)argv);
    } 
}


/* Destroy the given folder. */

void TocDeleteFolder(Toc toc)
{
    Toc toc2;
    int i, j, w;
    if (toc == NULL) return;
    TUGetFullFolderInfo(toc);

    w = -1;
    for (i=0 ; i<numFolders ; i++) {
	toc2 = folderList[i];
	if (toc2 == toc)
	    w = i;
	else if (toc2->validity == valid)
	    for (j=0 ; j<toc2->nummsgs ; j++)
		if (toc2->msgs[j]->desttoc == toc)
		    MsgSetFate(toc2->msgs[j], Fignore, (Toc) NULL);
    }
    if (w < 0) Punt("Couldn't find it in TocDeleteFolder!");
    NukeDirectory(toc->path);
    if (toc->validity == valid) {
	for (i=0 ; i<toc->nummsgs ; i++) {
	    MsgSetScrnForce(toc->msgs[i], (Scrn) NULL);
	    MsgFree(toc->msgs[i]);
	}
	XtFree((char *) toc->msgs);
    }
    XtFree((char *)toc);
    numFolders--;
    for (i=w ; i<numFolders ; i++) folderList[i] = folderList[i+1];
}


/*
 * Display the given toc in the given scrn.  If scrn is NULL, then remove the
 * toc from all scrns displaying it.
 */

void TocSetScrn(Toc toc, Scrn scrn)
{
    Cardinal i;

    if (toc == NULL && scrn == NULL) return;
    if (scrn == NULL) {
	for (i=0 ; i<toc->num_scrns ; i++)
	    TocSetScrn((Toc) NULL, toc->scrn[i]);
	return;
    }
    if (scrn->toc == toc) return;
    if (scrn->toc != NULL) {
	for (i=0 ; i<scrn->toc->num_scrns ; i++)
	    if (scrn->toc->scrn[i] == scrn) break;
	if (i >= scrn->toc->num_scrns)
	    Punt("Couldn't find scrn in TocSetScrn!");
	scrn->toc->scrn[i] = scrn->toc->scrn[--scrn->toc->num_scrns];
    }
    scrn->toc = toc;
    if (toc == NULL) {
	TUResetTocLabel(scrn);
	TURedisplayToc(scrn);
	StoreWindowName(scrn, progName);
    } else {
	toc->num_scrns++;
	toc->scrn = (Scrn *) XtRealloc((char *) toc->scrn,
				       (unsigned)toc->num_scrns*sizeof(Scrn));
	toc->scrn[toc->num_scrns - 1] = scrn;
	TUEnsureScanIsValidAndOpen(toc, True);
	TUResetTocLabel(scrn);
	if (app_resources.prefix_wm_and_icon_name) {
	    char wm_name[64];
	    int length = strlen(progName);
	    (void) strncpy(wm_name, progName, length);
	    (void) strncpy(wm_name + length , ": ", 2);
	    (void) strcpy(wm_name + length + 2, toc->foldername);
	    StoreWindowName(scrn, wm_name);
	}
	else
	    StoreWindowName(scrn, toc->foldername);
	TURedisplayToc(scrn);
	SetCurrentFolderName(scrn, toc->foldername);
    }
    EnableProperButtons(scrn);
}



/* Remove the given message from the toc.  Doesn't actually touch the file.
   Also note that it does not free the storage for the msg. */

void TocRemoveMsg(Toc toc, Msg msg)
{
    Msg newcurmsg;
    MsgList mlist;
    int i;
    if (toc->validity == unknown)
	TUGetFullFolderInfo(toc);
    if (toc->validity != valid)
	return;
    newcurmsg = TocMsgAfter(toc, msg);
    if (newcurmsg) newcurmsg->changed = TRUE;
    newcurmsg = toc->curmsg;
    if (msg == toc->curmsg) {
	newcurmsg = TocMsgAfter(toc, msg);
	if (newcurmsg == NULL) newcurmsg = TocMsgBefore(toc, msg);
	toc->curmsg = NULL;
    }
    toc->length -= msg->length;
    if (msg->visible) toc->lastPos -= msg->length;
    for(i = TUGetMsgPosition(toc, msg), toc->nummsgs--; i<toc->nummsgs ; i++) {
	toc->msgs[i] = toc->msgs[i+1];
	if (msg->visible) toc->msgs[i]->position -= msg->length;
    }
    for (i=0 ; i<toc->numsequences ; i++) {
	mlist = toc->seqlist[i]->mlist;
	if (mlist) DeleteMsgFromMsgList(mlist, msg);
    }

    if (msg->visible && toc->num_scrns > 0 && !toc->needsrepaint)
	TSourceInvalid(toc, msg->position, -msg->length);
    TocSetCurMsg(toc, newcurmsg);
    TUSaveTocFile(toc);
}
    


void TocRecheckValidity(Toc toc)
{
    Cardinal i;

    if (toc && toc->validity == valid && TUScanFileOutOfDate(toc)) {
	if (app_resources.block_events_on_busy) ShowBusyCursor();

	TUScanFileForToc(toc);
	if (toc->source)
	    TULoadTocFile(toc);
	for (i=0 ; i<toc->num_scrns ; i++)
	    TURedisplayToc(toc->scrn[i]);

	if (app_resources.block_events_on_busy) UnshowBusyCursor();
    }
}


/* Set the current message. */

void TocSetCurMsg(Toc toc, Msg msg)
{
    Msg msg2;
    Cardinal i;

    if (toc->validity != valid) return;
    if (msg != toc->curmsg) {
	msg2 = toc->curmsg;
	toc->curmsg = msg;
	if (msg2)
	    MsgSetFate(msg2, msg2->fate, msg2->desttoc);
    }
    if (msg) {
	MsgSetFate(msg, msg->fate, msg->desttoc);
	if (toc->num_scrns) {
	    if (toc->stopupdate)
		toc->needsrepaint = TRUE;
	    else {
		for (i=0 ; i<toc->num_scrns ; i++)
		    XawTextSetInsertionPoint(toc->scrn[i]->tocwidget,
						msg->position);
	    }
	}
    }
}


/* Return the current message. */

Msg TocGetCurMsg(Toc toc)
{
    return toc->curmsg;
}




/* Return the message after the given one.  (If none, return NULL.) */

Msg TocMsgAfter(Toc toc, Msg msg)
{
    int i;
    i = TUGetMsgPosition(toc, msg);
    do {
	i++;
	if (i >= toc->nummsgs)
	    return NULL;
    } while (!(toc->msgs[i]->visible));
    return toc->msgs[i];
}



/* Return the message before the given one.  (If none, return NULL.) */

Msg TocMsgBefore(Toc toc, Msg msg)
{
    int i;
    i = TUGetMsgPosition(toc, msg);
    do {
	i--;
	if (i < 0)
	    return NULL;
    } while (!(toc->msgs[i]->visible));
    return toc->msgs[i];
}



/* The caller KNOWS the toc's information is out of date; rescan it. */

void TocForceRescan(Toc toc)
{
    register Cardinal i;

    if (toc->num_scrns) {
	toc->viewedseq = toc->seqlist[0];
	for (i=0 ; i<toc->num_scrns ; i++)
	    TUResetTocLabel(toc->scrn[i]);
	TUScanFileForToc(toc);
	TULoadTocFile(toc);
	for (i=0 ; i<toc->num_scrns ; i++)
	    TURedisplayToc(toc->scrn[i]);
    } else {
	TUGetFullFolderInfo(toc);
	(void) unlink(toc->scanfile);
	toc->validity = invalid;
    }
}



/* The caller has just changed a sequence list.  Reread them from mh. */

void TocReloadSeqLists(Toc toc)
{
    Cardinal i;

    TocSetCacheValid(toc);
    TULoadSeqLists(toc);
    TURefigureWhatsVisible(toc);
    for (i=0 ; i<toc->num_scrns ; i++) {
	TUResetTocLabel(toc->scrn[i]);
	EnableProperButtons(toc->scrn[i]);
    }
}


/*ARGSUSED*/
void XmhReloadSeqLists(
    Widget	w,
    XEvent	*event,
    String	*params,
    Cardinal	*num_params)
{
    Scrn scrn = ScrnFromWidget(w);
    TocReloadSeqLists(scrn->toc);
    TUCheckSequenceMenu(scrn->toc);
}



/* Return TRUE if the toc has an interesting sequence. */

int TocHasSequences(Toc toc)
{
    return toc && toc->numsequences > 1;
}


/* Change which sequence is being viewed. */

void TocChangeViewedSeq(Toc toc, Sequence seq)
{
    if (seq == NULL) seq = toc->viewedseq;
    toc->viewedseq = seq;
    toc->force_reset = True; /* %%% force Text source to be reset */
    TURefigureWhatsVisible(toc);
}


/* Return the sequence with the given name in the given toc. */

Sequence TocGetSeqNamed(Toc toc, char *name)
{
    register int i;
    if (name == NULL)
	return (Sequence) NULL;

    for (i=0 ; i<toc->numsequences ; i++)
	if (strcmp(toc->seqlist[i]->name, name) == 0)
	    return toc->seqlist[i];
    return (Sequence) NULL;
}


/* Return the sequence currently being viewed in the toc. */

Sequence TocViewedSequence(Toc toc)
{
    return toc->viewedseq;
}


/* Set the selected sequence in the toc */

void TocSetSelectedSequence(
    Toc		toc,
    Sequence	sequence)
{
    if (toc) 
	toc->selectseq = sequence;
}


/* Return the sequence currently selected */

Sequence TocSelectedSequence(Toc toc)
{
    if (toc) return (toc->selectseq);
    else return (Sequence) NULL;
}


/* Return the list of messages currently selected. */

#define SrcScan XawTextSourceScan

MsgList TocCurMsgList(Toc toc)
{
    MsgList result;
    XawTextPosition pos1, pos2;

    if (toc->num_scrns == 0) return NULL;
    result = MakeNullMsgList();
    XawTextGetSelectionPos( toc->scrn[0]->tocwidget, &pos1, &pos2); /* %%% */
    if (pos1 < pos2) {
	pos1 = SrcScan(toc->source, pos1, XawstEOL, XawsdLeft, 1, FALSE);
	pos2 = SrcScan(toc->source, pos2, XawstPositions, XawsdLeft, 1, TRUE);
	pos2 = SrcScan(toc->source, pos2, XawstEOL, XawsdRight, 1, FALSE);
	while (pos1 < pos2) {
	    AppendMsgList(result, MsgFromPosition(toc, pos1, XawsdRight));
	    pos1 = SrcScan(toc->source, pos1, XawstEOL, XawsdRight, 1, TRUE);
	}
    }
    return result;
}



/* Unset the current selection. */

void TocUnsetSelection(Toc toc)
{
    if (toc->source)
        XawTextUnsetSelection(toc->scrn[0]->tocwidget);
}



/* Create a brand new, blank message. */

Msg TocMakeNewMsg(Toc toc)
{
    Msg msg;
    static int looping = False;
    TUEnsureScanIsValidAndOpen(toc, False);
    msg = TUAppendToc(toc, "####  empty\n");
    if (FileExists(MsgFileName(msg))) {
	if (looping++) Punt( "Cannot correct scan file" );
        DEBUG2("**** FOLDER %s WAS INVALID; msg %d already existed!\n",
	       toc->foldername, msg->msgid);
	TocForceRescan(toc);
	return TocMakeNewMsg(toc); /* Try again.  Using recursion here is ugly,
				      but what the hack ... */
    }
    CopyFileAndCheck("/dev/null", MsgFileName(msg));
    looping = False;
    return msg;
}


/* Set things to not update cache or display until further notice. */

void TocStopUpdate(Toc toc)
{
    Cardinal i;

    for (i=0 ; i<toc->num_scrns ; i++)
	XawTextDisableRedisplay(toc->scrn[i]->tocwidget);
    toc->stopupdate++;
}


/* Start updating again, and do whatever updating has been queued. */

void TocStartUpdate(Toc toc)
{
    Cardinal i;

    if (toc->stopupdate && --(toc->stopupdate) == 0) {
	for (i=0 ; i<toc->num_scrns ; i++) {
	    if (toc->needsrepaint) 
		TURedisplayToc(toc->scrn[i]);
	    if (toc->needslabelupdate)
		TUResetTocLabel(toc->scrn[i]);
	}
	if (toc->needscachesave)
	    TUSaveTocFile(toc);
    }
    for (i=0 ; i<toc->num_scrns ; i++)
	XawTextEnableRedisplay(toc->scrn[i]->tocwidget);
}



/* Something has happened that could later convince us that our cache is out
   of date.  Make this not happen; our cache really *is* up-to-date. */

void TocSetCacheValid(Toc toc)
{
    TUSaveTocFile(toc);
}


/* Return the full folder pathname of the given toc, prefixed w/'+' */

char *TocMakeFolderName(Toc toc)
{
    char* name = XtMalloc((Cardinal) (strlen(toc->path) + 2) );
    (void)sprintf( name, "+%s", toc->path );
    return name;
}

char *TocName(Toc toc)
{
    return toc->foldername;
}



/* Given a foldername, return the corresponding toc. */

Toc TocGetNamed(char *name)
{
    int i;
    for (i=0; i<numFolders ; i++)
	if (strcmp(folderList[i]->foldername, name) == 0) return folderList[i];
    return NULL;
}


Boolean TocHasChanges(Toc toc)
{
    int i;
    for (i=0 ; i<toc->nummsgs ; i++)
	if (toc->msgs[i]->fate != Fignore) return True;

    return False;
}



/* Throw out all changes to this toc, and close all views of msgs in it.
   Requires confirmation by the user. */

/*ARGSUSED*/
static void TocCataclysmOkay(
    Widget	widget,		/* unused */
    XtPointer	client_data,
    XtPointer	call_data)	/* unused */
{
    Toc			toc = (Toc) client_data;
    register int	i;

    for (i=0; i < toc->nummsgs; i++)
	MsgSetFate(toc->msgs[i], Fignore, (Toc)NULL);

/* Doesn't make sense to have this MsgSetScrn for loop here. dmc. %%% */
    for (i=0; i < toc->nummsgs; i++)
	MsgSetScrn(toc->msgs[i], (Scrn) NULL, (XtCallbackList) NULL, 
		   (XtCallbackList) NULL);
}
	
int TocConfirmCataclysm(
    Toc			toc,
    XtCallbackList	confirms,
    XtCallbackList	cancels)
{	
    register int	i;

    static XtCallbackRec yes_callbacks[] = {
	{TocCataclysmOkay,	(XtPointer) NULL},
	{(XtCallbackProc) NULL,	(XtPointer) NULL},
	{(XtCallbackProc) NULL,	(XtPointer) NULL}
    };

    if (! toc)
	return 0;

    if (TocHasChanges(toc)) {
	char		str[300];
	Widget		tocwidget;

	(void)sprintf(str,"Are you sure you want to remove all changes to %s?",
		      toc->foldername);
	yes_callbacks[0].closure = (XtPointer) toc;
	yes_callbacks[1].callback = confirms[0].callback;
	yes_callbacks[1].closure = confirms[0].closure;

	tocwidget = NULL;
	for (i=0; i < toc->num_scrns; i++)
	    if (toc->scrn[i]->mapped) {
		tocwidget = toc->scrn[i]->tocwidget;
		break;
	    }

	PopupConfirm(tocwidget, str, yes_callbacks, cancels);
	return NEEDS_CONFIRMATION;
    }
    else {
/* Doesn't make sense to have this MsgSetFate for loop here. dmc. %%% */
	for (i=0 ; i<toc->nummsgs ; i++)
	    MsgSetFate(toc->msgs[i], Fignore, (Toc)NULL);

	for (i=0 ; i<toc->nummsgs ; i++)
	    if (MsgSetScrn(toc->msgs[i], (Scrn) NULL, confirms, cancels))
		return NEEDS_CONFIRMATION;
	return 0;
    }
}
    

/* Commit all the changes in this toc; all messages will meet their 'fate'. */

/*ARGSUSED*/
void TocCommitChanges(
    Widget	widget,		/* unused */
    XtPointer	client_data,	
    XtPointer	call_data)	/* unused */
{
    Toc toc = (Toc) client_data;
    Msg msg;
    int i, cur = 0;
    char str[100], **argv = NULL;
    FateType curfate, fate; 
    Toc desttoc;
    Toc curdesttoc = NULL;
    XtCallbackRec	confirms[2];

    confirms[0].callback = TocCommitChanges;
    confirms[0].closure = (XtPointer) toc;
    confirms[1].callback = (XtCallbackProc) NULL;
    confirms[1].closure = (XtPointer) NULL;

    if (toc == NULL) return;
    for (i=0 ; i<toc->nummsgs ; i++) {
	msg = toc->msgs[i];
	fate = MsgGetFate(msg, (Toc *)NULL);
	if (fate != Fignore && fate != Fcopy)
	    if (MsgSetScrn(msg, (Scrn) NULL, confirms, (XtCallbackList) NULL)
		== NEEDS_CONFIRMATION)
	        return;
    }
    XFlush(XtDisplay(toc->scrn[0]->parent));
    for (i=0 ; i<numFolders ; i++)
	TocStopUpdate(folderList[i]);
    toc->haschanged = TRUE;
    if (app_resources.block_events_on_busy) ShowBusyCursor();

    do {
	curfate = Fignore;
	i = 0;
	while (i < toc->nummsgs) {
	    msg = toc->msgs[i];
	    fate = MsgGetFate(msg, &desttoc);
	    if (curfate == Fignore && fate != Fignore) {
		curfate = fate;
		argv = MakeArgv(2);
		switch (curfate) {
		  case Fdelete:
		    argv[0] = XtNewString("rmm");
		    argv[1] = TocMakeFolderName(toc);
		    cur = 2;
		    curdesttoc = NULL;
		    break;
		  case Fmove:
		  case Fcopy:
		    argv[0] = XtNewString("refile");
		    cur = 1;
		    curdesttoc = desttoc;
		    break;
		  default:
		    break;
		}
	    }
	    if (curfate != Fignore &&
		  curfate == fate && desttoc == curdesttoc) {
		argv = ResizeArgv(argv, cur + 1);
		(void) sprintf(str, "%d", MsgGetId(msg));
		argv[cur++] = XtNewString(str);
		MsgSetFate(msg, Fignore, (Toc)NULL);
		if (curdesttoc) {
		    (void) TUAppendToc(curdesttoc, MsgGetScanLine(msg));
		    curdesttoc->haschanged = TRUE;
		}
		if (curfate != Fcopy) {
		    TocRemoveMsg(toc, msg);
		    MsgFree(msg);
		    i--;
		}
		if (cur > 40)
		    break;	/* Do only 40 at a time, just to be safe. */
	    } 
	    i++;
	}
	if (curfate != Fignore) {
	    switch (curfate) {
	      case Fmove:
	      case Fcopy:
		argv = ResizeArgv(argv, cur + 4);
		argv[cur++] = XtNewString(curfate == Fmove ? "-nolink"
				       			   : "-link");
		argv[cur++] = XtNewString("-src");
		argv[cur++] = TocMakeFolderName(toc);
		argv[cur++] = TocMakeFolderName(curdesttoc);
		break;
	      default:
		break;
	    }
	    if (app_resources.debug) {
		for (i = 0; i < cur; i++)
		    (void) fprintf(stderr, "%s ", argv[i]);
		(void) fprintf(stderr, "\n");
		(void) fflush(stderr);
	    }
	    DoCommand(argv, (char *) NULL, (char *) NULL);
	    for (i = 0; argv[i]; i++)
		XtFree((char *) argv[i]);
	    XtFree((char *) argv);
	}
    } while (curfate != Fignore);
    for (i=0 ; i<numFolders ; i++) {
	if (folderList[i]->haschanged) {
	    TocReloadSeqLists(folderList[i]);
	    folderList[i]->haschanged = FALSE;
	}
	TocStartUpdate(folderList[i]);
    }

    if (app_resources.block_events_on_busy) UnshowBusyCursor();
}



/* Return whether the given toc can incorporate mail. */

int TocCanIncorporate(Toc toc)
{
    return (toc && (toc == InitialFolder || toc->incfile));
}


/* Incorporate new messages into the given toc. */

int TocIncorporate(Toc toc)
{
    char **argv;
    char str[100], *file, *ptr;
    Msg msg, firstmessage = NULL;
    FILEPTR fid;

    argv = MakeArgv(toc->incfile ? 7 : 4);
    argv[0] = "inc";
    argv[1] = TocMakeFolderName(toc);
    argv[2] = "-width";
    (void) sprintf(str, "%d", app_resources.toc_width);
    argv[3] = str;
    if (toc->incfile) {
	argv[4] = "-file";
	argv[5] = toc->incfile;
	argv[6] = "-truncate";
    }
    if (app_resources.block_events_on_busy) ShowBusyCursor();

    file = DoCommandToFile(argv);
    XtFree(argv[1]);
    XtFree((char *)argv);
    TUGetFullFolderInfo(toc);
    if (toc->validity == valid) {
	fid = FOpenAndCheck(file, "r");
	TocStopUpdate(toc);
	while ((ptr = ReadLineWithCR(fid))) {
	    if (atoi(ptr) > 0) {
		msg = TUAppendToc(toc, ptr);
		if (firstmessage == NULL) firstmessage = msg;
	    }
	}
	if (firstmessage && firstmessage->visible) {
	    TocSetCurMsg(toc, firstmessage);
	}
	TocStartUpdate(toc);
	myfclose(fid);
    }
    DeleteFileAndCheck(file);

    if (app_resources.block_events_on_busy) UnshowBusyCursor();

    toc->mailpending = False;
    return (firstmessage != NULL);
}


/* The given message has changed.  Rescan it and change the scanfile. */

void TocMsgChanged(Toc toc, Msg msg)
{
    char **argv, str[100], str2[10], *ptr;
    int length, delta;
    int i;
    FateType fate;
    Toc desttoc;

    if (toc->validity != valid) return;
    fate = MsgGetFate(msg, &desttoc);
    MsgSetFate(msg, Fignore, (Toc) NULL);
    argv = MakeArgv(6);
    argv[0] = "scan";
    argv[1] = TocMakeFolderName(toc);
    (void) sprintf(str, "%d", msg->msgid);
    argv[2] = str;
    argv[3] = "-width";
    (void) sprintf(str2, "%d", app_resources.toc_width);
    argv[4] = str2;
    argv[5] = "-noheader";
    ptr = DoCommandToString(argv);
    XtFree(argv[1]);
    XtFree((char *) argv);
    if (strcmp(ptr, msg->buf) != 0) {
	length = strlen(ptr);
	delta = length - msg->length;
	XtFree(msg->buf);
	msg->buf = ptr;
	msg->length = length;
	toc->length += delta;
	if (msg->visible) {
	    if (delta != 0) {
		for (i=TUGetMsgPosition(toc, msg)+1; i<toc->nummsgs ; i++)
		    toc->msgs[i]->position += delta;
		toc->lastPos += delta;
	    }
	    for (i=0 ; i<toc->num_scrns ; i++)
		TURedisplayToc(toc->scrn[i]);
	}
	MsgSetFate(msg, fate, desttoc);
	TUSaveTocFile(toc);
    } else XtFree(ptr);
}



Msg TocMsgFromId(Toc toc, int msgid)
{
    int h, l, m;
    l = 0;
    h = toc->nummsgs - 1;
    if (h < 0) {
	if (app_resources.debug) {
	    char str[100];
	    (void)sprintf(str, "Toc is empty! folder=%s\n", toc->foldername);
	    DEBUG( str )
	}
	return NULL;
    }
    while (l < h - 1) {
	m = (l + h) / 2;
	if (toc->msgs[m]->msgid > msgid)
	    h = m;
	else
	    l = m;
    }
    if (toc->msgs[l]->msgid == msgid) return toc->msgs[l];
    if (toc->msgs[h]->msgid == msgid) return toc->msgs[h];
    if (app_resources.debug) {
	char str[100];
	(void) sprintf(str,
		      "TocMsgFromId search failed! hi=%d, lo=%d, msgid=%d\n",
		      h, l, msgid);
	DEBUG( str )
    }
    return NULL;
}

/* Sequence names are put on a stack which is specific to the folder. 
 * Sequence names are very volatile, so we make our own copies of the strings.
 */

/*ARGSUSED*/
void XmhPushSequence(
    Widget	w,
    XEvent	*event,
    String	*params,
    Cardinal	*count)
{
    Scrn	scrn = ScrnFromWidget(w);
    Toc		toc;
    Cardinal	i;

    if (! (toc = scrn->toc)) return;
    
    if (*count == 0) {
	if (toc->selectseq)
	    Push(&toc->sequence_stack, XtNewString(toc->selectseq->name));
    }
    else
	for (i=0; i < *count; i++) 
	    Push(&toc->sequence_stack, XtNewString(params[i]));
}


/*ARGSUSED*/
void XmhPopSequence(
    Widget	w,		/* any widget on the screen of interest */
    XEvent	*event,
    String	*params,
    Cardinal	*count)
{
    Scrn	scrn = ScrnFromWidget(w);
    char	*seqname;
    Widget	sequenceMenu, selected, original;
    Button	button;
    Sequence	sequence;

    if ((seqname = Pop(&scrn->toc->sequence_stack)) != NULL) {

	button = BBoxFindButtonNamed(scrn->mainbuttons,
				     MenuBoxButtons[XMH_SEQUENCE].button_name);
	sequenceMenu = BBoxMenuOfButton(button);

	if ((selected = XawSimpleMenuGetActiveEntry(sequenceMenu)))
	    ToggleMenuItem(selected, False);

	if ((original = XtNameToWidget(sequenceMenu, seqname))) {
	    ToggleMenuItem(original, True);
	    sequence = TocGetSeqNamed(scrn->toc, seqname);
	    TocSetSelectedSequence(scrn->toc, sequence);
	}
	XtFree(seqname);
    }
}
