[alt.sources] Xkal 1.13 - Part03/05

ferguson@cs.rochester.edu (George Ferguson) (03/14/91)

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 3 (of 5)."
# Contents:  day.h db.c db.h edit-defaults.c edit-defaults.h edit.c
#   edit.h month.c month.h patchlevel.h resources.h strcasecmp.c
#   string-convert.c util.h xkal-automail
# Wrapped by ferguson@swan.cs.rochester.edu on Wed Mar 13 13:50:00 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'day.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'day.h'\"
else
echo shar: Extracting \"'day.h'\" \(1104 characters\)
sed "s/^X//" >'day.h' <<'END_OF_FILE'
X/*
X *	day.h : Data types and external defs for day stuff
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: day.h,v 2.1 91/02/28 11:21:12 ferguson Exp $
X */
X#ifndef DAY_H
X#define DAY_H
X
X#include "month.h"
X#include "db.h"
X
Xtypedef struct _DayTextData {
X	Widget time;		/* Label */
X	Widget text;		/* Text */
X} DayTextData;
Xtypedef struct _DayFormData {
X	int day,month,year;	/* who am i */
X	DayTextData **items;	/* widgets */
X	Widget date;		/* Label (if used) */
X	Widget form;		/* Form */
X	Widget shell;		/* Shell for popups */
X	DayButtonData *buttonData;	/* button pressed if any */
X	String *msgText;	/* original texts */
X	Msgrec **msg;		/* pointers to db entries */
X	Boolean changed;	/* any edit here? */
X	Boolean editing;	/* being edited? */
X} DayFormData;
X
Xextern DayFormData *createDayFormData(),*createPopupDayFormData();
Xextern void setDayFormData(),clearDayFormData();
Xextern void checkpointAppoints();
Xextern int timetopos();
Xextern void postotime();
X
X
Xextern DayFormData *currentDayFormData;
Xextern int numDisplayedAppoints;
X
X#endif /* DAY_H */
END_OF_FILE
if test 1104 -ne `wc -c <'day.h'`; then
    echo shar: \"'day.h'\" unpacked with wrong size!
