[net.sources] visual calendar/time-browser/event-reminder, here it is.

tims@zeus.UUCP (Tim Stoehr) (05/01/86)

I got sufficient interest to post this.  Questions/comments welcome.

#!/bin/sh-----cut here-----cut here-----cut here-----cut here-----
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	month.h #	display.c #	lunar.c #	month.c #	schedule.c #	time.c #	user.c #	Makefile #	month.man #	cheat_sheet 
echo shar: extracting month.h
cat - << \SHAR_EOF > month.h
#define TOP_MONTH_ROW 3
#define YEAR_ROW 16
#define YEAR_COL 12
#define TIME_ROW 20
#define DURATION_ROW 21
#define DESCRIPTION_ROW 22
#define ACCEPT_ROW 23
#define LAST_YEAR_COL 75
#define DATE_COL 0
#define MONTHLY_COL 15
#define YEARLY_COL 23
#define EVERY_COL 30
#define NTH_COL 36
#define LAST_COL 41
#define SMTWTFS_COL 46
#define TIME_COL 11
#define MINUTE_COL 14
#define ACCEPT_COL 11
#define CANCEL_COL 18
#define SMONTH_COL 0
#define SDAY_COL 3
#define SYEAR_COL 6

#define MONTHS 0
#define YEARS 1
#define DAYS 2
#define SCHEDULE 3

#define MAX_DAILY_EVENTS 50

#define MAX_EVENT_STRING_LENGTH 70

#define NOTHING 0
#define ACCEPT 1
#define CANCEL 2

struct event_rec {
	char event_month;
	char event_day;
	short event_year;
	char monthly;
	char yearly;
	char every;
	char smtwtfs[7];
	char nth, last;
	char nth_is_on;
	char hour;
	char minute;
	char duration_hours;
	char duration_minutes;
	char event_string[MAX_EVENT_STRING_LENGTH];
	struct event_rec *next_event;
};

struct mdate {
	short month;
	short year;
};
SHAR_EOF
echo shar: extracting display.c
cat - << \SHAR_EOF > display.c
#include <curses.h>
#include "month.h"

short days;
short crow, ccol;
short current_area;
short message_line_filled;
char *blankout = "                        ";
extern short SCHEDULE_ROW;

char *days_of_week =
"Sunday    Monday   Tuesday Wednesday  Thursday    Friday  Saturday";

short schedule_cols[] = {
	0, SMONTH_COL, SDAY_COL, SYEAR_COL, MONTHLY_COL, YEARLY_COL,
	EVERY_COL, NTH_COL, LAST_COL, SMTWTFS_COL,
	SMTWTFS_COL+3, SMTWTFS_COL+6, SMTWTFS_COL+9, SMTWTFS_COL+12,
	SMTWTFS_COL+15, SMTWTFS_COL+18, -1
};

char *month_names[] = {
	"         ",
	"JANUARY  ",
	"FEBRUARY ",
	"MARCH    ",
	"APRIL    ",
	"MAY      ",
	"JUNE     ",
	"JULY     ",
	"AUGUST   ",
	"SEPTEMBER",
	"OCTOBER  ",
	"NOVEMBER ",
	"DECEMBER " ,
	"         "
};

char *smtwtfs_names[] = {
	"Sun",
	"Mon",
	"Tue",
	"Wed",
	"Thu",
	"Fri",
	"Sat",
};

extern short month, day, year, start_day, edit_flag;
extern short this_month, this_day, this_year;
extern struct event_rec current_event;
short first_year;

print_screen()
{
	print_month(month);
	print_year(year);
	print_day_headers();
	print_cal(month, year, 0);
	print_all_months();
	print_all_years(year);
	hl_month_year(month, 1, year, 1);
}

print_month(month)
int month;
{
	mvaddstr(0, 35, month_names[month]);
}

print_year(year)
int year;
{
	char nbuf[8];

	sprintf(nbuf, "%4d", year);
	mvaddstr(0, 47, nbuf);
}

print_day_headers()
{
	mvaddstr(2, 12, days_of_week);
}

print_cal(month, year, all_events_list)
register month, year;
char *all_events_list;
{
	short i, month_is_current, cday;
	short row = 4, col = 13, standing_out = 0;
	char nbuf[6];

	start_day = get_start_day(month, year);
	days = days_in(month, year);
	if (day > days) {
		day = days;
	} else if (day < 1) {
		day = 1;
	}

	month_is_current = ((month == this_month) && (year == this_year));

	for (i = 1, cday = 1; i <= 42; i++) {

		if ((cday <= days) && (i >= (start_day + 1))) {
			if (all_events_list && all_events_list[cday]) {
				sprintf(nbuf, "(%2d)", cday);
			} else {
				sprintf(nbuf, " %2d ", cday);
			}
			cday++;
		} else {
			strcpy(nbuf, "    ");
		}
		if (month_is_current && ((cday-1) == this_day)) {
			standout();
			standing_out = 1;
			month_is_current = 0;
		}
		mvaddstr(row, col, nbuf);

		if (standing_out) {
			standing_out = 0;
			standend();
		}
		if ((i % 7) == 0) {
			row += 2;
			col = 13;
		} else {
			col += 10;
		}
	}
}

print_all_months()
{
	short i;

	standout();
	for (i = 0; i <= 13; i++) {
		mvaddstr(TOP_MONTH_ROW + i, 0, month_names[i]);
	}
	standend();
}

print_all_years(year)
int year;
{
	short i;
	char nbuf[8];

	first_year = year - 4;
	standout();
	move(YEAR_ROW, YEAR_COL);
	addstr("<<");
	for (i = first_year; i < (first_year + 10); i++) {
		sprintf(nbuf, " %4d ", i);
		addstr(nbuf);
	}
	addstr(">>");
	standend();
}

hl_month_year(month, mflag, year, yflag)
short month, mflag, year, yflag;
{
	short i;

	if (mflag != -1) {
		if (!mflag) {
			standout();
		}
		mvaddstr(TOP_MONTH_ROW + month, 0, month_names[month]);
		if (!mflag) {
			standend();
		}
	}
	if (yflag != -1) {
		if (!yflag) {
			standout();
		}
		move(YEAR_ROW, 14 + (6 * (year - first_year)));
		for (i = 0; i < 6; i++) {
			addch(inch());
		}
		if (!yflag) {
			standend();
		}
	}
}

start_display()
{
	goto_day(day);
}

goto_this_day(gmonth, gday, gyear)
int gmonth, gday, gyear;
{
	month = gmonth;
	year = gyear;
	day = gday;

	print_screen();

	switch(current_area) {
	case MONTHS:
		goto_month(month);
		break;
	case DAYS:
		goto_day(day);
		break;
	case YEARS:
		goto_year(year);
		break;
	}
}

goto_month(month)
int month;
{
	crow = TOP_MONTH_ROW + month;
	ccol = 9;
	current_area = MONTHS;
}

goto_day(tday)
short tday;
{
	day = tday;
	get_row_col_from_day(&crow, &ccol, day);
	current_area = DAYS;
}

goto_year(year)
int year;
{
	crow = YEAR_ROW;
	ccol = YEAR_COL + 3 + (6 * (year - first_year));
	current_area = YEARS;
}

goto_schedule()
{
	current_area = SCHEDULE;
	crow = SCHEDULE_ROW;
	ccol = MONTHLY_COL;
}

move_cursor(dir)
register short dir;
{
	short mday, row, col;

	if ((current_area != SCHEDULE) &&
	    ((dir == 'm') || (dir == 'y') || (dir == 'd'))) {
		if (dir == 'm') {
			goto_month(month);
		} else if (dir == 'y') {
			goto_year(year);
		} else {
			goto_day(day);
		}
		return;
	}
	switch (current_area) {
	case MONTHS:
		switch(dir) {
		case 'j':
			if (crow <= (TOP_MONTH_ROW + 12)) {
				crow++;
			}
			break;
		case 'k':
			if (crow > TOP_MONTH_ROW) {
				crow--;
			}
			break;
		}
		break;
	case YEARS:
		switch(dir) {
		case 'h':
			if (ccol > YEAR_COL) {
				if (ccol == (YEAR_COL + 3)) {
					ccol = YEAR_COL;
				} else {
					ccol -= 6;
				}
			} else {
				shift_years(-1);
			}
			break;
		case 'l':
			if (ccol < LAST_YEAR_COL) {
				if (ccol == (LAST_YEAR_COL - 6)) {
					ccol = LAST_YEAR_COL;
				} else if (ccol == YEAR_COL) {
					ccol = YEAR_COL + 3;
				} else {
					ccol += 6;
				}
			} else {
				shift_years(1);
			}
			break;
		}
		break;
	case DAYS:

		row = crow;
		col = ccol;

		switch(dir) {
		case 'h':
			if (col > 15) {
				col -= 10;
			}
			break;
		case 'j':
			if (row < 14) {
				row += 2;
			}
			break;
		case 'k':
			if (row > 4) {
				row -= 2;
			}
			break;
		case 'l':
			if (col < 74 ) {
				col += 10;
			}
			break;
		}
		if ((mday = get_day_from_row_col(row, col)) > 0) {
			day = mday;
			crow = row;
			ccol = col;
		}
		break;
	case SCHEDULE:
		schedule_move_cursor(dir);
		break;
	}
}

