[comp.sources.unix] v12i006: vmail - screen-based mail handler, Part03/03

jz@mulga.oz.au (Justin Zobel) (10/14/87)

Submitted by: jz@mulga.oz.au
Posting-number: Volume 12, Issue 6
Archive-name: vmail/Part03

This is the third of three parts of vmail, an interactive mail handler
that sits on top of MH.
    vmail has a number of advantages over raw MH.  It is screen-based and
faster (and more convenient) than the MH show-scan-rmm refile cycle.
vmail makes it feasible for users to organise and keep track of moderate
volumes of mail without wasting too much time, and is very simple to use.
It has been in use at Melbourne University Computer Science Department
for about six months without any problems, and has become the interface of
choice for many users.

: ---------------------------------------- cut here

echo x - "load.c" 2>&1
sed "s/^X//" >"load.c" <<'!The!End!'
X/* --------------------
X	vmail -- load.c
X
X	Routines to find folders, find all mail items in folders.  Empty
X	folders are ignored.
X
X	Copyright (C) J. Zobel, University of Melbourne, October 1987.
X-------------------- */
X
X#include "defs.h"
X#include <ctype.h>
X
X
X/* --------------------
X	Find all folders in mail directory, set up linked list.
X-------------------- */
Xvoid
Xfind_folders()
X{
X	struct direct *dp;
X	struct stat statbuf;
X	DIR		*dirp;
X	int		i, n = 0, scmp();
X	char	str[LEN], **fds, **ftmp;
X	folder	tmp, last = (folder) NULL;
X
X	stat(mail_dir, &statbuf);
X		/* guess number of folders - 10 is an error bound */
X	ftmp = fds = (char **) malloc(sizeof(char *) * (statbuf.st_nlink+10));
X	dirp = opendir(mail_dir);
X	for(dp=readdir(dirp) ; dp != (struct direct *)NULL ; dp=readdir(dirp)) {
X		if(strcmp(dp->d_name, ".") == 0)
X			continue;
X		if(strcmp(dp->d_name, "..") == 0)
X			continue;
X		sprintf(str, "%s/%s", mail_dir, dp->d_name);
X		stat(str, &statbuf);
X		if(! (statbuf.st_mode & S_IFDIR))
X			continue;
X		if(! (statbuf.st_mode & S_IREAD) || ! (statbuf.st_mode & S_IWRITE)
X				|| ! (statbuf.st_mode & S_IEXEC)) {
X			printf("Folder %s unreadable.\n", dp->d_name);
X			continue;
X		}
X		*ftmp = NEWSTR(strlen(dp->d_name)+1);
X		strcpy(*ftmp, dp->d_name);
X		n++, ftmp++;
X	}
X	closedir(dirp);
X	if(n == 0) {
X		printf("No folders.\n");
X		exit(1);
X	}
X	qsort(fds, n, sizeof(char *), scmp);
X	for(i=0, ftmp=fds ; i < n ; ftmp++, i++) {
X		tmp = NEW(mail_folder);
X		tmp->name = *ftmp;
X		tmp->last = tmp->mail = (item) NULL;
X		tmp->pages = tmp->pagenum = 1;
X		tmp->valid = false;
X		tmp->prev = last;
X		tmp->next = (folder) NULL;
X		if(last != (folder) NULL)
X			last->next = tmp;
X		else
X			folders = tmp;
X		last = tmp;
X	}
X}
X
X
Xint
Xscmp(p, q)
X	char	**p, **q;
X{
X	return(strcmp(*p, *q));
X}
X
X
X/* --------------------
X	Find all mail items in given folder, throw away folder record if no
X	mail items.  Returns next folder in linked list, adds new folder
X	records to linked list if original record overflows.
X-------------------- */
Xfolder
Xfind_mail(flr, load_time)
X	folder	flr;
X	int		load_time;
X{
X	struct direct *dp, *readdir();
X	DIR		*dirp, *opendir();
X	int		i, n = 0, items[MAXITEMS], *itmp = items, dcmp();
X	char	str[LEN];
X	item	tmp, last = (item) NULL;
X	folder	new_folder();
X
X	sprintf(str, "%s/%s", mail_dir, flr->name);
X	dirp = opendir(str);
X	if(dirp == (DIR *) NULL) {
X		if(load_time)
X			printf("%s: strange folder.\n", str);
X	} else {
X		for(dp=readdir(dirp) ; dp!=(struct direct *) NULL ; dp=readdir(dirp)) {
X					/* get numbers of all mail items */
X			if(n >= MAXITEMS) {
X				no_control();
X				printf("\nMore than %d items in folder.\n",MAXITEMS-1);
X				exit(1);
X			}
X				/* ignore anything that is not string of digits */
X			if(! digits(dp->d_name))
X				continue;
X					/* assume any file that is string of digits is a mail
X					   item and not a directory */
X			*itmp = atoi(dp->d_name);
X			n++, itmp++;
X		}
X		closedir(dirp);
X	}
X	if(n == 0) {		/* Empty folder - delete from list */
X		flr->valid = EMPTY;
X		if(load_time)
X			printf("\t%s: empty\n", flr->name);
X		if(flr->prev != (folder) NULL)
X			flr->prev->next = flr->next;
X		else
X			folders = flr->next;
X		if(flr->next != (folder) NULL)
X			flr->next->prev = flr->prev;
X		return(flr->next);
X	} else
X		qsort(items, n, sizeof(int), dcmp);
X	if(load_time)		/* show status of folder */
X		if(n == 1)
X			printf("\t%s: %d\n", flr->name, *items);
X		else
X			printf("\t%s: %d-%d (%d items)\n",flr->name,*items,*(items+n-1),n);
X	for(i=0, itmp=items ; i < n ; itmp++, i++) {
X			/* add item to list of items in folder */
X		tmp = NEW(mail_item);
X		get_title(flr, tmp, *itmp);
X		if(i % lines == 0 && last != (item) NULL) {
X				/* add new record for folder */
X			flr->last = last;
X			flr = new_folder(flr);
X			last = (item) NULL;
X		}
X		tmp->next = (item) NULL;
X		tmp->prev = last;
X		if(last != (item) NULL)
X			last->next = tmp;
X		else
X			flr->mail = tmp;
X		last = tmp;
X	}
X	flr->last = last;
X	return(flr->next);
X}
X
X
Xint
Xdcmp(a, b)
X	int		*a, *b;
X{
X	return(*a - *b);
X}
X
X
X/* --------------------
X	Return true if string consists only of digits.
X-------------------- */
Xint
Xdigits(str)
X	char	*str;
X{
X	for( ; *str != '\0' ; str++)
X		if(! isdigit(*str))
X			return(false);
X	return(true);
X}
X
X
X/* --------------------
X	Make header line from mail item.
X-------------------- */
Xvoid
Xget_title(flr, mail, num)
X	folder	flr;
X	item	mail;
X	int		num;
X{
X	FILE	*fp, *fopen();
X	char	date[10], str[LEN], subj[42], from[20], to[16], fill[42];
X	char	repl = ' ', *first(), *s, *fgets();
X	int		i;
X
X	subj[0] = from[0] = to[0] = fill[0] = '\0';
X	sprintf(str, "%s/%s/%d", mail_dir, flr->name, num);
X	fp = fopen(str, "r");
X	while((s=fgets(str, LEN, fp)) != (char *) NULL && *str != '\n') {
X			/* while not eof and reading header */
X		str[strlen(str)-1] = '\0';
X			/* should also check date */
X		if(! lstrncmp("date:", str, 5))
X			get_date(str+5, date);
X		else if(! lstrncmp("subject:", str, 8)) {
X			s = first(str+8);
X			strncpy(subj, s, 41);
X			subj[41] = '\0';
X		} else if(! lstrncmp("replied:", str, 8))
X			repl = '-';
X		else if(! lstrncmp("from:", str, 5)) {
X			s = first(str+5);
X			if(! isuser(s)) {
X				strncpy(from, s, 19);
X				from[19] = '\0';
X			}
X		} else if(! lstrncmp("to:", str, 3)) {
X			s = first(str+3);
X			strncpy(to, s, 15);
X			to[15] = '\0';
X		} else if(! lstrncmp("apparently-to:", str, 14)) {
X			s = first(str+14);
X			strncpy(to, s, 15);
X			to[15] = '\0';
X		}
X	}
X	if(s != (char *) NULL) {
X		for(i=0 ; i < 50 && fgets(str, LEN, fp) != (char *) NULL ;) {
X			str[strlen(str)-1] = ' ';
X			strncpy(fill+i, str, 42-i);
X			i += strlen(str);
X		}
X		fill[41] = '\0';
X	}
X	fclose(fp);
X	if(subj[0] == '\0')
X		strcpy(subj, "(none)");
X	flatten(subj);
X	flatten(fill);
X	if(from[0] == '\0')
X		sprintf(str, "  %8s %cTo: %-15s  %s << %s",date,repl,to,subj,fill);
X	else
X		sprintf(str, "  %8s %c%-19s  %s << %s",date,repl,from,subj,fill);
X	str[cols-6] = '\0';
X	mail->title = NEWSTR(strlen(str)+1);
X	mail->number = num;
X	strcpy(mail->title, str);
X}
X
X
X#ifdef USDATE
X#define DAY1	date[3]
X#define DAY2	date[4]
X#define MTH1	date[0]
X#define MTH2	date[1]
X#else
X#define DAY1	date[0]
X#define DAY2	date[1]
X#define MTH1	date[3]
X#define MTH2	date[4]
X#endif
X#define YR1		date[6]
X#define YR2		date[7]
X
X/* --------------------
X	Get date from first argument; assumes format is one of
XDate: Fri, 3 Apr 87 ...						(a)
XDate: Wed, 17 Jun 87 ...					(b)
XDate: 07 Sep 87 ...							(c)
XDate: Tue Sep 29 12:27:01 EST 1987			(d)
X	Dates are put into Imperial form (dd-mm-yy), or US form (mm-dd-yy)
X	if -DUSDATE is set.
X
X	This routine should be more flexible re: recognizing date formats.
X-------------------- */
Xvoid
Xget_date(str, date)
X	char	*str, *date;
X{
X	int		i;
X	bool	get_month();
X	char	*next_token();
X
X	date[2] = date[5] = '-';
X	date[8] = '\0';
X	for(; *str == ' ' || *str == '\t' ; str++)
X		;
X	if(*str == '\0')
X		goto unknown;
X	if(! isdigit(*str))		/* day of month not first field */
X		str = next_token(str);		/* skip day of week */
X	if(str == (char *) NULL + 1)
X		goto unknown;
X
X	if(isdigit(*str)) {		/* one of (a), (b) or (c) formats */
X		if(isdigit(*(str+1)))	/* two-number date */
X#ifdef USDATE
X			DAY1 = *str, DAY2 = *(++str);
X		else
X			DAY1 = '0', DAY2 = *str;
X#else
X			DAY1 = (*str == '0') ? ' ' : *str, DAY2 = *(++str);
X		else
X			DAY1 = ' ', DAY2 = *str;
X#endif
X		str = next_token(str);	/* go to month */
X		if(str == (char *) NULL + 1)
X			goto unknown;
X		if(! get_month(str, date))
X			goto unknown;
X		str = next_token(str);	/* go to year */
X		if(str == (char *) NULL + 1)
X			goto unknown;
X		if(! isdigit(*str) || ! isdigit(*(str+1)))
X			goto unknown;
X		YR1 = *str, YR2 = *(str+1);
X	} else {	/* format (d) */
X		if(! get_month(str, date))
X			goto unknown;
X		str = next_token(str);	/* go to day */
X		if(str == (char *) NULL + 1)
X			goto unknown;
X		if(! isdigit(*str) || ! isdigit(*(str+1)))
X			goto unknown;
X#ifdef USDATE
X		DAY1 = *str, DAY2 = *(++str);
X#else
X		DAY1 = (*str == '0') ? ' ' : *str, DAY2 = *(++str);
X#endif
X		str += strlen(str) - 2;		/* go to year */
X		if(! isdigit(*str) || ! isdigit(*(str+1)))
X			goto unknown;
X		YR1 = *str, YR2 = *(str+1);
X	}
X	return;		/* date is ok */
X
Xunknown:			/* erase date */
X	for(i=0 ; i < 8 ; i++)
X		date[i] = ' ';
X}
X
X
Xstatic char *month[] = {
X	"jan", "feb", "mar", "apr", "may", "jun",
X	"jul", "aug", "sep", "oct", "nov", "dec"
X};
X
X/* --------------------
X	Put the month from the first part of str in date as a number.
X-------------------- */
Xbool
Xget_month(str, date)
X	char	*str, *date;
X{
X	int		i;
X
X	for(i=0 ; i < 12 && lstrncmp(month[i], str, 3) != 0 ; i++)
X		;
X	if(i == 12)		/* string doesn't match month */
X		return(false);
X	else {	
X		if(i < 9)	/* single digit month */
X#ifdef USDATE
X			MTH1 = ' ', MTH2 = i + '0' + 1;
X#else
X			MTH1 = '0', MTH2 = i + '0' + 1;
X#endif
X		else
X			MTH1 = '1', MTH2 = i + '0' - 9;
X		return(true);
X	}
X}
X
X
X/* --------------------
X	Replace tabs and newlines in string by spaces.
X-------------------- */
Xvoid
Xflatten(str)
X	char	*str;
X{
X	char	prev, *s = str;
X
X	for(prev='\0' ; (*s = *str) != '\0' ; prev = *str, str++) {
X		if(*str == '\t')
X			*s = ' ';
X		if(prev != ' ' || *s != ' ')
X			s++;
X	}
X}
X
X
X/* --------------------
X	Return pointer to first non-white character in string.
X-------------------- */
Xchar *
Xfirst(str)
X	char	*str;
X{
X	for(; *str == ' ' || *str == '\t' || *str == '\n' ; str++)
X		;
X	return(str);
X}
X
X
X/* --------------------
X	Check given string for occurence of user's name.
X-------------------- */
Xint
Xisuser(str)
X	char	*str;
X{
X	int		len = strlen(user);
X
X	for(; *str != '\0' ; str++)
X			/* assume all chars in all unames alphanumeric */
X		if(*str == *user && strncmp(str, user, len) == 0 &&
X				(*(str+len) < 'a' || *(str+len) > 'z') &&
X				(*(str+len) < 'A' || *(str+len) > 'Z') &&
X				(*(str+len) < '0' || *(str+len) > '9'))
X			return(true);
X	return(false);
X}
X
X
X/* --------------------
X	Make a new folder record.
X-------------------- */
Xfolder
Xnew_folder(flr)
X	folder	flr;
X{
X	folder	f;
X
X	f = NEW(mail_folder);
X	f->prev = flr;
X	f->next = flr->next;
X	flr->next = f;
X	if(f->next != (folder) NULL)
X		f->next->prev = f;
X	f->pagenum = f->pages = flr->pages + 1;
X	f->name = flr->name;
X	f->valid = true;
X	f->mail = f->last = (item) NULL;
X	flr = f;
X	for(f=f->prev; f != (folder) NULL && f->name == flr->name ; f=f->prev)
X		f->pages = flr->pages;
X	return(flr);
X}
X
X
X/* --------------------
X	Find next free slot in directory for mail item (that is, find next
X	unused number) -- structures in vmail may not be up to date if user
X	has initiated a "send" process.
X-------------------- */
Xint
Xnext_vacant(flr)
X	folder	flr;
X{
X	struct direct *dp, *readdir();
X	DIR		*dirp, *opendir();
X	char	str[LEN];
X	int		i, n = 0;
X
X	sprintf(str, "%s/%s", mail_dir, flr->name);
X	dirp = opendir(str);
X	for(dp=readdir(dirp) ; dp != (struct direct *) NULL ; dp=readdir(dirp)) {
X			/* ignore anything that is not string of digits */
X		if(! digits(dp->d_name))
X			continue;
X		if((i = atoi(dp->d_name)) > n)
X			n = i;
X	}
X	closedir(dirp);
X	return(n+1);
X}
!The!End!

