[alt.sources] Xkal 1.13 - Part04/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 4 (of 5)."
# Contents:  resources.c util.c xkal-automail.man xkal-mail
#   xkal-mail.man xkal.appoints xkal.c xkal.h
# Wrapped by ferguson@swan.cs.rochester.edu on Wed Mar 13 13:50:03 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'resources.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'resources.c'\"
else
echo shar: Extracting \"'resources.c'\" \(15331 characters\)
sed "s/^X//" >'resources.c' <<'END_OF_FILE'
X/*
X *	resources.c: Defines the application resources used
X *		     in XtAppInitialize().
X *
X *	Also, getResources() is called instead of initGraphics() when
X *	we're in non-interactive mode, to try and get the few resources
X *	we need to avoid opening a display.
X *
X *	Needs XAPPLOADDIR defined during compilation.
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Feb 1991.
X *
X *	$Id: resources.c,v 2.4 91/03/13 13:31:31 ferguson Exp $
X *
X */
X#include <stdio.h>
X#include <ctype.h>
X#include <X11/Intrinsic.h>
X#include <X11/StringDefs.h>
X#include "xkal.h"
X#include "app-resources.h"
Xextern char *getenv();
X
X/*
X * Functions defined here:
X */
Xvoid getResources();
Xstatic void readResourceValue(),setResourceValue();
X
X/*
X * Data defined here: (check resources.h if these change!!)
X */
X/*
X *	Non-widget resources settable on command line.
X */
XXrmOptionDescRec xkalOptions[] = {
X    { "-appoints",	".personalAppoints",XrmoptionSepArg,	"~/.appoints" },
X    { "-date",		".date",	XrmoptionSepArg,	"" },
X    { "-numMonths",	".numMonths",	XrmoptionSepArg,	"1" },
X    { "-bothShown",	".bothShown",	XrmoptionNoArg,		"True" },
X    { "-nobothShown",	".bothShown",	XrmoptionNoArg,		"False" },
X    { "-version",	".version",	XrmoptionNoArg,		"True" },
X    { "-listOnly",	".listOnly",	XrmoptionNoArg,		"True" },
X    { "-silent",	".silent",	XrmoptionNoArg,		"True" },
X    { "-exitUsesLevels",".exitUsesLevels",XrmoptionNoArg,	"True" },
X    { "-noexitUsesLevels",".exitUsesLevels",XrmoptionNoArg,	"False" },
X    { "-opaqueDates",	".opaqueDates",	XrmoptionNoArg,		"True" },
X    { "-noopaqueDates",	".opaqueDates",	XrmoptionNoArg,		"False" },
X    { "-dowLabels",	".dowLabels",	XrmoptionNoArg,		"True" },
X    { "-nodowLabels",	".dowLabels",	XrmoptionNoArg,		"False" },
X    { "-titlebar",	".useTitlebar",	XrmoptionNoArg,		"True" },
X    { "-notitlebar",	".useTitlebar",	XrmoptionNoArg,		"False" },
X    { "-checkpointInterval",".checkpointInterval",XrmoptionSepArg,"5" },
X};
X/*
X * Non-widget resources obtained from the resource manager
X */
XXtResource xkalResources[] = {
X    { "systemAppoints", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,systemAppoints), XtRImmediate, "" },
X    { "personalAppoints", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,personalAppoints), XtRImmediate, "~/.appoints" },
X    { "backupExtension", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,backupExtension), XtRImmediate, "~" },
X    { "outputFormat", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,outputFormat), XtRImmediate,
X						"%w%~%d%~%m%~%y%~%t%~%l%~" },
X    { "daySlashMonth", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,daySlashMonth), XtRImmediate, (XtPointer)False },
X
X    { "date", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,date), XtRImmediate, "" },
X    { "numMonths", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,numMonths), XtRImmediate, (XtPointer)1 },
X    { "bothShown", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,bothShown), XtRImmediate, (XtPointer)True },
X    { "useTitlebar", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,useTitlebar), XtRImmediate, (XtPointer)False },
X    { "version", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,version), XtRImmediate, (XtPointer)False },
X    { "listOnly", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,listOnly), XtRImmediate, (XtPointer)False },
X    { "silent", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,silent), XtRImmediate, (XtPointer)False },
X    { "exitUsesLevels", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,exitUsesLevels), XtRImmediate, (XtPointer)False },
X    { "opaqueDates", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,opaqueDates), XtRImmediate, (XtPointer)False },
X    { "checkpointInterval", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,checkpointInterval),XtRImmediate,(XtPointer)5 },
X
X    { "dowLabels", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,dowLabels), XtRImmediate, (XtPointer)False },
X    { "longDowStrings", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,longDowStrings), XtRImmediate,
X			"Sunday Monday Tuesday Wednesday Thursday \
X			 Friday Saturday" },
X    { "shortDowStrings", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,shortDowStrings), XtRImmediate,
X			"Sun Mon Tue Wed Thu Fri Sat" },
X    { "longMonthStrings", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,longMonthStrings), XtRImmediate,
X			"January February March April May June \
X		 	 July August September October November December" },
X    { "shortMonthStrings", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,shortMonthStrings), XtRImmediate,
X			"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec" },
X    { "dowOffset", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dowOffset), XtRImmediate, (XtPointer)0 },
X
X    { "appsStartTime", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,appsStartTime), XtRImmediate, "8:00" },
X    { "appsEndTime", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,appsEndTime), XtRImmediate, "17:00" },
X    { "appsIncrement", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,appsIncrement), XtRImmediate, ":30" },
X    { "appsUseAmPm", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,appsUseAmPm), XtRImmediate, (XtPointer)False },
X    { "numNotes", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,numNotes), XtRImmediate, (XtPointer)2 },
X    { "notesLabel", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,notesLabel), XtRImmediate, "NOTES" },
X    { "rearrangeSilently", "Boolean", XtRBoolean, sizeof(Boolean),
X      XtOffset(AppResources *,rearrangeSilently),XtRImmediate,(XtPointer)False},
X    { "noonStr", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,noonStr), XtRImmediate, "Noon" },
X    { "midnightStr", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,midnightStr), XtRImmediate, "Midnight" },
X
X    { "levelDelim", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,levelDelim),XtRImmediate,"@@" },
X    { "maxLevel", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,maxLevel), XtRImmediate, (XtPointer)7 },
X    { "defaultLevel", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,defaultLevel), XtRImmediate, (XtPointer)1 },
X
X    { "dateFont1", "Font", XtRFont, sizeof(Font),
X      XtOffset(AppResources *,dateFont1), XtRString, "*fixed*bold*" },
X    { "dateFont2", "Font", XtRFont, sizeof(Font),
X      XtOffset(AppResources *,dateFont2), XtRString, "*fixed*bold*" },
X    { "dateFont3", "Font", XtRFont, sizeof(Font),
X      XtOffset(AppResources *,dateFont3), XtRString, "*fixed*bold*" },
X    { "dateFont12", "Font", XtRFont, sizeof(Font),
X      XtOffset(AppResources *,dateFont12), XtRString, "*fixed*bold*" },
X
X    { "datePosition1", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,datePosition1), XtRImmediate, "+10+10" },
X    { "datePosition2", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,datePosition2), XtRImmediate, "+10+10" },
X    { "datePosition3", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,datePosition3), XtRImmediate, "+10+10" },
X    { "datePosition12", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,datePosition12), XtRImmediate, "+10+10" },
X
X    { "dateWidth1", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateWidth1), XtRImmediate, (XtPointer)50 },
X    { "dateWidth2", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateWidth2), XtRImmediate, (XtPointer)18 },
X    { "dateWidth3", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateWidth3), XtRImmediate, (XtPointer)18 },
X    { "dateWidth12", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateWidth12), XtRImmediate, (XtPointer)10 },
X
X    { "dateHeight1", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateHeight1), XtRImmediate, (XtPointer)50 },
X    { "dateHeight2", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateHeight2), XtRImmediate, (XtPointer)18 },
X    { "dateHeight3", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateHeight3), XtRImmediate, (XtPointer)18 },
X    { "dateHeight12", "Int", XtRInt, sizeof(int),
X      XtOffset(AppResources *,dateHeight12), XtRImmediate, (XtPointer)10 },
X
X    { "noDayShade", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,noDayShade), XtRImmediate, "wide_weave" },
X    { "shades", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,shades), XtRImmediate,
X		"gray3 gray3 light_gray light_gray gray gray flipped_gray" },
X    { "colors", "String", XtRString, sizeof(String),
X      XtOffset(AppResources *,colors), XtRImmediate,
X		"Green Green Blue Blue Yellow Yellow Red" },
X
X    { "revision", "Revision", XtRString, sizeof(String),
X      XtOffset(AppResources *,revision), XtRImmediate, "" },
X};
X
X/*
X * Resources that we need for non-interactive mode.
X * See also setResourceValue() if you change the order or content of these.
X */
X#define NUM_RESOURCES 17
Xstatic char *strings[NUM_RESOURCES] = {
X	"systemAppoints",
X	"personalAppoints",
X	"date",
X	"listOnly",
X	"silent",
X	"exitUsesLevels",
X	"longDowStrings",
X	"shortDowStrings",
X	"longMonthStrings",
X	"shortMonthStrings",
X	"daySlashMonth",
X	"levelDelim",
X	"backupExtension",
X	"version",
X	"notesLabel",		/* needed for listAppoints() */
X	"noonStr",
X	"midnightStr",
X};
Xstatic int strpos[NUM_RESOURCES];
X
Xstatic char className[] = "Xkal";
X
X#define SKIPSPACE(FP,C)	while (C == ' ' || C == '\t') C = getc(FP);
X#define SKIPWHITE(FP,C)	while (C == ' ' || C == '\t' || C == '\n') C = getc(FP);
X#define SKIPTOEOL(FP,C)	while (C != EOF && C != '\n') C = getc(FP);
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X *	Uses the array xkalResources[] for the defaults, looks for an
X *	app-defaults file in either $XAPPLRESDIR or XAPPLOADDIR, and
X *	checks command line, in that order (doesn't check ~/.Xdefaults,
X *	yet). Modifies *argcp and argv to remove parsed options.
X *
X *	This allows us to get resources without opening the connection
X *	to the X server.
X *
X */
Xvoid
XgetResources(argcp,argv)
Xint *argcp;
Xchar **argv;
X{
X    FILE *fp;
X    char *s,**av,**retargv,buf[1024];
X    int i,j,c,argc;
X
X    for (i=0 ; i < XtNumber(xkalResources); i++)
X	for (j=0; j < NUM_RESOURCES; j++)
X	    if (strcmp(strings[j],xkalResources[i].resource_name) == 0)
X		setResourceValue(j,xkalResources[i].default_addr);
X    if ((s=getenv("XAPPLRESDIR")) != NULL)
X	strcpy(buf,s);
X    else
X	strcpy(buf,XAPPLOADDIR);
X    strcat(buf,"/");
X    strcat(buf,className);
X    if ((fp=fopen(buf,"r")) != NULL) {
X	while ((c=getc(fp)) != EOF) {
X	    /* skip leading whitespace */
X	    SKIPWHITE(fp,c);
X	    /* got a comment then skip to EOL */
X	    if (c == '!') {
X		SKIPTOEOL(fp,c);
X		continue;
X	    }
X	    /* skip class name and . or * after it if they are present */
X	    i = 0;
X	    while (c == className[i++])
X		c = getc(fp);
X	    if (c == '.' || c == '*')
X		c = getc(fp);
X	    /* all strings can potentially match so reset pos array */
X	    for (i=0; i < NUM_RESOURCES; i++)
X		strpos[i] = 0;
X	    /* gather characters in resource name and match strings */
X	    while (c != EOF && c != '\n' && c != '.' && c != '*') {
X		for (i=0; i < NUM_RESOURCES; i++) {
X		    /* if already mismatched then skip this possibility */
X		    if (strpos[i] == -1)
X			continue;
X		    /* if matched resource name then set it */
X		    if (c == ':' && strings[i][strpos[i]] == '\0') {
X			readResourceValue(fp,buf);
X			setResourceValue(i,XtNewString(buf));
X			/* done with this line */
X			break;
X		    } else if (c == strings[i][strpos[i]]) {
X			/* haven't matched yet, but still ok */
X			strpos[i] += 1;
X		    } else {
X			/* can't be this resource since mismatch */
X			strpos[i] = -1;
X		    }
X		} /* for i = 0 to NUM_RESOURCES */
X		c = getc(fp);
X	    } /* while c != '\n' */
X	} /* while !feof(fp)) */
X	fclose(fp);
X    }
X    retargv = argv + 1;
X    for (argc = *argcp-1, av = argv+1; argc; argc--,av++) {
X	if (strncmp(*av,"-a",2) == 0) {
X	    setResourceValue(1,*(av+1));
X	    av += 1;
X	    argc -= 1;
X	    *argcp -= 2;
X	} else if (strncmp(*av,"-d",2) == 0) {
X	    setResourceValue(2,*(av+1));
X	    av += 1;
X	    argc -= 1;
X	    *argcp -= 2;
X	} else if (strncmp(*av,"-l",2) == 0) {
X	    setResourceValue(3,(char *)True);
X	    *argcp -= 1;
X	} else if (strncmp(*av,"-s",2) == 0) {
X	    setResourceValue(4,(char *)True);
X	    *argcp -= 1;
X	} else if (strncmp(*av,"-e",2) == 0) {
X	    setResourceValue(5,(char *)True);
X	    *argcp -= 1;
X	} else if (strncmp(*av,"-v",2) == 0) {
X	    setResourceValue(13,(char *)True);
X	    *argcp -= 1;
X	} else {
X	    *retargv++ = *av;
X	}
X    }
X}
X
X/*
X * readResourceValue(fp): Assumes that fp just read `:'. Fills in buf with
X * the resource value and eats the final newline. Newlines can be escaped with
X * backslashes.
X */
Xstatic void
XreadResourceValue(fp,buf)
XFILE *fp;
Xchar buf[];
X{
X    int c,n;
X    Boolean slash;
X
X    c = getc(fp);
X    SKIPSPACE(fp,c);
X    if (c == EOF || c == '\n') {
X	buf[0] = '\0';
X	return;
X    }
X    n = 0;
X    slash = False;
X    do {
X	/* got newline */
X	if (c == '\n') {
X	    if (slash) {
X	    /* it was escaped with backslash */
X		slash = False;
X		continue;
X	    } else {
X		/* otherwise its end of the resource */
X		break;
X	    }
X	}
X	if (slash)
X	    /* last backslash wasn't escaping \n so add it */
X	    buf[n++] = '\\';
X	if (c == '\\') {
X	    /* we just had a backslash (maybe an escaped one) */
X	    slash = !slash;
X	} else {
X	    /* otherwise we want this char */
X	    buf[n++] = c;
X	    slash = False;
X	}
X    } while ((c=getc(fp)) != EOF && n < 1022);
X    buf[n] = '\0';
X    if (n == 1023 && c != EOF)
X	fprintf(stderr,"%s: resource line too long for dumb parser: \"%s...\"\n",program,buf);
X}
X
X/*
X * setResourceValue() : Decodes which into the actual resource entry to set,
X *	and sets it.
X */
Xstatic void
XsetResourceValue(which,value)
Xint which;
Xchar *value;
X{
X    Boolean bool;
X
X    if (which == 3 || which == 4 || which == 5 || which == 10 || which == 13)
X	if ((int)value > 1)
X	    bool = (strcasecmp(value,"True") == 0 || strcmp(value,"1") == 0);
X	else
X	    bool = (Boolean)value;
X    switch (which) {
X	case 0: appResources.systemAppoints = value;
X		break;
X	case 1: appResources.personalAppoints = value;
X		break;
X	case 2: appResources.date = value;
X		break;
X	case 3: appResources.listOnly = bool;
X		break;
X	case 4: appResources.silent = bool;
X		break;
X	case 5: appResources.exitUsesLevels = bool;
X		break;
X	case 6: appResources.longDowStrings = value;
X		break;
X	case 7: appResources.shortDowStrings = value;
X		break;
X	case 8: appResources.longMonthStrings = value;
X		break;
X	case 9: appResources.shortMonthStrings = value;
X		break;
X	case 10: appResources.daySlashMonth = bool;
X		 break;
X	case 11: appResources.levelDelim = value;
X		 break;
X	case 12: appResources.backupExtension = value;
X		 break;
X	case 13: appResources.version = bool;
X		 break;
X	case 14: appResources.notesLabel = value;
X		 break;
X	case 15: appResources.noonStr = value;
X		 break;
X	case 16: appResources.midnightStr = value;
X		 break;
X    }
X}
END_OF_FILE
if test 15331 -ne `wc -c <'resources.c'`; then
    echo shar: \"'resources.c'\" unpacked with wrong size!