fi
# end of 'day.h'
fi
if test -f 'db.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'db.c'\"
else
echo shar: Extracting \"'db.c'\" \(15341 characters\)
sed "s/^X//" >'db.c' <<'END_OF_FILE'
X/*
X *	db.c : Database routines for xkal.
X *	       Handles searching and updating the appointment graph.
X *	       See db.h for a description of the data structure.
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: db.c,v 2.2 91/03/13 13:31:16 ferguson Exp $
X *
X * The following can be used in place of the ones from X11/Intrinsic.h:
X *	#define XtNew(T)	(T *)malloc(sizeof(T))
X *	#define XtFree(X)	if ((X) != NULL) free(X)
X *	#define XtNewString(S)	strcpy(malloc(strlen(S)+1),S)
X *
X */
X#include <stdio.h>
X#include <string.h>
X#include <ctype.h>
X#include <errno.h>
X#include <sys/types.h>			/* for writeDb() backup only */
X#include <sys/stat.h>			/*  "	  "	    "	  "  */
X#include <X11/Intrinsic.h>
X#include "db.h"
X#include "util.h"
X#include "date-strings.h"
X#include "app-resources.h"		/* for levelDelim and daySlashMonth */
X#ifdef USE_ALERT
X#include "alert.h"
X#endif
Xextern char *program;
X
X/*
X * Functions defined here:
X */
Xvoid initDb();
XMsgrec *addEntry();
XBoolean deleteEntry();
Xint lookupEntry();
Xvoid readDb(), writeDb(), writeAppoint();
X
X/*
X * Data defined here (could be static but we might hack on the DB later,
X * like we do with xkal2xremind and the like).
X */
XYearrec *Db[8];
X
X/*	-	-	-	-	-	-	-	-	*/
X
X/*VARARGS1*/
Xstatic void
Xdprintf(fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9)
Xchar *fmt,*a1,*a2,*a3,*a4,*a5,*a6,*a7,*a8,*a9;
X{
X#ifdef debug
X    printf(fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9);
X#endif
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * initDb() : Pretty lame, and unneeded for most UNIX boxes, but I put
X *	it here anyway. We never erase the db, so we don't have to
X *	worry about this.
X */
Xvoid
XinitDb()
X{
X    int i;
X
X    for (i=0; i < 8; i++)
X	Db[i] = NULL;		/* should free them all */
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * addEntry() : Add an entry to the appropriate place in the database
X *	by traversing it and possibly adding nodes. Not much to it.
X */
XMsgrec *
XaddEntry(dow,year,mon,day,hour,mins,text,flag,level)
Xint dow,year,mon,day,hour,mins;
Xchar *text;
Xint flag,level;
X{
X    Yearrec *yp;
X    Monrec *mp;
X    Dayrec *dp;
X    Msgrec *xp;
X
X    if (Db[dow] == NULL) {
X	Db[dow] = XtNew(Yearrec);
X	Db[dow]->year = year;
X	Db[dow]->mons = NULL;
X	Db[dow]->next = NULL;
X    }
X    for (yp = Db[dow]; yp->year != year && yp->next != NULL; yp = yp->next) ;
X    if (yp->year != year) {
X	yp->next = XtNew(Yearrec);
X	yp->next->year = year;
X	yp->next->mons = NULL;
X	yp->next->next = NULL;
X	yp = yp->next;
X    }
X    if (yp->mons == NULL) {
X	yp->mons = XtNew(Monrec);
X	yp->mons->mon = mon;
X	yp->mons->days = NULL;
X	yp->mons->next = NULL;
X    }
X    for (mp = yp->mons; mp->mon != mon && mp->next != NULL; mp = mp->next) ;
X    if (mp->mon != mon) {
X	mp->next = XtNew(Monrec);
X	mp->next->mon = mon;
X	mp->next->days = NULL;
X	mp->next->next=NULL;
X	mp = mp->next;
X    }
X
X    if (mp->days == NULL) {
X	mp->days = XtNew(Dayrec);
X	mp->days->day = day;
X	mp->days->msgs = NULL;
X	mp->days->next = NULL;
X    }
X    for (dp = mp->days; dp->day != day && dp->next != NULL; dp = dp->next) ;
X    if (dp->day != day) {
X	dp->next = XtNew(Dayrec);
X	dp->next->day = day;
X	dp->next->msgs = NULL;
X	dp->next->next=NULL;
X	dp = dp->next;
X    }
X
X    if (dp->msgs == NULL) {
X	dp->msgs = XtNew(Msgrec);
X	dp->msgs->next = NULL;
X    } else {
X	xp = dp->msgs;
X	dp->msgs = XtNew(Msgrec);
X	dp->msgs->next = xp;
X    }
X    dp->msgs->dow = dow;
X    dp->msgs->year = year;
X    dp->msgs->month = mon;
X    dp->msgs->day = day;
X    dp->msgs->hour = hour;
X    dp->msgs->mins = mins;
X    dp->msgs->text = XtNewString(text);
X    dp->msgs->system = flag;
X    dp->msgs->level = level;
X    return(dp->msgs);
X}
X
X/*
X * deleteEntry() : Removes an entry matching the given parameters from
X *	the database, and tidies up afterwards. Note that everything must
X *	match, the text as well.
X */
XBoolean
XdeleteEntry(dow,year,mon,day,hour,mins,text)
Xint dow,year,mon,day,hour,mins;
Xchar *text;
X{
X    Yearrec *yp,*yp2;
X    Monrec *mp,*mp2;
X    Dayrec *dp,*dp2;
X    Msgrec *xp,*xp2;
X
X    for (yp = Db[dow]; yp != NULL && yp->year != year; yp = yp->next) ;
X    if (yp == NULL)
X	return(False);
X    for (mp = yp->mons; mp != NULL && mp->mon != mon; mp = mp->next) ;
X    if (mp == NULL)
X	return(False);
X    for (dp = mp->days; dp != NULL && dp->day != day; dp = dp->next) ;
X    if (dp == NULL)
X	return(False);
X    for (xp = dp->msgs; xp != NULL; xp = xp2) {
X	xp2 = xp->next;
X	if (xp->hour == hour && xp->mins == mins &&
X						strcmp(xp->text, text) == 0) {
X	    XtFree(xp->text);		/* free the text */
X	    if (dp->msgs == xp)		/* and the msgrec */
X		dp->msgs = xp->next;
X	    else {
X		for (xp2 = dp->msgs; xp2->next != xp; xp2 = xp2->next);
X		xp2->next = xp->next;
X	    }
X	    XtFree(xp);
X	    if (dp->msgs == NULL) {	/* if no more entries, free the day */
X		if (mp->days == dp)
X		    mp->days = dp->next;
X		else {
X		    for (dp2 = mp->days; dp2->next != dp; dp2 = dp2->next);
X		    dp2->next = dp->next;
X		}
X		XtFree(dp);
X	    }
X	    if (mp->days == NULL) {	/* if no more days, free the month */
X		if (yp->mons == mp)
X		    yp->mons = mp->next;
X		else {
X		    for (mp2 = yp->mons; mp2->next != mp; mp2 = mp2->next);
X		    mp2->next = mp->next;
X		}
X		XtFree(mp);
X	    }
X	    if (yp->mons == NULL) {	/* if no more months, free the year */
X		if (Db[dow] == yp)
X		    Db[dow] = Db[dow]->next;
X		else {
X		    for (yp2 = Db[dow]; yp2->next != yp; yp2 = yp2->next);
X		    yp2->next = yp->next;
X		}
X		XtFree(yp);
X	    }
X	    return(True);
X	}
X    }
X    return(False);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * lookupEntry() : Fills in at most max entries in result[] with pointers
X *	to messages for the given date/time. Returns the number of
X *	entries filled or -1 if there were more than max entries (but the
X *	ones that were filled, ie max of them, are valid). As a special
X *	case, if max is -1, then no entries are filled (result can be NULL)
X *	and the number of entries is returned. (If useLevel is True, then
X *	the total of the levels of all applicable appointments is returned
X *	rather than the number of appointments.)
X *
X *	The definition of a match is complicated by the fact that items in
X *	the database may be only partially specified. The approach is as
X *	follows:
X *	(a) First consider all db records which do not specify a dow (ie.
X *	    from Db[0]). Within these, the day, month, and year must match
X *	    if given, otherwise they are assumed to match. The hour and
X *	    minutes must match explicitly.
X *	(b) Then consider all db records specifying the dow of the given date.
X *	    If the record does not also specify a day, then the month and
X *	    year must match if given, as above, and the time must match
X *	    exactly. If the record specifies both dow and day, then the
X *	    interpretation is "the first dow after date", so we have to
X *	    check if the current date is within a week of the date in the
X *	    record.
X *
X *	Note that the strings are not copied.
X */
Xint
XlookupEntry(day,mon,year,hour,mins,max,result,useLevel)
Xint day,mon,year,hour,mins,max;
XMsgrec *result[];
X{
X    Yearrec *yp;
X    Monrec *mp;
X    Dayrec *dp;
X    int num,n,i;
X
X    dprintf("lookup for %d:%02d, %d %s %d...\n",hour,mins,day,
X						shortMonthStr[mon-1],year);
X    num = 0;
X    dprintf("  checking DB with dow = 0...\n");
X    for (yp = Db[0]; yp != NULL; yp = yp->next) {
X	dprintf("    year = %d\n", yp->year);
X	if (yp->year == year || yp->year == 0)
X	    for (mp = yp->mons; mp != NULL; mp = mp->next) {
X		dprintf("      mon = %d\n", mp->mon);
X		if (mp->mon == mon || mp->mon == 0)
X		    for (dp = mp->days; dp != NULL; dp = dp->next) {
X			dprintf("\tday = %d\n", dp->day);
X			if (dp->day == day || dp->day == 0) {
X			    n = lookupEntryDay(dp,hour,mins,max-num,
X							result+num,useLevel);
X			    if (n < 0)
X				return(-1);
X			    else
X				num += n;
X			}
X		}
X	}
X    }
X    dprintf("  checking DB with real dow...\n");
X    for (yp = Db[computeDOW(day,mon,year)]; yp != NULL; yp = yp->next) {
X	dprintf("    year = %d\n", yp->year);
X	for (mp = yp->mons; mp != NULL; mp = mp->next) {
X	    dprintf("      mon = %d\n", mp->mon);
X	    for (dp = mp->days; dp != NULL; dp = dp->next) {
X		dprintf("\tday = %d\n", dp->day);
X		if (dp->day == 0) {
X		    if ((yp->year == 0 || yp->year == year) &&
X			(mp->mon == 0 || mp->mon == mon)) {
X			n = lookupEntryDay(dp,hour,mins,max-num,
X							result+num,useLevel);
X			if (n < 0)
X			    return(-1);
X			else
X			    num += n;
X		    }
X		} else {	/* have dow and day */
X		    int d,m,y;
X
X		    d = day;
X		    m = mon;
X		    y = year;
X		    dprintf("\tscanning back from %d %d %d\n",d,m,y);
X		    for (i=0; i < 7; i++)
X			if (dp->day == d && (yp->year == 0 || yp->year == y) &&
X					    (mp->mon == 0 || mp->mon == m))
X			    break;
X			else
X			    prevDay(&d,&m,&y);
X		    if (i < 7) {
X			n = lookupEntryDay(dp,hour,mins,max-num,
X							result+num,useLevel);
X			if (n < 0)
X			    return(-1);
X			else
X			    num += n;
X		    }
X		}
X	    }
X	}
X    }
X    dprintf("returning %d\n", num);
X    return(num);
X}
X
X/*
X * lookupEntryDay() : Scans the list of messages associated with the
X *	given Dayrec and fills in at most max entries of result[] with
X *	pointers to the messages. Returns the number of entries filled in,
X *	or -1 if there were more entries than max (but all that were filled
X *	in, ie. max of them, are valid).
X *
X *	If the given hour or mins is -1, then it's assumed to match, making
X *	it easy to get all the entries for a day.
X */
Xstatic int
XlookupEntryDay(dp,hour,mins,max,result,useLevel)
XDayrec *dp;
Xint hour,mins,max;
XMsgrec *result[];
XBoolean useLevel;
X{
X    Msgrec *xp;
X    int num;
X
X    num = 0;
X    for (xp = dp->msgs; xp != NULL; xp = xp->next) {
X	dprintf("\t  h:m = %d:%02d\n",xp->hour,xp->mins);
X	if ((hour == -1 || xp->hour == -1 || xp->hour == hour) &&
X	    (mins == -1 || xp->mins == -1 || xp->mins == mins)) {
X	    dprintf("\t    YES\n");
X	    if (max >= 0 && num >= max)
X		return (-1);
X	    else if (max > 0)
X		result[num] = xp;
X	    if (useLevel)
X		num += xp->level;
X	    else
X		num += 1;
X	}
X    }
X    return(num);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * readDb() : Reads the given file into the database, setting the flag
X *	bit on new entries appropriately. This function understands
X *	comments starting with #, and the #include capability. It uses
X *	parseLine to actually decode the date spec.
X */
Xvoid
XreadDb(name,systemFlag)
Xchar *name;
Xint systemFlag;
X{
X    FILE *fp;
X    char *fname,buf[256];
X    int line,i,n,c;
X    int dow,y,m,d,h,mins,lev;
X    char *t;
X
X    fname = expandFilename(name);
X    if ((fp=fopen(fname,"r")) == NULL) {
X	if (errno != ENOENT)	/* appointment file may not exist */
X	    perror(fname);
X	return;
X    }
X    c = line = 0;
X    while (c != EOF) {
X	n = 0;
X	line += 1;
X	while (n < 255 && (c=getc(fp)) != EOF && c != '\n')
X	    buf[n++] = c;
X	buf[n] = '\0';
X	if (n == 0) {
X	    continue;
X	} else if (c != EOF && c != '\n') {
X	    fprintf(stderr,"%s: line %d too long, file %s\n",program,
X								line,fname);
X	    while ((c=getc(fp)) != EOF && c != '\n') ;
X	}
X	i = 0;
X	while (isspace(buf[i]))
X	    i += 1;
X	if (buf[i] == '#') {
X	    if (strncmp(buf+i+1,"include",7) == 0) {
X		i += 8;
X		while (isspace(buf[i]))
X		    i += 1;
X		readDb(buf+i,systemFlag);
X	    }
X	    continue;
X	}
X	parseLine(buf+i,&dow,&y,&m,&d,&h,&mins,&t,&lev);
X#ifdef debug
X	if (dow != 0)
X	    printf("%s ",shortDowStr[dow-1]);
X	if (d != 0)
X	    printf("%2d ",d);
X	if (m != 0)
X	    printf("%s ",shortMonthStr[m-1]);
X	if (y != 0)
X	    printf("%4d ",y);
X	if (h != -1)
X	    printf("%2d:%02d ",h,mins);
X	if (lev != 0)
X	    printf("@%d@ ",lev);
X	printf("%s\n",t);
X#endif
X	addEntry(dow,y,m,d,h,mins,t,systemFlag,lev);
X    }
X    fclose(fp);
X}
X
X/*
X * writeDb() : Writes any non-system database entries to the given file
X *	in the canonical format. If writeAll is True, then system entries are
X *	also written.
X */
Xvoid
XwriteDb(name,writeAll)
Xchar *name;
XBoolean writeAll;
X{
X    FILE *fp;
X    char *fname,*backup;
X    struct stat stbuf;
X    int dow;
X    Yearrec *yp;
X    Monrec *mp;
X    Dayrec *dp;
X    Msgrec *xp;
X
X    fname = expandFilename(name);
X    if (*appResources.backupExtension && stat(fname,&stbuf) == 0) {
X	backup = XtMalloc(strlen(fname)+strlen(appResources.backupExtension)+1);
X	strcpy(backup,fname);
X	strcat(backup,appResources.backupExtension);
X	(void)unlink(backup);
X	if (link(fname,backup) < 0) {
X#ifdef USE_ALERT
X	    alert("Error: Can't backup \"%s\"; save aborted.",fname);
X#else
X	    fprintf(stderr,"\007%s: can't backup \"%s\"; save aborted\n",program,fname);
X#endif
X	    XtFree(backup);
X	    return;
X	}
X	(void)unlink(fname);
X	XtFree(backup);
X    }
X    if ((fp=fopen(fname,"w")) == NULL) {
X	perror(fname);
X	return;
X    }
X    for (dow = 0; dow < 8; dow++)
X      for (yp = Db[dow]; yp != NULL; yp = yp->next)
X	for (mp = yp->mons; mp != NULL; mp = mp->next)
X	    for (dp = mp->days; dp != NULL; dp = dp->next)
X		for (xp = dp->msgs; xp != NULL; xp = xp->next)
X		    if (!xp->system || writeAll)
X			writeAppoint(fp,xp);
X
X    fclose(fp);
X}
X
X/*
X * writeAppoint(fp,msg) : Formats the msg onto the given stream.
X *
X * The following escapes are possible in appResources.outputFormat:
X *	%w	short form of day of the week
X *	%W	long form of day of the week
X *	%d	day
X *	%m	short form of month
X *	%M	long form of month
X *	%n	numeric form of month
X *	%y	year
X *	%Y	year modulo 100
X *	%t	time
X *	%l	criticality level
X *	%~	space iff previous pattern was printed
X *	%/	slash iff previous pattern was printed
X * Any other character is simply printed. The text of the appointment follows,
X * then a newline.
X */
Xvoid
XwriteAppoint(fp,msg)
XFILE *fp;
XMsgrec *msg;
X{
X    char *s;
X    Boolean flag;
X
X    s = appResources.outputFormat;
X    flag = False;
X    while (*s) {
X	if (*s == '%') {
X	    s += 1;
X	    switch (*s) {
X		case '\0': fprintf(fp,"%");
X			   break;
X		case '~': if (flag)
X			      fprintf(fp," ");
X			   break;
X		case '/': if (flag)
X			      fprintf(fp,"/");
X			   break;
X		case 'w': if (msg->dow > 0)
X			      fprintf(fp,"%s",shortDowStr[msg->dow-1]);
X			  flag = (msg->dow > 0);
X			  break;
X		case 'W': if (msg->dow > 0)
X			      fprintf(fp,"%s",longDowStr[msg->dow-1]);
X			  flag = (msg->dow > 0);
X			  break;
X		case 'd': if (msg->day > 0)
X			      fprintf(fp,"%d",msg->day);
X			  flag = (msg->day > 0);
X			  break;
X		case 'm': if (msg->month > 0)
X			      fprintf(fp,"%s",shortMonthStr[msg->month-1]);
X			  flag = (msg->month > 0);
X			  break;
X		case 'M': if (msg->month > 0)
X			      fprintf(fp,"%s",longMonthStr[msg->month-1]);
X			  flag = (msg->month > 0);
X			  break;
X		case 'n': if (msg->month > 0)
X			      fprintf(fp,"%d",msg->month);
X			  flag = (msg->month > 0);
X			  break;
X		case 'y': if (msg->year > 0)
X			      fprintf(fp,"%d",msg->year);
X			  flag = (msg->year > 0);
X			  break;
X		case 'Y': if (msg->year > 0)
X			      fprintf(fp,"%d",msg->year%100);
X			  flag = (msg->year > 0);
X			  break;
X		case 't': if (msg->hour != -1)
X			     fprintf(fp,"%s",timetostr(msg->hour*60+msg->mins));
X			  flag = (msg->hour != -1);
X			  break;
X		case 'l': if (msg->level > 0) {
X			      fprintf(fp,"%c",*appResources.levelDelim);
X			      fprintf(fp,"%d",msg->level);
X			      if (*(appResources.levelDelim+1))
X				  fprintf(fp,"%c",*(appResources.levelDelim+1));
X			      else
X				  fprintf(fp,"%c",*appResources.levelDelim);
X			  }
X			  flag = (msg->level > 0);
X			  break;
X		default: fprintf(fp,"%c",*s);
X	    } /* switch */
X	} else {
X	   fprintf(fp,"%c",*s);
X	}
X	if (*s)
X	    s += 1;
X    } /* while */
X    fprintf(fp,"%s\n",msg->text);
X}
END_OF_FILE
if test 15341 -ne `wc -c <'db.c'`; then
    echo shar: \"'db.c'\" unpacked with wrong size!
fi
# end of 'db.c'
fi
if test -f 'db.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'db.h'\"
else
echo shar: Extracting \"'db.h'\" \(1242 characters\)
sed "s/^X//" >'db.h' <<'END_OF_FILE'
X/*
X *	db.h : External defs for database users.
X *
X *      George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: db.h,v 2.1 91/02/28 11:21:15 ferguson Exp $
X */
X
X#ifndef DB_H
X#define DB_H
X
X/*
X * The appointment database is a hierarchical data structure. It has an
X * array indexed by dow (and 0 for unspecified dow) of year structures,
X * which in turn each contain a list of month structures, each containing
X * a list day structures, each containing a list of message structures
X * that store the actual reminders.
X *
X * The key to using incompletely-specified patterns is described in
X * lookupEntry().
X */
Xtypedef struct _msgrec {
X	int dow,year,month,day;		/* redundant, but useful */
X	int hour,mins;
X	char *text;
X	int system;
X	int level;
X	struct _msgrec *next;
X} Msgrec;
Xtypedef struct _dayrec {
X	int day;
X	Msgrec *msgs;
X	struct _dayrec *next;
X} Dayrec;
Xtypedef struct _monrec {
X	int mon;
X	Dayrec *days;
X	struct _monrec *next;
X} Monrec;
Xtypedef struct _yearrec {
X	int year;
X	Monrec *mons;
X	struct _yearrec *next;
X} Yearrec;
X
Xextern Yearrec *Db[8];
X
Xextern void initDb(), readDb(), writeDb();
Xextern Msgrec *addEntry();
Xextern Boolean deleteEntry();
Xextern int lookupEntry();
X
X#endif /* DB_H */
END_OF_FILE
if test 1242 -ne `wc -c <'db.h'`; then
    echo shar: \"'db.h'\" unpacked with wrong size!
fi
# end of 'db.h'
fi
if test -f 'edit-defaults.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'edit-defaults.c'\"
else
echo shar: Extracting \"'edit-defaults.c'\" \(9136 characters\)
sed "s/^X//" >'edit-defaults.c' <<'END_OF_FILE'
X/*
X *	edit-defaults.c : Modify default parameters
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Feb 1991.
X *
X *	$Id: edit-defaults.c,v 2.1 91/02/28 11:21:17 ferguson Exp $
X */
X#include <stdio.h>
X#include <X11/Intrinsic.h>
X#include <X11/Shell.h>
X#include <X11/StringDefs.h>
X#include <X11/Xaw/Form.h>
X#include <X11/Xaw/Command.h>
X#include <X11/Xaw/Label.h>
X#include <X11/Xaw/AsciiText.h>
X#include <X11/Xaw/Cardinals.h>
X#include "xkal.h"
X#include "month.h"
X#include "day.h"
X#include "app-resources.h"
X#ifdef USE_ALERT
X#include "alert.h"
X#endif
X
X/*
X * Functions declared in this file
X */
Xvoid editDefaults();
Xvoid focusNextDefaultsItem(),focusPrevDefaultsItem();
X
Xstatic void initDefaultsShell(),setDefaultsItems(),setText();
Xstatic void applyProc(),revertProc(),dismissProc();
X
X/*
X * Data declared in this file
X */
Xstatic Widget defaultsShell,defaultsForm;
Xstatic Widget applyButton,revertButton,dismissButton;
Xstatic Widget pAppsLabel,pAppsText,bakExtLabel,bakExtText,dateLabel,dateText;
Xstatic Widget numMonLabel,numMonText;
Xstatic Widget chkIntLabel,chkIntText,reaSilLabel,reaSilText;
Xstatic Widget defLevLabel,defLevText,levDelLabel,levDelText;
X
X#define ACTION_PROC(NAME)	void NAME(w,event,params,num_params) \
X					Widget w; \
X					XEvent *event; \
X					String *params; \
X					Cardinal *num_params;
X
X#define CALLBACK_PROC(NAME)	static void NAME(w,client_data,call_data) \
X					Widget w; \
X					caddr_t client_data,call_data;
X
X/*	-	-	-	-	-	-	-	-	*/
X/* External interface (action) procedure */
X/*
X * editDefaults() : Pops up (possibly creating) the defaults editor,
X *	and fills it with the information from the current values of
X *	the application defaults.
X */
X/*ARGSUSED*/
XACTION_PROC(editDefaults)
X{
X    if (defaultsShell == NULL)
X	initDefaultsShell();
X    setDefaultsItems();
X    XtPopup(defaultsShell,XtGrabNone);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Initialization procedures */
X/*
X * initDefaultsShell() : Create the popup defaults editor.
X */
Xstatic void
XinitDefaultsShell()
X{
X    defaultsShell = XtCreatePopupShell("defaultsShell",topLevelShellWidgetClass,
X							toplevel,NULL,ZERO);
X    defaultsForm = XtCreateManagedWidget("defaultsForm",formWidgetClass,
X						defaultsShell,NULL,ZERO);
X    applyButton = XtCreateManagedWidget("applyButton",commandWidgetClass,
X							defaultsForm,NULL,ZERO);
X    XtAddCallback(applyButton,"callback",applyProc,NULL);
X    revertButton = XtCreateManagedWidget("revertButton",commandWidgetClass,
X							defaultsForm,NULL,ZERO);
X    XtAddCallback(revertButton,"callback",revertProc,NULL);
X    dismissButton = XtCreateManagedWidget("dismissButton",commandWidgetClass,
X							defaultsForm,NULL,ZERO);
X    XtAddCallback(dismissButton,"callback",dismissProc,NULL);
X
X    pAppsLabel = XtCreateManagedWidget("personalAppointsLabel",labelWidgetClass,
X							defaultsForm,NULL,ZERO);
X    pAppsText = XtCreateManagedWidget("personalAppointsText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    bakExtLabel = XtCreateManagedWidget("backupExtensionLabel",labelWidgetClass,
X							defaultsForm,NULL,ZERO);
X    bakExtText = XtCreateManagedWidget("backupExtensionText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    dateLabel = XtCreateManagedWidget("dateLabel",labelWidgetClass,
X							defaultsForm,NULL,ZERO);
X    dateText = XtCreateManagedWidget("dateText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    numMonLabel = XtCreateManagedWidget("numMonthsLabel",labelWidgetClass,
X							defaultsForm,NULL,ZERO);
X    numMonText = XtCreateManagedWidget("numMonthsText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    chkIntLabel = XtCreateManagedWidget("checkpointIntervalLabel",
X				labelWidgetClass,defaultsForm,NULL,ZERO);
X    chkIntText = XtCreateManagedWidget("checkpointIntervalText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    reaSilLabel = XtCreateManagedWidget("rearrangeSilentlyLabel",
X				labelWidgetClass,defaultsForm,NULL,ZERO);
X    reaSilText = XtCreateManagedWidget("rearrangeSilentlyText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    defLevLabel = XtCreateManagedWidget("defaultLevelLabel",labelWidgetClass,
X							defaultsForm,NULL,ZERO);
X    defLevText = XtCreateManagedWidget("defaultLevelText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X    levDelLabel = XtCreateManagedWidget("levelDelimLabel",labelWidgetClass,
X							defaultsForm,NULL,ZERO);
X    levDelText = XtCreateManagedWidget("levelDelimText",
X				asciiTextWidgetClass,defaultsForm,NULL,ZERO);
X}
X
X/*
X * setDefaultsItems() : Sets the values in the defaults editor from the
X *	current state of the application resources.
X */
Xstatic void
XsetDefaultsItems()
X{
X    char buf[8];
X
X    setText(pAppsText,appResources.personalAppoints);
X    setText(bakExtText,appResources.backupExtension);
X    setText(dateText,appResources.date);
X    sprintf(buf,"%d",appResources.numMonths);
X    setText(numMonText,buf);
X    sprintf(buf,"%d",appResources.checkpointInterval);
X    setText(chkIntText,buf);
X    setText(reaSilText,appResources.rearrangeSilently ? "True" : "False");
X    sprintf(buf,"%d",appResources.defaultLevel);
X    setText(defLevText,buf);
X    setText(levDelText,appResources.levelDelim);
X}
X
X/*
X * setText() : Set the value of a text item.
X */
Xstatic void
XsetText(item,text)
XWidget item;
Xchar *text;
X{
X    Arg args[1];
X
X    XtSetArg(args[0],XtNstring,text);
X    XtSetValues(item,args,ONE);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Callback procedures */
X/*
X * applyProc() : Callback for apply button - Set the application resources
X *	from the items on the defaults editor panel. Some of these require
X *	special action when changed, and this routine does that.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(applyProc)
X{
X    Arg args[1];
X    char *s;
X    int n;
X
X    XtSetArg(args[0],XtNstring,&(appResources.personalAppoints));
X    XtGetValues(pAppsText,args,ONE);
X    XtSetArg(args[0],XtNstring,&(appResources.backupExtension));
X    XtGetValues(bakExtText,args,ONE);
X    XtSetArg(args[0],XtNstring,&s);
X    XtGetValues(dateText,args,ONE);
X    if (strcmp(s,appResources.date) != 0) {
X	appResources.date = XtNewString(s);		/* leak! */
X	parseDate(appResources.date,&currentDay,&currentMon,&currentYear);
X    }
X    XtSetArg(args[0],XtNstring,&s);
X    XtGetValues(numMonText,args,ONE);
X    if (strcmp(s,"1") != 0 && strcmp(s,"2") != 0 &&
X				strcmp(s,"3") != 0 && strcmp(s,"12") != 0) {
X#ifdef USE_ALERT
X	alert("Error: numMonths must be 1, 2, 3, or 12.");
X#else
X	fprintf(stderr,"\007%s: numMonths must be 1, 2, 3, or 12\n",program);
X#endif
X    } else if (n != appResources.numMonths) {
X	n = ONE;
X	setNumMonths((Widget)NULL,(XEvent *)NULL,&s,&n);
X    }
X    XtSetArg(args[0],XtNstring,&s);
X    XtGetValues(chkIntText,args,ONE);
X    n = atoi(s);
X    if (n != appResources.checkpointInterval) {
X	appResources.checkpointInterval = n;
X	XtRemoveTimeOut(timeoutId);
X	if (n > 0)
X	    timeoutId = XtAppAddTimeOut(app_con,(unsigned long)(n*60000),
X							    timeoutProc,NULL);
X    }
X    XtSetArg(args[0],XtNstring,&s);
X    XtGetValues(reaSilText,args,ONE);
X    n  = (strcasecmp(s,"True") == 0 || strcmp(s,"1") == 0);
X    if (n != appResources.rearrangeSilently) {
X	appResources.rearrangeSilently = n;
X	if (currentDayFormData != NULL) {
X	    checkpointAppoints(currentDayFormData);
X	    setDayFormData(currentDayFormData);
X	}
X    }
X    XtSetArg(args[0],XtNstring,&s);
X    XtGetValues(defLevText,args,ONE);
X    appResources.defaultLevel = atoi(s);
X    XtSetArg(args[0],XtNstring,&(appResources.levelDelim));
X    XtGetValues(levDelText,args,ONE);
X}
X
X/*
X * revertProc() : Callback for revert button - Reset the text strings to
X *      the way they were when editDefaults was called last. Does NOT undo
X *      any changed that were made.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(revertProc)
X{
X    setDefaultsItems();
X}
X
X/*
X * dismissProc() : Callback for dismiss button - Pop down the editor.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(dismissProc)
X{
X    XtPopdown(defaultsShell);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Action procedures */
X/*
X * focusNextDefaultstem() : Move input focus to next defaults editor item,
X *	wrapping at bottom.
X */
X/*ARGSUSED*/
XACTION_PROC(focusNextDefaultsItem)
X{
X    if (w == pAppsText)
X	w = bakExtText;
X    else if (w == bakExtText)
X	w = dateText;
X    else if (w == dateText)
X	w = numMonText;
X    else if (w == numMonText)
X	w = chkIntText;
X    else if (w == chkIntText)
X	w = reaSilText;
X    else if (w == reaSilText)
X	w = defLevText;
X    else if (w == defLevText)
X	w = levDelText;
X    else if (w == levDelText)
X	w = pAppsText;
X    else
X	return;
X    XSetInputFocus(display,XtWindow(w),RevertToParent,CurrentTime);
X}
X
X/*
X * focusNextDefaultsItem() : Move input focus to previous defaults editor item,
X *	wrapping at top.
X */
X/*ARGSUSED*/
XACTION_PROC(focusPrevDefaultsItem)
X{
X    if (w == pAppsText)
X	w = levDelText;
X    else if (w == bakExtText)
X	w = pAppsText;
X    else if (w == dateText)
X	w = bakExtText;
X    else if (w == numMonText)
X	w = dateText;
X    else if (w == chkIntText)
X	w = numMonText;
X    else if (w == reaSilText)
X	w = chkIntText;
X    else if (w == defLevText)
X	w = reaSilText;
X    else if (w == levDelText)
X	w = defLevText;
X    else
X	return;
X    XSetInputFocus(display,XtWindow(w),RevertToParent,CurrentTime);
X}
END_OF_FILE
if test 9136 -ne `wc -c <'edit-defaults.c'`; then
    echo shar: \"'edit-defaults.c'\" unpacked with wrong size!
fi
# end of 'edit-defaults.c'
fi
if test -f 'edit-defaults.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'edit-defaults.h'\"
else
echo shar: Extracting \"'edit-defaults.h'\" \(359 characters\)
sed "s/^X//" >'edit-defaults.h' <<'END_OF_FILE'
X/*
X *	edit.h : External defs for editing global defaults
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Feb 1991.
X *
X *	$Id: edit-defaults.h,v 2.1 91/02/28 11:21:18 ferguson Exp $
X */
X#ifndef EDIT_DEFAULTS_H
X#define EDIT_DEFAULTS_H
X
Xextern void editDefaults();
Xextern void focusNextDefaultsItem(), focusPrevDefaultsItem();
X
X#endif /* EDIT_DEFAULTS_H */
END_OF_FILE
if test 359 -ne `wc -c <'edit-defaults.h'`; then
    echo shar: \"'edit-defaults.h'\" unpacked with wrong size!
fi
# end of 'edit-defaults.h'
fi
if test -f 'edit.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'edit.c'\"
else
echo shar: Extracting \"'edit.c'\" \(11588 characters\)
sed "s/^X//" >'edit.c' <<'END_OF_FILE'
X/*
X *	edit.c : Edit an appointment in detail
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Feb 1991.
X *
X *	$Id: edit.c,v 2.1 91/02/28 11:21:20 ferguson Exp $
X */
X#include <stdio.h>
X#include <X11/Intrinsic.h>
X#include <X11/Shell.h>
X#include <X11/StringDefs.h>
X#include <X11/Xaw/Form.h>
X#include <X11/Xaw/Command.h>
X#include <X11/Xaw/Label.h>
X#include <X11/Xaw/AsciiText.h>
X#include <X11/Xaw/Cardinals.h>
X#include "xkal.h"
X#include "month.h"
X#include "day.h"
X#include "db.h"
X#include "util.h"
X#include "date-strings.h"
X#include "app-resources.h"			/* for notesLabel */
X#ifdef USE_ALERT
X#include "alert.h"
X#endif
X
X/*
X * Functions declared in this file
X */
Xvoid editAppoint();
Xvoid focusNextEditItem(),focusPrevEditItem();
X
Xstatic void initEditShell(),setEditItems(),setText();
Xstatic void applyProc(),revertProc(),deleteProc(),dismissProc();
Xstatic Boolean getCurrentEditMsg();
X
X/*
X * Data declared in this file
X */
Xstatic Widget editShell,editForm;
Xstatic Widget applyButton,revertButton,deleteButton,dismissButton;
Xstatic Widget dowLabel,dowText,dayLabel,dayText,monLabel,monText;
Xstatic Widget yearLabel,yearText,timeLabel,timeText;
Xstatic Widget textLabel,textText,levelLabel,levelText;
Xstatic DayFormData *currentEditFormData;
Xstatic Msgrec currentEditMsg;
X
X#define ACTION_PROC(NAME)	void NAME(w,event,params,num_params) \
X					Widget w; \
X					XEvent *event; \
X					String *params; \
X					Cardinal *num_params;
X
X#define CALLBACK_PROC(NAME)	static void NAME(w,client_data,call_data) \
X					Widget w; \
X					caddr_t client_data,call_data;
X
X/*	-	-	-	-	-	-	-	-	*/
X/* External interface (action) procedure */
X/*
X * editAppoint() : Pops up (possibly creating) the appointment editor,
X *	and fills it with the information from the appointment corresponding
X *	to the text widget taht this action is invoked from.
X */
X/*ARGSUSED*/
XACTION_PROC(editAppoint)
X{
X    Arg args[1];
X    int i,type;
X
X    if (currentDayFormData == NULL)
X	return;
X    for (i=0; i < numDisplayedAppoints; i++)
X	if (w == currentDayFormData->items[i]->time ||
X	    w == currentDayFormData->items[i]->text)
X	    break;
X    if (i == numDisplayedAppoints)
X	return;
X    if (w == currentDayFormData->items[i]->time)
X	w = currentDayFormData->items[i]->text;
X    XtSetArg(args[0],XtNeditType,&type);
X    XtGetValues(w,args,ONE);
X    if (type != XawtextEdit) {
X	XBell(display,0);
X	return;
X    }
X    if (editShell == NULL)
X	initEditShell();
X    checkpointAppoints(currentDayFormData);
X    currentEditFormData = currentDayFormData;
X    currentEditFormData->editing = True;
X    if (currentEditFormData->msg[i] == NULL) {
X	currentEditMsg.dow = 0;
X	currentEditMsg.day = currentEditFormData->day;
X	currentEditMsg.month = currentEditFormData->month;
X	currentEditMsg.year = currentEditFormData->year;
X	postotime(i,&(currentEditMsg.hour),&(currentEditMsg.mins));
X	currentEditMsg.text = "";
X	currentEditMsg.level = appResources.defaultLevel;
X    } else {
X	currentEditMsg.dow = currentEditFormData->msg[i]->dow;
X	currentEditMsg.day = currentEditFormData->msg[i]->day;
X	currentEditMsg.month = currentEditFormData->msg[i]->month;
X	currentEditMsg.year = currentEditFormData->msg[i]->year;
X	currentEditMsg.hour = currentEditFormData->msg[i]->hour;
X	currentEditMsg.mins = currentEditFormData->msg[i]->mins;
X	currentEditMsg.text = XtNewString(currentEditFormData->msg[i]->text);
X	currentEditMsg.level = currentEditFormData->msg[i]->level;
X    }
X    setEditItems(&currentEditMsg);
X    XtPopup(editShell,XtGrabNone);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Initialization procedures */
X
X/*
X * initEditShell() : Create the popup appointment editor.
X */
Xstatic void
XinitEditShell()
X{
X    editShell = XtCreatePopupShell("editShell",topLevelShellWidgetClass,
X							toplevel,NULL,ZERO);
X    editForm = XtCreateManagedWidget("editForm",formWidgetClass,
X							editShell,NULL,ZERO);
X    applyButton = XtCreateManagedWidget("applyButton",commandWidgetClass,
X							editForm,NULL,ZERO);
X    XtAddCallback(applyButton,"callback",applyProc,NULL);
X    revertButton = XtCreateManagedWidget("revertButton",commandWidgetClass,
X							editForm,NULL,ZERO);
X    XtAddCallback(revertButton,"callback",revertProc,NULL);
X    deleteButton = XtCreateManagedWidget("deleteButton",commandWidgetClass,
X							editForm,NULL,ZERO);
X    XtAddCallback(deleteButton,"callback",deleteProc,NULL);
X    dismissButton = XtCreateManagedWidget("dismissButton",
X					commandWidgetClass,editForm,NULL,ZERO);
X    XtAddCallback(dismissButton,"callback",dismissProc,NULL);
X    dowLabel = XtCreateManagedWidget("dowLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    dowText = XtCreateManagedWidget("dowText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    dayLabel = XtCreateManagedWidget("dayLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    dayText = XtCreateManagedWidget("dayText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    monLabel = XtCreateManagedWidget("monLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    monText = XtCreateManagedWidget("monText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    yearLabel = XtCreateManagedWidget("yearLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    yearText = XtCreateManagedWidget("yearText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    timeLabel = XtCreateManagedWidget("timeLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    timeText = XtCreateManagedWidget("timeText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    textLabel = XtCreateManagedWidget("textLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    textText = XtCreateManagedWidget("textText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    levelLabel = XtCreateManagedWidget("levelLabel",labelWidgetClass,
X							editForm,NULL,ZERO);
X    levelText = XtCreateManagedWidget("levelText",asciiTextWidgetClass,
X							editForm,NULL,ZERO);
X    XtRealizeWidget(editShell);		/* so we can set titlebar */
X}
X
X/*
X * setEditItems() : Sets the values in the appointment editor text items
X *	to values ffrom the given appoint.
X */
Xstatic void
XsetEditItems(msg)
XMsgrec *msg;
X{
X    char buf[8];
X
X    if (msg->dow == 0)
X	setText(dowText,"");
X    else
X	setText(dowText,shortDowStr[msg->dow-1]);
X    if (msg->day == 0)
X	setText(dayText,"");
X    else {
X	sprintf(buf,"%d",msg->day);
X	setText(dayText,buf);
X    }
X    if (msg->month == 0)
X	setText(monText,"");
X    else
X	setText(monText,shortMonthStr[msg->month-1]);
X    if (msg->year == 0)
X	setText(yearText,"");
X    else {
X	sprintf(buf,"%d",msg->year);
X	setText(yearText,buf);
X    }
X    if (msg->hour == -1)
X	setText(timeText,appResources.notesLabel);
X    else
X	setText(timeText,timetostr(msg->hour*60+msg->mins));
X    setText(textText,msg->text);
X    sprintf(buf,"%d",msg->level);
X    setText(levelText,buf);
X}
X
X/*
X * setText() : Set the given text item's value to the given string.
X */
Xstatic void
XsetText(item,text)
XWidget item;
Xchar *text;
X{
X    Arg args[1];
X
X    XtSetArg(args[0],XtNstring,text);
X    XtSetValues(item,args,ONE);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Callback procedures */
X
X/*
X * applyProc() : Callback for apply button - Add an entry with the fields
X *	given in the editor panel to the DB. Removes any other that matches
X *	exactly (including the text). Resets the month display to reflect
X *	the change (and the day within the month).
X */
X/*ARGSUSED*/
XCALLBACK_PROC(applyProc)
X{
X    Msgrec msg;
X
X    if (!getCurrentEditMsg(&msg))
X	return;
X    deleteEntry(msg.dow,msg.year,msg.month,msg.day,msg.hour,msg.mins,msg.text);
X    addEntry(msg.dow,msg.year,msg.month,msg.day,msg.hour,msg.mins,msg.text,
X							    False,msg.level);
X    appointsChanged = True;
X    setDayFormData(currentEditFormData,currentEditFormData->day,
X					currentEditFormData->month,
X					currentEditFormData->year);
X    shadeButton(currentEditFormData->buttonData,(GC)NULL,0,0);
X}
X
X/*
X * revertProc() : Callback for revert button - Reset the text strings to
X *	the way they were when editAppoint was called last. Does NOT undo
X *	any changed that were made.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(revertProc)
X{
X    setEditItems(&currentEditMsg);
X}
X
X/*
X * deleteProc() : Callback for delete button - Removes any appoint that
X *	exactly matches the items on the editor panel. Updates the month
X *	display to reflect the change.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(deleteProc)
X{
X    Msgrec msg;
X
X    checkpointAppoints(currentEditFormData);
X    if (!getCurrentEditMsg(&msg))
X	return;
X    if (deleteEntry(msg.dow,msg.year,msg.month,msg.day,msg.hour,msg.mins,
X								msg.text)) {
X	appointsChanged = True;
X	setDayFormData(currentEditFormData,currentEditFormData->day,
X					currentEditFormData->month,
X					currentEditFormData->year);
X	shadeButton(currentEditFormData->buttonData,(GC)NULL,0,0);
X    }
X}
X
X/*
X * dismissProc() : Callback for dismiss button - Pop down the editor.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(dismissProc)
X{
X    currentEditFormData->editing = False;
X    XtFree(currentEditMsg.text);
X    XtPopdown(editShell);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Action procedures */
X/*
X * focusNextEditItem() : Move input focus to next editor item, wrapping
X *	at bottom.
X */
X/*ARGSUSED*/
XACTION_PROC(focusNextEditItem)
X{
X    if (w == dowText)
X	w = dayText;
X    else if (w == dayText)
X	w = monText;
X    else if (w == monText)
X	w = yearText;
X    else if (w == yearText)
X	w = timeText;
X    else if (w == timeText)
X	w = textText;
X    else if (w == textText)
X	w = levelText;
X    else if (w == levelText)
X	w = dowText;
X    else
X	return;
X    XSetInputFocus(display,XtWindow(w),RevertToParent,CurrentTime);
X}
X
X/*
X * focusPrevEditItem() : Move input focus to previous editor item, wrapping
X *	at top.
X */
X/*ARGSUSED*/
XACTION_PROC(focusPrevEditItem)
X{
X    if (w == dowText)
X	w = levelText;
X    else if (w == dayText)
X	w = dowText;
X    else if (w == monText)
X	w = dayText;
X    else if (w == yearText)
X	w = monText;
X    else if (w == timeText)
X	w = yearText;
X    else if (w == textText)
X	w = timeText;
X    else if (w == levelText)
X	w = textText;
X    else
X	return;
X    XSetInputFocus(display,XtWindow(w),RevertToParent,CurrentTime);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * getCurrentEditMsg() : Fills in the given Msgrec with values taken from
X *	the editor text items.
X */
Xstatic Boolean
XgetCurrentEditMsg(msg)
XMsgrec *msg;
X{
X    Arg args[1];
X    char *s;
X    int i,t;
X
X    XtSetArg(args[0],XtNstring,&s);
X    XtGetValues(dowText,args,ONE);
X    if (!*s)
X	msg->dow = 0;
X    else {
X	for (i=0; i < 7; i++)
X	    if (strcasecmp(s,longDowStr[i]) == 0 ||
X		strcasecmp(s,shortDowStr[i]) == 0)
X		break;
X	if (i == 7) {
X#ifdef USE_ALERT
X	    alert("Error: bad dow string: \"%s\".",s);
X#else
X	    fprintf(stderr,"\007%s: bad dow string: \"%s\"\n",program,s);
X#endif
X	    return(False);
X	} else
X	    msg->dow = i+1;
X    }
X    XtGetValues(dayText,args,ONE);
X    msg->day = atoi(s);			/* no checking! */
X    XtGetValues(monText,args,ONE);
X    if (!*s)
X	msg->month = 0;
X    else {
X	for (i=0; i < 7; i++)
X	    if (strcasecmp(s,longMonthStr[i]) == 0 ||
X		strcasecmp(s,shortMonthStr[i]) == 0)
X		break;
X	if (i == 7) {
X#ifdef USE_ALERT
X	    alert("Error: bad month string: \"%s\".",s);
X#else
X	    fprintf(stderr,"\007%s: bad month string: \"%s\"\n",program,s);
X#endif
X	    return(False);
X	} else
X	    msg->month = i+1;
X    }
X    XtGetValues(yearText,args,ONE);
X    msg->year = atoi(s);			/* no checking! */
X    XtGetValues(timeText,args,ONE);
X    if (!*s || strcasecmp(s,appResources.notesLabel) == 0)
X	msg->hour = msg->mins = -1;
X    else {
X	t = strtotime(s);
X	msg->hour = t / 60;
X	msg->mins = t % 60;
X    }
X    XtGetValues(textText,args,ONE);
X    msg->text = s;
X    XtGetValues(levelText,args,ONE);
X    msg->level = atoi(s);			/* no checking! */
X    return(True);
X}
END_OF_FILE
if test 11588 -ne `wc -c <'edit.c'`; then
    echo shar: \"'edit.c'\" unpacked with wrong size!
fi
# end of 'edit.c'
fi
if test -f 'edit.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'edit.h'\"
else
echo shar: Extracting \"'edit.h'\" \(324 characters\)
sed "s/^X//" >'edit.h' <<'END_OF_FILE'
X/*
X *	edit.h : External defs for the popup appointment editor panel
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Feb 1991.
X *
X *	$Id: edit.h,v 2.1 91/02/28 11:21:21 ferguson Exp $
X */
X#ifndef EDIT_H
X#define EDIT_H
X
Xextern void editAppoint();
Xextern void focusNextEditItem(),focusPrevEditItem();
X
X#endif /* EDIT_H */
END_OF_FILE
if test 324 -ne `wc -c <'edit.h'`; then
    echo shar: \"'edit.h'\" unpacked with wrong size!
fi
# end of 'edit.h'
fi
if test -f 'month.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'month.c'\"
else
echo shar: Extracting \"'month.c'\" \(6947 characters\)
sed "s/^X//" >'month.c' <<'END_OF_FILE'
X/*
X *	month.c : Widget creation and action binding for the month
X *		  forms, and their sub-widgets.
X *
X *      George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: month.c,v 2.1 91/02/28 11:21:22 ferguson Exp $
X */
X#include <X11/Intrinsic.h>
X#include <X11/Shell.h>
X#include <X11/StringDefs.h>
X#include <X11/Xaw/Form.h>
X#include <X11/Xaw/Command.h>
X#include <X11/Xaw/Toggle.h>
X#include <X11/Xaw/Label.h>
X#include <X11/Xaw/AsciiText.h>
X#include <X11/Xaw/Cardinals.h>
X#include "xkal.h"
X#include "month.h"
X#include "day.h"
X#include "db.h"
X#include "app-resources.h"
X#include "date-strings.h"
X
X/*
X * Functions defined in this file
X */
XMonthFormData *createMonthFormData();
Xvoid setMonthFormData();
Xvoid selectDay();
Xvoid shadeButton();
X
Xstatic void dayButtonCallbackProc();
Xstatic void getGCAndXY();
X
X/*
X * Data defined in this file
X */
Xstatic Widget radioGroup;
X
X#define CALLBACK_PROC(NAME)     static void NAME(w,client_data,call_data) \
X					Widget w; \
X					caddr_t client_data,call_data;
X
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * createMonthFormData() : Returns a monthFormData whose Form is unmanaged.
X */
XMonthFormData *
XcreateMonthFormData(parent,name,num)
XWidget parent;
Xchar *name;
Xint num;		/* 1,2,3,12 */
X{
X    MonthFormData *m;
X    DayButtonData *d;
X    Widget widget;
X    Arg args[3];
X    char text[16];
X    int row,col,index;
X    int w,h;
X
X    m = XtNew(MonthFormData);
X    m->form = XtCreateWidget(name,formWidgetClass,parent,NULL,ZERO);
X    switch (num) {
X	case 1: w = appResources.dateWidth1;
X		h = appResources.dateHeight1;
X		break;
X	case 2: w = appResources.dateWidth2;
X		h = appResources.dateHeight2;
X		break;
X	case 3: w = appResources.dateWidth3;
X		h = appResources.dateHeight3;
X		break;
X	case 12: w = appResources.dateWidth12;
X		 h = appResources.dateHeight12;
X		 break;
X    }
X    sprintf(text,"monthLabel%d",num);
X    m->label = XtCreateManagedWidget(text,labelWidgetClass,m->form,NULL,ZERO);
X    if (appResources.dowLabels) {
X	sprintf(text,"dowLabel%d",num);
X	XtSetArg(args[0],XtNfromVert,m->label);
X	XtSetArg(args[1],XtNfromHoriz,NULL);
X	index = appResources.dowOffset;
X	for (col=0; col < 7; col++) {
X	    XtSetArg(args[2],XtNlabel,shortDowStr[index]);
X	    widget = XtCreateManagedWidget(text,labelWidgetClass,m->form,
X								args,THREE);
X	    index = (index == 6) ? 0 : index+1;
X	    XtSetArg(args[1],XtNfromHoriz,widget);
X	}
X	XtSetArg(args[0],XtNfromVert,widget);
X    } else {
X	XtSetArg(args[0],XtNfromVert,m->label);
X    }
X    sprintf(text,"dayButton%d",num);
X    for (row=0; row < 6; row++) {
X	XtSetArg(args[1],XtNfromHoriz,NULL);
X	for (col=0; col < 7; col++) {
X	    d = m->days[row*7+col] = XtNew(DayButtonData);
X	    d->button = XtCreateManagedWidget(text,toggleWidgetClass,
X							m->form,args,TWO);
X	    XtAddCallback(d->button,"callback",dayButtonCallbackProc,d);
X	    XtSetArg(args[2],XtNradioData,d->button);
X	    XtSetValues(d->button,args+2,ONE);
X	    if (radioGroup == NULL)
X		radioGroup = d->button;
X	    XawToggleChangeRadioGroup(d->button,radioGroup);
X	    d->pixmap = XCreatePixmap(display,root,w,h,
X						DefaultDepthOfScreen(screen));
X
X	    XtSetArg(args[1],XtNfromHoriz,d->button);
X	}
X        XtSetArg(args[0],XtNfromVert,d->button);
X    }
X    return(m);
X}
X
X/*
X * setMonthFormData() : Draw the individual days in the month, including
X *	the date and shading for criticality.
X */
Xvoid
XsetMonthFormData(m,num,month,year)
XMonthFormData *m;
Xint num,month,year;
X{
X    DayButtonData *d;
X    Arg args[1];
X    GC gc;
X    char text[16];
X    int first,numDays;
X    int i,x,y;
X
X    getGCAndXY(num,&gc,&x,&y);
X    XawFormDoLayout(m->form,False);
X    XawToggleUnsetCurrent(radioGroup);
X    sprintf(text,"%s %d",longMonthStr[month-1],year);
X    XtSetArg(args[0],XtNlabel,text);
X    XtSetValues(m->label,args,ONE);
X    first = firstDOW(month,year)-1-appResources.dowOffset;
X    numDays = lastDay(month,year);
X    for (i=0; i < 42; i++) {
X	d = m->days[i];
X	if (i < first || i >= first+numDays) {
X	    d->day = 0;
X	    XFillRectangle(display,d->pixmap,emptyGC,0,0,50,50);
X	    XtSetArg(args[0],XtNbitmap,d->pixmap);
X	    XtSetValues(d->button,args,ONE);
X	} else {
X	    d->day = i-first+1;
X	    d->month = month;
X	    d->year = year;
X	    shadeButton(d,gc,x,y);
X	}
X    }
X    XawFormDoLayout(m->form,True);
X    m->month = month;
X    m->year = year;
X}
X
X/*
X * setGCAndXY() : Depending on num, parses the appropriate geometry
X *	string into *xp and *yp, and puts the correct GC* into gcp.
X *	This function is here so it can be outside the loop in
X *	setMonthFormData(), but really belongs in shadeButton.
X */
Xstatic void
XgetGCAndXY(num,gcp,xp,yp)
XGC *gcp;
Xint *xp,*yp;
X{
X    int w,h;
X
X    switch (num) {
X	case 1: XParseGeometry(appResources.datePosition1,xp,yp,&w,&h);
X		*gcp = dateGC1;
X		break;
X	case 2: XParseGeometry(appResources.datePosition2,xp,yp,&w,&h);
X		*gcp = dateGC2;
X		break;
X	case 3: XParseGeometry(appResources.datePosition3,xp,yp,&w,&h);
X		*gcp = dateGC3;
X		break;
X	case 12: XParseGeometry(appResources.datePosition12,xp,yp,&w,&h);
X		 *gcp = dateGC12;
X		 break;
X    }
X}
X
X/*
X * shadeButton() : Shades a dayButton. This is global so it can be called
X *	after the appointments have changed without redrawing everything.
X */
Xvoid
XshadeButton(d,gc,x,y)
XDayButtonData *d;
XGC gc;
Xint x,y;
X{
X    Arg args[1];
X    char text[4];
X    int n;
X
X    if (gc == (GC)NULL)
X	getGCAndXY(appResources.numMonths,&gc,&x,&y);
X    n = lookupEntry(d->day,d->month,d->year,-1,-1,-1,NULL,True);
X    if (n > appResources.maxLevel)
X	n = appResources.maxLevel;
X    XFillRectangle(display,d->pixmap,shadeGC[n],0,0,
X			appResources.dateWidth1,appResources.dateHeight1);
X    sprintf(text,"%d",d->day);
X    if (appResources.opaqueDates)
X	XDrawImageString(display,d->pixmap,gc,x,y,text,strlen(text));
X    else
X	XDrawString(display,d->pixmap,gc,x,y,text,strlen(text));
X    XtSetArg(args[0],XtNbitmap,d->pixmap);
X    XtSetValues(d->button,args,ONE);
X}
X
X/*
X * selectDay() : Used to highlight a day (usually on startup or from
X *	the "today" action) without the user having clicked on it.
X */
Xvoid
XselectDay(m,day,mon,year)
XMonthFormData *m;
Xint day,mon,year;
X{
X    int which;
X
X    which = firstDOW(mon,year)-appResources.dowOffset+day-2;
X    XawToggleSetCurrent(radioGroup,m->days[which]->button);
X}
X
X/*
X * dayButtonCallbackProc() : The callback when a day is clicked on (or
X *	at least when the "notify" action is invoked). Pops up a new
X *	dayForm is there isn't currently one, and sets it or the current
X *	one to the new day's appoints.
X */
X/*ARGSUSED*/
XCALLBACK_PROC(dayButtonCallbackProc)
X{
X    DayButtonData *d = (DayButtonData *)client_data;
X
X    if (d->day == 0)
X	return;
X    if (currentDayFormData == NULL)
X	currentDayFormData = createPopupDayFormData();
X    else
X	checkpointAppoints(currentDayFormData);
X    currentDayFormData->buttonData = d;
X    if (XawToggleGetCurrent(radioGroup) != NULL)
X	setDayFormData(currentDayFormData,d->day,d->month,d->year);
X    else
X	clearDayFormData(currentDayFormData);
X}
END_OF_FILE
if test 6947 -ne `wc -c <'month.c'`; then
    echo shar: \"'month.c'\" unpacked with wrong size!
fi
# end of 'month.c'
fi
if test -f 'month.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'month.h'\"
else
echo shar: Extracting \"'month.h'\" \(637 characters\)
sed "s/^X//" >'month.h' <<'END_OF_FILE'
X/*
X *	month.h : Data types and external defs for month stuff
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: month.h,v 2.1 91/02/28 11:21:24 ferguson Exp $
X */
X#ifndef MONTH_H
X#define MONTH_H
X
Xtypedef struct _DayButtonData {
X	int day,month,year;
X	Pixmap pixmap;
X	Widget button;		/* Command */
X} DayButtonData;
Xtypedef struct _MonthFormData {
X	int month,year;
X	DayButtonData *days[42];
X	Widget label;		/* Label */
X	Widget form;		/* Form */
X} MonthFormData;
X
Xextern MonthFormData *createMonthFormData();
Xextern void setMonthFormData();
Xextern void selectDay();
X
X#endif /* MONTH_H */
END_OF_FILE
if test 637 -ne `wc -c <'month.h'`; then
    echo shar: \"'month.h'\" unpacked with wrong size!
fi
# end of 'month.h'
fi
if test -f 'patchlevel.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'patchlevel.h'\"
else
echo shar: Extracting \"'patchlevel.h'\" \(135 characters\)
sed "s/^X//" >'patchlevel.h' <<'END_OF_FILE'
X/*
X *	patchlevel.h - Version information
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 13 Mar 1991.
X */
X
X#define XKAL_VERSION	1.13
END_OF_FILE
if test 135 -ne `wc -c <'patchlevel.h'`; then
    echo shar: \"'patchlevel.h'\" unpacked with wrong size!
fi
# end of 'patchlevel.h'
fi
if test -f 'resources.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'resources.h'\"
else
echo shar: Extracting \"'resources.h'\" \(569 characters\)
sed "s/^X//" >'resources.h' <<'END_OF_FILE'
X/*
X *	resources.h: Resource loading code and definitions.
X *
X *	Most other modules should include app-resources.h for access to
X *	the resources after they've been loaded.
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Feb 1991.
X *
X *	$Id: resources.h,v 2.2 91/03/13 13:31:35 ferguson Exp $
X *
X */
X#ifndef RESOURCES_H
X#define RESOURCES_H
X
Xextern XrmOptionDescRec xkalOptions[15];	/* cmd line options */
Xextern XtResource xkalResources[53];		/* applications resources */
X
Xextern void getResources();		/* load resources without display */
X
X#endif /* RESOURCES_H */
END_OF_FILE
if test 569 -ne `wc -c <'resources.h'`; then
    echo shar: \"'resources.h'\" unpacked with wrong size!
fi
# end of 'resources.h'
fi
if test -f 'strcasecmp.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'strcasecmp.c'\"
else
echo shar: Extracting \"'strcasecmp.c'\" \(405 characters\)
sed "s/^X//" >'strcasecmp.c' <<'END_OF_FILE'
X/*
X *	strcasecmp(s,t): Just like strcmp(3) but ignores case.
X *
X *	Adapted from strcmp() in K&&R, page 102.
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 12 Feb 1991.
X */
X
Xint
Xstrcasecmp(s,t)
Xchar *s,*t;
X{
X    for ( ; *s == *t ||
X	    (*s >= 'A' && *s <= 'Z' && *s-'A'+'a' == *t) ||
X	    (*t >= 'A' && *t <= 'Z' && *t-'A'+'a' == *s) ; s++, t++)
X	if (*s == '\0')
X	    return(0);
X    return(*s - *t);
X}
END_OF_FILE
if test 405 -ne `wc -c <'strcasecmp.c'`; then
    echo shar: \"'strcasecmp.c'\" unpacked with wrong size!
fi
# end of 'strcasecmp.c'
fi
if test -f 'string-convert.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'string-convert.c'\"
else
echo shar: Extracting \"'string-convert.c'\" \(1158 characters\)
sed "s/^X//" >'string-convert.c' <<'END_OF_FILE'
X/*
X *	string-convert.c : A kludgy way to prevent getting a warning
X *		message of the form "can't convert NULL to type Widget".
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 8 Feb 1991.
X *
X *	This code is lifted from lib/Xt/Converters.c in the X11R4 source
X *	tree. I would register my own converter for string to widget, but
X *	there's no way to get the default converter, so I'd end up copying
X *	or rewriting even more code. By the way, this works because the
X *	converter returns NULL when it fails, which is what we want.
X *
X *	$Id: string-convert.c,v 2.0 91/02/08 15:55:32 ferguson Exp $
X */
X#include <X11/Intrinsic.h>
X#include <X11/StringDefs.h>
X
X/* from IntrinsicI.h */
Xstatic String XtNconversionError = "conversionError";
Xextern String XtCXtToolkitError;
X
Xvoid XtStringConversionWarning(from, toType)
X    String from, toType;
X{
X	String params[2];
X	Cardinal num_params = 2;
X
X	if (strcasecmp(from,"NULL") == 0 &&
X	    strcasecmp(toType,XtRWidget) == 0)
X	    return;
X
X	params[0] = from;
X	params[1] = toType;
X	XtWarningMsg(XtNconversionError,"string",XtCXtToolkitError,
X		    "Cannot convert string \"%s\" to type %s",
X		    params,&num_params);
X}
END_OF_FILE
if test 1158 -ne `wc -c <'string-convert.c'`; then
    echo shar: \"'string-convert.c'\" unpacked with wrong size!
fi
# end of 'string-convert.c'
fi
if test -f 'util.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'util.h'\"
else
echo shar: Extracting \"'util.h'\" \(506 characters\)
sed "s/^X//" >'util.h' <<'END_OF_FILE'
X/*
X *	util.h : External defs for callers of misc routines.
X *
X *      George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *      $Id: util.h,v 2.1 91/02/28 11:21:39 ferguson Exp $
X *
X */
X#ifndef UTIL_H
X#define UTIL_H
X
Xextern int computeDOW(), firstDOW(), lastDay();
Xextern void nextDay(),prevDay(),getCurrentDate();
Xextern int parseDate();
Xextern void parseLine();
Xextern char *expandFilename();
Xextern int strtotime();
Xextern char *timetostr();
X
X#endif /* UTIL_H */
END_OF_FILE
if test 506 -ne `wc -c <'util.h'`; then
    echo shar: \"'util.h'\" unpacked with wrong size!
fi
# end of 'util.h'
fi
if test -f 'xkal-automail' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal-automail'\"
else
echo shar: Extracting \"'xkal-automail'\" \(906 characters\)
sed "s/^X//" >'xkal-automail' <<'END_OF_FILE'
X#!/bin/sh 
X#
X#	xkal-automail : Schedules an at job for the time given as
X#		argument. The job will basically do what xkal-mail
X#		does, namely mail the day's appointments if there are
X#		any non-zero-level ones. The job reschedules itself
X#		for the next day.
X#
X#	Note that the first automatic mailing will be "tomorrow" at
X#	the given time, not today, even it could be.
X#
X#	George Ferguson, ferguson@cs.rochester.edu, 19 Feb 1991.
X#
X
Xxkal=xkal
Xmailer=Mail
Xtmp=/tmp/xkal-automail$USER
X
Xcase $# in
X    1) when=$1 ; rec=${USER:?} ;;
X    2) when=$1 ; rec=$2 ;;
X    *) echo 'usage: xkal-automail when [recipient]' 1>&2; exit 1
Xesac
X
Xat -s $when tomorrow >/dev/null 2>&1 <<END_OF_SCRIPT
Xtrap "rm -f $tmp; exit 1" 1 2 3 15
Xif $xkal -listOnly -exitUsesLevels -date +1d >$tmp
Xthen
X    :
Xelse
X    $mailer -s "Reminders from xkal for \`head -1 $tmp\`" $rec <$tmp
Xfi
Xrm -f $tmp
Xxkal-automail $when $rec
XEND_OF_SCRIPT
END_OF_FILE
if test 906 -ne `wc -c <'xkal-automail'`; then
    echo shar: \"'xkal-automail'\" unpacked with wrong size!
fi
chmod +x 'xkal-automail'
# end of 'xkal-automail'
fi
echo shar: End of archive 3 \(of 5\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 5 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 5 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0

-- 
George Ferguson			ARPA: ferguson@cs.rochester.edu
University of Rochester		UUCP: {decvax,rutgers}!rochester!ferguson
Rochester  NY  14627-0226	VOX:  (716) 275-2527