schedule_move_cursor(dir)
short dir;
{
	short i;

	switch(dir) {
	case 'H':
		if (crow == SCHEDULE_ROW) {
			ccol = DATE_COL;
		}
		break;
	case 'L':
		if (crow == SCHEDULE_ROW) {
			ccol = SMTWTFS_COL + 18;
		}
		break;
	case 'h':
	case 'l':
		if (crow == SCHEDULE_ROW) {
			i = 0;
			while (schedule_cols[++i] != ccol) ;
			i += ((dir == 'h') ? -1 : 1);
			if (schedule_cols[i] != -1) {
				ccol = schedule_cols[i];
			}
		} else if ((crow == TIME_ROW) || (crow == DURATION_ROW)) {
			ccol = (dir == 'h') ? TIME_COL : MINUTE_COL;
		} else if (crow == ACCEPT_ROW) {
			ccol = (dir == 'h') ? ACCEPT_COL : CANCEL_COL;
		}
		break;
	case '\t':
	case '\n':
	case '\r':
		if (crow == SCHEDULE_ROW) {
			crow += 2;
			ccol = TIME_COL;
		} else if (crow == DESCRIPTION_ROW) {
			crow = ACCEPT_ROW;
			ccol = ACCEPT_COL;
		} else if (crow == ACCEPT_ROW)  {
			crow = SCHEDULE_ROW;
			ccol = MONTHLY_COL;
		} else if (crow == DURATION_ROW) {
			crow = DESCRIPTION_ROW;
			ccol = TIME_COL + strlen(current_event.event_string);
			handle_event_description();
		} else {
			crow++;
		}
		break;
	case '\033':
		goto_day(day);
		break;
	}
}

selection()
{
	short new_year;
	int x;

	switch(current_area) {
		case MONTHS:
			if ((crow - TOP_MONTH_ROW) != month) {
				if (crow <= TOP_MONTH_ROW) {
					hl_month_year(month, 0, 0, -1);
					month = 12;
					shift_years(-1);
					hl_month_year(month, 1, 0, -1);
					crow = TOP_MONTH_ROW + 13;
				} else if (crow > (TOP_MONTH_ROW + 12)) {
					hl_month_year(month, 0, 0, -1);
					month = 1;
					shift_years(1);
					hl_month_year(month, 1, 0, -1);
					crow = TOP_MONTH_ROW;
				} else {
					hl_month_year(month, 0, 0, -1);
					month = crow - TOP_MONTH_ROW;
					hl_month_year(month, 1, 0, -1);
					print_cal(month, year, 0);
				}
				print_month(month);
			}
			break;
		case YEARS:
			if (ccol == YEAR_COL) {
				shift_years(-10);
			} else if (ccol == LAST_YEAR_COL) {
				shift_years(10);
			} else {
				new_year = first_year +
					   ((ccol - (YEAR_COL + 3)) / 6);
				if (new_year != year) {
					hl_month_year(0, -1, year, 0);
					year = new_year;
				}
				print_cal_hl_year(month, year);
			}
			break;
		case SCHEDULE:
			if (crow == SCHEDULE_ROW) {
				select_regularity_col(ccol);
			} else if (crow == ACCEPT_ROW) {
				x = (ccol == ACCEPT_COL) ? ACCEPT : CANCEL;
				accept_cancel(ccol == ACCEPT_COL);
				if (edit_flag) {
					return(x);
				}
			} else {
				move_cursor('\t');
			}
			break;
	}
	return(NOTHING);
}

shift_years(shift)
short shift;
{
	if (((year + shift) < (first_year + 10)) &&
	    ((year + shift) >= first_year)) {
		hl_month_year(0, -1, year, 0);
		year += shift;
		hl_month_year(0, -1, year, 1);
	} else {
		year += shift;
		print_all_years(first_year + shift + 4);
	}
	print_cal_hl_year(month, year);
}

print_cal_hl_year(month, year)
int month, year;
{
	print_cal(month, year, 0);
	print_year(year);
	hl_month_year(0, -1, year, 1);
}

get_row_col_from_day(row, col, day)
short *row, *col, day;
{
	*row =  4 + (((start_day + day - 1) / 7) *  2);
	*col = 15 + (((start_day + day - 1) % 7) * 10);
}

get_day_from_row_col(row, col)
short row, col;
{
	short mday;

	mday = (7 * ((row - 4)) / 2) +
	       ((col - 14) / 10) - start_day + 1;

	if ((mday <= days) && (mday > 0)) {
		return(mday);
	}
	return(0);
}

print_event_regularity(event)
struct event_rec *event;
{
	if (event->monthly) {
		standout();
	}
	mvaddstr(SCHEDULE_ROW, MONTHLY_COL, "monthly");
	standend();
	if (event->yearly) {
		standout();
	}
	mvaddstr(SCHEDULE_ROW, YEARLY_COL, "yearly");
	standend();
	if (event->every) {
		standout();
	}
	mvaddstr(SCHEDULE_ROW, EVERY_COL, "every");
	standend();
	print_smtwtfs(event->smtwtfs);
	print_nth(event);
	if (event->last) {
		standout();
	}
	mvaddstr(SCHEDULE_ROW, LAST_COL, "last");
	standend();
}

print_smtwtfs(smtwtfs)
char smtwtfs[];
{
	short i;
	char *s;

	move(SCHEDULE_ROW, SMTWTFS_COL);

	for (i = 0; i < 7; i++) {
		if (smtwtfs[i]) {
			standout();
		}
		addstr(smtwtfs_names[i]);
		if (smtwtfs[i]) {
			standend();
		}
	}
}

hl_schedule(col, hflag)
register col, hflag;
{
	register int ch;
	short i;

	move(SCHEDULE_ROW, col);

	if (hflag) {
		standout();
	}

	if ((col < SMTWTFS_COL) || (col > (SMTWTFS_COL+18))) {
		while((ch = inch()) != ' ') {
			move(SCHEDULE_ROW, col);
			addch(ch);
			col++;
		}
	} else {
		move(SCHEDULE_ROW, col);

		for (i = 0; i < 3; i++) {
			addch(inch());
		}
	}
	standend();
}

display_event(event)
struct event_rec *event;
{
	clear_schedule_area();
	print_date(event->event_month, event->event_day, event->event_year);
	print_event_regularity(event);
	print_time(event);
	print_duration(event);
	print_event_description(event);
	print_accept();
}

print_accept()
{
	mvaddstr(ACCEPT_ROW, TIME_COL, "Accept/Cancel");
}

print_time(event)
struct event_rec *event;
{
	char buf[32];
	short hour;
	char *apm;

	hour = event->hour;
	apm = (hour < 12) ? "AM" : "PM";

	if (hour > 12) {
		hour = hour % 12;
	}
	if (hour == 0) {
		hour = 12;
	}
	sprintf(buf, "time:  %2d:%02d %s", hour, event->minute, apm);
	mvaddstr(TIME_ROW, 4, buf);
}

print_duration(event)
struct event_rec *event;
{
	char buf[32];

	sprintf(buf, "duration:  %2d:%02d", event->duration_hours,
		event->duration_minutes);
	mvaddstr(DURATION_ROW, 0, buf);
}

print_event_description(event)
struct event_rec *event;
{
	char buf[100];

	sprintf(buf, "event:  %s", event->event_string);
	mvaddstr(DESCRIPTION_ROW, 3, buf);
}