fi
# end of 'resources.c'
fi
if test -f 'util.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'util.c'\"
else
echo shar: Extracting \"'util.c'\" \(9910 characters\)
sed "s/^X//" >'util.c' <<'END_OF_FILE'
X/*
X *	util.c : Some miscellaneous routines for handling dates.
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *      $Id: util.c,v 2.2 91/03/13 13:31:41 ferguson Exp $
X *
X */
X
X#include <sys/time.h>
X#include <ctype.h>
X#include "date-strings.h"		/* for parseDate() */
X#include "app-resources.h"		/* for appsUseAmPm, etc. */
Xextern char *getenv();			/* for $HOME */
Xextern char *program;			/* for error messages */
X
X/*
X * Functions defined here:
X */
Xint computeDOW(), firstDOW(), lastDay();
Xvoid nextDay(),prevDay(),getCurrentDate();
Xint parseDate();
Xvoid parseLine();
Xchar *expandFilename();
Xint strtotime();
Xchar *timetostr();
X
X/*
X * Data defined here:
X */
Xstatic int mon_max[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
Xstatic char filename[1024];
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X *	computeDOW(day,mon,year) : Returns the day of the week for the
X *		requested date (1=Sunday, 2=Monday, etc).
X *		This newer, faster, method is courtesy findeis@alberta.
X */
Xint
XcomputeDOW(d,m,y)
Xint d,m,y;
X{
X    static int dp [12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};
X    register int y4, s;
X
X    y = y - 1901;
X    s = (y/4) * 5;
X    y4 = y % 4;
X    if ((y4 == 3) && (m>2)) s++;
X    s = s + y4 + dp[m-1] + d + 1;
X    return (s % 7 + 1);
X}
X
X/*
X *	firstDOW(mon,year) : Returns the day of the week for the first
X *		day of the given month and year.
X */
Xint
XfirstDOW(mon,year)
Xint mon,year;
X{
X    return(computeDOW(1,mon,year));
X}
X
X/*
X *	lastDay(mon,year) : Returns the last day of the requested
X *		month (and handles leap years, etc).
X */
Xint
XlastDay(mon,year)
Xint mon,year;
X{
X    if ((mon == 1)&&(dysize(year) == 366))
X	    return(29);
X    else return(mon_max[mon-1]);
X}
X
X/*
X *	nextDay(d,m,y) : Increment d,m,y to the next day
X */
Xvoid
XnextDay(dp,mp,yp)
Xint *dp,*mp,*yp;
X{
X    if (*dp != lastDay(*mp,*yp))
X	*dp += 1;
X    else {
X	*dp = 1;
X	if (*mp != 12)
X	    *mp += 1;
X	else {
X	    *yp += 1;
X	    *mp = 1;
X	}
X    }
X}
X
X/*
X *	prevDay(d,m,y) : Decrement d,m,y to the previous day
X */
Xvoid
XprevDay(dp,mp,yp)
Xint *dp,*mp,*yp;
X{
X    if (*dp != 1)
X	*dp -= 1;
X    else {
X	if (*mp != 1)
X	    *mp -= 1;
X	else {
X	    *yp -= 1;
X	    *mp = 12;
X	}
X	*dp = lastDay(*mp,*yp);
X    }
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X *	getCurrentDate() : Day is 1-31, Month is 1-12, Year is 1900-.
X */
Xvoid
XgetCurrentDate(dp,mp,yp)
Xint *dp,*mp,*yp;
X{
X  struct timeval t;
X  struct timezone tz;
X  struct tm *r;
X
X  gettimeofday(&t,&tz);
X  r = localtime(&(t.tv_sec));
X  *dp = r->tm_mday;
X  *mp = r->tm_mon+1;
X  *yp = r->tm_year+1900;
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X *	parseDate(text,dp,mp,yp) : Parse the string text into a date which is
X *		either an absolute or a relative date as follows:
X *
X *	rel date = [+-]{<num>[dmy]}*
X *	abs date = [<day><mon><year>]* where <day> = <num> less than 32
X *					     <mon> = <string>
X *					     <year>= <num> greater than 32
X *	The variables pointed to by dp,mp,yp should contain the current date
X *	and are filled in with the new date.
X *	Whitespace is skipped.
X *	Returns -1 if the date was garbled, else 0.
X */
Xint
XparseDate(text,dp,mp,yp)
Xchar *text;
Xint *dp,*mp,*yp;
X{
X    int day,mon,year,last,num,sign;
X
X    day = mon = year = 0;
X    last = lastDay(*mp,*yp);
X    if (*text == '+' || *text == '-') {	/* relative date */
X	sign = *text++;			/* save sign */
X	while (*text) {			/* parse string... */
X	    while (isspace(*text))			/* skip white space */
X		text += 1;
X	    num = 0;
X	    while (*text >= '0' && *text <= '9') {	/* get a number */
X		num = num * 10 + *text - '0';
X		text += 1;
X	    }
X	    switch(*text) {				/* and a specifier */
X		case '\0':		/* no specifier => days */
X		case 'D' :
X		case 'd' : day = num;
X			   break;
X		case 'M' :
X		case 'm' : mon = num;
X			   break;
X		case 'Y' :
X		case 'y' : year = num;
X			   break;
X		default: return(-1);
X	    }
X	    if (*text != '\0')			/* continue unless at end */
X		text += 1;
X	}
X	if (sign == '+') {		/* now set the `current' date */
X	    *dp += day;
X	    if (*dp > last) {
X		*dp -= last;
X		*mp += 1;
X	    }
X	    *mp += mon;
X	    if (*mp > 12) {
X		*mp -= 12;
X		*yp += 1;
X	    }
X	    *yp += year;
X	} else {
X	    *yp -= year;
X	    *mp -= mon;
X	    if (*mp < 1) {
X		*mp += 12;
X		*yp -= 1;
X	    }
X	    *dp -= day;
X	    if (*dp < 1) {
X		*mp -= 1;
X		if (*mp < 1) {
X		    *mp = 12;
X		    *yp -= 1;
X		}
X		*dp += last;
X	    }
X	}
X    } else {				/* absolute date, use parser */
X	int dow,y,m,d,h,mins,l;
X	char *t;
X
X	parseLine(text,&dow,&y,&m,&d,&h,&mins,&t,&l);
X	if (y != 0)
X	    *yp = y;
X	if (m != 0)
X	    *mp = m;
X	if (d != 0)
X	    *dp = d;
X    }
X    return(0);
X}
X
X/*
X * parseLine() : Given a text buffer presumed to be a (possibly incomplete)
X *	date spec followed by the text of the reminder, this function
X *	parses it as best it can and sets the various parameters. The
X *	following are understood (where # is any consecutive run of digits):
X *		#:#	=	hours:mins (24hr)
X *		#:#am	=	hours:mins
X *		#:#pm	=	hours+12:mins
X *		#/#	=	month/day (depends on daySlashMonth)
X *		#/#/#	=	month/day/year ("   "        "     )
X *		#am	=	hours
X *		#pm	=	hours (+12)
X *		# <= 31	=	day
X *		# > 31	=	year
X *		@#@	=	level (depends on levelDelim)
X *	Also, either the long or short forms of months and days of the week
X *	set the appropriate parameter. Parsing stops (and the text is assumed
X *	to start) at the first non-numerical string which cannot be
X *	interpreted as one of these.
X *
X *	If parameters weren't set, they are set to 0, except hours and mins
X *	which are set to -1 (since 0 is a valid value).
X */
Xvoid
XparseLine(buf,dowp,yp,mp,dp,hp,minsp,tp,lp)
Xchar buf[];
Xint *dowp,*yp,*mp,*dp,*hp,*minsp;
Xchar **tp;
Xint *lp;
X{
X    int i,j,n;
X    char word[32];
X
X    *dowp = *yp = *mp = *dp = *lp = 0;
X    *hp = *minsp = -1;
X    *tp = NULL;
X    i = 0;
X    while (buf[i]) {
X	while (isspace(buf[i]))
X	    i += 1;
X	if (!buf[i]) {
X	    break;
X	} else if (isdigit(buf[i])) {
X	    n = 0;
X	    while (isdigit(buf[i]))
X		n = n * 10 + buf[i++] - '0';
X	    if (buf[i] == ':') {
X		*hp = n;
X		i += 1;
X		*minsp = 0;
X		while (isdigit(buf[i]))
X		    *minsp = *minsp * 10 + buf[i++] - '0';
X		if (buf[i] == 'p' && buf[i+1] == 'm' && *hp < 12) {
X		    *hp += 12;
X		    i += 2;
X		} else if (buf[i] == 'a' && buf[i+1] == 'm')
X		    i += 2;
X	    } else if (buf[i] == 'p' && buf[i+1] == 'm') {
X		*hp = n + 12;
X		*minsp = 0;
X		i += 2;
X	    } else if (buf[i] == 'a' && buf[i+1] == 'm') {
X		*hp = n;
X		*minsp = 0;
X		i += 2;
X	    } else if (buf[i] == '/') {
X		*mp = n;
X		i += 1;
X		*dp = 0;
X		while (isdigit(buf[i]))
X		    *dp = *dp * 10 + buf[i++] - '0';
X		if (buf[i] == '/') {
X		    i += 1;
X		    *yp = 0;
X		    while (isdigit(buf[i]))
X			*yp = *yp * 10 + buf[i++] - '0';
X		}
X		if (appResources.daySlashMonth && *dp <= 12) {
X		    n = *mp;
X		    *mp = *dp;
X		    *dp = n;
X		}
X	    } else if (n <= 31) {
X		*dp = n;
X	    } else {
X		*yp = n;
X	    }
X	} else if (buf[i] == *(appResources.levelDelim)) {
X	    i += 1;
X	    *lp = 0;
X	    while (isdigit(buf[i]))
X		*lp = *lp * 10 + buf[i++] - '0';
X	    i += 1;
X	} else {		/* !isdigit(buf[i]) */
X	    j = 0;
X	    do {
X		word[j++] = buf[i++];
X	    } while (isalpha(buf[i]) && j < 32);
X	    word[j] = '\0';
X	    if (strcasecmp(word, appResources.noonStr) == 0) {
X		*hp = 12;
X		*minsp = 0;
X		continue;
X	    } else if (strcasecmp(word, appResources.midnightStr) == 0) {
X		*hp = *minsp = 0;
X		continue;
X	    }
X	    for (j = 0; j < 7; j++)
X		if (strcasecmp(word, longDowStr[j]) == 0)
X		    break;
X	    if (j != 7) {
X		*dowp = j + 1;
X		continue;
X	    }
X	    for (j = 0; j < 12; j++)
X		if (strcasecmp(word, longMonthStr[j]) == 0)
X		    break;
X	    if (j != 12) {
X		*mp = j + 1;
X		continue;
X	    }
X	    for (j = 0; j < 7; j++)
X		if (strcasecmp(word, shortDowStr[j]) == 0)
X		    break;
X	    if (j != 7) {
X		*dowp = j + 1;
X		if (buf[i] == '.')
X		    i += 1;
X		continue;
X	    }
X	    for (j = 0; j < 12; j++)
X		if (strcasecmp(word, shortMonthStr[j]) == 0)
X		    break;
X	    if (j != 12) {
X		*mp = j + 1;
X		if (buf[i] == '.')
X		    i += 1;
X		continue;
X	    }
X	    *tp = buf + i - strlen(word);
X	    break;		/* start of text, apparently */
X	}
X    }
X}
X
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * expandFilename() : Replaces a leading tilde by the value of $HOME,
X *	and returns a pointer to the resulting filename.
X */
Xchar *
XexpandFilename(name)
Xchar *name;
X{
X    char *s;
X
X    if (*name == '~') {
X	if ((s=getenv("HOME")) == NULL) {
X	    strcpy(filename,name);
X	} else {
X	    strcpy(filename,s);
X	    strcat(filename,name+1);
X	}
X    } else {
X	strcpy(filename,name);
X    }
X    return(filename);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Minutes to/from ascii conversions */
X
X/*
X * strtotime(s) : Returns number of minutes in s, allowing "#:#" and am/pm,
X *	"noon", and "midnight". Skips leading white space, doesn't errorcheck
X *	at all.
X */
Xint
Xstrtotime(s)
Xchar *s;
X{
X    int h,m;
X    char *t;
X
X    while (isspace(*s))
X	s += 1;
X    if (strncasecmp(s,appResources.noonStr,
X				strlen(appResources.noonStr)) == 0)
X	return(12*60);
X    else if (strncasecmp(s,appResources.midnightStr,
X				strlen(appResources.midnightStr)) == 0)
X	return(0);
X    t = s;
X    h = (int)strtol(s,&s,10);
X    if (*s == ':') {
X	s += 1;
X	m = (int)strtol(s,&s,10);
X    } else
X	m = 0;
X    if (s == t)		/* no numbers, some trash */
X	return(-1);
X    if (*s == 'p')	/* otherwise it's 'a' or '\0' so okay */
X	h += 12;
X    return(h*60+m);
X}
X
X/*
X * timetostr(t) : Formats t minutes into hh:mm, possibly with am/pm, and
X *	possibly with noonStr or midnightStr.
X *	Returns a pointer to the static string.
X */
Xchar *
Xtimetostr(t)
Xint t;
X{
X    static char *s = "XX:XXxxyy";
X    int h,m;
X
X    h = t / 60;
X    m = t % 60;
X    if (appResources.appsUseAmPm)
X	if (h == 12)
X	    if (m == 0)
X		sprintf(s,appResources.noonStr);
X	    else
X		sprintf(s,"12:%02dpm",m);
X	else if (h == 0)
X	    if (m == 0)
X		sprintf(s,appResources.midnightStr);
X	    else
X		sprintf(s,"00:%02dam",m);
X	else if (h > 12)
X	    sprintf(s,"%2d:%02dpm",h-12,m);
X	else
X	    sprintf(s,"%2d:%02dam",h,m);
X    else
X	sprintf(s,"%2d:%02d",h,m);
X    return(s);
X}
END_OF_FILE
if test 9910 -ne `wc -c <'util.c'`; then
    echo shar: \"'util.c'\" unpacked with wrong size!
fi
# end of 'util.c'
fi
if test -f 'xkal-automail.man' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal-automail.man'\"
else
echo shar: Extracting \"'xkal-automail.man'\" \(1302 characters\)
sed "s/^X//" >'xkal-automail.man' <<'END_OF_FILE'
X.\"
X.\"
X.\"	xkal-automail : Automatically mail non-zero reminders.
X.\"
X.\"	George Ferguson, ferguson@cs.rochester.edu, 19 Feb 1991.
X.\"
X.\"	$Id: xkal-automail.man,v 1.1 91/02/28 11:21:52 ferguson Exp $
X.\"
X.TH XKAL-AUTOMAIL 1 "19/2/91"
X.ds ]W U of Rochester
X.SH NAME
Xxkal-automail \- automatically mail reminders using xkal
X.SH SYNOPSIS
X.B xkal-mail
Xwhen
X[recipient]
X.SH DESCRIPTION
X.PP
XXkal-automail arranges to automatically mail the day's messages at
Xthe given time each day, to either the given recipient or to
X.B $USER
Xif no recipient is specified. The
X.B when
Xargument should be as for
X.BR at (1).
XNo message is mailed if there are no non-zero-criticality
Xreminders that day.
X.PP
XNote that the first automatic mailing will be "tomorrow" at
Xthe given time, not today, even it could be.
X.SH ENVIRONMENT
XUSER					- default recipient for mail
X.SH FILES
X.PP
X.nf
X.na
X~/.appoints			- default appointment file
X$LIBDIR/xkal.appoints	- default system appintment file
X.ad
X.fi
X.SH BUGS
X.PP
XIf the system crashes while the at script is executing, it won't be able
Xto reschedule itself. The solution is to use xkal-mail in a personal
Xcrontab, if your system supports these.
X.SH "SEE ALSO"
X.PP
XX(1),
Xxkal(1),
Xxkal-mail(1).
X.SH AUTHOR
X.PP
XGeorge Ferguson, University of Rochester
X.br
X(ferguson@cs.rochester.edu)
END_OF_FILE
if test 1302 -ne `wc -c <'xkal-automail.man'`; then
    echo shar: \"'xkal-automail.man'\" unpacked with wrong size!
fi
# end of 'xkal-automail.man'
fi
if test -f 'xkal-mail' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal-mail'\"
else
echo shar: Extracting \"'xkal-mail'\" \(512 characters\)
sed "s/^X//" >'xkal-mail' <<'END_OF_FILE'
X#!/bin/sh 
X#
X#	xkal-mail : Mails today's reminders if there are any that
X#		are above criticality level 0.
X#
X#	George Ferguson, ferguson@cs.rochester.edu, 19 Feb 1991.
X#
X
Xxkal=xkal
Xmailer=Mail
Xtmp=/tmp/xkal-mail$$
X
X
Xcase $# in
X    0) rec=${USER:?};;
X    1) rec=$1;;
X    *) echo 'usage: xkal-mail [recipient]' 1>&2; exit 1
Xesac
X
Xtrap "rm -f $tmp; exit 1" 1 2 3 15
X
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
END_OF_FILE
if test 512 -ne `wc -c <'xkal-mail'`; then
    echo shar: \"'xkal-mail'\" unpacked with wrong size!