echo x - "low.c" 2>&1
sed "s/^X//" >"low.c" <<'!The!End!'
X/* --------------------
X	vmail -- low.c
X
X	Low level screen update and data structure update routines, other
X	similar stuff.
X
X	Copyright (C) J. Zobel, University of Melbourne, October 1987.
X-------------------- */
X
X#include "defs.h"
X
X/* --------------------
X	Get string from terminal (visibly).
X-------------------- */
Xvoid
Xget_string(mes, str)
X	char	*mes, *str;
X{
X	char	c;
X	int		i;
X
X	move(STATUS, 0);
X	clrtoeol();
X	addstr(mes);
X	refresh();
X	for(i=0 ; (c = getchar()) != '\n' && c != '\r' && c != ESC ; )
X		if(i > 0 && c == '\b') {
X			i--;
X			mvaddch(STATUS, strlen(mes)+i, ' ');
X			move(STATUS, strlen(mes)+i);
X			refresh();
X		} else if(c != '\b') {
X			str[i++] = c;
X			addch(c);
X			refresh();
X		}
X	str[i] = '\0';
X	move(y, 0);
X}
X
X
X/* --------------------
X	Put string on status line.
X-------------------- */
Xvoid
Xaddstatus(str, stand)
X	char	*str;
X	bool	stand;
X{
X	move(STATUS, 0);
X	clrtoeol();
X	if(stand)
X		standout();		/* doesn't do anything? */
X	addstr(str);
X	if(stand)
X		standend();
X	move(y, 0);
X	refresh();
X}
X
X
X/* --------------------
X	Fudge deleteline because curses wont delete the line immediately
X	after a ^Z, and because it doesnt clear the last line of the screen.
X-------------------- */
Xvoid
Xdeleteline()
X{
X	mvaddstr(y, 0, "scribble");
X	deleteln();
X	move(FIRST+lines-1, 0);
X	clrtoeol();
X	move(y, 0);
X}
X
X
Xint
Xputch(c)
X	char	c;
X{
X	putchar(c);	/* turn macro into routine */
X}
X
X
X/* --------------------
X	Put prompt on screen, wait for getchar().
X-------------------- */
Xint
Xuse_prompt(win)
X	WINDOW	*win;
X{
X	int		c = (cols-42)/2;
X
X	mvwaddstr(win,FIRST+lines-1,c,"-- hit space to continue, q to quit -- ");
X	wrefresh(win);
X	return(getchar());
X}
X
X
X/* --------------------
X	Put info for mail item on screen at row i.
X-------------------- */
Xvoid
Xshow_title(str, i, mail)
X	char	*str;
X	int		i;
X	item	mail;
X{
X	sprintf(str, "%4d%s", mail->number, mail->title);
X	mvaddstr(i, 0, str);
X}
X
X
X/* --------------------
X	Put header for page at top of screen.
X-------------------- */
Xvoid
Xadd_page_header(str)
X	char	*str;
X{
X	sprintf(str, "%s (page %d of %d) ", curflr->name, curflr->pagenum,
X															curflr->pages);
X	mvaddstr(TITLE, 0, str);
X}
X
X
X/* --------------------
X	Flush typeahead.
X-------------------- */
Xchar
Xflushin()
X{
X	struct sgttyb sg;
X
X	if(do_flush) {
X		ioctl(0, TIOCGETP, &sg);
X		ioctl(0, TIOCSETP, &sg);
X	}
X	return(getchar());
X}
X
X
X/* --------------------
X	Print message, wait for getchar();
X-------------------- */
Xvoid
Xhold_end()
X{
X	tputs(SO, 1, putch);
X	printf("-- any character to continue --");
X	tputs(SE, 1, putch);
X	fflush(stdout);
X	getchar();
X}
X
X
X#define ctol(c)		(((c) >= 'A' && (c) <= 'Z') ? c-'A'+'a' : c)
X
X/* --------------------
X	Case independent strncmp.
X-------------------- */
Xlstrncmp(s1, s2, n)
X	char *s1, *s2;
X	int n;
X{
X	for( ; ctol(*s1) == ctol(*s2) && n > 0 ; s1++, s2++, n--)
X		;
X	return((n > 0) ? *s1 - *s2 : 0);
X}
X
X
X/* --------------------
X	String processing - more useful form of index.
X	Skip current non-white then skip white, replacing by null.
X-------------------- */
Xchar *
Xnext_token(t)
X	char	*t;
X{
X	for( ; *t != ' ' && *t != '\t' && *t != '\0' ; t++)
X		;
X	for( ; *t == ' ' || *t == '\t' ; t++)
X		*t = '\0';
X	return(t);
X}
!The!End!