scroll_time(dir)
register dir;
{
	short hour, minute, d;

	if (crow == TIME_ROW) {
		hour = current_event.hour;
		minute = current_event.minute;
	} else if (crow == DURATION_ROW) {
		hour = current_event.duration_hours;
		minute = current_event.duration_minutes;
	} else if (crow != SCHEDULE_ROW) {
		return;
	}
	if (ccol == TIME_COL) {
		if (dir == ' ') {
			hour = (hour + 1) % 24;
		} else {
			hour = (hour + 23) % 24;
		}
	} else if (ccol == MINUTE_COL) {
		if (dir == ' ') {
			minute = (minute + 15) % 60;
		} else {
			minute = (minute + 45) % 60;
		}
	} else  {
		if (ccol == SMONTH_COL) {
			current_event.event_month += ((dir == ' ') ? 1 : -1);
			if (current_event.event_month > 12) {
				current_event.event_month = 1;
			} else if (current_event.event_month <= 0) {
				current_event.event_month = 12;
			}
		} else if (ccol == SDAY_COL) {
			d = days_in(current_event.event_month,
			    current_event.event_year);
			current_event.event_day += ((dir == ' ') ? 1 : -1);
			if (current_event.event_day > d) {
				current_event.event_day = 1;
			} else if (current_event.event_day <= 0) {
				current_event.event_day = d;
			}
		} else if (ccol == SYEAR_COL) {
			current_event.event_year += ((dir == ' ') ? 1 : -1);
			if (current_event.event_year < 0) {
				current_event.event_year = this_year;
			}
		} else if (ccol == NTH_COL) {
			current_event.nth += ((dir == ' ') ? 1 : -1);
			if (current_event.nth < 1) {
				current_event.nth = 53;
			} else if (current_event.nth > 53) {
				current_event.nth = 1;
			}
		}
	}
	if (crow == TIME_ROW) {
		current_event.hour = hour;
		current_event.minute = minute;
		print_time(&current_event);
	} else if (crow == DURATION_ROW) {
		current_event.duration_hours = hour;
		current_event.duration_minutes = minute;
		print_duration(&current_event);
	} else if (ccol <= SYEAR_COL) {
		print_date(current_event.event_month,
		    current_event.event_day, current_event.event_year);
	} else if (ccol == NTH_COL) {
		current_event.nth_is_on = 1;
		print_nth(&current_event);

	}
}

hl_all(event, mf, yf, ef, nf, lf, xf)
struct event_rec *event;
register mf, yf, ef, nf, lf, xf;
{
	short i;

	toggle_char(&(event->monthly), mf, MONTHLY_COL);
	toggle_char(&(event->yearly), yf, YEARLY_COL);
	toggle_char(&(event->every), ef, EVERY_COL);
	toggle_char(&(event->nth_is_on), nf, NTH_COL);
	toggle_char(&(event->last), lf, LAST_COL);

	if (xf != -1) {
		for (i = 0; i < 7; i++) {
			toggle_char(&(event->smtwtfs[i]), xf,
			    (SMTWTFS_COL+(3*i)));
		}
	}
}

toggle_char(c, f, col)
char *c;
register f, col;
{
	if (f == 1) {
		if (!(*c)) {
			*c = 1;
			hl_schedule(col, f);
		}
	} else if (!f) {
		if (*c) {
			*c = 0;
			hl_schedule(col, f);
		}
	}
}

print_date(month, day, year)
int month, day, year;
{
	char buf[64];

	standout();
	sprintf(buf, "%2d/%2d/%4d", month, day, year);
	mvaddstr(SCHEDULE_ROW, DATE_COL, buf);
	standend();
}

error_message(str, sound)
char *str;
int sound;
{
	mvaddstr(0, 0, blankout);
	mvaddstr(0, 0, str);
	refresh();
	if (sound) {
		sound_bell();
	}
	message_line_filled = 1;
}

sound_bell()
{
	putchar(7);
	fflush(stdout);
}

clear_schedule_area()
{
	move(SCHEDULE_ROW, 0);
	clrtobot();
}

blank_out(col)
int col;
{
	while (col < 35) {
		addch(' ');
		col++;
	}
}
print_nth(event)
struct event_rec *event;
{
	char *ns, buf[10];
	short n;

	if (event->nth_is_on) {
		standout();
	}
	n = event->nth;
	if (event->nth > 13) {
		n %= 10;
	}

	switch(n) {
	case 0:
	case 4:
	case 5:
	case 6:
	case 7:
	case 8:
	case 9:
	case 10:
	case 11:
	case 12:
	case 13:
		ns = "th";
		break;
	case 1:
		ns = "st";
		break;
	case 2:
		ns = "nd";
		break;

	case 3:
		ns = "rd";
		break;
	}
	sprintf(buf, "%02d%s", event->nth, ns);
	mvaddstr(SCHEDULE_ROW, NTH_COL, buf);
	standend();
}

remind(str, minutes)
char *str;
int minutes;
{
	char s[50];

	if (minutes > 1) {
		sprintf(s, "In %d minutes", minutes);
	} else if (minutes == 1) {
		strcpy(s, "In 1 minute");
	} else {
		strcpy(s, "Right now");
	}
	printf("\n%s   \n%s.   \n", str, s);
	sound_bell();
}

clear_message_line()
{
	message_line_filled = 0;
	mvaddstr(0, 0, blankout);
}

incr(ch)
int ch;
{
	short inc;

	inc = ((ch == 'n') || (ch == '+')) ? 1 : -1;

	switch (current_area) {
	case MONTHS:
		hl_month_year(month, 0, 0, -1);
		if ((month == 1) && (inc < 0)) {
			shift_years(-1);
			month = 12;
		} else if ((month == 12) && (inc > 0)) {
			shift_years(1);
			month = 1;
		} else {
			month += inc;
		}
		hl_month_year(month, 1, 0, -1);
		print_month(month);
		goto_month(month);
		print_cal(month, year, 0);
		break;
	case DAYS:
		if (((inc > 0) && (day < days)) || ((inc < 0) && (day > 1))) {
			goto_day(day + inc);
		}
		break;
	case YEARS:
		shift_years(inc);
		goto_year(year);
		break;
	}
}
SHAR_EOF
echo shar: extracting lunar.c
cat - << \SHAR_EOF > lunar.c
#include <curses.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>

/* Globals. */
double		 Fraction;

/* Linked in later. */
char		*rindex();
time_t		 time();
struct tm	*localtime();


#define LINES		24
#define WIDTH		80
#define CENTER		((WIDTH - 2 * LINES) / 2)
#define BRIGHT		'@'
#define LEDGE		'('
#define REDGE		')'
#define FULL		0.5
#define TwoPi		(2 * 3.14159)
#define ZERO		0.03

extern short month, day, year;

lunar()
{
		long yday;

		yday = (long) (days_since_jan1(month, day, year) + 1);
		Calculate(yday, ((long) (year - 1900)), 11L, 0L);
		Draw();
		refresh();
		get_char();
		clear();
		print_screen();
}

long
Calculate(julian, year, hour, minute)
long julian, year, hour, minute;
{
	static struct tm *tm;
	register long	 Length;
	register long	 Phase;
	register long	 DeltaH;
	register long	 Delta;
	register long	 offset;
	/*time_t		 tick;

	static short called_before = 0;

	if (!called_before) {
		tick	= time((time_t *)0);
		tm		= localtime(&tick);
		called_before = 1;
	} else {
		tm->tm_yday++;
	}
	julian	= tm->tm_yday + 1;
	year	= tm->tm_year - 78;
	hour	= tm->tm_hour;
	minute	= tm->tm_min;*/

	year -= 78;
	Length	= (double)2551 / 60 * 1000 + (double)443 / 60;
	offset	= ((year * 365L + julian) * 24L + hour) * 60L + minute;
	Delta	= offset - (273L * 24L + 13L) * 60L + 23L;
	Phase	= Delta - (Delta / Length) * Length;

	Fraction	= (double)Phase / Length;
	return(Phase);
}

int
CharPos(x)
	double		x;
{
	register int	i;

	i = x * LINES + 0.5;
	if ((i += LINES + CENTER) < 1)
	i = 1;
	return(i);
}


Draw()
{
	register char	*p;
	register int	 i;
	register int	 end;
	register double	 y;
	register double	 cht;
	register double	 squisher;
	register double	 horizon;
	register double	 terminator;
	char		 Buffer[256];
	int line = 1;

	/* Clear screen? */
	clear();

	if (Fraction < FULL)
	squisher = cos(TwoPi * Fraction);
	else
	squisher = cos(TwoPi * (Fraction - FULL));

	cht = (double)2.0 / (LINES - 6.0);
	for (y = 0.93; y > -1.0; y -= cht)
	{
	for (i = sizeof Buffer, p = Buffer; --i >= 0; )
		*p++ = ' ';
	horizon = sqrt(1.0 - y * y);
	Buffer[    CharPos(-horizon)]	= LEDGE;
	Buffer[i = CharPos( horizon)]	= REDGE;
	Buffer[++i]			= '\0';
	terminator = horizon * squisher;
	if (Fraction > ZERO && Fraction < (1.0 - ZERO))
	{
		if (Fraction < FULL)
		{
		i   = CharPos( terminator);
		end = CharPos( horizon);
		}
		else
		{
		i   = CharPos(-horizon);
		end = CharPos( terminator);
		}
		while (i <= end)
		Buffer[i++] = BRIGHT;
	}
	mvaddstr(line++, 1, Buffer);
	}
	move(LINES-1, 0);
	refresh();
}
SHAR_EOF
echo shar: extracting month.c
cat - << \SHAR_EOF > month.c
#include <curses.h>
#include <signal.h>
#include <utmp.h>
#include "month.h"

short initialized = 0;
short dhour, dminute, dsecond;
extern short crow, ccol, update_schedule, updating;
extern short this_month, this_day, this_year, SCHEDULE_ROW;
extern struct event_rec events;
extern struct mdate mdates[];