fi
chmod +x 'xkal-mail'
# end of 'xkal-mail'
fi
if test -f 'xkal-mail.man' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal-mail.man'\"
else
echo shar: Extracting \"'xkal-mail.man'\" \(942 characters\)
sed "s/^X//" >'xkal-mail.man' <<'END_OF_FILE'
X.\"
X.\"
X.\"	xkal-mail : Mails today's reminders if there are any that are above
X.\"		criticality level 0.
X.\"
X.\"	George Ferguson, ferguson@cs.rochester.edu, 19 Feb 1991.
X.\"
X.\"	$Id: xkal-mail.man,v 1.1 91/02/28 11:21:56 ferguson Exp $
X.\"
X.TH XKAL-MAIL 1 "19/2/91"
X.ds ]W U of Rochester
X.SH NAME
Xxkal-mail \- mail reminders using xkal
X.SH SYNOPSIS
X.B xkal-mail
X[recipient]
X.SH DESCRIPTION
X.PP
XXkal-mail mails today's reminders if there are any that are above
Xcriticality level 0.
XIf no argument is given,
Xthe mail message is sent to the value of the
Xenvironment variable
X.BR $USER ,
Xwhich ought to be set automatically.
X.SH ENVIRONMENT
XUSER					- default recipient for mail
X.SH FILES
X.PP
X.nf
X.na
X~/.appoints			- default appointment file
X$LIBDIR/xkal.appoints	- default system appintment file
X.ad
X.fi
X.SH "SEE ALSO"
X.PP
XX(1),
Xxkal(1),
Xxkal-automail(1).
X.SH AUTHOR
X.PP
XGeorge Ferguson, University of Rochester
X.br
X(ferguson@cs.rochester.edu)
END_OF_FILE
if test 942 -ne `wc -c <'xkal-mail.man'`; then
    echo shar: \"'xkal-mail.man'\" unpacked with wrong size!