echo x - "main.c" 2>&1
sed "s/^X//" >"main.c" <<'!The!End!'
X/* --------------------
X	vmail -- main.c
X
X	Definitions of global variables, main switch.
X
X	Copyright (C) J. Zobel, University of Melbourne, October 1987.
X-------------------- */
X
X#include "defs.h"
X
Xfolder	folders = (folder) NULL, curflr, alternate = (folder) NULL;
Xitem	curmail;
Xchar 	*user, *pager = (char *) NULL, *editor = (char *) NULL,
X		*mail_dir = (char *) NULL, *shell = (char *) NULL, *context;
Xbool	top_level = true, do_flush = true, comp_args = true, repl_args = true,
X		forw_args = true;
Xint		lines, cols, y, folder_protect = FPROT;
Xjmp_buf	env;
X
X#define HELP		37
X
Xchar *help_scr[] = {
X"       .                Re-execute last command (if one of acdDefirR)",
X"                                   (on repeat, r does not prompt for folder)",
X"       /<exp>           Search forwards for title with <exp> (/ to repeat)",
X"       ?<exp>           Search backwards for title with <exp> (? to repeat)",
X"       <space>          Show current mail item",
X"       [n]<return>      Go forward one (or n) active page(s) of mail items",
X"       [n]<backspace>   Go back one (or n) active page(s) of mail items",
X"       ^L               Refresh",
X"       !                Invoke shell",
X"       |                Pipe current mail item into command",
X"       ^                Go to first active page",
X"       $                Go to last active page",
X"       a                Answer current mail item (call to \"repl\")",
X"       c                Compose new mail item (call to \"comp\")",
X"       C                Go to folder chooser",
X"       [n]d             Delete current (or next inclusive n) mail item(s)",
X"       D                Delete current mail item, show next",
X"       e                Edit current mail item",
X"       f                Forward current mail item (call to \"forw\")",
X"       F                List all folders",
X"       g,G              Go to named (or alternate) folder",
X"       h                Show this page",
X"       [n]H             Go to top of page (or nth from top line)",
X"       i                Incorporate new mail (call to \"inc\")",
X"       [n]j             Move cursor down one (or n) line(s)",
X"       [n]k             Move cursor up one (or n) line(s)",
X"       [n]L             Go to bottom of page (or nth from bottom line)",
X"       M                Go to middle of page",
X"       [n]n             Go forward one (or n) folders, make new folder active",
X"       [n]p             Go back one (or n) folders, make new folder active",
X"       P                Print name of alternate folder",
X"       q                Exit from vmail",
X"       r,R              Refile item in named (or previous) folder",
X"       s                Save current item in named file",
X"       u                Undo most recent deletion (1 mail item only)",
X"       v                Make current folder inactive",
X"       z                Pack current folder"
X};
X
X
X#define USAGE	printf( \
X"Usage: vmail [-inc] [-flush] [-comp] [-forw] [-ans] [+curfolder] folders ...\n"), \
X				exit(1)
X
Xmain(argc, argv)
X	int		argc;
X	char	*argv[];
X{
X	char	c, last = '\0', get_number(), flushin();
X	int		count = 1,		/* accumulate count to give to command */
X			pcount = 1;
X	bool	flag = false;	/* used to check whether to reset count */
X
X	argc--, argv++;
X
X	process_args(argc, argv);
X#ifndef VERSION
X#define VERSION	"(local version)"
X#endif
X	printf("vmail %s -- reading mail headers\n", VERSION);
X	init(argc, argv);
X	setjmp(env);		/* return point from interrupt */
X	c = flushin();
X	for(;;) {
X		while(c == '.')
X			if(last == '\0') {
X				beep();
X				c = flushin();
X			} else {
X				count = pcount;
X				c = last;
X			}
X		switch(c) {
X			case '|':	/* pipe mail to command */
X				do_pipe();
X				break;
X			case '!':	/* call shell */
X				call_shell();
X				break;
X			case '/':	/* search forwards */
X				search(true);
X				break;
X			case '?':	/* search backwards */
X				search(false);
X				break;
X			case '^':	/* first active page */
X				goto_first_page();
X				break;
X			case '$':	/* last active page */
X				goto_last_page();
X				break;
X			case '\n':	/* next folder */
X			case '\r':	/* next folder */
X				next_page(count, true);
X				break;
X			case ' ':	/* show current item */
X				show_mail();
X				break;
X			case DEL:	/* previous folder */
X			case '\b':	/* previous folder */
X				prev_page(count, true);
X				break;
X			case 'a':	/* answer - call to repl */
X				repl();
X				break;
X			case 'c':	/* compose - fork of comp */
X				comp();
X				break;
X			case 'C':	/* go to folder chooser */
X				choose();
X				break;
X			case 'd':	/* delete item */
X				delete_item(count);
X				break;
X			case 'D':	/* delete item, show next */
X				change_item(false);
X				show_mail();
X				break;
X			case 'e':	/* edit current mail item */
X				edit();
X				break;
X			case 'f':	/* forward - call to forw */
X				forw();
X				break;
X			case 'F':	/* list all folders */
X				list_folders();
X				break;
X			case 'g':	/* go to named folder */
X				goto_folder(true);
X				break;
X			case 'G':	/* go to last-named folder */
X				goto_folder(false);
X				break;
X			case 'h':	/* help */
X				help();
X				break;
X			case 'H':	/* go to top of page */
X				cursor_first(count);
X				break;
X			case 'i':	/* inc */
X				inc();
X				break;
X			case 'j':	/* cursor down */
X				cursor_down(count);
X				break;
X			case 'k':	/* cursor up */
X				cursor_up(count);
X				break;
X			case CTRL_L: /* redraw */
X				display_page();
X				break;
X			case 'L':	/* go to bottom of page */
X				cursor_last(count);
X				break;
X			case 'M':	/* go to middle of page */
X				cursor_middle();
X				break;
X			case 'n':	/* next folder - load if not there */
X				goto_next_folder(count);
X				break;
X			case 'p':	/* prev folder - load if not there */
X				goto_prev_folder(count);
X				break;
X			case 'P':	/* print name of last-named folder */
X				show_folder();
X				break;
X			case 'q':	/* quit */
X				to_normal();
X				fix_mh();
X				exit(0);
X				break;
X			case 'r':	/* move item to named folder */
X				move_item(true);
X				break;
X			case 'R':	/* move item to previous folder */
X				move_item(false);
X				break;
X			case 's':	/* save item in named file */
X				save_item();
X				break;
X			case 'u':	/* undo */
X				undo();
X				break;
X			case 'v':	/* make folder inactive */
X				inactive();
X				break;
X			case 'z':	/* pack current folder */
X				pack_folder();
X				break;
X			case '0':	/* start of count */
X			case '1':
X			case '2':
X			case '3':
X			case '4':
X			case '5':
X			case '6':
X			case '7':
X			case '8':
X			case '9':
X				c = get_number(c, &count);
X				flag = true;
X				break;
X			default:
X				addstatus("command unknown -- `h' for help", true);
X				break;
X		}
X		switch(c) {
X				/* repeatable commands */
X			case 'r':	/* refile item in named folder */
X				last = 'R';
X				pcount = 1;
X				break;
X			case 'a':	/* reply - call to repl */
X			case 'c':	/* compose - fork of comp */
X			case 'd':	/* delete item */
X			case 'D':	/* delete item, show next */
X			case 'e':	/* edit current mail item */
X			case 'f':	/* forward - call to forw */
X			case 'i':	/* inc */
X			case 'R':	/* refile to previous folder */
X			case 'w':	/* write to file */
X				last = c;
X				pcount = count;
X				break;
X			default:
X				break;
X		}
X		if(!flag) {
X			c = flushin();
X			count = 1;
X		} else {
X			flag = false;
X		}
X	}
X}
X
X
X/* --------------------
X	Read a number from terminal.
X-------------------- */
Xchar
Xget_number(c, count)
X	char	c;
X	int		*count;
X{
X	*count = c - '0';
X	while((c = getchar()) >= '0' && c <= '9')
X		*count = *count * 10 + c - '0';
X	return(c);
X}
X
X
XWINDOW *helpwin = (WINDOW *) NULL;
X
X/* --------------------
X	Display help messages on help screen.
X-------------------- */
Xhelp()
X{
X	WINDOW	*newwin();
X	int		i, j;
X
X	if(helpwin == (WINDOW *) NULL)
X		helpwin = newwin(0, 0, 0, 0);
X	wclear(helpwin);
X	for(j=i=0 ; i <= HELP ; i++, j++) {
X		if((i+2) % lines == 0) {
X			if(use_prompt(helpwin) == 'q')
X				break;
X			j=0;
X			wclear(helpwin);
X		}
X		if(i >= HELP) {
X			use_prompt(helpwin);
X			break;
X		}
X		mvwaddstr(helpwin, j+1, 0, help_scr[i]);
X	}
X	display_page();
X}
X
X
X/* --------------------
X	Process arguments, command-line or given in profile.
X-------------------- */
Xvoid
Xprocess_args(argc, argv)
X	int argc;
X	char *argv[];
X{
X	for( ; argc > 0 ; argc--, argv++)
X		if(!strcmp(argv[0], "-inc")) {	/* incorporate mail before starting */
X			if(!vfork()) {
X				execlp(INC, INC, 0);
X				printf("Warning: can't execute %s\n", INC);
X				exit(0);
X			}
X			wait(0);
X			printf("\n");
X		} else if(!strcmp(argv[0], "-flush"))	/* don't flush typeahead */
X			do_flush = false;
X		else if(!strcmp(argv[0], "-forw"))		/* no args to forw */
X			forw_args = false;
X		else if(!strcmp(argv[0], "-comp"))		/* no args to comp */
X			comp_args = false;
X		else if(!strcmp(argv[0], "-ans"))		/* no args to repl */
X			repl_args = false;
X		else if(*argv[0] == '-') {
X			printf("%s: illegal option\n", argv[0]);
X			USAGE;
X		}
X}
!The!End!