main(argc, argv)
int argc;
char *argv[];
{
	check_args(argc, argv);
	initialize();
	print_screen();
	start_display();
	user();
	terminate();
}

initialize()
{
	int blast_out(), i;

	signal(SIGINT, blast_out);
	signal(SIGQUIT, blast_out);
	get_current_date();
	read_schedule();

	for (i = 0; i < 12; i++) {
		mdates[i].month = this_month;
		mdates[i].year = this_year;
	}
	initscr();
	SCHEDULE_ROW = (LINES < 24) ? 17 : 18;
	initialized = 1;
	crmode();
	noecho();
}

terminate()
{
	if (updating) {
		return;
	}
	signal(SIGINT, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);

	if (initialized) {
		if (update_schedule) {
			mvaddstr(0, 0, "updating schedule\t");
		} else {
			mvaddstr(0, 0, "schedule unchanged\t");
		}
		refresh();
		if (update_schedule) {
			if (write_schedule() == -1) {
				sound_bell();
				mvaddstr(0, 0, "cannot create .month");
			}
		}
	}
	move(LINES-1, 0);
	clrtoeol();
	refresh();
	endwin();
	exit(0);
}

blast_out()
{
	update_schedule = 0;
	terminate();
}

check_args(argc, argv)
int argc;
char *argv[];
{
	short i, daemon = 0;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch(argv[i][1]) {
			case 'd':
				daemon = i;
				break;
			default:
				goto BA;
			}
		} else {
BA:			printf("Bad argument: %s\n", argv[i]);
			terminate();
		}
	}
	if (daemon) {
		if (!fork()) {
			signal(SIGINT, SIG_IGN);
			signal(SIGQUIT, SIG_IGN);
			signal(SIGTSTP, SIG_IGN);
			daemonize();
		}
		exit(0);
	}
}

daemonize()
{
	int do_nothing();
	struct event_rec *eptr;
	short minutes, eminutes, diff, seconds;

	printf("daemon started\n");
	fflush(stdout);
AGAIN:
	get_current_date();
	minutes = (60 * dhour) + dminute;

	seconds = (60 * (15 - (dminute % 15) - 1)) + (60 - dsecond);
	if (seconds < 60) {
		seconds = 900;
	}
	signal(SIGALRM, do_nothing);
	alarm(seconds);
	if (!logged_in()) {
		terminate();
	}
	read_schedule();

	eptr = events.next_event;

	while (eptr) {

		if (event_matches_date(eptr)) {
			eminutes = (((short)60) * ((short)eptr->hour)) +
			    ((short)eptr->minute);
			diff = eminutes - minutes;
			if ((diff >= 0) && (diff <= 15)) {
				remind(eptr->event_string, diff);
			}
		}
		eptr = eptr->next_event;
	}
	pause();
	goto AGAIN;
}

logged_in()
{
	static struct utmp u_buf;
	struct utmp t_buf;
	int fd, retval = 0;
	static short called_before = 0;
	char *ttyname(), *tname;
AGAIN:
	if ((fd = open("/etc/utmp", 0)) < 0) {
		return(0);
	}
	if (!called_before) {
		tname = ttyname(0) + 5;
	}
	while (read(fd, &t_buf, sizeof(struct utmp)) > 0) {
		if (!called_before) {
			if (!strcmp(tname, t_buf.ut_line)) {
				u_buf = t_buf;
				break;
			}
		} else if (byte_comp(&u_buf, &t_buf, sizeof(struct utmp))) {
				close(fd);
				retval = 1;
				break;
		}
	}
	close(fd);
	if (!called_before) {
		called_before = 1;
		goto AGAIN;
	}
	return(retval);
}

do_nothing()
{
}

byte_comp(s1, s2, n)
register char *s1, *s2;
register int n;
{
	short i;

	for (i = 0; i < n; i++) {
		if (*(s1++) != *(s2++)) {
			return(0);
		}
	}
	return(1);
}
SHAR_EOF
echo shar: extracting schedule.c
cat - << \SHAR_EOF > schedule.c
#include <curses.h>
#include "month.h"

struct event_rec events = {0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0},
0, 0, 0, 0, 0, 0, 0, 0, 0};
struct event_rec current_event = {0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0},
1, 0, 0, 10, 0, 1, 0, "", 0};
short update_schedule = 0;
short updating = 0, edit_flag = 0, put_into_schedule, parsed_correctly;
char schedule_file_name[75];
short SCHEDULE_ROW;

char *grids[] = {
"mid.  .  .  1a .  .  .  2a .  .  .  3a .  .  .  4a .  .  .  5a .  .  .  6a",
"6a .  .  .  7a .  .  .  8a .  .  .  9a .  .  .  10a.  .  .  11a.  .  .  noon",
"noon  .  .  1p .  .  .  2p .  .  .  3p .  .  .  4p .  .  .  5p .  .  .  6p",
"6p .  .  .  7p .  .  .  8p .  .  .  9p .  .  .  10p.  .  .  11p.  .  .  mid"
};
extern short month, day, year, days;

post_event(month, day, year)
short month, day, year;
{
	goto_schedule();
	current_event.event_month = month;
	current_event.event_day = day;
	current_event.event_year = year;
	display_event(&current_event);
}

read_schedule()
{
	char *getenv(), *malloc(), *s;
	int fd, rec_size;
	struct event_rec event_buf, *event_ptr, *chain_ptr;

	chain_ptr = events.next_event;	/* free old events */
	while (chain_ptr) {
		event_ptr = chain_ptr;
		chain_ptr = chain_ptr->next_event;
		free(event_ptr);
	}
	events.next_event = 0;

	if (!(s = getenv("HOME"))) {
		s = ".";
	}
	strcpy(schedule_file_name, s);
	strcat(schedule_file_name, "/.month");

	rec_size = sizeof(struct event_rec);

	if ((fd = open(schedule_file_name, 0)) != -1) {

		chain_ptr = &events;

		while (read(fd, &event_buf, rec_size) == rec_size) {
			if (event_ptr = (struct event_rec *) malloc(rec_size)) {
				chain_ptr->next_event = event_ptr;
				chain_ptr = event_ptr;
				*chain_ptr = event_buf;
				chain_ptr->next_event = (struct event_rec *)0;
			} else {
				break;
			}
		}
		close(fd);
	}
}

write_schedule()
{
	int fd;
	struct event_rec *chain_ptr;

	updating = 1;

	if ((fd = creat(schedule_file_name, 0640)) == -1) {
		return(-1);
	}
	chain_ptr = events.next_event;

	while (chain_ptr) {
		if (!is_passed_event(chain_ptr)) {
			write(fd, (char *)chain_ptr, sizeof(struct event_rec));
		}
		chain_ptr = chain_ptr->next_event;
	}
	close(fd);
	updating = 0;
	return(0);
}

select_regularity_col(col)
register col;
{
	short i, hflag;

	switch(col) {
	case MONTHLY_COL:
		hl_all(&current_event, (current_event.monthly ? 0 : 1),
		    0, -1, -1, -1, -1);
		break;
	case YEARLY_COL:
		hl_all(&current_event, 0, (current_event.yearly ? 0 : 1),
		    -1, -1, -1, -1);
		break;
	case EVERY_COL:
		hl_all(&current_event, -1, -1,
		   (current_event.every ? 0 : 1), -1, -1, -1);
		break;
	case SMTWTFS_COL:
	case SMTWTFS_COL + 3:
	case SMTWTFS_COL + 6:
	case SMTWTFS_COL + 9:
	case SMTWTFS_COL + 12:
	case SMTWTFS_COL + 15:
	case SMTWTFS_COL + 18:
		i = (col - SMTWTFS_COL) / 3;
		hflag = (current_event.smtwtfs[i] = !current_event.smtwtfs[i]);
		hl_schedule(col, hflag);
		hl_all(&current_event, -1, -1, -1, -1, -1, -1);
		break;
	case NTH_COL:
		hl_all(&current_event, -1, -1, -1,
		    (current_event.nth_is_on ? 0 : 1), -1, -1);
		break;
	case LAST_COL:
		hl_all(&current_event, -1, -1, -1, -1,
		    (current_event.last ? 0 : 1), -1);
		break;
	}
}

accept_cancel(is_accept)
short is_accept;
{
	if (is_accept) {
		accept_current_event();
	} else {
		cancel_current_event();
		display_event(&current_event);
	}
	goto_day(day);
}

accept_current_event()
{
	if ((parse_event(&current_event) != -1)) {
		if (get_answer("Put into schedule? ", "done", "change cancelled")
		    == 'y') {
			if (!edit_flag) {
				link_event(&current_event);
			}
			put_into_schedule = 1;
		}
	}
}

cancel_current_event()
{
	if (!edit_flag) {
		current_event = events;
	}
}