fi
# end of 'xkal-mail.man'
fi
if test -f 'xkal.appoints' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal.appoints'\"
else
echo shar: Extracting \"'xkal.appoints'\" \(343 characters\)
sed "s/^X//" >'xkal.appoints' <<'END_OF_FILE'
X#
X#	xkal.appoints : A system reminders file for xkal
X#
X#	George Ferguson, ferguson@cs.rochester.edu, 12 Feb 1991.
X#
X#	$Id: xkal.appoints,v 1.1 91/02/28 11:22:13 ferguson Exp $
X#
X
X# The first Monday after the 1st of September
XMon 1 Sep Labor Day
X
XOct 8 1990 Columbus Day (observed)
XOct 12 1990 Columbus Day
X
X12/25 Christmas
X1/1 New Year's Day
X
END_OF_FILE
if test 343 -ne `wc -c <'xkal.appoints'`; then
    echo shar: \"'xkal.appoints'\" unpacked with wrong size!
fi
# end of 'xkal.appoints'
fi
if test -f 'xkal.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal.c'\"
else
echo shar: Extracting \"'xkal.c'\" \(22649 characters\)
sed "s/^X//" >'xkal.c' <<'END_OF_FILE'
X/*
X *	xkal.c : graphical appointment tool
X *
X *	George Ferguson, ferguson@cs.rochester.edu,  27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: xkal.c,v 2.2 91/02/28 11:23:24 ferguson Exp $
X *
X */
X#ifndef lint
Xstatic char ident[] = "@(#) xkal version 1.1 - ferguson@cs.rochester.edu";
X#endif
X#include <stdio.h>
X#include <ctype.h>
X#include <X11/Intrinsic.h>
X#include <X11/StringDefs.h>
X#include <X11/Xmu/Drawing.h>
X#include <X11/Xaw/Form.h>
X#include <X11/Xaw/MenuButton.h>
X#include <X11/Xaw/Cardinals.h>	
X#include "EzMenu.h"
X#include "app-resources.h"
X#include "resources.h"
X#include "month.h"
X#include "day.h"
X#include "edit.h"
X#include "edit-defaults.h"
X#include "db.h"
X#include "util.h"
X#include "date-strings.h"
X#include "patchlevel.h"
X#ifdef USE_ALERT
X#include "alert.h"
X#endif
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * Functions defined in this file:
X */
Xint main();
Xvoid quit(),quit_nosave(),save(),next(),prev(),today(),setNumMonths();
Xvoid focusThisItem(), focusNoItem(), focusNextItem(), focusPrevItem();
Xvoid escapeToSystem();
Xvoid timeoutProc();
X
Xstatic void initGraphics(), initGCs(), initWidgets(), createMonthFormDatas();
Xstatic void setMonths(), selectCurrentDay();
Xstatic void listAppoints(), checkAndSaveAppoints();
Xstatic int countAppoints();
Xstatic void cvtStringToPixels(), cvtStringToPixmaps(), syntax();
Xstatic Pixmap readBitmap();
X
X/*
X * Global graphics data
X */
XDisplay *display;
XScreen *screen;
XWindow root;
XGC dateGC1,dateGC2,dateGC3,dateGC12,emptyGC,*shadeGC;
X
X/*
X * Global widget data
X */
XXtAppContext app_con;
XWidget toplevel;
XWidget bothForm,allForm;
XAppResources appResources;
XMonthFormData *monthFormData1,*monthFormData2[2];
XMonthFormData *monthFormData3[3],*monthFormData12[12];
X
X/*
X * Other global data
X */
Xchar *program;
Xint currentDay,currentMon,currentYear;		/* today */
Xint mainDay,mainMon,mainYear;			/* display */
Xint appointsChanged = False;			/* need to save? */
XXtIntervalId timeoutId;				/* auto update */
X
X/*
X * Action binding table
X */
Xstatic XtActionsRec cmdActionsTable[] = {
X	{ "xkal-quit", quit },
X	{ "xkal-exit", quit_nosave },
X	{ "xkal-save", save },
X	{ "xkal-next", next },
X	{ "xkal-prev", prev },
X	{ "xkal-today", today },
X	{ "xkal-numMonths", setNumMonths },
X	{ "xkal-system", escapeToSystem },
X	{ "xkal-focus-this-item", focusThisItem },
X	{ "xkal-focus-no-item", focusNoItem },
X	{ "xkal-focus-next-item", focusNextItem },
X	{ "xkal-focus-prev-item", focusPrevItem },
X	{ "xkal-edit-appoint", editAppoint },
X	{ "xkal-focus-next-edit-item", focusNextEditItem },
X	{ "xkal-focus-prev-edit-item", focusPrevEditItem },
X	{ "xkal-edit-defaults", editDefaults },
X	{ "xkal-focus-next-defaults-item", focusNextDefaultsItem },
X	{ "xkal-focus-prev-defaults-item", focusPrevDefaultsItem },
X};
X
X/*
X *	Widget and non-widget resources if the application defaults
X *	file can't be found.
X *	[ Generated automatically from Xkal.ad. ]
X */
Xstatic String fallbackResources[] = {
X#include "Xkal.ad.h"
X	NULL
X};
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/*	-	-	-	-	-	-	-	-	*/
X
Xint
Xmain(argc,argv)
Xint argc;
Xchar **argv;
X{
X    int i;
X
X    program = *argv;
X    for (i=1; i < argc; i++)		/* quick check for non-interactive */
X	if (strncmp(argv[i],"-l",2) == 0 || strncmp(argv[i],"-s",2) == 0 ||
X						strncmp(argv[i],"-v",2) == 0)
X	    break;
X    if (i != argc)
X	getResources(&argc,argv);	/* found, so don't need display */
X    else
X	initGraphics(&argc,argv);	/* not found, so interactive mode */
X    if (argc > 1) {
X	syntax(argc,argv);
X	XtDestroyApplicationContext(app_con);
X	exit(-1);
X    }
X    initDb();
X    initDateStrings();
X    getCurrentDate(&currentDay,&currentMon,&currentYear);
X    if (appResources.systemAppoints && *appResources.systemAppoints)
X	readDb(appResources.systemAppoints,True);
X    if (appResources.personalAppoints && *appResources.personalAppoints)
X	readDb(appResources.personalAppoints,False);
X    if (appResources.date != NULL && *(appResources.date) != '\0') {
X	parseDate(appResources.date,&currentDay,&currentMon,&currentYear);
X	if (currentYear < 1901) {
X	    fprintf(stderr,"%s: invalid year (too early): %d\n",program,
X								currentYear);
X	    XtDestroyApplicationContext(app_con);
X	    exit(-1);
X	}
X    }
X    if (appResources.version) {
X	printf("xkal %.2f - George Ferguson (ferguson@cs.rochester.edu)\n",
X								XKAL_VERSION);
X	exit(0);
X    }
X    if (appResources.listOnly) {
X	listAppoints(currentDay,currentMon,currentYear);
X    }
X    if (appResources.listOnly || appResources.silent) {
X	exit(countAppoints(currentDay,currentMon,currentYear,
X						 appResources.exitUsesLevels));
X    }
X    mainDay = currentDay;
X    mainMon = currentMon;
X    mainYear = currentYear;
X    initGCs();
X    initWidgets();
X    XtRealizeWidget(toplevel);
X    if (currentDayFormData != NULL)
X	selectCurrentDay();
X    if (appResources.checkpointInterval > 0)
X	timeoutId = XtAppAddTimeOut(app_con,
X			(unsigned long)(appResources.checkpointInterval*60000),
X			timeoutProc,NULL);
X    XtAppMainLoop(app_con);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Initialization routines */
X
X/*
X * initGraphics() : Initialze X connection and toplevel widget.
X */
Xstatic void
XinitGraphics(argcp,argv)
Xint *argcp;
Xchar **argv;
X{
X    char *rev,*strchr();
X
X    toplevel = XtAppInitialize(&app_con, "Xkal",
X			       xkalOptions, XtNumber(xkalOptions),
X			       argcp,argv,fallbackResources,NULL,ZERO);
X    XtGetApplicationResources(toplevel,(XtPointer)&appResources,
X                              xkalResources,XtNumber(xkalResources),NULL,ZERO);
X    rev = strchr(fallbackResources[0],'$');
X    if (rev && strcmp(appResources.revision,rev) != 0) {
X	fprintf(stderr,"%s: app-defaults release not %s\n",program,rev);
X	fprintf(stderr,"%s: you may have an outdated app-defaults file\n",
X								program);
X    }
X    XtAppAddActions(app_con,cmdActionsTable,XtNumber(cmdActionsTable));
X    display = XtDisplay(toplevel);
X    screen = XtScreen(toplevel);
X    root = RootWindowOfScreen(screen);
X}
X
X/*
X * initGCs() : Initialize GC's for drawing operations.
X */
Xstatic void
XinitGCs()
X{
X    XGCValues values;
X    XtGCMask mask;
X    Pixel *pixels;
X    Pixmap *pixmaps;
X    int i;
X
X    /*
X     * initialize date GC's
X     */
X    values.foreground = (unsigned long)1;
X    values.background = (unsigned long)0;
X    values.function = GXset;
X    mask = GCForeground | GCBackground | GCFunction | GCFont;
X    values.font = appResources.dateFont1;
X    dateGC1 = XCreateGC(display,root,mask,&values);
X    values.font = appResources.dateFont2;
X    dateGC2 = XCreateGC(display,root,mask,&values);
X    values.font = appResources.dateFont3;
X    dateGC3 = XCreateGC(display,root,mask,&values);
X    values.font = appResources.dateFont12;
X    dateGC12 = XCreateGC(display,root,mask,&values);
X    /*
X     * initialize empty GC
X     */
X    values.function = GXcopy;
X    values.fill_style = FillTiled;
X    values.tile = readBitmap(appResources.noDayShade);
X    mask = GCForeground | GCBackground | GCFunction | GCFillStyle | GCTile;
X    emptyGC = XCreateGC(display,root,mask,&values);
X    /*
X     * initialize shading GC's
X     */
X    values.function = GXcopy;
X    mask = GCFunction;
X    shadeGC = (GC *)XtCalloc(appResources.maxLevel+1,sizeof(GC));
X    shadeGC[0] = XCreateGC(display,root,mask,&values);
X    if (DefaultDepthOfScreen(screen) > 1) {	/* COLOR */
X	pixels = (Pixel *)XtCalloc(appResources.maxLevel,sizeof(Pixel));
X	cvtStringToPixels(appResources.colors,appResources.maxLevel,pixels);
X	for (i=1; i <= appResources.maxLevel; i++) {
X	    values.foreground = pixels[i-1];
X	    values.fill_style = FillSolid;
X	    values.function = GXcopy;
X	    mask = GCForeground | GCBackground | GCFunction;
X	    shadeGC[i] = XCreateGC(display,root,mask,&values);
X	}
X	XtFree(pixels);
X    } else {					/* B&W */
X	pixmaps = (Pixmap *)XtCalloc(appResources.maxLevel,sizeof(Pixmap));
X	cvtStringToPixmaps(appResources.shades,appResources.maxLevel,pixmaps);
X	for (i=1; i <= appResources.maxLevel; i++) {
X	    values.background = (unsigned long)0;
X	    values.tile = pixmaps[i-1];
X	    values.fill_style = FillTiled;
X	    values.function = GXcopy;
X	    mask = GCForeground | GCBackground | GCTile | GCFillStyle |
X								GCFunction;
X	    shadeGC[i] = XCreateGC(display,root,mask,&values);
X	}
X	XtFree(pixmaps);
X    }
X}
X
X/*
X * initWidgets() : Create necessary widgets and menus (ie. those that
X *	aren't created on the fly).
X */
Xstatic void
XinitWidgets()
X{
X    bothForm = XtCreateManagedWidget("bothForm",formWidgetClass,toplevel,
X								NULL,ZERO);
X    XtCreateManagedWidget("fileButton",menuButtonWidgetClass,bothForm,
X								NULL,ZERO);
X    XtCreatePopupShell("fileMenu",ezMenuWidgetClass,bothForm,NULL,ZERO);
X    XtCreateManagedWidget("viewButton",menuButtonWidgetClass,bothForm,
X								NULL,ZERO);
X    XtCreatePopupShell("viewMenu",ezMenuWidgetClass,bothForm,NULL,ZERO);
X    XtCreateManagedWidget("otherButton",menuButtonWidgetClass,bothForm,
X								NULL,ZERO);
X    XtCreatePopupShell("otherMenu",ezMenuWidgetClass,bothForm,NULL,ZERO);
X    allForm = XtCreateManagedWidget("allMonthsForm",formWidgetClass,bothForm,
X								NULL,ZERO);
X    createMonthFormDatas(appResources.numMonths);
X    if (appResources.bothShown)
X	currentDayFormData = createDayFormData(bothForm);
X    setMonths();
X}
X
X/*
X * createMonthFormDatas(n) : Create n monthForms as children of the
X *	allForm widget.
X */
Xstatic void
XcreateMonthFormDatas(n)
Xint n;
X{
X    char *name = "monthFormXX_XX";
X    int i;
X
X    switch (n) {
X	case 1: monthFormData1 = createMonthFormData(allForm,"monthForm1",1);
X		XtManageChild(monthFormData1->form);
X		break;
X	case 2: for (i=0; i < 2; i++) {
X		    sprintf(name,"monthForm2_%d",i+1);
X		    monthFormData2[i] = createMonthFormData(allForm,name,2);
X		    XtManageChild(monthFormData2[i]->form);
X		}
X		break;
X	case 3: for (i=0; i < 3; i++) {
X		    sprintf(name,"monthForm3_%d",i+1);
X		    monthFormData3[i] = createMonthFormData(allForm,name,3);
X		    XtManageChild(monthFormData3[i]->form);
X		}
X		break;
X	case 12: for (i=0; i < 12; i++) {
X		    sprintf(name,"monthForm12_%d",i+1);
X		    monthFormData12[i] = createMonthFormData(allForm,name,12);
X		    XtManageChild(monthFormData12[i]->form);
X		}
X		break;
X    }
X}
X
X/*
X * setMonths() : Redraw the months.
X */
Xstatic void
XsetMonths()
X{
X    int i,prevMon,prevYear,nextMon,nextYear;
X
X    XawFormDoLayout(allForm,False);
X    switch (appResources.numMonths) {
X	case 1:  setMonthFormData(monthFormData1,1,mainMon,mainYear);
X		 break;
X	case 2:	 if (mainMon == 12) {
X		     nextMon = 1;
X		     nextYear = mainYear+1;
X		 } else {
X		     nextMon = mainMon+1;
X		     nextYear = mainYear;
X		 }
X		 setMonthFormData(monthFormData2[0],2,mainMon,mainYear);
X		 setMonthFormData(monthFormData2[1],2,nextMon,nextYear);
X		 break;
X	case 3:  prevMon = mainMon - 1;
X		 nextMon = mainMon + 1;
X		 prevYear = nextYear = mainYear;
X		 if (mainMon == 1) {
X		     prevMon = 12;
X		     prevYear = mainYear - 1;
X		 } else if (mainMon == 12) {
X		     nextMon = 1;
X		     nextYear = mainYear + 1;
X		 }
X		 setMonthFormData(monthFormData3[0],3,prevMon,prevYear);
X		 setMonthFormData(monthFormData3[1],3,mainMon,mainYear);
X		 setMonthFormData(monthFormData3[2],3,nextMon,nextYear);
X		 break;
X	case 12: for (i=0; i < 12; i++)
X		     setMonthFormData(monthFormData12[i],12,i+1,mainYear);
X		 break;
X    }
X    XawFormDoLayout(allForm,True);
X}
X
X/*
X * selectCurrentDay() : Higlight current day after figuring out which
X *	form its in.
X */
Xstatic void
XselectCurrentDay()
X{
X    switch (appResources.numMonths) {
X	case 1: selectDay(monthFormData1,mainDay,mainMon,mainYear);
X		break;
X	case 2: selectDay(monthFormData2[0],mainDay,mainMon,mainYear);
X		break;
X	case 3: selectDay(monthFormData3[1],mainDay,mainMon,mainYear);
X		break;
X	case 12: selectDay(monthFormData12[mainMon-1],mainDay,mainMon,mainYear);
X		 break;
X    }
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X
X/*
X * quit() : Save appoints if changed, then quit.
X */
X/*ARGSUSED*/
XACTION_PROC(quit)
X{
X    checkAndSaveAppoints(False);
X    XtDestroyApplicationContext(app_con);
X    exit(0);
X}
X
X/*
X * quit_nosave() : Quit without saving.
X */
X/*ARGSUSED*/
XACTION_PROC(quit_nosave)
X{
X    XtDestroyApplicationContext(app_con);
X    exit(0);
X}
X
X/*
X * next() : Display next month.
X */
X/*ARGSUSED*/
XACTION_PROC(next)
X{
X    if (appResources.numMonths < 12) {
X	if (mainMon == 12) {
X	    mainMon = 1;
X	    mainYear += 1;
X	} else {
X	    mainMon += 1;
X	}
X    } else {
X	mainYear += 1;
X    }
X    setMonths();
X}
X
X/*
X * prev() : Display previous month.
X */
X/*ARGSUSED*/
XACTION_PROC(prev)
X{
X    if (appResources.numMonths < 12) {
X	if (mainMon == 1) {
X	    mainMon = 12;
X	    mainYear -= 1;
X	} else {
X	    mainMon -= 1;
X	}
X    } else {
X	mainYear -= 1;
X    }
X    setMonths();
X}
X
X/*
X * today() : Highlight today, possibly resetting months. Also, if date
X *	was not given as resource, then get it again in case we've been
X *	runnign for a while.
X */
X/*ARGSUSED*/
XACTION_PROC(today)
X{
X    /* Reset the current date in case we've been running for a long time. */
X    if (appResources.date == NULL || *(appResources.date) == '\0')
X	getCurrentDate(&currentDay,&currentMon,&currentYear);
X    mainDay = currentDay;
X    if (mainMon != currentMon || mainYear != currentYear) {
X	mainMon = currentMon;
X	mainYear = currentYear;
X	setMonths();
X    }
X    selectCurrentDay();
X}
X
X/*
X * setNumMonths(n) : Change the number of months being displayed, possibly
X *	creating new monthForms. n should be 1, 2, 3, or 12.
X */
X/*ARGSUSED*/
XACTION_PROC(setNumMonths)
X{
X    int n,i;
X
X    if (*num_params != ONE) {
X	fprintf(stderr,"%s: bad parms to xkal-numMonths()\n",program);
X	return;
X    }
X    n = atoi(params[0]);
X    if (n != 1 && n != 2 && n != 3 && n != 12) {
X	fprintf(stderr,"%s: bad num for xkal-numMonths(): %d\n",program,n);
X	return;
X    }
X    if (n == appResources.numMonths)
X	return;
X    XawFormDoLayout(bothForm,False);
X    switch (appResources.numMonths) {
X	case 1: XtUnmanageChild(monthFormData1->form);
X		break;
X	case 2: for (i=0; i < 2; i++)
X		    XtUnmanageChild(monthFormData2[i]->form);
X		break;
X	case 3: for (i=0; i < 3; i++)
X		    XtUnmanageChild(monthFormData3[i]->form);
X		break;
X	case 12: for (i=0; i < 12; i++)
X		    XtUnmanageChild(monthFormData12[i]->form);
X		break;
X    }
X    appResources.numMonths = n;
X    switch (appResources.numMonths) {
X	case 1: if (monthFormData1 == NULL)
X		    createMonthFormDatas(1);
X		setMonths();
X		XtManageChild(monthFormData1->form);
X		break;
X	case 2: if (monthFormData2[0] == NULL)
X		    createMonthFormDatas(2);
X		setMonths();
X		for (i=0; i < 2; i++)
X		    XtManageChild(monthFormData2[i]->form);
X		break;
X	case 3: if (monthFormData3[0] == NULL)
X		    createMonthFormDatas(3);
X		setMonths();
X		for (i=0; i < 3; i++)
X		    XtManageChild(monthFormData3[i]->form);
X		break;
X	case 12: if (monthFormData12[0] == NULL)
X		     createMonthFormDatas(12);
X		 setMonths();
X		 for (i=0; i < 12; i++)
X		     XtManageChild(monthFormData12[i]->form);
X		 break;
X    }
X    XawFormDoLayout(bothForm,True);
X}
X
X/*
X * save() : Save appoints regardless of whether they've changed or not.
X */
X/*ARGSUSED*/
XACTION_PROC(save)
X{
X    checkAndSaveAppoints(True);
X}
X
X/*
X * escapeToSystem(str) : execute "str" by the shell.
X */
X/*ARGSUSED*/
XACTION_PROC(escapeToSystem)
X{
X    if (*num_params != 1) {
X#ifdef USE_ALERT
X	alert("Error: too many arguments to xkal-system().");
X#else
X	fprintf(stderr,"\007%s: too many arguments to xkal-system()\n",program);
X#endif
X	return;
X    }
X    system(params[0]);
X}
X
X/*
X * focusThisItem() : Force input focus to this widget.
X */
X/*ARGSUSED*/
XACTION_PROC(focusThisItem)
X{
X    XWindowAttributes attrs;
X
X    XGetWindowAttributes(display,XtWindow(w),&attrs);
X    if (attrs.map_state != IsViewable)
X	return;
X    XSetInputFocus(display,XtWindow(w),RevertToParent,CurrentTime);
X}
X
X/*
X * focusNoItem() : Unset the input focus.
X */
X/*ARGSUSED*/
XACTION_PROC(focusNoItem)
X{
X    XSetInputFocus(display,PointerRoot,RevertToParent,CurrentTime);
X}
X
X/*
X * focusNextItem() : Switch input focus to next appointment slot, wrapping
X *	at bottom.
X */
X/*ARGSUSED*/
XACTION_PROC(focusNextItem)
X{
X    int i;
X
X    for (i=0; i < numDisplayedAppoints; i++)
X	if (w == currentDayFormData->items[i]->text)
X	    break;
X    if (i == numDisplayedAppoints)
X	return;
X    else if (i == numDisplayedAppoints-1)
X	i = 0;
X    else
X	i += 1;
X    XSetInputFocus(display,XtWindow(currentDayFormData->items[i]->text),
X						RevertToParent,CurrentTime);
X}
X
X/*
X * focusPrevItem() : Switch input focus to previous appointment slot, wrapping
X *	at top.
X */
X/*ARGSUSED*/
XACTION_PROC(focusPrevItem)
X{
X    int i;
X
X    for (i=0; i < numDisplayedAppoints; i++)
X	if (w == currentDayFormData->items[i]->text)
X	    break;
X    if (i == numDisplayedAppoints)
X	return;
X    else if (i == 0)
X	i = numDisplayedAppoints-1;
X    else
X	i -= 1;
X    XSetInputFocus(display,XtWindow(currentDayFormData->items[i]->text),
X						RevertToParent,CurrentTime);
X}
X
X/*
X * timeoutProc() : Timer callback - saves appoints if they've changed and
X *	resets timer.
X */
X/*ARGSUSED*/
Xvoid
XtimeoutProc(data,id)
XXtPointer data;
XXtIntervalId *id;
X{
X    checkAndSaveAppoints(False);
X    if (appResources.checkpointInterval > 0)
X	timeoutId = XtAppAddTimeOut(app_con,
X			(unsigned long)(appResources.checkpointInterval*60000),
X			timeoutProc,NULL);
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Miscellaneous routines */
X
X/*
X * listAppoints() : Print all appoints for day to stdout.
X */
Xstatic void
XlistAppoints(day,mon,year)
Xint day,mon,year;
X{
X    Msgrec **apps;
X    int dow,n,i;
X
X    dow = computeDOW(day,mon,year);
X    printf("%s %d %s %d\n",shortDowStr[dow-1],day,shortMonthStr[mon-1],year);
X    n = lookupEntry(day,mon,year,-1,-1,-1,NULL,False);
X    if (n == 0)
X	return;
X    apps = (Msgrec **)XtCalloc(n,sizeof(Msgrec *));
X    (void)lookupEntry(day,mon,year,-1,-1,n,apps,False);
X    for (i=0; i < n; i++)
X	if (apps[i]->hour == -1)
X	    printf("\t%s %s\n",appResources.notesLabel,apps[i]->text);
X	else if (apps[i]->mins == -1)
X	    if (apps[i]->hour >= 12)
X		printf("\t%2dpm %s\n",apps[i]->hour-12,apps[i]->text);
X	    else
X		printf("\t%2dam %s\n",apps[i]->hour,apps[i]->text);
X	else
X	    printf("\t%2d:%02d %s\n",apps[i]->hour,apps[i]->mins,apps[i]->text);
X    XtFree(apps);
X}
X
X/*
X * countAppoints() : Return number of appoints (flag = False) or criticality
X *	total (flag = True) for day.
X */
Xstatic int
XcountAppoints(day,mon,year,flag)
Xint day,mon,year;
XBoolean flag;
X{
X    
X    return(lookupEntry(day,mon,year,-1,-1,-1,NULL,flag));
X}
X
X/*
X * checkAndSaveAppoints() : Transfer data from dayForm to DB, then save
X *	appoints if they've changed.
X */
Xstatic void
XcheckAndSaveAppoints(force)
XBoolean force;
X{
X    if (currentDayFormData != NULL)
X	checkpointAppoints(currentDayFormData);
X    if (force || appointsChanged)
X	writeDb(appResources.personalAppoints,False);
X    appointsChanged = False;
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/* Miscellaneous conversion routines */
X
X/*
X * cvtStringToPixels() : Converts a string that is a whitespace-separated
X *	list of pixel names, to an array of Pixels.
X */
Xstatic void
XcvtStringToPixels(str,max,pixels)
Xchar *str;
Xint max;
XPixel *pixels;
X{
X    XColor screen_in_out,exact;
X    char buf[64];
X    int i,j;
X
X    strcpy(buf,XtDefaultBackground);
X    for (i=0; i < max; i++) {
X	if (*str) {
X	    while (isspace(*str))
X		str += 1;
X	    if (!*str)
X		break;
X	    j = 0;
X	    while (*str && j < 63 && !isspace(*str))
X		buf[j++] = *str++;
X	    buf[j] = '\0';
X	    if (*str && !isspace(*str)) {
X		fprintf(stderr,"%s: color name too long: %s\n",program,buf);
X		while (*str && !isspace(*str))
X		    str += 1;
X	    }
X	}
X	XAllocNamedColor(display,DefaultColormapOfScreen(screen),buf,
X							&screen_in_out,&exact);
X	pixels[i] = screen_in_out.pixel;
X    }
X}
X
X/*
X * cvtStringToPixmaps() : Converts a string that is a whitespace-separated
X *	list of bitmap names, to an array of Pixmaps.
X */
Xstatic void
XcvtStringToPixmaps(str,max,pixmaps)
Xchar *str;
Xint max;
XPixmap *pixmaps;
X{
X    char buf[64];
X    int i,j;
X
X    strcpy(buf,"gray");
X    for (i=0; i < max; i++) {
X	if (*str) {
X	    while (isspace(*str))
X		str += 1;
X	    if (!*str)
X		break;
X	    j = 0;
X	    while (*str && j < 63 && !isspace(*str))
X		buf[j++] = *str++;
X	    buf[j] = '\0';
X	    if (*str && !isspace(*str)) {
X		fprintf(stderr,"%s: shade name too long: %s\n",program,buf);
X		while (*str && !isspace(*str))
X		    str += 1;
X	    }
X	}
X	pixmaps[i] = readBitmap(buf);
X    }
X}
X
X/*
X * readBitmap() : Reads the given file using XmuLocateBitmapFile protocol,
X *	then converts to Pixmap and returns it. If not found, prints error
X *	message and returns a default Pixmap.
X */
Xstatic Pixmap
XreadBitmap(name)
Xchar *name;
X{
X    Pixmap b;
X    int w,h,xhot,yhot;
X
X    b = XmuLocateBitmapFile(screen,name,NULL,ZERO,&w,&h,&xhot,&yhot);
X    if (b == NULL) {
X	fprintf(stderr,"%s: couldn't find bitmap \"%s\"\n",program,name);
X	return(XCreatePixmap(display,screen,2,2,DefaultDepthOfScreen(screen)));
X    } else
X	return(XmuCreatePixmapFromBitmap(display,root,b,w,h,
X					DefaultDepthOfScreen(screen),1,0));
X}
X
X/*	-	-	-	-	-	-	-	-	*/
X/*
X * syntax() : Print whatever caused the error and the usage message.
X */
Xstatic void
Xsyntax(argc,argv)
Xint argc;
Xchar **argv;
X{
X    argv += 1;
X    if (argc > 2 || (strcmp(*argv,"-help") != 0 && strcmp(*argv,"-?") != 0)) {
X	fprintf(stderr,"%s: bad argument(s): ",program);
X	while (--argc)
X	    fprintf(stderr,"%s ",*argv++);
X	fprintf(stderr,"\n");
X    }
X    fprintf(stderr,"usage: %s",program);
X    fprintf(stderr,"\t-appoints file\tuse file for personal appointments\n");
X    fprintf(stderr,"\t\t-date date\tuse given date for `today'\n");
X    fprintf(stderr,"\t\t-numMonths 1|2|3|12\tstart displaying 1, 2, 3 or 12 months\n");
X    fprintf(stderr,"\t\t-bothShown\tput day display next to month(s)\n");
X    fprintf(stderr,"\t\t-nobothShown\tpopup days as needed\n");
X    fprintf(stderr,"\t\t-version\tprint version info to stdout\n");
X    fprintf(stderr,"\t\t-listOnly\tprint appoints to stdout\n");
X    fprintf(stderr,"\t\t-silent\t\texit with status == number of appoints\n");
X    fprintf(stderr,"\t\t-exitUsesLevels\texit status is criticality total\n");
X    fprintf(stderr,"\t\t-noexitUsesLevels\texit status is number of appoints\n");
X    fprintf(stderr,"\t\t-opaqueDates\tprint day numbers opaquely\n");
X    fprintf(stderr,"\t\t-noopaqueDates\tprint day numbers transparently\n");
X    fprintf(stderr,"\t\t-dowLabels\tprint day of week at top of columns\n");
X    fprintf(stderr,"\t\t-nodowLabels\tdon't print dow at top of columns\n");
X    fprintf(stderr,"\t\t-titlebar\tuse titlebars to display dates\n");
X    fprintf(stderr,"\t\t-notitlebar\tdon't use titlebars to display dates\n");
X    fprintf(stderr,"\t\t-checkpointInterval mins\tmins between autosaves\n");
X    fprintf(stderr,"\t\t-xrm 'resource: value'\tpass arbitrary resources\n");
X}
END_OF_FILE
if test 22649 -ne `wc -c <'xkal.c'`; then
    echo shar: \"'xkal.c'\" unpacked with wrong size!
fi
# end of 'xkal.c'
fi
if test -f 'xkal.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xkal.h'\"
else
echo shar: Extracting \"'xkal.h'\" \(850 characters\)
sed "s/^X//" >'xkal.h' <<'END_OF_FILE'
X/*
X *	xkal.h : Global decls for X, widgets, and other stuff
X *
X *	George Ferguson, ferguson@cs.rochester.edu, 27 Oct 1990.
X *	Version 1.1 - 27 Feb 1991.
X *
X *	$Id: xkal.h,v 2.1 91/02/28 11:21:45 ferguson Exp $
X *
X */
X#ifndef XKAL_H
X#define XKAL_H
X
Xextern Display *display;
Xextern Screen *screen;
Xextern Window root;
Xextern GC dateGC1,dateGC2,dateGC3,dateGC12,emptyGC,*shadeGC;
X
Xextern XtAppContext app_con;
Xextern Widget toplevel;
X
Xextern char *program;
Xextern int currentDay,currentMon,currentYear;
Xextern int mainDay,mainMon,mainYear;
Xextern int appointsChanged;
Xextern XtIntervalId timeoutId;
X
Xextern int main();
Xextern void quit(),quit_nosave(),save(),next(),prev(),today(),setNumMonths();
Xextern void focusThisItem(), focusNoItem(), focusNextItem(), focusPrevItem();
Xextern void escapeToSystem();
Xextern void timeoutProc();
X
X#endif /* XKAL_H */
END_OF_FILE
if test 850 -ne `wc -c <'xkal.h'`; then
    echo shar: \"'xkal.h'\" unpacked with wrong size!
fi
# end of 'xkal.h'
fi
echo shar: End of archive 4 \(of 5\).
cp /dev/null ark4isdone
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