echo x - "move.c" 2>&1
sed "s/^X//" >"move.c" <<'!The!End!'
X/* --------------------
X	vmail -- move.c
X
X	Routines to delete or move a mail item from current folder.
X
X	Copyright (C) J. Zobel, University of Melbourne, October 1987.
X-------------------- */
X
X#include "defs.h"
X#include <errno.h>
X
Xstatic char prevfile[LEN] = "";
X
Xextern int errno;
X
X/* --------------------
X	Move current item to folder prevfile (get_name = false) or as read from
X	terminal (get_name = true).
X-------------------- */
Xvoid
Xmove_item(get_name)
X	bool	get_name;
X{
X	char	*s, str[LEN], str2[LEN];
X	folder	f, new_folder(), create_folder();
X	item	m;
X	bool	redraw,						/* true if screen to be refreshed */
X			change_item();
X	int		fdi, fdo, i = FIRST-1, num, N;
X
X		/* get name if required */
X	if(get_name || *prevfile == '\0') {
X		get_string("folder? ", str);
X		if(*str == '\0') {
X			addstatus("no name given for folder", true);
X			return;
X		}
X		strcpy(prevfile, str);
X	} else
X		strcpy(str, prevfile);
X		/* find first page of named folder */
X	sprintf(str2, "refiling to %s ...", str);
X	addstatus(str2, false);
X	GOTO_NAME(f, str);
X	if(f == (folder) NULL) {	/* create folder */
X		f = create_folder(str);
X		if(f == (folder) NULL)
X			return;
X	} else {
X		if(f->name == curflr->name) {
X			addstatus("can't move item to current folder", true);
X			return;
X		}
X		if(f->valid) {		/* goto last mail item in folder */
X			LAST_OF_NAME(f);
X			for(i=FIRST, m=f->mail ; m->next != (item) NULL ; i++, m=m->next)
X				;
X		}
X	}
X		/* remember current location of mail */
X	m = curmail;
X	num = curmail->number;
X	s = curflr->name;
X		/* delete item from current folder, update current folder & screen */
X	redraw = change_item(true);
X	if(f->valid)		/* update structures of mail items */
X		if(i > lines) {
X				/* create new folder record */
X			f = new_folder(f);
X			f->mail = f->last = m;
X			m->prev = m->next = (item) NULL;
X			N = f->prev->last->number;
X		} else {
X				/* insert at end of list of mail items */
X			m->prev = f->last; m->next = (item) NULL;
X			f->last->next = m;
X			f->last = m;
X			N = m->prev->number;
X		}
X	else
X		N = next_vacant(f);
X/* to avoid race between "send" or other process in background and vmail
X   foreground, compute next free slot, dont just use given value 
X*/
X	sprintf(str2, "%s/%s/%d", mail_dir, f->name, N);
X		/* loop until unused file name found */
X	for(errno=0 ; (fdo = open(str2, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0
X											&& errno == EEXIST ; errno=0) {
X		N = N + 1;
X		sprintf(str2, "%s/%s/%d", mail_dir, f->name, N);
X	}
X	if(f->valid)
X		m->number = N;
X	sprintf(str, "%s/%s/%d", mail_dir, s, num);
X	fdi = open(str, O_RDONLY);
X	while((i = read(fdi, str2, LEN)) > 0)	/* copy original to new */
X		write(fdo, str2, i);
X	close(fdi);
X	close(fdo);
X	unlink(str);							/* remove original */
X	if(redraw)
X		display_page();
X	else {
X		add_page_header(str);
X		move(y, 0);
X		refresh();
X	}
X	addstatus("refiled", true);
X	if(curflr == (folder) NULL) {
X		to_normal();
X		exit(1);
X	}
X}
X
X
X/* --------------------
X	Delete count items.
X-------------------- */
Xvoid
Xdelete_item(count)
X	int		count;
X{
X	bool	redraw = false, change_item();
X	item	m;
X	char	str[LEN];
X
X	for( ; count > 0 ; count--) {
X		m = curmail->next;
X		redraw = change_item(false);
X		if(redraw || m != curmail) /* on new page, or last item deleted */
X			break;
X	}
X	if(redraw)
X		display_page();
X	else {
X		add_page_header(str);
X		move(y, 0);
X		refresh();
X	}
X}
X
X
X/* --------------------
X	Structure for deleted item.
X-------------------- */
Xstruct {
X	folder flr;
X	int number;
X} deleted = {(folder) NULL, 0};
X
X
X/* --------------------
X	Either delete (do_move = false) or prepare to move (do_move = true) item.
X	Delete item from current folder, update screen, find new current folder
X	if current folder has become empty.
X-------------------- */
Xbool
Xchange_item(do_move)
X	bool	do_move;
X{
X	item	tmp, m = curmail;
X	folder	F, p, f = curflr, pval, nval;
X	char	s1[LEN], s2[LEN];
X	bool	redraw, doexit = false;
X
X	if(curmail->next == (item) NULL && curmail->prev == (item) NULL) {
X			/* have last item in page */
X		redraw = true;
X		pval = curflr->prev; PREV_VALID(pval);
X		nval = curflr->next; NEXT_VALID(nval);
X		if(pval == (folder) NULL && nval == (folder) NULL) {
X						/* no more active pages */
X			if(! do_move)
X				addstatus("Deleting last active mail item -- bye", true);
X			doexit = true;
X		} else {
X				/* update pages, pagenum */
X			for(p=curflr->prev ; p != (folder) NULL && p->name == curflr->name
X																	; p=p->prev)
X				p->pages -= 1;
X			for(p=curflr->next ; p != (folder) NULL && p->name == curflr->name
X																	; p=p->next)
X				p->pages -= 1, p->pagenum -= 1;
X				/* find next active page, drop current page from list */
X			if(curflr->prev != (folder) NULL)
X				curflr->prev->next = curflr->next;
X			else
X				folders = curflr->next;
X			if(curflr->next != (folder) NULL)
X				curflr->next->prev = curflr->prev;
X			curflr = (nval == (folder) NULL) ? pval : nval;
X			curmail = curflr->mail;
X		}
X	} else {
X		redraw = false;
X		deleteline();
X			/* move first item from next to current */
X		if(curflr->next != (folder) NULL && curflr->name == curflr->next->name)
X			show_title(s1, FIRST+lines-1, curflr->next->mail);
X		for(p=curflr->next ; p != (folder) NULL && p->name == curflr->name ;
X																	p=p->next) {
X			tmp = p->mail;
X			p->mail = tmp->next;
X			if(p->mail == (item) NULL) { /* remove folder from list */
X				p->prev->next = p->next;
X				if(p->next != (folder) NULL)
X					p->next->prev = p->prev;
X					/* update page counts */
X				for(F=p->prev ; F != (folder) NULL && F->name == p->name
X																	; F=F->prev)
X					F->pages -= 1;
X			} else
X				p->mail->prev = (item) NULL;
X			p->prev->last->next = tmp;
X			tmp->prev = p->prev->last;
X			tmp->next = (item) NULL;
X			p->prev->last = tmp;
X		}
X			/* delete item from linked list of items */
X		if(m->prev == (item) NULL)
X			curflr->mail = m->next;
X		else
X			m->prev->next = m->next;
X		if(m->next == (item) NULL) {
X			curflr->last = m->prev;
X			curmail = m->prev;
X			y--;
X		} else {
X			m->next->prev = m->prev;
X			curmail = m->next;
X		}
X		move(y, 0);
X	}
X	if(! do_move) {
X		deleted.flr = f;
X		deleted.number = m->number;
X		sprintf(s1, "%s/%s/%d", mail_dir, f->name, m->number);
X		sprintf(s2, "%s/%s/#%d", mail_dir, f->name, m->number);
X		rename(s1, s2);
X		if(doexit) {
X			to_normal();
X			exit(0);
X		}
X	}
X	return(redraw);
X}
X
X
X/* --------------------
X	Create folder of given name if user agrees, creating directory and
X	entry in linked list of folders.
X-------------------- */
Xfolder
Xcreate_folder(str)
X	char	*str;
X{
X	struct stat statbuf;
X	char	str2[LEN], c;
X	folder	fnew, p, f;
X
X	squash(str);
X	sprintf(str2, "%s does not exist (or is empty) - create? ", str);
X	mvaddstr(STATUS, 0, str2); refresh();
X	c = getchar();
X	move(STATUS, 0); clrtoeol(); move(y, 0); refresh();
X	if(c != 'y')
X		return((folder) NULL);
X	for(p=(folder) NULL, f=folders ; f != (folder) NULL &&
X							strcmp(str, f->name) > 0 ; p=f, f=f->next)
X		;
X		/* create physical folder */
X	sprintf(str2, "%s/%s", mail_dir, str);
X	if(stat(str2, &statbuf)) {		/* doesn't exist */
X		if(mkdir(str2, folder_protect)) {
X			addstatus("Cannot make folder", true);
X			return((folder) NULL);
X		}
X	} else
X		if(!(statbuf.st_mode & S_IREAD) || !(statbuf.st_mode & S_IWRITE)
X									|| !(statbuf.st_mode & S_IEXEC)) {
X				addstatus("Cannot write in folder", true);
X				return((folder) NULL);
X		}
X			/* make a new folder record, insert it */
X	fnew = NEW(mail_folder);
X	fnew->name = NEWSTR(strlen(str)+1);
X	strcpy(fnew->name, str);
X	fnew->mail = fnew->last = (item) NULL;
X	fnew->next = fnew->prev = (folder) NULL;
X	fnew->pages = fnew->pagenum = 1;
X	fnew->valid = false;
X	if(p == (folder) NULL) {
X		fnew->next = folders;
X		folders->prev = fnew;
X		folders = fnew;
X	} else {
X		p->next = fnew;
X		if(f != (folder) NULL)
X			f->prev = fnew;
X		fnew->prev = p;
X		fnew->next = f;
X	}
X	return(fnew);
X}
X
X
X/* --------------------
X	Crude undo.  Sophisticated undo rather too painful to code.
X-------------------- */
Xvoid
Xundo()
X{
X	folder	f = deleted.flr, p;
X	char	s1[LEN], s2[LEN];
X
X	if(f == (folder) NULL) {
X		addstatus("nothing to undo", true);
X		return;
X	} else {
X		addstatus("undoing ...", true);
X		deleted.flr = (folder) NULL;
X	}
X	sprintf(s1, "%s/%s/#%d", mail_dir, f->name, deleted.number);
X	sprintf(s2, "%s/%s/%d", mail_dir, f->name, deleted.number);
X	rename(s1, s2);
X		/* find first page of folder */
X	p = f; FRST_OF_NAME(p);
X		/* find last page of folder */
X	LAST_OF_NAME(f);
X	curflr = p;
X	p->next = f->next;
X/* should free old folder/mail records */
X/*	p->valid = false; */
X	p->mail = p->last = (item) NULL;
X	p->pagenum = p->pages = 1;
X	if(p->next != (folder) NULL)
X		p->next->prev = p;
X	find_mail(curflr, false);
X	curmail = curflr->mail;
X	y = FIRST;
X	display_page();
X}
X
X
X/* --------------------
X	Pack current folder.  Unsets "deleted" if last removed record was
X	on current folder.
X-------------------- */
Xvoid
Xpack_folder()
X{
X	folder	f = curflr;
X	item	m;
X	char	path[LEN], s1[LEN], s2[LEN];
X	bool	found;
X	int		newnum = 1;
X
X	addstatus("Packing folder ...", false);
X	FRST_OF_NAME(f);
X		/* unset undo if packing that folder */
X	if(deleted.flr != (folder) NULL && deleted.flr->name == curflr->name)
X		deleted.flr = (folder) NULL;
X	sprintf(path, "%s/%s/", mail_dir, curflr->name);
X	for( ; f != (folder) NULL && f->name == curflr->name ; f=f->next)
X		for(m=f->mail ; m != (item) NULL ; m=m->next) {
X			for(found=false ; !found && newnum < m->number ; ) {
X				sprintf(s1, "%s%d", path, newnum);
X				if(! access(s1, R_OK))
X					newnum++;
X				else
X					found = true;
X			}
X			if(found) {
X				sprintf(s2, "%s%d", path, m->number);
X				rename(s2, s1);
X				m->number = newnum;
X			}
X		}
X	display_page();
X}
!The!End!

echo x - "page.c" 2>&1
sed "s/^X//" >"page.c" <<'!The!End!'
X/* --------------------
X	vmail -- page.c
X
X	Routines for paging between folders.
X
X	Copyright (C) J. Zobel, University of Melbourne, October 1987.
X-------------------- */
X
X#include "defs.h"
X
X/* --------------------
X	Clear screen, show current page of mail items.
X-------------------- */
Xvoid
Xdisplay_page()
X{
X	int		i;
X	item	mail;
X	char	str[LEN];
X
X	clear();
X	add_page_header(str);
X	for(i=FIRST, mail=curflr->mail ; mail != (item) NULL ; i++, mail=mail->next)
X		show_title(str, i, mail);
X	move(y, 0);
X	refresh();
X}
X
X
X/* --------------------
X	Find previous valid page of mail items (times count).
X-------------------- */
Xvoid
Xprev_page(count, display)
X	int		count;
X	bool	display;
X{
X	bool	show = false;
X	folder	f, p = curflr;
X
X	for( ; count > 0 ; count--) {
X		f = curflr->prev; PREV_VALID(f);
X		if(f == (folder) NULL)
X			break;
X		else {
X			show = true;
X			y = FIRST;
X			curflr = f;
X			curmail = curflr->mail;
X		}
X	}
X	if(p != curflr)
X		alternate = p;
X	if(display) {
X		if(show)
X			display_page();
X		if(f == (folder) NULL)
X			addstatus("first active page", true);
X	}
X}
X
X
X/* --------------------
X	Find next valid page of mail items (times count).
X-------------------- */
Xvoid
Xnext_page(count, display)
X	int		count;
X	bool	display;
X{
X	bool	show = false;
X	folder	f, p = curflr;
X
X	for( ; count > 0 ; count--) {
X		f = curflr->next; NEXT_VALID(f);
X		if(f == (folder) NULL)
X			break;
X		else {
X			show = true;
X			y = FIRST;
X			curflr = f;
X			curmail = curflr->mail;
X		}
X	}
X	if(p != curflr)
X		alternate = p;
X	if(display) {
X		if(show)
X			display_page();
X		if(f == (folder) NULL)
X			addstatus("last active page", true);
X	}
X}
X
X
X/* --------------------
X	Move cursor up count lines.
X-------------------- */
Xvoid
Xcursor_up(count)
X	int count;
X{
X	bool	redraw = false;
X
X	addstatus("", false);				/* clear status line */
X	for( ; count > 0 ; count--)
X		if(curmail->prev == (item) NULL) {
X			if(curflr->prev == (folder) NULL ||
X									curflr->name != curflr->prev->name) {
X				addstatus("first page of folder", true);
X				break;
X			} else {
X				redraw = true;
X				prev_page(1, false);
X					/* find last mail item on page */
X				for( ; curmail->next != (item) NULL ; y++)
X					curmail=curmail->next;
X			}
X		} else {
X			y--;
X			curmail = curmail->prev;
X		}
X	if(redraw)
X		display_page();
X	else {
X		move(y, 0);
X		refresh();
X	}
X}
X
X
X/* --------------------
X	Move cursor down count lines.
X-------------------- */
Xvoid
Xcursor_down(count)
X	int		count;
X{
X	bool	redraw = false;
X
X	addstatus("", false);				/* clear status line */
X	for( ; count > 0 ; count--)
X		if(curmail->next == (item) NULL) {
X			if(curflr->next == (folder) NULL ||
X									curflr->name != curflr->next->name) {
X				addstatus("last page of folder", true);
X				break;
X			} else {
X				redraw = true;
X				next_page(1, false);
X			}
X		} else {
X			y++;
X			curmail = curmail->next;
X		}
X	if(redraw)
X		display_page();
X	else {
X		move(y, 0);
X		refresh();
X	}
X}
X
X
X/* --------------------
X	Go to next folder, make active if not so already.
X-------------------- */
Xvoid
Xgoto_next_folder(count)
X	int		count;
X{
X	bool	changed = false;
X	folder	f, p = curflr;
X
X	for( ; count > 0 ; count--) {
X			/* skip other pages of current folder */
X		for(f=p ; p->next->name == f->name ; p=p->next)
X			;
X		if(p->next == (folder) NULL) {
X			addstatus("last folder", true);
X			break;
X		} else {
X			changed = true;
X			p = p->next;
X		}
X	}
X	if(changed) {
X		do {
X			if(!p->valid) {
X				p->valid = true;
X				addstatus("reading mail headers ...", true);
X				find_mail(p, false);
X			}
X			if(p->valid == EMPTY)
X				p = p->next;
X		} while(p != (folder) NULL && p->valid != true);
X		if(p == (folder) NULL)
X			addstatus("can't go forward that many folders", true);
X		else {
X			alternate = curflr;
X			curflr = p;
X			curmail = p->mail;
X			y = FIRST;
X			display_page();
X		}
X	}
X}
X
X
X/* --------------------
X	Goto previous folder, make active if not so already.
X-------------------- */
Xvoid
Xgoto_prev_folder(count)
X	int		count;
X{
X	bool	changed = false;
X	folder	f, p = curflr;
X
X	for( ; count > 0 ; count--) {
X			/* skip other pages of current folder */
X		for(f=p ; p->prev->name == f->name ; p=p->prev)
X			;
X		if(curflr->prev == (folder) NULL) {
X			addstatus("first folder", true);
X			break;
X		} else {
X			changed = true;
X			p = p->prev;
X		}
X	}
X	if(changed) {
X		do {
X			if(!p->valid) {
X				p->valid = true;
X				addstatus("reading mail headers ...", true);
X				find_mail(p, false);
X			}
X			if(p->valid == EMPTY)
X				p = p->prev;
X		} while(p != (folder) NULL && p->valid != true);
X		if(p == (folder) NULL)
X			addstatus("can't go back that many folders", true);
X		else {
X				/* skip other pages of current folder */
X			for(f=p ; p->prev->name == f->name ; p=p->prev)
X				;
X			alternate = curflr;
X			curflr = p;
X			curmail = p->mail;
X			y = FIRST;
X			display_page();
X		}
X	}
X}
X
X
X/* --------------------
X	Make new folder current as specified - read from terminal (get_name =
X	true) or use value in alternate (get_name = false).
X-------------------- */
Xvoid
Xgoto_folder(get_name)
X	bool	get_name;
X{
X	char	buf[LEN], *str = buf;
X	folder	f;
X	int		i;
X
X	if(get_name || alternate == (folder) NULL) {
X			/* no alternate folder */
X		get_string("folder? ", str);
X		for(i=0 ; *str == ' ' ; i++, str++)
X			;
X		for(; i < LEN && buf[i] != ' ' && buf[i] != '\0' ; i++)
X			;
X		if(i < LEN)
X			buf[i] = '\0';
X		GOTO_NAME(f, str);
X		if(f == (folder) NULL) {
X			addstatus("no such folder", true);
X			return;
X		}
X		if(!f->valid) {
X			f->valid = true;
X			addstatus("reading mail headers ...", true);
X			find_mail(f, false);
X			if(f->valid == EMPTY) {
X				sprintf(str, "%s -- folder empty", f->name);
X				addstatus(str, true);
X				return;
X			}
X		}
X	} else
X		f = alternate;
X	if(f == curflr)
X		addstatus("already in that folder", true);
X	else {
X		alternate = curflr;
X		curflr = f;
X		curmail = f->mail;
X		y = FIRST;
X		display_page();
X	}
X}
X
X
X/* --------------------
X	Go to first line of page, as offset by count.
X-------------------- */
Xvoid
Xcursor_first(count)
X	int		count;
X{
X	item	m = curflr->mail;
X
X	addstatus("", false);				/* clear status line */
X	for(y=FIRST ; m != (item) NULL && count > 1 ; count--, y++, m=m->next)
X		;
X	if(m == (item) NULL)
X		beep();
X	else {
X		curmail = m;
X		move(y, 0);
X		refresh();
X	}
X}
X
X
X/* --------------------
X	Go to middle line of page.
X-------------------- */
Xvoid
Xcursor_middle()
X{
X	item	m, p;
X	bool	tog = false;
X
X	addstatus("", false);				/* clear status line */
X		/* find middle of page - p traverses page at half speed */
X	for(y=FIRST, m=p=curflr->mail ; m != (item) NULL ; m=m->next, tog= !tog)
X		if(tog) {
X			y++;
X			p = p->next;
X		}
X	curmail = p;
X	move(y, 0);
X	refresh();
X}
X
X
X/* --------------------
X	Go to last line of page, as offset by count.
X-------------------- */
Xvoid
Xcursor_last(count)
X	int		count;
X{
X	item	m = curflr->mail;
X
X	addstatus("", false);				/* clear status line */
X		/* find last item, including y-value */
X	for(y=FIRST ; m->next != (item) NULL ; y++, m=m->next)
X		;
X	for(; m != (item) NULL && count > 1 ; count--, y--, m=m->prev)
X		;
X	if(m == (item) NULL)
X		beep();
X	else {
X		curmail = m;
X		move(y, 0);
X		refresh();
X	}
X}
X
X
X/* --------------------
X	Go to first page.  ***should use count as offset*** 
X-------------------- */
Xvoid
Xgoto_first_page()
X{
X	folder	f;
X
X	f = folders; NEXT_VALID(f);
X	if(f == (folder) NULL) {
X		/* no valid folders - bleah?! */
X		addstatus("no valid folders, exiting", true);
X		to_normal();
X		exit(1);
X	} else if(f != curflr) {
X		alternate = curflr;
X		curflr = f;
X		curmail = f->mail;
X		y = FIRST;
X		display_page();
X	}
X}
X
X
X/* --------------------
X	Go to last page.  ***should use count as offset***
X-------------------- */
Xvoid
Xgoto_last_page()
X{
X	folder	f, p;
X
X	for(f=folders ; f != (folder) NULL ; f=f->next)
X		if(f->valid)
X			p = f;
X	if(p == (folder) NULL) {
X		/* no valid folders - bleah?! */
X		addstatus("no valid folders, exiting", true);
X		to_normal();
X		exit(1);
X	} else if(p != curflr) {
X		alternate = curflr;
X		curflr = p;
X		curmail = p->mail;
X		y = FIRST;
X		display_page();
X	}
X}
!The!End!
exit