link_event(event)
struct event_rec *event;
{
	struct event_rec *t, *ptr;
	char *malloc();

	if (!(ptr = (struct event_rec *)
	    malloc(sizeof(struct event_rec)))) {
		return;
	}
	*ptr = *event;
	t = events.next_event;
	events.next_event = ptr;
	ptr->next_event = t;
	update_schedule++;
}

parse_event(event)
struct event_rec *event;
{
	short hs;

	hs = has_smtwtfs(event->smtwtfs);

	if ((event->every || event->nth_is_on || event->last) && !hs) {
		error_message("missing day of week", 1);
		return(-1);
	}
	if (hs && !event->every && !event->nth_is_on && !event->last) {
MQ:		error_message("missing qualifier", 1);
		return(-1);
	}
	if (!event->every &&
	    (event->monthly || event->yearly) &&
		(event->nth_is_on || event->last)) {
		error_message("need 'every'", 1);
		return(-1);
	}
	if (event->last && !event->monthly && !event->yearly) {
		error_message("monthly or yearly?", 1);
		return(-1);
	}
	if ((event->nth_is_on || event->last) &&
	    (!event->monthly && !event->yearly && !event->every)) {
		goto MQ;
	}
	parsed_correctly = 1;
	return(0);
}


overview()
{
	short i, j, row, col, hour, minute, duration, n;
	struct event_rec *events_today[MAX_DAILY_EVENTS];
	char *grid;

	get_daily_events(events_today);

	clear_schedule_area();

	for (i = 0; i < 4; i++) {
		mvaddstr((SCHEDULE_ROW + i + i), 1, grids[i]);
	}

	standout();
	i = 0;

	while (events_today[i]) {

		hour = events_today[i]->hour;
		minute = events_today[i]->minute;

		row = SCHEDULE_ROW + ((hour / 6) * 2);

		duration = (events_today[i]->duration_hours * 60) +
		    events_today[i]->duration_minutes;
		col = 1 + (12 * (hour % 6)) + (3 * (minute / 15));
		n = hour / 6;
		grid = grids[n];
		duration /= 15;

		move(row, col);

		for (j = 0; j < duration; j++) {
			addch(grid[col - 1]);
			addch(grid[col]);
			addch(grid[col + 1]);

			col += 3;
			if (col > 72) {
				col = 1;
				row += 2;

				if (row > (SCHEDULE_ROW + 6)) {
					row = SCHEDULE_ROW;
				}
				move(row, col);
				grid = grids[++n % 4];
			}
		}
		i++;
	}
	standend();
}

get_daily_events(events_today)
struct event_rec *events_today[];
{
	short i = 0;
	struct event_rec *eptr;

	eptr = events.next_event;

	while (eptr && (i < (MAX_DAILY_EVENTS - 1))) {
		if (event_matches_date(eptr)) {
			if (!events_today) {
				return(1);
			}
			events_today[i++] = eptr;
		}
		eptr = eptr->next_event;
	}
	if (events_today) {
		events_today[i] = 0;
	}
	return(i);
}

scan_today_events()
{
	struct event_rec *events_today[MAX_DAILY_EVENTS];
	short i, j, k, ch, n;

	if ((n = get_daily_events(events_today)) <= 0) {
		error_message("No events this day", 0);
		clear_schedule_area();
		return;
	}
	sort_events(events_today, n);

	for (i = 0; i < n; i++) {
		current_event = *events_today[i];
		display_event(events_today[i]);
GETCH:
		ch = get_npdeq();

		switch(ch) {
		case '\0':
			i--;
			break;
		case '\033':
		case 'q':
			goto OUT;
		case 'n':
			j = i + 1;
			while ((j < n) && (!events_today[j])) {
				j++;
			}
			if (j >= n) {
				/*sound_bell();
				goto GETCH;*/
				goto OUT;
			}
			i = j - 1;
			break;
		case 'p':
			j = i - 1;
			while ((j >= 0) && (!events_today[j])) {
				j--;
			}
			if (j < 0) {
				sound_bell();
				goto GETCH;
			}
			i = j - 1;
			break;
		case 'd':
			delete_event(events_today[i]);
			events_today[i] = 0;
			for (k = i+1; k < n; k++) {
				if (events_today[k] != 0) {
					i = k;
					break;
				}
			}
			if (events_today[i] == 0) {
				for (k = i-1; k >= 0; k--) {
					if (events_today[k] != 0) {
						i = k;
						break;
					}
				}
			}
			if (events_today[i] != 0) {
				i--;
			} else {
				goto OUT;
			}
			break;
		case 'e':
EDIT:		goto_schedule();
			edit_flag = 1;
			parsed_correctly = 0;
			put_into_schedule = 0;
			if (user() == ACCEPT) {
				if (parsed_correctly && put_into_schedule) {
					*events_today[i] = current_event;
					update_schedule++;
				} else if (!parsed_correctly) {
					goto EDIT;
				}
			} else {
				display_event(events_today[i]);
			}
			edit_flag = 0;
			i--;
			break;
		}
	}
OUT:	goto_day(day);
}

scan_every_event()
{
	register short ch;
	struct event_rec *ptr;

	ptr = events.next_event;

	while (ptr) {
		current_event = *ptr;
		display_event(ptr);

		ch = get_nq();

		switch(ch) {
			case 'n':
				ptr = ptr->next_event;
				break;
			case 'q':
				goto RET;
				break;
		}
	}
RET: ;
}

delete_event(event)
struct event_rec *event;
{
	struct event_rec *ptr;

	ptr = &events;

	while (ptr && (ptr->next_event != event)) {
		ptr = ptr->next_event;
	}
	if (ptr) {
		ptr->next_event = ptr->next_event->next_event;
		free((char *)event);
	}
	update_schedule++;
}

sort_events(e, n)
register struct event_rec *e[];
int n;
{
	register struct event_rec *t;
	register i, j;
	short f;

	for (i = 0; i < n; i++) {
		for (j = (n - 1), f = 0; j > 0; j--) {
			if ((e[j]->hour < e[j-1]->hour) ||
			    ((e[j]->hour == e[j-1]->hour) &&
			    (e[j]->minute < e[j-1]->minute))) {
				t = e[j];
				e[j] = e[j-1];
				e[j-1] = t;
				f++;
			}
		}
		if (f == 0) {
			break;
		}
	}
}

show_all_events(month, year)
register month, year;
{
	register struct event_rec *eptr;
	short i;
	char match_list[32];
	short tday;

	tday = day;

	for (i = 1; i <= days; i++) {

		eptr = events.next_event;
		match_list[i] = 0;
		day = i;

		while (eptr) {
			if (event_matches_date(eptr)) {
				match_list[i] = 1;
				break;
			}
			eptr = eptr->next_event;
		}
	}
	day = tday;
	print_cal(month, year, match_list);
}
SHAR_EOF
echo shar: extracting time.c
cat - << \SHAR_EOF > time.c
#include <sys/time.h>
#include "month.h"

short month, year, day;
short this_month, this_day, this_year;
short start_day;

extern short dhour, dminute, dsecond, days;

get_current_date()
{
	struct timeval tp;
	struct timezone tzp;
	struct tm *tmp, *localtime();

	gettimeofday(&tp, &tzp);
	tmp = localtime(&tp.tv_sec);
	year = this_year = 1900 + tmp->tm_year;
	month = this_month = tmp->tm_mon + 1;
	day = this_day = tmp->tm_mday;
	dhour = tmp->tm_hour;
	dminute = tmp->tm_min;
	dsecond = tmp->tm_sec;
}

jan1(year)
register year;
{
	register day;

	day = 4 + year + (year + 3) / 4;

	if (year > 1800) {
		day -= (year - 1701) / 100;
		day += (year - 1601) / 400;
	}

	if(year > 1752)
		day += 3;

	return(day % 7);
}

is_leap_year(year)
int year;
{
	int day;

	day = jan1(year);
	return((((jan1(year + 1) + 7 - day) % 7) == 2) ? 1 : 0);
}

get_start_day(month, year)
register month, year;
{
	short day, i;

	day = jan1(year);

	for (i = 1; i < month; i++) {
		day = (day + days_in(i, year)) % 7;
	}
	return(day);
}

days_in(month, year)
register month, year;
{
	int days;

	switch(month) {
	case 1:
	case 3:
	case 5:
	case 7:
	case 8:
	case 10:
	case 12:
		days = 31;
		break;
	case 4:
	case 6:
	case 9:
	case 11:
		days = 30;
		break;
	case 2:
		days = 28 + is_leap_year(year);
		break;
	}
	return(days);
}

is_passed_event(event)
struct event_rec *event;
{
	if (event->monthly || event->yearly || event->every ||
	    (event->event_year > this_year)) {
		return(0);
	}
	if (event->event_year < this_year) {
		return(1);
	}
	/* now we know it's this year */
	if (event->event_month > this_month) {
		return(0);
	}
	if (event->event_month < this_month) {
		return(1);
	}
	/* now we know it's this month */
	if (event->event_day < this_day) {
		return(1);
	}
	return(0);
}

is_before(m, d, y, month, day, year)
register m, d, y, month, day, year;
{
	if (y < year) {
		return(1);
	}
	if (y > year) {
		return(0);
	}
	if (m < month) {
		return(1);
	}
	if (m > month) {
		return(0);
	}
	if (d < day) {
		return(1);
	}
	return(0);
}

has_smtwtfs(smtwtfs)
register char *smtwtfs;
{
	register i;

	for (i = 0; i < 7; i++) {
		if (smtwtfs[i]) {
			return(1);
		}
	}
	return(0);
}

event_matches_date(event)
register struct event_rec *event;
{
	short last;
	int n;
	/* check if current date is before start date of event */

	if (is_before(month, day, year, event->event_month, event->event_day,
	    event->event_year)) {
		return(0);
	}

	/* one time events */

	if ((event->event_year == year) && (event->event_month == month) &&
	    (event->event_day == day) && !event->every &&
	    !event->nth_is_on && !event->last) {
		return(1);
	}

	/* once monthly or once yearly events */

	if (!event->every && !event->nth_is_on && !event->last) {
		if (event->monthly) {
			if (event->event_day == day) {
				return(1);
			}
		} else if (event->yearly) {
			if ((event->event_month == month) &&
			    (event->event_day == day)) {
				return(1);
			}
		}
	}
	if ((event->monthly || event->yearly) && !event->every &&
	    !event->nth_is_on && !event->last) {
		if (event->monthly && (event->event_day == day)) {
			return(1);
		}
		if (event->yearly && (event->event_month == month) &&
		    (event->event_day == day)) {
			return(1);
		}
	}
	if (!event->smtwtfs[(day-1+start_day)%7]) {
		return(0);
	}
	/* everys */

	if (event->every) {

		/* every smtwtf */

		if (!event->nth_is_on && !event->last) {
EVDAY:		    if (event->smtwtfs[((day-1+start_day)%7)]) {
			return(1);
		    }
		    return(0);
		}

		/* every monthly/yearly */

		if (event->monthly || event->yearly) {
			/* every monthly not-1st2nd3rdlast */
			if (!event->nth_is_on && !event->last) {
				goto EVDAY;
			}

			/* every monthly/yearly with one of 1st2nd3rdlast */

			if (event->nth_is_on) {
				if (event->monthly &&
				    (nth_smtwtfs_of_month(&last) ==
				    (event->nth - 1))) {
					return(1);
				}
				if (event->yearly &&
					(nth_smtwtfs_of_year(&last) ==
					(event->nth - 1))) {
					return(1);
				}
			}
			if (event->last) {
				if (event->monthly) {
					nth_smtwtfs_of_month(&last);
					if (last) {
						return(1);
					}
				}
				if (event->yearly) {
					nth_smtwtfs_of_year(&last);
					if (last) {
						return(1);
					}
				}
			}

		} else {
			/* every not-monthly and not-yearly */
			if (!event->nth_is_on && !event->last) {
				goto EVDAY;
			}
			/* every nth/dayofweek */
			if (event->nth == 1) {
				return(1);
			}
			n = how_many_since(event->event_month,
			    event->event_day, event->event_year);
			if ((n % (event->nth)) == 1) {
				return(1);
			}
		}
	}
	return(0);
}

nth_smtwtfs_of_month(last)
short *last;
{
	*last = ((day + 7) > days) ? 1 : 0;
	return((day - 1) / 7);
}

nth_smtwtfs_of_year(last)
short *last;
{
	short days, i;

	for (i = 1, days = day; i < month; i++) {
		days += days_in(i, year);
	}

	*last = ((days + 7) > (365 + is_leap_year(year))) ? 1 : 0;
	return((days-1) / 7);
}

how_many_since(m, d, y)
int m, d, y;
{
	register total_days_passed;
	int i;

	if (y < year) {
		total_days_passed = 0;
		for (i = m; i <= 12; i++) {
			total_days_passed += days_in(i, y);
		}
		total_days_passed -= (d - 1);
		for (i = (y + 1); i < year; i++) {
			total_days_passed += (365 + is_leap_year(i));
		}
		for (i = 1; i < month; i++) {
			total_days_passed += days_in(i, year);
		}
		total_days_passed += day;
	} else if (m == month) {
		total_days_passed = day - d + 1;
	} else {
		total_days_passed = 1 - d;
		for (i = m; i < month; i++) {
			total_days_passed += days_in(i, year);
		}
		total_days_passed += day;
	}
	return((((total_days_passed - 1) / 7) + 1));
}

days_since_jan1(month, day, year)
{
	int days = 0, i;

	for (i = 1; i < month; i++) {
		days += days_in(i, year);
	}
	days += day;
	return(days);
}
SHAR_EOF
echo shar: extracting user.c
cat - << \SHAR_EOF > user.c
#include <curses.h>
#include "month.h"

extern short crow, ccol, current_area, month, day, year, edit_flag;
extern short this_month, this_day, this_year, message_line_filled, days;
extern struct event_rec current_event;
extern struct mdate mdates[];
struct mdate mdates[12];

user()
{
	register short ch;
	short n, m, y;
	char *prompt;
	struct mdate tmp;

	if (get_daily_events(0)) {
		sound_bell();
		mvaddstr(0, 0, "event today");
	}

	for (;;) {
MR:		move(crow, ccol);
		refresh();

		m = month; y = year;
GETCH:
		ch = get_char();

		switch(ch) {
		case '/':
			date_search();
			break;
		case ';':
			if (current_area != SCHEDULE) {
				goto_this_day(mdates[10].month, 1, mdates[10].year);
				tmp = mdates[10];
				mdates[10] = mdates[11];
				mdates[11] = tmp;
				goto MR;
				}
			break;
		case 'Q':
			if (current_area != SCHEDULE) {
				terminate();
			}
			break;
		case '\033':
			if (edit_flag) {
				return(CANCEL);
			}
		case 'L':
			if (current_area == DAYS) {
				lunar();
			} else {
				warn_day();
			}
			break;
		case 'j':
		case 'k':
		case 'l':
		case 'h':
		case 'y':
		case 'm':
		case 'd':
		case '\t':
			move_cursor(ch);
			break;
		case 'n':
		case '+':
		case 'p':
		case '-':
			incr(ch);
			break;
		case '\n':

		case '\r':
			if ((n = selection()) != NOTHING) {
				return(n);
			}
			break;
		case ' ':
		case '\b':
			scroll_time(ch);
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			switch(current_area) {
			case DAYS:
				prompt = "day: ";
				break;
			case MONTHS:
				prompt = "month: ";
				break;
			case YEARS:
				prompt = "year: ";
				break;
			case SCHEDULE:
				switch(ccol) {
				case SMONTH_COL:
					prompt = "month: ";
					break;
				case SDAY_COL:
					prompt = "day: ";
					break;
				case SYEAR_COL:
					prompt = "year: ";
					break;
				case NTH_COL:
					prompt = "nth: ";
					break;
				default:
				    goto GETCH;
				}
				break;
			default:
			    goto GETCH;
			}
			if ((n = enter_number(ch, prompt)) >= 0) {
				do_number(n);
			}
			break;
		case 'P':
			if (current_area != SCHEDULE) {
				post_event(month, day, year);
			}
			break;
		case 'A':
			if (current_area != SCHEDULE) {
				show_all_events(month, year);
			}
			break;
		case 'S':
			if (current_area == DAYS) {
				scan_today_events();
			} else {
				warn_day();
			}
			break;
		case 'E':
			if (current_area != SCHEDULE) {
				scan_every_event();
			}
			break;
		case 'O':
			if (current_area == DAYS) {
				overview();
			} else {
				clear_schedule_area();
				warn_day();
			}
			break;
		case 'T':
			if (current_area != SCHEDULE) {
				goto_this_day(this_month, this_day, this_year);
			}
			break;
		case 'M':
		case 'G':
			if (current_area != SCHEDULE) {
				mark_day((ch == 'G'));
			}
			break;
		default:
			goto GETCH;
			break;
		}
		if ((m != month) || (y != year)) {		/* changed months */
			mdates[10] = mdates[11];
			mdates[11].month = month;
			mdates[11].year = year;
		}
	}
}

enter_number(ch, prompt)
short ch;
char *prompt;
{
	char nbuf[6];
	short col, first_col, i = 0;
	int retval = -1;

	first_col = col = strlen(prompt);

	mvaddstr(0, 0, prompt);

	for (;;) {
		if ((ch >= '0') && (ch <= '9')) {
			if ((col < (first_col + 2)) ||
			    (((current_area == YEARS) ||
			    ((current_area == SCHEDULE)) &&
			    (ccol == SYEAR_COL)) &&
			    (col < (first_col + 4)))) {
				nbuf[i++] = ch;
				mvaddch(0, col++, ch);
				refresh();
			}
		} else if ((ch == '\n') || (ch == '\r')) {
			nbuf[i] = 0;
			retval = atoi(nbuf);
			break;
		} else if (ch == '\b') {
			if (col > first_col) {
				i--; col--;
				mvaddch(0, col, ' ');
				move(0, col);
				refresh();
			}
		} else if (ch == '\033') {
			break;
		}
		ch = get_char();
	}
	clear_message_line();
	return(retval);
}

do_number(n)
short n;
{
	if (current_area == YEARS) {
		if (n > 0) {
			shift_years(n - year);
			goto_year(year);
		}
	} else if (current_area == MONTHS) {
		if ((n <= 12) && (n >= 1)) {
			crow = TOP_MONTH_ROW + n;
			selection();
		}
	} else if (current_area == DAYS) {
		if ((n >= 1) && (n <= days)) {
			goto_day(n);
		}
	} else if (current_area == SCHEDULE) {
		switch(ccol) {
		case SMONTH_COL:
			if ((n >= 1) && (n <= 12)) {
				current_event.event_month = n;
			}
			break;
		case SDAY_COL:
			if ((n >= 1) && (n <= days)) {
				current_event.event_day = n;
			}
			break;
		case SYEAR_COL:
			if (n > 0) {
				current_event.event_year = n;
			}
			break;
		case NTH_COL:
				if ((n > 0) && (n <= 53)) {
					current_event.nth = n;
					current_event.nth_is_on = 1;
					print_nth(&current_event);
				}
			break;
		default:
			return;
		}
		if (ccol != NTH_COL) {
			print_date(current_event.event_month,
			current_event.event_day, current_event.event_year);
		}
	}
}

handle_event_description()
{
	short ch;

	for (;;) {
		move(crow, ccol);
		refresh();
		ch = get_char();

		if ((ch >= ' ') && (ch <= '~')) {
			if (ccol <= 78) {
				addch(ch);
				current_event.event_string[ccol-TIME_COL] = ch;
				ccol++;
				current_event.event_string[ccol-TIME_COL] = 0;
			}
		} else {
			switch(ch) {
			case '\n':
			case '\r':
			case '\t':
				schedule_move_cursor(ch);
				return;
				break;
			case '\b':
				if (ccol > TIME_COL) {
					ccol--;
					mvaddch(crow, ccol, ' ');
					current_event.event_string
					    [ccol-TIME_COL] = 0;
				}
				break;
			case '\004':
			case '\025':
			case '\030':
				current_event.event_string[0] = 0;
				ccol = TIME_COL;
				move(crow, TIME_COL);
				clrtoeol();
				break;
			case '\027':
				while ((ccol > TIME_COL) && (current_event.event_string
					[--ccol - TIME_COL] == ' ')) ;
				while ((ccol > TIME_COL) && (current_event.event_string
					[(ccol - 1) - TIME_COL] != ' ')) {
					ccol--;
				}
				move(crow, ccol);
				clrtoeol();
				break;
			}
		}
	}
}

get_answer(prompt, yes_string, no_string)
char *prompt, *yes_string, *no_string;
{
	char *s;
	short retval, col;

	mvaddstr(0, 0, prompt);
	refresh();

	retval = get_char();

	s = (retval == 'y') ? yes_string : no_string;
	mvaddstr(0, 0, s);
	col = strlen(s);

	if (edit_flag) {
		addstr(" -more-");
		move(0, col + 7);
		blank_out(col + 7);
		move(0, col + 7);
		refresh();
		get_char();
	} else {
		blank_out(col);
	}
	return(retval);
}

get_nq()
{
	register ch;

	mvaddstr(0, 0, "[n,q] ");
	refresh();

	for (;;) {
		ch = get_char();

		switch(ch) {
		case 'q':
		case '\033':
			clear_message_line();
			return('q');
			break;
		case 'n':
		case '\n':
		case '\r':
			clear_message_line();
			return('n');
			break;
		}
	}
}

get_npdeq()
{
	register ch;

	mvaddstr(0, 0, "[n,p,d,e,q] ");
	refresh();

	for (;;) {
		ch = get_char();

		switch(ch) {
		case '\n':
		case '\r':
			ch = 'n';		/* no break statement */
		case 'n':
		case 'p':
		case 'e':
		case '\033':
		case 'q':
			clear_message_line();
			return(ch);
			break;
		case 'd':
			ch = get_answer("really delete? ", "", "");
			if (ch == 'y') {
				mvaddstr(0, 0, "deleted");
			} else {
				mvaddstr(0, 0, "not deleted");
			}
			addstr(" -more-");
			refresh();
			get_char();
			clear_message_line();
			return((ch == 'y') ? 'd' : '\0');
			break;
		}
	}
}

mark_day(goto_flag)
short goto_flag;
{
	short ch;

	mvaddstr(0, 0, "mark: ");
	refresh();

	for (;;) {
		ch = get_char();

		switch(ch) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':

			mvaddch(0, 6, ch);
			refresh();
			message_line_filled = 1;

			ch -= '0';

			if (goto_flag) {
				goto_this_day(mdates[ch].month, 1, mdates[ch].year);
			} else {
				mdates[ch].month = month;
				mdates[ch].year =  year;
			}
			return;
			break;
		case '\033':
		case '\n':
		case '\r':
			clear_message_line();
			return;
			break;
		}
	}
}

get_char()
{
	register int ch;
GETCH:
	ch = getchar() & 0377;

	if (message_line_filled) {
		clear_message_line();
	}

	switch(ch) {
	case '\014':
	case '\022':
		wrefresh(curscr);
		goto GETCH;
		break;
	}
	return(ch);
}

date_search()
{
	register short ch;
	short col, slash_count;
	char buf[16];
	int i = 0, m, d, y;

STARTOVER:

	mvaddstr(0, 0, "date: ");
	buf[col = 0] = 0;
	slash_count = 0;

	for (;;) {
		refresh();
GETCH:
		ch = get_char();

		switch(ch) {
		case '\b':
			if (col > 0) {
				if (buf[col - 1] == '/') {
					slash_count--;
				}
				buf[--col] = 0;
				mvaddch(0, col + 6, ' ');
				move(0, col + 6);
			}
			break;
		case '/':
			if ((slash_count >= 2) || ((col > 0) && (buf[col-1] == '/')) ||
				(col == 0)) {
				sound_bell();
			} else {
				addch(ch);
				buf[col++] = ch;
				buf[col] = 0;
				slash_count++;
			}
			break;
		case '0':
			if ((col == 0) || (buf[col-1] == '/')) {
				goto GETCH;
			}					/* no break statement */
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			if (
			((col == 2) && (buf[col-1] != '/')) ||
			((slash_count == 1)&&(buf[col-1] != '/')&&(buf[col-2] != '/'))||
			((slash_count == 2) && (buf[col-1] != '/') && (buf[col-2] != '/') &&
				(buf[col-3] != '/') && (buf[col-4] != '/')) ||
			((col == 1) && ((ch > '2') || (buf[0] > '1')))
			) {
				sound_bell();
				goto GETCH;
			} else {
				addch(ch);
				buf[col++] = ch;
				buf[col] = 0;
			}
			break;
		case '\n':
		case '\r':
			if ((slash_count < 2) || (buf[col-1] == '/')) {
				sound_bell();
				goto GETCH;
			} else {
				sscanf(buf, "%d/%d/%d", &m, &d, &y);
				if (y < 100) {
					y += 1900;
				}
				goto_this_day(m, d, y);	/* no break statement! */
			}
		case '\033':
			goto RET;
			break;
		case '\004':
		case '\025':
		case '\030':
			clear_message_line();
			goto STARTOVER;
			break;
		default:
			goto GETCH;
			break;
		}
	}
RET:
	clear_message_line();
}

warn_day()
{
	mvaddstr(0, 0, "put cursor on day-more-");
	refresh();
	sound_bell();
	get_char();
	clear_message_line();
}
SHAR_EOF
echo shar: extracting Makefile
cat - << \SHAR_EOF > Makefile
MONTH_OBJS = display.o lunar.o month.o schedule.o time.o user.o

CC = cc

CFLAGS = -c

month: $(MONTH_OBJS)
	$(CC) $(MONTH_OBJS) -lm -lcurses -ltermlib -o month

display.o: display.c month.h
	$(CC) $(CFLAGS) display.c

lunar.o: lunar.c 
	$(CC) $(CFLAGS) lunar.c

month.o: month.c month.h
	$(CC) $(CFLAGS) month.c

schedule.o: schedule.c month.h
	$(CC) $(CFLAGS) schedule.c

time.o: time.c month.h
	$(CC) $(CFLAGS) time.c

user.o: user.c month.h
	$(CC) $(CFLAGS) user.c
SHAR_EOF
echo shar: extracting month.man
cat - << \SHAR_EOF > month.man
month(1)

NAME
	month - a visual monthly calendar and time/event browser

SYNOPSIS
	month [-d]

DESCRIPTION

    Overview

	Month displays a calendar of the current month of the current year,
	with the current day highlighted.  It then allows the user to browse
	to any month/day/year he chooses, and to schedule and recall events
	for a day or for some regular repeating series of days.

    Screen Areas

	There are four distinct areas of the screen.  The days area where
	the days of the month are listed in calendar format, the months area
	where the months of the year are listed, the years area where a
	sequence of years are listed, and the schedule area, which may be
	blank and occupies lines 19-24 on the terminal. (lines below 24 are
	not used)

    Commands
	
	Quitting
		You may type 'Q' almost any time to quit.  This will update
		your event database if you have made any changes.
		Your event database is a file called ".month" in your
		home directory.
		Control-c or Control-\ can be used any time for immediate
		abort and no event database update.
		Any time you quit in any of these ways, you will be informed
		of whether your event database has been updated.

	Cursor motion

	    'h', 'l', 'k', and 'j' are used to move the cursor left, right,
	    up and down respectively within a screen area.  In some cases,
	    explained later, 'j' and 'k' will not work, and a <TAB> or <CR>
	    is used to move between fields in a wrap-around fashion.

	Selection

	    <CR> and <LF> are used to select items/commands at the cursor
	    position.
	
	Direct entry of numbers

	    The user may type the number of a desired month, day, or year
	    whenever the cursor is appropriately positioned.  This is
	    true in all screen areas.  <ESC> is used to abort the function.
	
	Scrolling numbers

	    In the schedule area, numbers may be scrolled forwards and
	    backwards with the <SPACE> and <BACKSPACE> keys respectively.
	    This is the only way to change hours and minutes.
	
	Time browsing

	    'm', 'd' and 'y' are used to move into the months area, the
	    days area or the years area respectively.  This is only when
	    time browsing in these three panes.  To get to a particular
	    month or year, move to the appropriate area and onto the
	    desired month or year, and select it. (<CR>)  Years may be
	    scrolled a year at a time by using the scroll areas marked
	    by '<<' and '>>'.  Attempting to move passed these areas will
	    scroll by one year, selecting them scrolls by ten years.
	    The last month of the previous year, or the first month of
	    next year, may be obtained by selecting the area above
	    January or below December respectively.  The cursor is the
	    positioned for immediate return via a subsequent selection.

	    'n' and 'p' can be used to go to the next or previous month,
	    day, or year, depending on which screen area you are in.

	    'M' is used to mark a specific date.  You will be prompted for
	    an identifier which is a single digit between '0' and '9'.
	    Once a mark has been set at a certain date, you may jump to
	    that date from any other date with the command below.

	    'G' is used to go to a previously set mark.  You will be
	    prompted for the mark's identifying digit.

	    ';' is used to go directly to the last date you viewed which
	    was in a different month than currently displayed.  Use the
	    same command again to return to where you were originally.

	    'T' is used to go directly the actual, real current date, which
		is the date initially displayed upon startup.
	
	Overviewing a day

	    'O' will fill the schedule area with a read only view
	    of your day according to your event database.  Four
	    six-hour grids appear showing which hours of the
	    day have been pre-scheduled.  The cursor must be placed
	    on the day to be viewed with this function.

	Overviewing a month

	    'A' will mark all the days on the calendar that have
	    at least one event posted.  This feature is especially
	    useful before scanning; described next.

	Scanning events

		'S' will cause a sequential list of events for the current day
		to be displayed in the schedule area.  The events for any given
		day may be scanned, deleted, or modified.
	    After displaying each one, the prompt "[n,p,d,e,q]" is put
	    up and will respond to these character commands:

		'n': go to next event
		'p': go to previous event
		'd': delete this event
		'e': edit this event as during a posting described below
		'q': quit the scan and return to calendar
		<ESC>: same as 'q'

	Every event scan

	    'E' will display, one at a time, absolutely every event
	    in your event database.  The prompt "['n','q']" is displayed
	    and will respond to these character commands:

		'n': go to next event
		'q': quit the scan and return to calendar
		<ESC>: same as 'q'
	
	Posting an event

	    'P' is the command used to post an event.  The
	    cursor is placed into the schedule area with a host of
	    information displayed.  To discontinue, use <ESC> or
	    select CANCEL.  The cursor first appears on the first
	    line of the schedule area.  This line gives the starting
	    date for the event, and when it shall occur.  The user
	    may move into the starting date and change the month,
	    day and year by scrolling with <SPACE> and <BACKSPACE>,
	    or by directly typing it.  The other fields in this
	    first line may be moved onto and selected.  <TAB> will
	    move the cursor to the next line which contains the
	    time at which the event occurs.  'h' and 'l' move between
	    the hours and minutes fields which may be scrolled.  The
	    AM/PM indicator changes as the hours scroll across
	    12:00 boundaries.  <TAB> will move the cursor to the
	    next line which gives the duration of the event, and
	    it is edited in the same fashion.  <TAB> moves the cursor
	    to the next line which is a one line description of the
	    event, to be typed whenever the cursor is placed here.
	    <TAB> moves to the last line in the schedule area which
	    allows the user to select ACCEPT or CANCEL.  Selecting
	    ACCEPT will put the event into the user's event database,
	    after being asked if he really wants it to be.  Selecting
	    CANCEL aborts the process.  <TAB> returns to the first
	    line.

	    Event scheduling

		When and how often will an event occur?  This information
		is contained in the first line of the schedule area.  The
		date entered there is the starting date for the event,
		that is, the event will not be recalled until that date.
		This date is best entered by browsing to it, placing the
		cursor in the days area on the desired day, and then
		type 'P' to post the event, in which case the desired date
		automatically appears as the default, but may be edited.
		In the following examples, only the fields that need to be
		selected are mentioned, all others should be turned off.
		(not highlighted)  Examples:

		March 5, 1990 (once only)
		    3/5/1990
		
		Every Tuesday and Wednesday
		    m/d/y every TueWed
		
		The 7th of each month
		    m/7/y monthly
		
		Each July 4th

		    7/4/y yearly

		The 2nd and last sunday of each month
		    m/d/y monthly every 2nd last Sun

		The 1st and last friday of each year
		    m/d/y yearly every 1st last Fri

		Every other thursday
		    m/d/y every 2nd Thu
		    Note, this will include the 1st, 3rd, 5th, 7th, etc.
		    thursday, starting from the specified m/d/y

	Miscellaneous

	    'L' stands for lunar, and causes a picture of what the moon will
	    look like at 11:00PM on the day on which the cursor is placed.
		'^L' or '^R' will redraw the screen.

OPTIONS
	Specifying the -d flag causes a background daemon to be born that
	will wake up at 15 minute intervals during the current login
	session, check your event database, and print a message to your
	terminal with a bell if it finds an event that is 15 minutes, or
	less, away.  It will do this check upon invocation, then wake up
	on every 15-minute clock division until killed or you log out.

CAVEATS/BUGS
	Very few attempts have been made to prevent the user from browsing
	through negatively numbered years or years with more than four
	digits in them, the latter causing the years area to get messed up,
	but remains functional.
	In rare cases, events with a starting date before the year 1753,
	will not be recalled correctly.

FILES
	$HOME/.month
SHAR_EOF
echo shar: extracting cheat_sheet
cat - << \SHAR_EOF > cheat_sheet
'h', 'l', 'k', 'j' - move cursor left, right, up, down
<CR> and <LF> - select items/commands/scroll-areas
'0'-'9' - direct entry of date or marker numbers
<ESC> - abort function
<SPACE>/<BACKSPACE> - scroll hours/minutes forward/backward
'm', 'd' - and 'y' - move into the months, days, years areas
'n', 'p' - go to the next or previous month, day, or year
'M' - mark the currently displayed date
'G' - go to a previously marked date
';' - go to last visited date outside of currently displayed month
'T' - go to today's actual date (initial date displayed)
'O' - display schedule grid for currently displayed day
'A' - show days in current month that have items scheduled
'S' - scan the events for the currently displayed day
	In the scan
	'n': go to next event
	'p': go to previous event
	'd': delete this event
	'e': edit this event
	'q': quit the scan
	<ESC>: same as 'q'

'E' - scan every stored event
'P' - post an event (see man page)
'L' - print picture of moon for 11:00 PM of current day
'Q' - quit, store any event changes
^L, ^R - redraw screen
^C, ^\ - quit, ignore any event changes
SHAR_EOF

leeke@cascade.UUCP (05/03/86)

I downloaded this and used /bin/sh to extract the files, but I only got
a few out of the several listed in the comments.  Could you post
a corrected copy.

Steve Leeke