
/*************************************************************
* FiliAgenda v1.56 written by Tako Schotanus in oktober 1992 *
*************************************************************/

#define USE_BUILTIN_MATH

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <utility/date.h>
#include <intuition/intuition.h>
#include <libraries/commodities.h>
#include <workbench/startup.h>
#include <workbench/icon.h>
#include <workbench/workbench.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/commodities.h>
#include <proto/gadtools.h>
#include <proto/graphics.h>
#include <proto/utility.h>
#include <proto/icon.h>

#include "AgendaUI.h"

#define AGENDA_DATAFILENM	"S:FiliAgenda.dat"
#define AGENDA_DATAFILEID	"FILIAGENDA_DAT"
#define AGENDA_CONFIGFILENM	"S:FiliAgenda.cfg"
#define AGENDA_CONFIGFILEID	"FILIAGENDA_CFG"
#define HELPGUIDECMD		"C:AmigaGuide"
#define HELPMORECMD		"C:More"
#define HELPFILE		" S:FiliAgenda"
#define MSG1HEADERFMT		"\23341m\23332m%y/%m/%d %h:%n:%s %x %i\2330m"
#define MSG2HEADERFMT		"\23343m%y/%m/%d %h:%n:%s %x %i\2330m"
#define DEF_OUTPUT		"CON:////FiliAgenda Output/close/auto/wait"
#define DEF_POPKEY		"ctrl alt a"
#define DEF_PRI			0
#define	DEF_XPOS		109
#define DEF_YPOS		34
#define DEF_SHOWID		"*"

#define DT_ERROR		-1

#define USELESS			9999
#define STARTUP			0x80000000
#define RELATIVE		0x80000000

#define CALC_DATEFORMATERR	-1

#define CE_DATE			0
#define CE_TIME			1
#define CE_MSGCMD		2

#define NOSECSINDAY		(24 * 3600)	// Nr. of seconds in a day
#define MAXMSGLOG		30		// Max. no. messages stored
#define MAXMSG			2
#define	MAXIDLEN		8
#define	MAXINFOLEN		30
#define	MAXSETLEN		48
#define	MAXMSGLEN		256
#define	MAXCMDLEN		256
#define	MAXSHOWIDLEN		64
#define LISTVIEWLEN		46
#define LISTVIEWINFOLEN		26
#define LISTVIEWINFOIDLEN	35

#define SETGADATTR(xx, yy)	{ if (AgendaWnd) GT_SetGadgetAttrsA( AgendaGadgets[xx], AgendaWnd, NULL, (struct TagItem *)(yy) ); }
#define LISTVIEWPOS		*((WORD *)(((ULONG)(AgendaGadgets[GD_ListGad] + 1)) + 4))
#define STR_FROM_GAD(xx)	(((struct StringInfo *)(AgendaGadgets[xx]->SpecialInfo))->Buffer)
#define AE_FROM_ND2(xx)		((struct AgendaEntry *)((ULONG)(xx) - sizeof(struct MinNode)))
#define AE_FROM_ND(xx)		((struct AgendaEntry *)((ULONG)(xx) - 2*sizeof(struct MinNode)))
#define EASYREQ(xx, yy)		EasyRequest( AgendaWnd, &ErrorES, NULL, xx, yy )
#define FindTT(dobj,tt)		(char *)FindToolType(dobj->do_ToolTypes, (UBYTE *)tt)
#define swap(xx, yy, tt)	{ tt tmp = xx; xx = yy; yy = tmp; }
#define SETRESULT(xx, yy)	{ if ((yy) > 31) (xx)[0] |= 1 << ((yy) - 32); else (xx)[1] |= 1 << (yy); }
#define CLRMASK(xx)		{ (xx)[0] = 0; (xx)[1] = 0; }
#define COPY(xx, yy)		{ (xx)[0] = (yy)[0]; (xx)[1] = (yy)[1]; }
#define ISZERO(xx)		(!((xx)[0] || (xx)[1]))

char toupper( char );
char tolower( char );
int ncstrcmp( char *, char *, int );
char *GetArg( char **, char * );
int IsNum( char * );
int IsNumber( char *, ULONG * );
int IsYear( char *, ULONG * );
int IsMonth( char *, ULONG * );
int IsDay( char *, ULONG * );
BOOL CalcSetMsk( char *, int (*)(), int, int, ULONG * );
BOOL DateTimeCheck( struct AgendaEntry *, int );
BOOL IDMatch( struct AgendaEntry * );
int MIN( ULONG * );
int MAX( ULONG * );
void CLR( ULONG *, int );
void AbsCalcMinTime( struct AgendaEntry *, int );
void RelCalcMinTime( struct AgendaEntry *, int, ULONG );
void CalcMinTime( struct AgendaEntry * );
void MakeLVLine( struct AgendaEntry * );
void InsertEntry( struct AgendaEntry *, BOOL );
struct AgendaEntry *MakeEntry( void );
void RemEntry( struct AgendaEntry * );
void MatchEntries( void );
void CopyEntries( int );
void EasyErr( char * );
void MakeCurrDateTime( struct AgendaEntry * );
void RefreshLnGads( struct AgendaEntry *, BOOL );
void UpdateLnGads( void );
void mfree( char ** );
char *MakeArg( char **, int, char );
char *MkWhenStr( struct AgendaEntry * );
BOOL EntryCheck( struct AgendaEntry * );
BOOL ReadAgendaEntry( void );
void DelAgendaEntry( struct AgendaEntry *, BOOL );
void getln( char *, int, int * );
BOOL ReadFile( char *, char * );
void ReadAgenda( void );
void ReadConfig( void );
ULONG GetTOD( void );
char *FormatTxt( char *, struct AgendaEntry * );
void InitTimer( void );
LONG CalcSecs2AE( struct AgendaEntry * );
void SetTimer( BOOL );
void HandleTimer( void );
void InitCx( void );
void HandleCx( void );
BOOL CalcWPos( char * );
int OpenMyWindow( void );
void GetArgs( int, char *[] );
void TheEnd( void );

struct AgendaEntry
{	struct MinNode	ae_Node;		// Main "appointment" list
	struct MinNode	ae_LVNode2;		// Full, hidden ListView-List
	struct Node	ae_LVNode;		// Actual List-View List
	BOOL		ae_InList;		// Is this entry in the ListView-list?

	char		ae_LVTxt[LISTVIEWLEN];	// Text displayed in listview

	char		*ae_ID;
	char		*ae_Info;

	char		*ae_Year[MAXMSG], *ae_Month[MAXMSG], *ae_Day[MAXMSG];
	char		*ae_Hour[MAXMSG], *ae_Min[MAXMSG], *ae_Sec[MAXMSG];
	char		*ae_Msg[MAXMSG];
	char		*ae_Cmd[MAXMSG];

	ULONG		ae_Time[MAXMSG];	// No. seconds from 1972 until event

	BOOL		ae_DisplayOK[MAXMSG];	// OK to display message ?
	BOOL		ae_MinChanged;		// Has this entry changed?
	int		ae_MinMsg;		// The earliest date/time
};

char *MsgEvent[MAXMSGLOG][2];

char Version[] = "$VER: " PROGRAMID " (" __DATE__ ")\n\r";

char *Template = "CX_PRIORITY/N/K,CX_POPKEY/K,CX_POPUP/S,NOBEEP/S,WPOS/K,SHOW/K,OUTPUT/K";

struct NewBroker NewBroker =
{	NB_VERSION,
	"FiliAgenda",
	PROG_AUTHORID,
	"Keep track of your appointments",
	NBU_NOTIFY | NBU_UNIQUE,
	COF_SHOW_HIDE,
	0,
	NULL,
	0
};

struct EasyStruct ErrorES =
{	sizeof (struct EasyStruct),
	0,
	"FiliAgenda Message",
	"%s",
	"%s",
};

char AboutTxt[] =
{	PROG_AUTHORID
	"\n\n"
	" For suggestions, bug reports etc.:\n"
	"         Tako Schotanus\n"
	"      Klokkengieterstraat 26\n"
	"        1825 AJ   Alkmaar\n"
	"         The Netherlands\n"
	"       aegee@bu.tudelft.nl\n"
	"\n"
	"Last compiled: " __DATE__ " " __TIME__
};

ULONG SysTags[] =
{	(SYS_Input),		0,
	(SYS_Output),		0,
	(SYS_Asynch),		TRUE,
	(SYS_UserShell),	TRUE,
	(TAG_DONE)
};

ULONG ListTags[] =
{	(GTLV_Labels), 0,
	(GTLV_Top), 0,
	(TAG_DONE)
};

ULONG StrTags[] =
{	(GTST_String), 0,
	(TAG_DONE)
};

char *MonthStr[] =
{	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

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

struct MinList		AgendaList, LGad2List;
struct AgendaEntry	*CurrAE, *FirstAE = (struct AgendaEntry *)-1;
struct ClockData	CCD;
struct MsgPort		*TimerPort = NULL, *CxPort = NULL;
struct timerequest	*TimerReq = NULL;
ULONG			YearMsk[MAXMSG][2], MonthMsk[MAXMSG][2], DayMsk[MAXMSG][4];
ULONG			HourMsk[MAXMSG][2], MinMsk[MAXMSG][2], SecMsk[MAXMSG][2];
CxObj			*Broker = NULL, *PopKey = NULL;
BOOL			Popup, Active, Beep;
int			Priority;
char			PopKeyStr[32], *Spaces = "                                        ";
ULONG			TimerDev = 1;
USHORT			FirstSec = 0, MsgCmd = 0;
char			CurrDate[3*MAXSETLEN+3], CurrTime[3*MAXSETLEN+3];
char			DataFileNm[] = AGENDA_DATAFILENM;
char			ConfigFileNm[] = AGENDA_CONFIGFILENM;
char			*ShowIDBuf = NULL, *OutFile, *DefOutFile = DEF_OUTPUT;
int			LineNr = -1, FirstDay, MsgEvCnt = 0, MsgEvPos = 0;
APTR			OldConTask = NULL;
char			*FileBuf = NULL, *CurrFileNm;
int			FileSize, FilePos, ErrCnt, omday;
BPTR			fout = NULL;



/******************/
/* Disable CTRL-C */
/******************/

int
CXBRK( void )
{
	return 0;
}



int
chkabort( void )
{
	return 0;
}



/*******************************/
/* Convert letter to uppercase */
/*******************************/

char
toupper(	char c		)
{
	if (c >= 'a' && c <= 'z')
		c = c - 'a' + 'A';

	return c;
}



/*******************************/
/* Convert letter to lowercase */
/*******************************/

char
tolower(	char c		)
{
	if (c >= 'A' && c <= 'Z')
		c = c - 'A' + 'a';

	return c;
}



/****************************************/
/* Compare two string disregarding case */
/****************************************/

int
ncstrcmp(	char *p,
		char *q,
		int l		)
{
	while (*p && *q && l--)
	{	if (toupper(*p) != toupper(*q))
			return (*p < *q) ? -1 : 1;
		p++;
		q++;
	};

	return 0;
}



/***************************************/
/* Get next argument from a string.    */
/* (arguments are seperated by , or -) */
/***************************************/

char *
GetArg(		char **str,
		char *sep	)
{	static char buf[MAXSETLEN+1];
	char *p = *str;
	int i;

	i = 0;
	while (*p && *p != ',' && *p != '-')
		buf[i++] = *p++;

	*sep = *p++;
	buf[i] = '\0';
	(*str) = p;

	return buf;
}



/******************************************/
/* Check if the string contains a number. */
/* Return its value or DT_ERROR.          */
/******************************************/

int
IsNum(		char *str	)
{	register char *p;

	p = str;
	while (*p)
	{	if (*p < '0' || *p > '9')
			return DT_ERROR;
		p++;
	};

	return atoi(str);
}



/*********************************************/
/* Check if string contains a number. Return */
/* its value (n) and set the n-th bit of res */
/* or return DT_ERROR.                       */
/*********************************************/

int
IsNumber(	char *str,
		ULONG *res	)
{	int n;

	if ((n = IsNum(str)) != DT_ERROR)
		SETRESULT(res, n);

	return n;
}



/***************************************************/
/* Check if string contains a number between 1992  */
/* and 2054, return its value (n-1992) and set the */
/* (n-1992)th bit of 'res' or return DT_ERROR.     */
/***************************************************/

int
IsYear(		char *str,
		ULONG *res	)
{	int x;

	if ((x = IsNum(str)) != DT_ERROR)
	{	SETRESULT(res, x - 1992);
		return x - 1992;
	};

	return DT_ERROR;
}



/******************************************************/
/* Check if string contains a number from 1 to 12 or  */
/* an abbreviation of a month (jan, feb, etc.).       */
/* Return the number of the month (n) and set the nth */
/* bit of 'res' or DT_ERROR.                          */
/******************************************************/

int
IsMonth(	char *str,
		ULONG *res	)
{	char buf[4];
	int i, n;

	buf[0] = toupper(str[0]);
	for (i=1; i<4; i++)
		buf[i] = tolower(str[i]);
	buf[3] = '\0';

	if ((n = IsNumber(buf, res)) == DT_ERROR)
	{	for (i=0; i<12; i++)
			if (!strcmp(buf, MonthStr[i]))
				n = i + 1;
		if (n != DT_ERROR)
			SETRESULT(res, n);
	};

	return n;
}



/****************************************************/
/* Check if string contains a number from 1 to 31,  */
/* an abbreviation of a weekday (sun, mon, etc.) or */
/* a abbrv. weekday and a digit (wed2, fri1 etc.).  */
/* If the string contained a number (n) the nth bit */
/* of 'res' is set (in the first 2 ULONGs), if it   */
/* contained a weekday-name the corresponding bit(s)*/
/* is(are) set in 'res' (the last 2 ULONGs).        */
/* Return DT_ERROR if something was wrong.          */
/****************************************************/

int
IsDay(		char *str,
		ULONG *res	)
{	char buf[5];
	int i, n, w = 0;

	buf[0] = toupper(str[0]);
	for (i=1; i<5; i++)
		buf[i] = tolower(str[i]);
	buf[4] = '\0';

	if ((n = IsNumber(buf, res)) == DT_ERROR)
	{	if (buf[3] > '0' && buf[3] < '6')
		{	w = buf[3] - '0';
			buf[3] = '\0';
		};
		for (n=0; n<7; n++)
			if (!strcmp(buf, WeekdayStr[n]))
				break;
		if (n == 7)
			return DT_ERROR;

		if (!w)
		{	SETRESULT(&res[2], n);
			SETRESULT(&res[2], n + 7);
			SETRESULT(&res[2], n + 14);
			SETRESULT(&res[2], n + 21);
			SETRESULT(&res[2], n + 28);
		}
		else
			SETRESULT(&res[2], n + (w-1) * 7);
		n++;
	};

	return n;
}



/*************************************************/
/* Find the earliest date in a string containing */
/* a (set of) date/time describing elements.     */
/* As this is a general function, it has to be   */
/* supplied with a appropriate function (IsYear, */
/* IsMonth, IsDay, IsNumber (hours, mins etc.))  */
/* transforming strings into numbers.            */
/* Return FALSE when an error occurred.          */
/*************************************************/

BOOL
CalcSetMsk(	char *str,
		int (*func)( char *, ULONG * ),
		int min,
		int max,
		ULONG *res		)
{	char sep, *p, *str2 = str;
	int i, x, x2;
	BOOL not = FALSE;

	if (!str)
		return FALSE;

	if (!strcmp(str2, "*"))
	{	x = min(max, 31);
		x2 = min(min, 31);
		res[1] = ((2 << x) - 1) & ~((1 << x2) - 1);
		if (max > 31)
		{	x = max - 32;
			x2 = max(min - 32, 0);
			res[0] = ((2 << x) - 1) & ~((1 << x2) - 1);
		};
		return TRUE;
	};

	if (*str2 == '!')
	{	not = TRUE;
		str2++;
	};

	str = str2;
	sep = ',';
	while (*str && sep == ',')
	{	p = GetArg(&str, &sep);
		x = func(p, res);
		if (!*p || x < min || x > max)
			return FALSE;
		if (sep == '-')
		{	p = GetArg(&str, &sep);
			x2 = func(p, res);
			if (!*p || x2<min || x2>max || (sep && sep != ','))
				return FALSE;
			if (x > x2)
				swap(x, x2, int);
			for (i = x+1; i < x2; i++)
				SETRESULT(res, i);
		};
	};

	return TRUE;
}



/*******************************************/
/* Display an error-message in a requester */
/*******************************************/

void
EasyErr(	char *txt	)
{	char buf[128];

	ErrCnt++;
	if (LineNr >= 0)
		sprintf(buf, "%s in line %d of %s", txt, LineNr, CurrFileNm);
	else
		sprintf(buf, "%s", txt);
	EASYREQ( buf, "Sorry" );
}



/**********************************************/
/* Check if the AgendaEntry contains a legal  */
/* description of time and date. Return FALSE */
/* if the format was wrong.                   */
/**********************************************/

BOOL
DateTimeCheck(	struct AgendaEntry *ae,
		int x			)
{	char *year, *month, *day, *hour, *min, *sec;
	BOOL dneg, tneg;

	CLRMASK(YearMsk[x]);
	CLRMASK(MonthMsk[x]);
	CLRMASK(DayMsk[x]);
	CLRMASK(&DayMsk[x][2]);
	CLRMASK(HourMsk[x]);
	CLRMASK(MinMsk[x]);
	CLRMASK(SecMsk[x]);

	year = ae->ae_Year[x];
	month = ae->ae_Month[x];
	day = ae->ae_Day[x];
	hour = ae->ae_Hour[x];
	min = ae->ae_Min[x];
	sec = ae->ae_Sec[x];

	if (dneg = (BOOL)(x && year && *year == '-'))
		year++;
	if (tneg = (BOOL)(x && hour && *hour == '-'))
		hour++;

	if (tneg && !dneg)
	{	EasyErr("The date must be relative too!");
		return FALSE;
	};

	if (!year && !month && !day)
		goto CalcTime;

	if (!dneg)
	{	if (!CalcSetMsk(year, IsYear, 0, 62, YearMsk[x]))
		{	EasyErr("Wrong format in 'years'");
			return FALSE;
		};
		if (!CalcSetMsk(month, IsMonth, 1, 12, MonthMsk[x]))
		{	EasyErr("Wrong format in 'months'");
			return FALSE;
		};
		if (!CalcSetMsk(day, IsDay, 1, 31, DayMsk[x]))
		{	EasyErr("Wrong format in 'days'");
			return FALSE;
		};
	}
	else
	{	if (!CalcSetMsk(year, IsNumber, 0, 62, DayMsk[x]))
		{	EasyErr("Wrong format in 'days'");
			return FALSE;
		};
		YearMsk[x][0] = RELATIVE;
		MonthMsk[x][1] = RELATIVE;	// Can't leave this empty
	};

CalcTime:;
	if (!hour && !min && !sec)
		goto CalcEnd;

	if (tneg)
		MonthMsk[x][0] = RELATIVE;
	if (hour && !ncstrcmp(hour, "Startup", -1))
	{	HourMsk[x][0] = STARTUP;
		MinMsk[x][0] = STARTUP;		// One is enough ofcourse, but I
		SecMsk[x][0] = STARTUP;		// can't leave any empty fields.
		return TRUE;
	};

	if (!CalcSetMsk(hour, IsNumber, 0, 23, HourMsk[x]))
	{	EasyErr("Wrong format in 'hours'");
		return FALSE;
	};
	if (!CalcSetMsk(min, IsNumber, 0, 59, MinMsk[x]))
	{	EasyErr("Wrong format in 'minutes'");
		return FALSE;
	};
	if (!CalcSetMsk(sec, IsNumber, 0, 59, SecMsk[x]))
	{	EasyErr("Wrong format in 'seconds'");
		return FALSE;
	};

CalcEnd:;
	return TRUE;
}



/***********************************************/
/* Concatenate the seperate strings for years, */
/* months etc. to make a complete Date-string  */
/* and a complete Time-string.                 */
/***********************************************/

void
MakeCurrDateTime(	struct AgendaEntry *ae	)
{	int l;
	char *p;

	*CurrDate = '\0';
	*CurrTime = '\0';

	if (ae)
	{	if (p = ae->ae_Year[FirstSec])
			strcat(CurrDate, ae->ae_Year[FirstSec]);
		strcat(CurrDate, "/");
		if (p = ae->ae_Month[FirstSec])
			strcat(CurrDate, ae->ae_Month[FirstSec]);
		strcat(CurrDate, "/");
		if (p = ae->ae_Day[FirstSec])
			strcat(CurrDate, ae->ae_Day[FirstSec]);
		l = strlen(CurrDate);
		if (!strcmp(CurrDate+l-2, "//"))
			*(CurrDate+l-2) = '\0';

		if (p = ae->ae_Hour[FirstSec])
			strcpy(CurrTime, ae->ae_Hour[FirstSec]);
		strcat(CurrTime, ":");
		if (p = ae->ae_Min[FirstSec])
			strcat(CurrTime, ae->ae_Min[FirstSec]);
		strcat(CurrTime, ":");
		if (p = ae->ae_Sec[FirstSec])
			strcat(CurrTime, ae->ae_Sec[FirstSec]);
		l = strlen(CurrTime);
		if (l >= 2 && !strcmp(CurrTime+l-2, "::"))
			*(CurrTime+l-2) = '\0';
	};
}



/******************************************/
/* The current AgendaEntry has changed so */
/* the string gadgets must be updated.    */
/******************************************/

void
RefreshLnGads(	struct AgendaEntry *ae,
		BOOL all		)
{	static struct AgendaEntry *cae;
	static USHORT cfs = 0, cmc = 0;

	if (all)
		cae = (struct AgendaEntry *)-1;

	if (ae != cae)
	{	StrTags[1] = 0;
		SETGADATTR( GD_IDLnGad, StrTags );
		SETGADATTR( GD_InfoLnGad, StrTags );
		if (ae)
		{	StrTags[1] = (ULONG)ae->ae_ID;
			SETGADATTR( GD_IDLnGad, StrTags );
			StrTags[1] = (ULONG)ae->ae_Info;
			SETGADATTR( GD_InfoLnGad, StrTags );
		};
	};

	StrTags[1] = 0;
	if (ae != cae || FirstSec != cfs)
	{	SETGADATTR( GD_DateLnGad, StrTags );
		SETGADATTR( GD_TimeLnGad, StrTags );
	};
	SETGADATTR( GD_MsgCmdLnGad, StrTags );

	if (ae)
	{	if (ae != cae || FirstSec != cfs)
		{	StrTags[1] = (ULONG)CurrDate;
			SETGADATTR( GD_DateLnGad, StrTags );
			StrTags[1] = (ULONG)CurrTime;
			SETGADATTR( GD_TimeLnGad, StrTags );
		};

		if (MsgCmd)
		{	StrTags[1] = (ULONG)ae->ae_Cmd[FirstSec];
			SETGADATTR( GD_MsgCmdLnGad, StrTags );
		}
		else
		{	StrTags[1] = (ULONG)ae->ae_Msg[FirstSec];
			SETGADATTR( GD_MsgCmdLnGad, StrTags );
		};
	};

	cae = ae;
	cfs = FirstSec;
	cmc = MsgCmd;
}



/***************************************************/
/* Convert the no. second stored in the Time field */
/* of the AgendaEntry into human readable form.    */
/***************************************************/

char *
MkWhenStr(	struct AgendaEntry *ae	)
{	static char buf[9], *p;
	struct ClockData cd;

	if (ae->ae_Time[0] == -1)
	{	strcpy(buf, "--------");
		return buf;
	};

	Amiga2Date(ae->ae_Time[0], &cd);

	if (cd.year > CCD.year)
	{	sprintf(buf, "%s %d", MonthStr[cd.month - 1], cd.year);
		return buf;
	};

	if (cd.month > CCD.month)
	{	sprintf(buf, " %2d %s", cd.mday, MonthStr[cd.month - 1]);
		return buf;
	};

	if (cd.mday == CCD.mday + 1)
	{	strcpy(buf, "Tomorrow");
		return buf;
	};
	if (cd.mday > CCD.mday + 1)
	{	p = "th";
		if (cd.mday == 1 || cd.mday == 21 || cd.mday == 31)
			p = "st";
		if (cd.mday == 2 || cd.mday == 22)
			p = "nd";
		if (cd.mday == 3 || cd.mday == 23)
			p = "rd";
		sprintf(buf, "%s %2d%s", WeekdayStr[cd.wday], cd.mday, p);
		return buf;
	};

	if (HourMsk[0][0] == STARTUP)
	{	strcpy(buf, "Startup");
		return buf;
	};

	sprintf(buf, "%02d:%02d:%02d", cd.hour, cd.min, cd.sec);
	return buf;
}



/*******************************************************/
/* Return the position of the lowest order bit in 'xp' */
/*******************************************************/

int
MIN(	ULONG *xp	)
{	register ULONG x, msk;
	register int p, cnt;

	if (ISZERO(xp))
		return USELESS;

	if (xp[1])
	{	x = xp[1];
		p = 0;
	}
	else
	{	x = xp[0];
		p = 32;
	};

	cnt = 16;
	msk = 0xffff;
	while (cnt > 0)
	{	if (x & msk)
			x &= msk;
		else
		{	x >>= cnt;
			p += cnt;
		};
		cnt >>= 1;
		msk >>= cnt;
	};

	return p;
}



/********************************************************/
/* Return the position of the highest order bit in 'xp' */
/********************************************************/

int
MAX(	ULONG *xp	)
{	register ULONG x, msk;
	register int p, cnt;

	if (ISZERO(xp))
		return USELESS;

	if (xp[0])
	{	x = xp[0];
		p = 63;
	}
	else
	{	x = xp[1];
		p = 31;
	};

	cnt = 16;
	msk = 0xffff0000;
	while (cnt > 0)
	{	if (x & msk)
			x >>= cnt;
		else
		{	x &= (msk >> cnt);
			p -= cnt;
		};
		cnt >>= 1;
		msk >>= cnt;
	};

	return p;
}



/******************************/
/* Clear the n-th bit of 'xp' */
/******************************/

void
CLR(	ULONG *x,
	int n		)
{
	if (n > 31)
		x[0] &= ~(1 << (n - 32));
	else
		x[1] &= ~(1 << n);
}



/*************************************************/
/* Calculate the no. seconds from 1972 until the */
/* date and time specified in the AgendaEntry.   */
/* (or as close as possible in the future)       */
/*************************************************/

void
AbsCalcMinTime(	struct AgendaEntry *ae,
		int i			)
{	ULONG ytmp[2], motmp[2], dtmp[2], dtmp2[2], htmp[2], mitmp[2], stmp[2];
	int yref, moref, dref, href, miref, sref;
	struct ClockData cd;
	int n, n2, n3, fd;

	yref = CCD.year;
	moref = CCD.month;
	dref = CCD.mday;
	href = CCD.hour;
	miref = CCD.min;
	sref = CCD.sec;

	if (YearMsk[i][0] == RELATIVE)
	{	Amiga2Date(ae->ae_Time[0], &cd);
		goto CMT_Hour;
	};
	COPY(ytmp, YearMsk[i]);
YearAgain:;
	do
	{	n = MIN(ytmp);
		if (n == USELESS)
			return;
		CLR(ytmp, n);
	} while (1992 + n < yref);
	cd.year = 1992 + n;
	if (cd.year > CCD.year)
		moref = dref = href = miref = sref = 0;

	COPY(motmp, MonthMsk[i]);
MonthAgain:;
	do
	{	n = MIN(motmp);
		if (n == USELESS)
			goto YearAgain;
		CLR(motmp, n);
	} while (n < moref);
	cd.month = n;

	// Get the weekday of day 1 of current month

	cd.mday = 1;
	cd.hour = cd.min = cd.sec = 0;
	Amiga2Date(Date2Amiga(&cd), &cd);
	fd = cd.wday;

	if (n > CCD.month)
		dref = href = miref = sref = 0;

	COPY(dtmp, DayMsk[i]);
	COPY(dtmp2, &DayMsk[i][2]);
DayAgain:;
	do
	{	n = MIN(dtmp);
		n2 = MIN(dtmp2);
		if (n2 != USELESS)
		{	if (n2 % 7 >= fd)
				n3 = n2 - fd + 1;
			else
				n3 = n2 - fd + 8;
		}
		else
			n3 = USELESS;
		if (n == USELESS && n2 == USELESS)
			goto MonthAgain;
		if (n != USELESS && n <= n3)
			CLR(dtmp, n);
		if (n2 != USELESS && n3 <= n)
			CLR(dtmp2, n2);
	} while (min(n, n3) < dref);
	cd.mday = min(n, n3);
	if (cd.mday > CCD.mday)
		href = miref = sref = 0;

CMT_Hour:;
	if (MonthMsk[i][0] == RELATIVE)
	{	ae->ae_Time[i] = ae->ae_Time[0];
		goto EndCalc;
	};
	if (HourMsk[i][0] == STARTUP)
	{	cd.hour = cd.min = cd.sec = 0;
		goto EndCalc;
	};
	COPY(htmp, HourMsk[i]);

HourAgain:;
	do
	{	n = MIN(htmp);
		if (n == USELESS)
			goto DayAgain;
		CLR(htmp, n);
	} while (n < href);
	cd.hour = n;
	if (n > CCD.hour)
		miref = sref = 0;

	COPY(mitmp, MinMsk[i]);
MinAgain:;
	do
	{	n = MIN(mitmp);
		if (n == USELESS)
			goto HourAgain;
		CLR(mitmp, n);
	} while (n < miref);
	cd.min = n;
	if (n > CCD.min)
		sref = 0;

	COPY(stmp, SecMsk[i]);
	do
	{	n = MIN(stmp);
		if (n == USELESS)
			goto MinAgain;
		CLR(stmp, n);
	} while (n < sref);
	cd.sec = n;

EndCalc:;
	if (!(ae->ae_Time[i] = CheckDate(&cd)))
		ae->ae_Time[i] = -1;
}



/**************************************************/
/* Calculate the no. seconds from 1972 until the  */
/* date and time relative to the primary event as */
/* specified in the AgendaEntry.                  */
/* (or as close as possible in the future)        */
/**************************************************/

void
RelCalcMinTime(	struct AgendaEntry *ae,
		int i,
		ULONG csecs		)
{	ULONG dtmp[2], dtmp2[2], htmp[2], mitmp[2], stmp[2];
	ULONG dsecs, hsecs, misecs, ssecs;
	int n;

	if (YearMsk[i][0] != RELATIVE)
		return;

	COPY(dtmp, DayMsk[i]);
	COPY(dtmp2, &DayMsk[i][2]);
DayAgain:;
	do
	{	dsecs = ae->ae_Time[i];
		n = MAX(dtmp);
		if (n == USELESS)
			return;
		CLR(dtmp, n);
		dsecs -= n * NOSECSINDAY;
	} while ((dsecs + NOSECSINDAY) < csecs);
	ssecs = dsecs;

	if (MonthMsk[i][0] != RELATIVE)
		goto EndCalc;
	if (HourMsk[i][0] == STARTUP)
		goto EndCalc;
	COPY(htmp, HourMsk[i]);
HourAgain:;
	do
	{	hsecs = dsecs;
		n = MAX(htmp);
		if (n == USELESS)
			goto DayAgain;
		CLR(htmp, n);
		hsecs -= n * 3600;
	} while ((hsecs + 3600) < csecs);

	COPY(mitmp, MinMsk[i]);
MinAgain:;
	do
	{	misecs = hsecs;
		n = MAX(mitmp);
		if (n == USELESS)
			goto HourAgain;
		CLR(mitmp, n);
		misecs -= n * 60;
	} while ((misecs + 60) < csecs);

	COPY(stmp, SecMsk[i]);
	do
	{	ssecs = misecs;
		n = MAX(stmp);
		if (n == USELESS)
			goto MinAgain;
		CLR(stmp, n);
		ssecs -= n;
	} while (ssecs < csecs);

EndCalc:;
	ae->ae_Time[i] = ssecs;
}



/***************************************************/
/* Calculate the no. seconds from 1972 until the   */
/* date and time specified in both the primary and */
/* secondary event as specified in the AgendaEntry.*/
/* (or as close as possible in the future)         */
/***************************************************/

void
CalcMinTime(	struct AgendaEntry *ae	)
{	ULONG ot, csecs;
	int i, m = 0;

	csecs = GetTOD();

	ae->ae_MinChanged = FALSE;
	for (i=0; i<MAXMSG; i++)
	{	ot = ae->ae_Time[i];
		ae->ae_Time[i] = -1;

		// ALL fields should have a value before trying
		// to calculate the earliest time.

		if (!(ISZERO(YearMsk[i]) || ISZERO(MonthMsk[i])
		|| (ISZERO(DayMsk[i]) && ISZERO(&DayMsk[i][2]))
		|| ISZERO(HourMsk[i]) || ISZERO(MinMsk[i])
		|| ISZERO(SecMsk[i])) && ae->ae_DisplayOK[i])
		{	AbsCalcMinTime(ae, i);
			if (i && ae->ae_Time[0] != -1)
				RelCalcMinTime(ae, i, csecs);
		};

		if (ae->ae_Time[i] != ot)
			ae->ae_MinChanged = TRUE;
		if (i && ae->ae_Time[i] < ae->ae_Time[m])
			m = i;
	};

	ae->ae_MinMsg = m;
}



void
MakeLVLine(	struct AgendaEntry *ae	)
{	char *p;

	p = ae->ae_LVTxt;
	*p = '\0';

	if (ae->ae_Info);
		strcpy(p, ae->ae_Info);
	p[LISTVIEWINFOLEN] = '\0';
	strncat(p, Spaces, LISTVIEWINFOLEN - strlen(p) + 1);
	if (ae->ae_ID);
		strcat(p, ae->ae_ID);
	strncat(p, Spaces, LISTVIEWINFOIDLEN - strlen(p) + 1);
	strcat(p, MkWhenStr(ae));
}



/******************************************************/
/* Insert an AgendaEntry in at least two of the three */
/* main lists. Calculates earliest date and makes the */
/* string used for the listview at the same time.     */
/******************************************************/

void
InsertEntry(	struct AgendaEntry *nae,
		BOOL settmr			)
{	register struct MinNode *ond;
	register struct AgendaEntry *ae;

	CalcMinTime(nae);

	MakeLVLine(nae);

	ond = NULL;
	ae = AE_FROM_ND2(LGad2List.mlh_Head);
	while (&ae->ae_LVNode2 != (void *)&LGad2List.mlh_Tail
	&& nae->ae_Time[0] >= ae->ae_Time[0])
	{	ond = &ae->ae_LVNode2;
		ae = AE_FROM_ND2(ae->ae_LVNode2.mln_Succ);
	};
	Insert((struct List *)&LGad2List, (struct Node *)&nae->ae_LVNode2, (struct Node *)ond);

	if (IDMatch(nae))
	{	ond = NULL;
		ae = AE_FROM_ND(ListGad0List.mlh_Head);
		while (&ae->ae_LVNode != (void *)&ListGad0List.mlh_Tail
		&& nae->ae_Time[0] >= ae->ae_Time[0])
		{	ond = (struct MinNode *)&ae->ae_LVNode;
			ae = AE_FROM_ND(ae->ae_LVNode.ln_Succ);
		};
		Insert((struct List *)&ListGad0List, (struct Node *)&nae->ae_LVNode, (struct Node *)ond);
		nae->ae_InList = TRUE;
	}
	else
		nae->ae_InList = FALSE;

	ond = NULL;
	ae = (struct AgendaEntry *)AgendaList.mlh_Head;
	while (ae != (void *)&AgendaList.mlh_Tail
	&& nae->ae_Time[nae->ae_MinMsg] >= ae->ae_Time[ae->ae_MinMsg])
	{	ond = &ae->ae_Node;
		ae = (struct AgendaEntry *)ae->ae_Node.mln_Succ;
	};
	Insert((struct List *)&AgendaList, (struct Node *)&nae->ae_Node, (struct Node *)ond);
	if (!ond && settmr)
		SetTimer( FALSE );
}



/******************************************/
/* Allocate memory for a new AgendaEntry. */
/* Set default values for its fields.     */
/******************************************/

struct AgendaEntry *
MakeEntry( void )
{	struct AgendaEntry *ae;
	int i;

	ae = (struct AgendaEntry *)malloc(sizeof(struct AgendaEntry));
	if (!ae)		/* No memory left ! */
		return NULL;

	memset(ae, 0, sizeof(struct AgendaEntry));

	ae->ae_InList = FALSE;
	ae->ae_LVNode.ln_Name = ae->ae_LVTxt;
	for (i=0; i<MAXMSG; i++)
		ae->ae_DisplayOK[i] = TRUE;

	return ae;
}



/****************************************************/
/* Remove an AgendaEntry from the three event lists */
/****************************************************/

void
RemEntry(	struct AgendaEntry *ae	)
{
	Remove((struct Node *)&ae->ae_Node);
	Remove((struct Node *)&ae->ae_LVNode2);
	if (ae->ae_InList)
	{	Remove((struct Node *)&ae->ae_LVNode);
		ae->ae_InList = FALSE;
	};
}



/**************************************************/
/* Check the format of the time/date definitions. */
/* Delete the entry if something is wrong.        */
/**************************************************/

BOOL
EntryCheck(	struct AgendaEntry *ae	)
{	int i;

	for (i=0; i<MAXMSG; i++)
		if (!DateTimeCheck(ae, i))
			return FALSE;

	return TRUE;
}



/*****************************************************/
/* Allocate memory to hold the contents of the first */
/* string and copy that string to the new buffer.    */
/*****************************************************/

void
MkNewLn(	char *gadbuf,
		char **caebuf		)
{	int l;

	if (!*caebuf)
	{	*caebuf = MakeArg(&gadbuf, 512, 0);
		return;
	};

	l = strlen(*caebuf) + 1;
	if (!gadbuf)
	{	mfree(caebuf);
		return;
	};

	if (strcmp(*caebuf, gadbuf))
	{	if ((l - 1) != strlen(gadbuf))
		{	mfree(caebuf);
			*caebuf = MakeArg(&gadbuf, 512, 0);
		}
		else
			strcpy(*caebuf, gadbuf);
	};
}



/********************************************/
/* Check if the ID field of the AgendaEntry */
/* matches the global ShowID buffer.        */
/********************************************/

BOOL
IDMatch(	struct AgendaEntry *ae	)
{	char sep, *p, *q;
	int l;

	sep = ',';
	p = ShowIDBuf;
	while (sep == ',' && *(q = GetArg(&p, &sep)))
	{	if (*q == '*')
			return TRUE;
		l = strlen(q) - 1;
		if (l >= 0 && !*q || *(q + l) != '*')
			l = -1;
		if (ae->ae_ID && !ncstrcmp(q, ae->ae_ID, l))
			return TRUE;
	};

	return FALSE;
}



/*************************************************/
/* Put all entries from LGad2List that match the */
/* global ShowID in the ListGad0List.            */
/*************************************************/

void
MatchEntries( void )
{	register struct AgendaEntry *ae;
	BOOL found = FALSE;

	while (RemHead((struct List *)&ListGad0List)) ;

	ae = AE_FROM_ND2(LGad2List.mlh_Head);
	while (&ae->ae_LVNode2 != (void *)&LGad2List.mlh_Tail)
	{	if (IDMatch(ae))
		{	AddTail((struct List *)&ListGad0List, (struct Node *)&ae->ae_LVNode);
			ae->ae_InList = TRUE;
		}
		else
			ae->ae_InList = FALSE;
		if (ae == CurrAE)
			found = TRUE;
		ae = AE_FROM_ND2(ae->ae_LVNode2.mln_Succ);
	};

	if (!found)
	{	CurrAE = NULL;
		MakeCurrDateTime( CurrAE );
		RefreshLnGads( CurrAE, TRUE );
	};
}



/***************************************************/
/* Copy the contents of the Date, Time, Msg or Cmd */
/* field of the current AgendaEntry to all entries */
/* in the ListGad0List.                            */
/***************************************************/

void
CopyEntries(	int lntype	)
{	struct AgendaEntry *ae;

	if (!CurrAE)
		return;

	ae = AE_FROM_ND(ListGad0List.mlh_Head);
	while (&ae->ae_LVNode != (void *)&ListGad0List.mlh_Tail)
	{	if (ae != CurrAE)
		switch (lntype)
		{	case CE_DATE:
				MkNewLn(CurrAE->ae_Year[FirstSec], &ae->ae_Year[FirstSec]);
				MkNewLn(CurrAE->ae_Month[FirstSec], &ae->ae_Month[FirstSec]);
				MkNewLn(CurrAE->ae_Day[FirstSec], &ae->ae_Day[FirstSec]);
				break;
			case CE_TIME:
				MkNewLn(CurrAE->ae_Hour[FirstSec], &ae->ae_Hour[FirstSec]);
				MkNewLn(CurrAE->ae_Min[FirstSec], &ae->ae_Min[FirstSec]);
				MkNewLn(CurrAE->ae_Sec[FirstSec], &ae->ae_Sec[FirstSec]);
				break;
			case CE_MSGCMD:
				if (MsgCmd)
					MkNewLn(CurrAE->ae_Cmd[FirstSec], &ae->ae_Cmd[FirstSec]);
				else
					MkNewLn(CurrAE->ae_Msg[FirstSec], &ae->ae_Msg[FirstSec]);
				break;
		};
		ae = AE_FROM_ND(ae->ae_LVNode.ln_Succ);
	};
}



/*******************************************************/
/* The user has changed the contents of one or more    */
/* string gadgets, change the AgendaEntry accordingly. */
/* (Put it at a new position in the lists if necessary)*/
/*******************************************************/

void
UpdateLnGads( void )
{	char *buf, *buf2, *p1, *p2, *p3, *p4, *p5, *p6;
	BOOL remove = TRUE;
	WORD pos;

	if (!CurrAE)
	{	CurrAE = MakeEntry();
		if (!CurrAE)
			return;

		remove = FALSE;
	};

	buf = (char *)STR_FROM_GAD(GD_IDLnGad);
	MkNewLn(buf, &CurrAE->ae_ID);

	buf = (char *)STR_FROM_GAD(GD_InfoLnGad);
	MkNewLn(buf, &CurrAE->ae_Info);

	buf = (char *)STR_FROM_GAD(GD_MsgCmdLnGad);
	if (MsgCmd)
		MkNewLn(buf, &CurrAE->ae_Cmd[FirstSec]);
	else
		MkNewLn(buf, &CurrAE->ae_Msg[FirstSec]);

	buf = (char *)STR_FROM_GAD(GD_DateLnGad);
	buf2 = (char *)STR_FROM_GAD(GD_TimeLnGad);
	if (strcmp(CurrDate, buf) || strcmp(CurrTime, buf2))
	{	p1 = CurrAE->ae_Year[FirstSec];
		p2 = CurrAE->ae_Month[FirstSec];
		p3 = CurrAE->ae_Day[FirstSec];
		p4 = CurrAE->ae_Hour[FirstSec];
		p5 = CurrAE->ae_Min[FirstSec];
		p6 = CurrAE->ae_Sec[FirstSec];
		CurrAE->ae_Year[FirstSec] = MakeArg(&buf, MAXSETLEN, '/');
		CurrAE->ae_Month[FirstSec] = MakeArg(&buf, MAXSETLEN, '/');
		CurrAE->ae_Day[FirstSec] = MakeArg(&buf, MAXSETLEN, '/');
		CurrAE->ae_Hour[FirstSec] = MakeArg(&buf2, MAXSETLEN, ':');
		CurrAE->ae_Min[FirstSec] = MakeArg(&buf2, MAXSETLEN, ':');
		CurrAE->ae_Sec[FirstSec] = MakeArg(&buf2, MAXSETLEN, ':');
		if (!EntryCheck(CurrAE))
		{	swap(CurrAE->ae_Year[FirstSec], p1, char *);
			swap(CurrAE->ae_Month[FirstSec], p2, char *);
			swap(CurrAE->ae_Day[FirstSec], p3, char *);
			swap(CurrAE->ae_Hour[FirstSec], p4, char *);
			swap(CurrAE->ae_Min[FirstSec], p5, char *);
			swap(CurrAE->ae_Sec[FirstSec], p6, char *);
		};
		mfree(&p1);
		mfree(&p2);
		mfree(&p3);
		mfree(&p4);
		mfree(&p5);
		mfree(&p6);
	}
	else
		EntryCheck(CurrAE);
	CurrAE->ae_DisplayOK[FirstSec] = TRUE;

	pos = LISTVIEWPOS;
	ListTags[1] = ListTags[3] = 0;
	SETGADATTR( GD_ListGad, ListTags );

	if (remove)
		RemEntry(CurrAE);
	InsertEntry(CurrAE, TRUE);

	ListTags[1] = (ULONG)&ListGad0List;
	ListTags[3] = (ULONG)pos;
	SETGADATTR( GD_ListGad, ListTags );

	if ((p1 = CurrAE->ae_Hour[FirstSec]) && !ncstrcmp(p1, "Startup", -1)
	&& CurrAE->ae_Time[FirstSec] < GetTOD())
		CurrAE->ae_DisplayOK[FirstSec] = FALSE;

	MakeCurrDateTime( CurrAE );
	RefreshLnGads( CurrAE, FALSE );
}



/********************************************/
/* The user clicked in the listview gadget. */
/* Make the entry clicked the current one.  */
/********************************************/

int
ListGadClicked(		struct IntuiMessage *im		)
{	register struct MinNode *p;
	register int i;

	/* routine when gadget "Appointment List  ID When   " is clicked. */

	for (i=0, p=ListGad0List.mlh_Head; i<im->Code; i++, p=p->mln_Succ) ;
	CurrAE = AE_FROM_ND(p);

	MakeCurrDateTime( CurrAE );
	RefreshLnGads( CurrAE, TRUE );

	return TRUE;
}



int
DateLnGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "Date" is clicked. */

	UpdateLnGads();

	return TRUE;
}



int
TimeLnGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "Time" is clicked. */

	UpdateLnGads();

	return TRUE;
}



int
DelGadClicked(		struct IntuiMessage *im		)
{	struct AgendaEntry *nae;
	struct Node *nd;
	WORD pos;

	/* routine when gadget "Del" is clicked. */

	if (CurrAE)
	{	nd = CurrAE->ae_LVNode.ln_Succ;
		if (nd != (void *)&ListGad0List.mlh_Tail)
			nae = AE_FROM_ND(nd);
		else
			nae = NULL;

		pos = LISTVIEWPOS;
		ListTags[1] = ListTags[3] = 0;
		SETGADATTR( GD_ListGad, ListTags );
		DelAgendaEntry( CurrAE, TRUE );
		ListTags[1] = (ULONG)&ListGad0List;
		ListTags[3] = (ULONG)pos;
		SETGADATTR( GD_ListGad, ListTags );

		CurrAE = nae;
		MakeCurrDateTime( CurrAE );
		RefreshLnGads( CurrAE, TRUE );
		SetTimer( FALSE );
	};

	return TRUE;
}



int
AddGadClicked(		struct IntuiMessage *im		)
{	struct AgendaEntry *ae;
	BOOL wc;
	int i;

	/* routine when gadget "Add" is clicked. */

	ae = MakeEntry();

	MkNewLn("New entry", &ae->ae_Info);

	wc = FALSE;
	i = strlen(ShowIDBuf);
	while (i--)
		if (!ShowIDBuf || ShowIDBuf[i] == ',' || ShowIDBuf[i] == '*')
			wc = TRUE;
	if (wc)
		MkNewLn("New", &ae->ae_ID);
	else
		MkNewLn(ShowIDBuf, &ae->ae_ID);

	if (EntryCheck(ae))
	{	InsertEntry(ae, TRUE);
		CurrAE = ae;
	}
	else
		DelAgendaEntry(ae, FALSE);

	MakeCurrDateTime( CurrAE );
	RefreshLnGads( CurrAE, TRUE );

	return TRUE;
}



int
InfoLnGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "Info" is clicked. */

	UpdateLnGads();

	return TRUE;
}



int
FirstSecGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "" is clicked. */

	FirstSec = im->Code;
	MakeCurrDateTime( CurrAE );
	RefreshLnGads( CurrAE, FALSE );

	return TRUE;
}



int
MsgCmdLnGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "Msg" is clicked. */

	UpdateLnGads();

	return TRUE;
}



int
MsgCmdGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "" is clicked. */

	MsgCmd = im->Code;
	RefreshLnGads( CurrAE, FALSE );

	return TRUE;
}



int
HideGadClicked(		struct IntuiMessage *im		)
{
	/* routine when gadget "Hide" is clicked. */

	if (AgendaWnd)
	{	AgendaLeft = AgendaWnd->LeftEdge;
		AgendaTop = AgendaWnd->TopEdge;
		CloseAgendaWindow();
		CloseDownScreen();
	};

	return TRUE;
}



int
SaveGadClicked(		struct IntuiMessage *im		)
{	struct AgendaEntry *ae;
	FILE *fp;
	int i;

	/* routine when gadget "Save" is clicked. */

	fp = fopen(DataFileNm, "w");
	while (!fp && EASYREQ( "Can't write data:\nFile is protected or in use", "Retry|Cancel" ))
		fp = fopen(DataFileNm, "w");
	if (!fp)
		exit(1);

	fprintf(fp, "%s\n", AGENDA_DATAFILEID);

	ae = (struct AgendaEntry *)AgendaList.mlh_Head;
	while (ae != (void *)&AgendaList.mlh_Tail)
	{	fprintf(fp, "\n%s\n", ae->ae_ID);
		fprintf(fp, "%s\n", ae->ae_Info);
		for (i=0; i<MAXMSG; i++)
		{	fprintf(fp, "%s/%s/%s\n", ae->ae_Year[i], ae->ae_Month[i], ae->ae_Day[i]);
			fprintf(fp, "%s:%s:%s\n", ae->ae_Hour[i], ae->ae_Min[i], ae->ae_Sec[i]);
			fprintf(fp, "%s\n", ae->ae_Msg[i]);
			fprintf(fp, "%s\n", ae->ae_Cmd[i]);
		};
		fprintf(fp, "#\n");
		ae = (struct AgendaEntry *)ae->ae_Node.mln_Succ;
	};

	fclose(fp);

	return TRUE;
}



int
HelpGadClicked(		struct IntuiMessage *im		)
{	char name[128];
	int l;

	/* routine when gadget "Help" is clicked. */

	strcpy(name, HELPGUIDECMD);
	if (!access(name, 0))
	{	l = strlen(name) + 1;
		strcat(name, HELPFILE);
		strcat(name, ".guide");
		if (!access(name + l, 0))
		{	System((STRPTR)name, (struct TagItem *)SysTags);
			return TRUE;
		};
	};

	strcpy(name, HELPMORECMD);
	if (!access(name, 0))
	{	l = strlen(name) + 1;
		strcat(name, HELPFILE);
		strcat(name, ".doc");
		if (!access(name + l, 0))
		{	System((STRPTR)name, (struct TagItem *)SysTags);
			return TRUE;
		};
	};

	EASYREQ( "You need the document or AmigaGuide file in\n"\
		 "your S: directory and the More or AmigaGuide\n"\
		 "command in your C: directory to get help.", "OK");

	return TRUE;
}



int
QuitGadClicked(	struct IntuiMessage *im		)
{
	/* routine when gadget "Cancel" is clicked. */

	return FALSE;
}



int
IDLnGadClicked(		struct IntuiMessage *im		)
{
	/* routine when gadget "ID" is clicked. */

	UpdateLnGads();

	return TRUE;
}



int
ShowIDLnGadClicked(	struct IntuiMessage *im		)
{	char *buf;
	WORD pos;

	/* routine when gadget "ShowID" is clicked. */

	pos = LISTVIEWPOS;
	ListTags[1] = ListTags[3] = 0;
	SETGADATTR( GD_ListGad, ListTags );

	buf = (char *)STR_FROM_GAD(GD_ShowIDLnGad);
	if (buf && strcmp(ShowIDBuf, buf))
	{	MkNewLn(buf, (char **)&ShowIDBuf);
		MatchEntries();
	};

	ListTags[1] = (ULONG)&ListGad0List;
	ListTags[3] = (ULONG)pos;
	SETGADATTR( GD_ListGad, ListTags );

	return TRUE;
}



int
AgendaMenuSaveConfig(	struct IntuiMessage *im		)
{	FILE *fp;

	/* routine when gadget "Save" is clicked. */

	fp = fopen(ConfigFileNm, "w");
	while (!fp && EASYREQ( "Can't write configuration:\nFile is protected or in use", "Retry|Cancel" ))
		fp = fopen(DataFileNm, "w");
	if (!fp)
		exit(1);

	fprintf(fp, "%s\n", AGENDA_CONFIGFILEID);

	AgendaLeft = AgendaWnd->LeftEdge;
	AgendaTop = AgendaWnd->TopEdge;
	fprintf(fp, "%d,%d\n", AgendaLeft, AgendaTop);
	fprintf(fp, "%s\n", ShowIDBuf);

	fclose(fp);

	return TRUE;
}



int
AgendaMenuAbout(	struct IntuiMessage *im		)
{
	/* routine when (sub)item "About" is selected. */

	EASYREQ(AboutTxt, "Great");

	return TRUE;
}



int
AgendaMenuDate2All(	struct IntuiMessage *im		)
{
	/* routine when (sub)item "Copy Date to all" is selected. */

	CopyEntries( CE_DATE );

	return TRUE;
}



int
AgendaMenuTime2All(	struct IntuiMessage *im		)
{
	/* routine when (sub)item "Copy Time to all" is selected. */

	CopyEntries( CE_TIME );

	return TRUE;
}



int
AgendaMenuMsgCmd2All(	struct IntuiMessage *im		)
{
	/* routine when (sub)item "Copy Msg/Cmd to all" is selected. */

	CopyEntries( CE_MSGCMD );

	return TRUE;
}



int
AgendaMenuShowMsgs(	struct IntuiMessage *im		)
{	char *q;
	int i, j;

	/* routine when (sub)item "Show last messages" is selected. */

	for (i=0; i<MsgEvCnt; i++)
	{	j = (abs(MsgEvPos - MsgEvCnt) + i) % MAXMSGLOG;
		q = MsgEvent[j][0];
		Write(fout, q, strlen(q));
		Write(fout, "\n", 1);
		q = MsgEvent[j][1];
		Write(fout, q, strlen(q));
		Write(fout, "\n", 1);
	};
	
	return TRUE;
}



int
AgendaMenuResetAgenda(	struct IntuiMessage *im		)
{	struct AgendaEntry *ae;

	/* routine when (sub)item "Show last messages" is selected. */

	if (EASYREQ( "All changes will be lost!", "Continue|Cancel" ))
	{	while (ae = (struct AgendaEntry *)RemHead((struct List *)&AgendaList))
			DelAgendaEntry(ae, FALSE);
		NewList((struct List *)&ListGad0List);
		NewList((struct List *)&LGad2List);

		ReadAgenda();
	};

	return TRUE;
}



int
AgendaCloseWindow( void )
{
	/* routine for "IDCMP_CLOSEWINDOW". */

	if (EASYREQ( "Hide interface or quit?", "Hide|Quit" ))
	{	if (AgendaWnd)
		{	AgendaLeft = AgendaWnd->LeftEdge;
			AgendaTop = AgendaWnd->TopEdge;
			CloseAgendaWindow();
			CloseDownScreen();
		};
		return TRUE;
	}
	else
		return FALSE;
}



/****************************************/
/* Free memory pointed to by p and make */
/* p 0. Do nothing if p is already 0.   */
/****************************************/

void
mfree(	char **p	)
{
	if (*p)
	{	free(*p);
		*p = NULL;
	};
}



/*********************************************/
/* Read a line from the FileBuffer in memory */
/*********************************************/

void
getln(		char *buf,
		int maxlen,
		int *pos		)
{	register int sp = 0;
	register char c;

	if (!FileBuf || FilePos >= FileSize)
	{	*buf = '\0';
		return;
	};

	while (FilePos < FileSize && (c = *(FileBuf + FilePos++)) && c != '\n' && maxlen--)
	{	if (c == ' ')
			sp++;
		else
			sp = 0;
		*buf++ = c;
	};

	if (sp)
		*(buf - sp - 1) = '\0';
	else
		*buf = '\0';

	LineNr++;
}



/******************************************************/
/* Copy the contents from a string to a newly created */
/* buffer until the character 'separator' is reached. */
/******************************************************/

char *
MakeArg(	char **buf,
		int maxlen,
		char seperator	)
{	register char *p, *q;
	int l;

	if (seperator)
	{	p = *buf;
		l = 0;
		while (*p && *p != seperator)
		{	p++;
			l++;
		};
	}
	else
	{	l = strlen(*buf);
		p = *buf + l - 1;
	};
	if (!l)
		return NULL;
	if (l > maxlen)
		l = maxlen;

	q = malloc(l + 1);
	if (!q)
		return NULL;

	strncpy(q, *buf, l);
	q[l] = '\0';

	*buf = p + 1;

	return q;
}


		
/***********************************************/
/* Read the contents of the FileBuffer into an */
/* AgendaEntry and give a warning if something */
/* goes wrong.                                 */
/***********************************************/

BOOL
ReadAgendaEntry( void )
{	struct AgendaEntry *ae;
	char buf[256], *p;
	int i;

	buf[0] = buf[255] = '\0';

	do
		getln(buf, 255, &FilePos);
	while (FilePos < FileSize && !*buf);
	if (FilePos >= FileSize)
		return ((BOOL)!*buf);

	ae = MakeEntry();
	if (!ae)
		return FALSE;

	p = buf;
	ae->ae_ID = MakeArg(&p, MAXIDLEN, 0);
	p = buf;
	getln(buf, 255, &FilePos);
	ae->ae_Info = MakeArg(&p, MAXINFOLEN, 0);

	for (i=0; i<MAXMSG; i++)
	{	p = buf;
		getln(buf, 255, &FilePos);
		ae->ae_Year[i] = MakeArg(&p, MAXSETLEN, '/');
		ae->ae_Month[i] = MakeArg(&p, MAXSETLEN, '/');
		ae->ae_Day[i] = MakeArg(&p, MAXSETLEN, '/');
		p = buf;
		getln(buf, 255, &FilePos);
		ae->ae_Hour[i] = MakeArg(&p, MAXSETLEN, ':');
		ae->ae_Min[i] = MakeArg(&p, MAXSETLEN, ':');
		ae->ae_Sec[i] = MakeArg(&p, MAXSETLEN, ':');
		p = buf;
		getln(buf, 255, &FilePos);
		ae->ae_Msg[i] = MakeArg(&p, MAXMSGLEN, 0);
		p = buf;
		getln(buf, 255, &FilePos);
		ae->ae_Cmd[i] = MakeArg(&p, MAXCMDLEN, 0);
	};

	getln(buf, 255, &FilePos);
	if (strcmp(buf, "#"))
	{	DelAgendaEntry(ae, FALSE);
		EasyErr("Expected '#'");
		return FALSE;
	};

	if (!EntryCheck(ae))
	{	DelAgendaEntry(ae, FALSE);
		return TRUE;
	}
	else
		InsertEntry(ae, TRUE);

	return TRUE;
}



/**************************************************/
/* Free all memory associated with an AgendaEntry */
/* and remove it from the lists if necessary.     */
/**************************************************/

void
DelAgendaEntry(		struct AgendaEntry *ae,
			BOOL rementry		)
{	int i;

	if (rementry)
		RemEntry(ae);

	mfree(&ae->ae_ID);
	mfree(&ae->ae_Info);

	for (i=0; i<MAXMSG; i++)
	{	mfree(&ae->ae_Year[i]);
		mfree(&ae->ae_Month[i]);
		mfree(&ae->ae_Day[i]);
		mfree(&ae->ae_Hour[i]);
		mfree(&ae->ae_Min[i]);
		mfree(&ae->ae_Sec[i]);
		mfree(&ae->ae_Msg[i]);
		mfree(&ae->ae_Cmd[i]);
	};

	mfree((char **)&ae);
}



/**********************************************************/
/* Read a complete file into a newly allocated FileBuffer */
/**********************************************************/

BOOL
ReadFile(	char *name,
		char *fid	)
{	char buf[256];
	FILE *fp;

	LineNr = 0;
	ErrCnt = 0;
	CurrFileNm = name;

	if (access(name, 0))
	{	LineNr = -1;
		return TRUE;
	};

	fp = fopen(name, "r");
	sprintf(buf, "Can't read from %s\nFile is probably in use", name);
	while (!fp && EASYREQ( buf, "Retry|Cancel" ))
		fp = fopen(name, "r");
	if (!fp)
		return FALSE;

	fseek(fp, 0, 2);
	FileSize = ftell(fp);
	fseek(fp, 0, 0);
	FileBuf = malloc(FileSize);
	if (!FileBuf)
	{	fclose(fp);
		LineNr = -1;
		return FALSE;
	};
	fread(FileBuf, FileSize, 1, fp);
	FilePos = 0;

	fclose(fp);

	getln(buf, 255, &FilePos);
	if (strcmp(buf, fid))
	{	fclose(fp);
		fp = NULL;
		sprintf(buf, "%s has wrong format!", name);
		if (EASYREQ( buf, "Make new|Quit" ))
		{	LineNr = -1;
			if (!remove(name))
				return TRUE;
			EASYREQ( "Couldn't make new file", "Sorry" );
		};
		exit(2);
	};

	return TRUE;
}



/*************************************************************/
/* Read configuration file and adjust corrsponding variables */
/*************************************************************/

void
ReadConfig( void )
{	char buf[256], *p, *tmp = DEF_SHOWID;

	if (!ShowIDBuf)
		ShowIDBuf = MakeArg((char **)&tmp, 255, '\0');

	if (!ReadFile(ConfigFileNm, AGENDA_CONFIGFILEID))
		return;

	getln(buf, 255, &FilePos);
	CalcWPos(buf);

	p = buf;
	getln(buf, 255, &FilePos);
	if (*p)
	{	mfree(&ShowIDBuf);
		ShowIDBuf = MakeArg(&p, MAXSHOWIDLEN, 0);
	};

	LineNr = -1;
	free(FileBuf);
	FileBuf = NULL;
}



/*****************************************/
/* Read the datafile into the FileBuffer */
/* and get all AgendaEntries from it.    */
/*****************************************/

void
ReadAgenda( void )
{
	if (!ReadFile(DataFileNm, AGENDA_DATAFILEID))
		exit(3);

	while (FilePos < FileSize && ErrCnt < 5)
	{	if (!ReadAgendaEntry())
			break;
	};

	LineNr = -1;
	free(FileBuf);
	FileBuf = NULL;
}



/******************************************************/
/* Get Time Of Day, store it in the global ClockData  */
/* structure, calculate which weekday was the first   */
/* day of this month and return the number of seconds */
/* from 1972 until now.                               */
/******************************************************/

ULONG
GetTOD( void )
{	struct DateStamp ds;
	ULONG secs;

	DateStamp(&ds);
	secs = ds.ds_Days * 24 * 3600 + ds.ds_Minute * 60 + ds.ds_Tick / 50;
	Amiga2Date(secs, &CCD);

	FirstDay = CCD.wday - (CCD.mday - 1) % 7;

	return secs;
}



/*******************************************/
/* Copy string to buffer and replace all   */
/* formatting symbols by the corresponding */
/* strings in the AgendaEntry.             */
/*******************************************/

char *
FormatTxt(	char *str,
		struct AgendaEntry *ae		)
{	static char buf[512];
	struct ClockData cd[2];
	char *p, *q;
	int x;

	Amiga2Date(ae->ae_Time[0], &cd[0]);
	cd[1] = CCD;

	p = str;
	q = buf;

	do
	{	while (*p && *p != '%')
			*q++ = *p++;

		if (!*p++)
			break;

		if (*p >= 'A' && *p <= 'Z')
			x = 0;
		else
			x = 1;
		switch (*p)
		{	case '\0':
				*q = '\0';
				break;
			case 'i':
				strcpy(q, ae->ae_Info);
				break;
			case 'x':
				strcpy(q, ae->ae_ID);
				break;
			case 'y':
			case 'Y':
				sprintf(q, "%4d", cd[x].year);
				break;
			case 'm':
			case 'M':
				sprintf(q, "%02d", cd[x].month);
				break;
			case 'd':
			case 'D':
				sprintf(q, "%02d", cd[x].mday);
				break;
			case 'h':
			case 'H':
				sprintf(q, "%d", cd[x].hour);
				break;
			case 'n':
			case 'N':
				sprintf(q, "%02d", cd[x].min);
				break;
			case 's':
			case 'S':
				sprintf(q, "%02d", cd[x].sec);
				break;
			default:
				*q++ = *p;
				*q = '\0';
				break;
		};
		q += strlen(q);
		p++;
	} while (*p);

	*q = '\0';
	return buf;
}



/***************************/
/* Initialise timer device */
/***************************/

void
InitTimer( void )
{
	TimerPort = CreatePort(NULL, 0);
	if (!TimerPort)
		exit(4);

	TimerReq = (struct timerequest *)CreateExtIO(TimerPort, sizeof(struct timerequest));
	if (!TimerReq)
		exit(5);

	TimerDev = OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)TimerReq, 0);
	if (TimerDev)
		exit(6);
}



/**************************************************/
/* Calculate the number of seconds from now until */
/* the next event. The time will be divided in    */
/* portions of 60 seconds so we'll be able to     */
/* synchronise every minute with the system time. */
/* If midnight is sooner than the next event the  */
/* number of seconds until midnight will be taken */
/* to be able to update the display at that time. */
/**************************************************/

LONG
CalcSecs2AE(	struct AgendaEntry *ae	)
{	struct ClockData cd;
	LONG dif, mndif;
	ULONG csecs;
	char *p;

	FirstAE = ae;

	if (ae && !ae->ae_DisplayOK[ae->ae_MinMsg])
		return 1;

	csecs = GetTOD();

	// Calculate number of seconds until midnight

	cd = CCD;
	cd.mday++;
	cd.hour = cd.min = cd.sec = 0;
	if (!CheckDate(&cd))
	{	cd.mday = 1;
		if (++cd.month > 12)
			cd.year++;
	};
	mndif = Date2Amiga(&cd) - csecs;

	// Is the date stored in 'ae': today?

	if (ae && ae->ae_Time[ae->ae_MinMsg] != -1)
	{
		// Calculate number of seconds until the time stored in 'ae'
		// (0 in case of 'Startup' on current date)

		if (((p = ae->ae_Hour[ae->ae_MinMsg]) && ncstrcmp(p, "Startup", -1))
		|| ae->ae_Time[ae->ae_MinMsg] > csecs)
			dif = ae->ae_Time[ae->ae_MinMsg] - csecs;
		else
			dif = 0;

		if (dif > mndif)
		{	dif = mndif;
			FirstAE = NULL;
		};
	}
	else
		dif = mndif;

	if (dif > 60)
		FirstAE = NULL;

	// return value: 0 <= dif <= 60

	return min(max(0, dif), 60);
}



/******************************************/
/* Set the timer device to the next event */
/******************************************/

void
SetTimer(	BOOL trig	)
{	LONG secs;
	struct AgendaEntry *ae;

	if (FirstAE != (struct AgendaEntry *)-1 && !trig)
	{	Forbid();
		if (!GetMsg(TimerPort))
		{	AbortIO((struct IORequest *)TimerReq);
			WaitIO((struct IORequest *)TimerReq);
		};
		Permit();
	};

	ae = (struct AgendaEntry *)AgendaList.mlh_Head;
	if (ae == (void *)&AgendaList.mlh_Tail)
	{	if (!FirstAE && !trig)
			return;
		ae = NULL;
	};

	secs = CalcSecs2AE( ae );
	if (secs == CALC_DATEFORMATERR)
		return;

	TimerReq->tr_node.io_Command = TR_ADDREQUEST;
	TimerReq->tr_time.tv_secs = secs;
	TimerReq->tr_time.tv_micro = 1;

	SendIO((struct IORequest *)TimerReq);
}



/**********************************************************/
/* The timer went off. If an event activates; display its */
/* message and/or run its command. And set the timer to   */
/* the next event.                                        */
/**********************************************************/

void
HandleTimer( void )
{	struct AgendaEntry *ae;
	char *p, *q, *fmt;
	ULONG csecs;
	WORD pos;
	int m;

	ae = FirstAE;

	if (!GetMsg(TimerPort))
		return;

	csecs = GetTOD();

	if (ae)
	{	m = ae->ae_MinMsg;
		if (ae->ae_DisplayOK[m])
		{	if (Beep)
				DisplayBeep(NULL);

			p = ae->ae_Msg[m];
			if (p)
			{	if (MsgEvCnt == MAXMSGLOG && MsgEvent[MsgEvPos][0])
				{	mfree(&MsgEvent[MsgEvPos][0]);
					mfree(&MsgEvent[MsgEvPos][1]);
				};
				if (m)
					fmt = MSG2HEADERFMT;
				else
					fmt = MSG1HEADERFMT;
				q = FormatTxt(fmt, ae);
				q = MsgEvent[MsgEvPos][0] = MakeArg(&q, 255, '\0');
				Write(fout, q, strlen(q));
				Write(fout, "\n", 1);
				q = FormatTxt(p, ae);
				q = MsgEvent[MsgEvPos][1] = MakeArg(&q, 255, '\0');
				Write(fout, q, strlen(q));
				Write(fout, "\n", 1);
				if (MsgEvCnt < MAXMSGLOG)
					MsgEvCnt++;
				if (++MsgEvPos == MAXMSGLOG)
					MsgEvPos = 0;
			};

			p = ae->ae_Cmd[m];
			if (p)
			{	p = FormatTxt(p, ae);
				System((STRPTR)p, (struct TagItem *)SysTags);
			};

			ae->ae_DisplayOK[m] = FALSE;
		};

		if (ae->ae_Time[ae->ae_MinMsg] < csecs)
		{	if (HourMsk[m][0] != STARTUP)
				ae->ae_DisplayOK[m] = TRUE;
			EntryCheck(ae);
			pos = LISTVIEWPOS;
			ListTags[1] = ListTags[3] = 0;
			SETGADATTR( GD_ListGad, ListTags );
			RemEntry(ae);
			InsertEntry(ae, FALSE);
			ListTags[1] = (ULONG)&ListGad0List;
			ListTags[3] = (ULONG)pos;
			SETGADATTR( GD_ListGad, ListTags );
		};
	};

	// Is it midnight ???

	if (omday != CCD.mday)
	{	pos = LISTVIEWPOS;
		ListTags[1] = ListTags[3] = 0;
		SETGADATTR( GD_ListGad, ListTags );
		ae = AE_FROM_ND2(LGad2List.mlh_Head);
		while (&ae->ae_LVNode2 != (void *)&LGad2List.mlh_Tail)
		{	MakeLVLine(ae);
			ae = AE_FROM_ND2(ae->ae_LVNode2.mln_Succ);
		};
		ListTags[1] = (ULONG)&ListGad0List;
		ListTags[3] = (ULONG)pos;
		SETGADATTR( GD_ListGad, ListTags );
	};
	omday = CCD.mday;

	SetTimer( TRUE );
}



/**********************************/
/* Initialise the commodity stuff */
/**********************************/

void
InitCx( void )
{
	CxPort = CreatePort( NULL, 0 );
	if (!CxPort)
		exit(7);

	NewBroker.nb_Pri = (BYTE)Priority;
	NewBroker.nb_Port = CxPort;
	Broker = CxBroker( &NewBroker, NULL );
	if (!Broker)
		exit(8);

	if (PopKey = HotKey( (STRPTR)PopKeyStr, CxPort, 0 ))
	{	if (CxObjError( PopKey ))
		{	DeleteCxObj( PopKey );
			strcpy(PopKeyStr, DEF_POPKEY);
			if (PopKey = HotKey( (STRPTR)PopKeyStr, CxPort, 0 ))
			{	if (CxObjError( PopKey ))
				{	*PopKeyStr = '\0';
					DeleteCxObj( PopKey );
				}
				else
					AttachCxObj( Broker, PopKey );
			};
		}
		else
			AttachCxObj( Broker, PopKey );
	}
	else
		*PopKeyStr = '\0';

	ActivateCxObj( Broker, TRUE );
	Active = TRUE;
}



/***************************/
/* Handle commodity events */
/***************************/

void
HandleCx( void )
{	ULONG type, id;
	CxMsg *cm;

	cm = (CxMsg *)GetMsg( CxPort );
	if (!cm)
		return;

	type = CxMsgType( cm );
	id = CxMsgID( cm );
	ReplyMsg((struct Message *)cm);

	switch (type)
	{	case CXM_IEVENT: /* PopKey was pressed */
			if (!AgendaWnd)
			{	if (SetupScreen())
					exit(9);
				if (OpenMyWindow())
					exit(10);
			};
			break;
		case CXM_COMMAND:
		switch (id)
		{	case CXCMD_DISABLE:
				ActivateCxObj( Broker, FALSE );
				Active = FALSE;
				break;
			case CXCMD_ENABLE:
				ActivateCxObj( Broker, TRUE );
				Active = TRUE;
				break;
			case CXCMD_UNIQUE:
			case CXCMD_APPEAR:
				if (!AgendaWnd)
				{	if (SetupScreen())
						exit(11);
					if (OpenMyWindow())
						exit(12);
				};
				break;
			case CXCMD_DISAPPEAR:
				if (AgendaWnd)
				{	AgendaLeft = AgendaWnd->LeftEdge;
					AgendaTop = AgendaWnd->TopEdge;
					CloseAgendaWindow();
					CloseDownScreen();
				};
				break;
			case CXCMD_KILL:
				exit(13);
		};
	};
}



/**************************************************/
/* Take a string containing two numbers seperated */
/* by a comma and store them as the x,y position  */
/* of the agenda window.                          */
/**************************************************/

BOOL
CalcWPos(	char *txt	)
{	char *p = txt;

	while (*p && *p != ',')
		p++;

	if (*p == ',')
	{	*p = '\0';
		AgendaLeft = atoi(txt);
		AgendaTop = atoi(p + 1);

		return FALSE;
	};

	return TRUE;
}



/******************************************************/
/* Handle the stuff about command line arguments when */
/* started from a shell or the ToolTypes when started */
/* from the WorkBench.                                */
/******************************************************/

void
GetArgs(	int argc,
		char *argv[]	)
{	LONG args[7];
	struct RDArgs *RDArgs;
	struct DiskObject *dobj;
	struct WBStartup *wbm;
	char *str;

	Beep = TRUE;
	Priority = DEF_PRI;
	strcpy(PopKeyStr, DEF_POPKEY);

	if (argc)
	{	Popup = FALSE;
		memset(args, 0, sizeof(args));
		if (RDArgs = ReadArgs( Template, args, NULL ))
		{	if (args[0])
			{	Priority = args[0];
				if (Priority < -128 && Priority > 127)
					Priority = DEF_PRI;
			};
			strcpy( PopKeyStr, (args[1]) ? (char *)args[1] : DEF_POPKEY);
			Popup = (BOOL)args[2];
			Beep = (BOOL)!args[3];
			if (args[4])
				if (CalcWPos((char *)args[4]))
					Write(Output(), "Missing ',' in WPOS\n", 20);
			if (args[5])
			{	mfree(&ShowIDBuf);
				ShowIDBuf = MakeArg((char **)&args[5], MAXSHOWIDLEN, 0);
			};
			if (args[6])
				OutFile = MakeArg((char **)&args[6], 255, 0);
		}
		else
			Write(Output(), "Wrong arguments\n", 16);
		FreeArgs( RDArgs );
	}
	else
	{	Popup = TRUE;
		wbm = (struct WBStartup *)argv;

		/* Set the current directory to the one the project lives in. */

		CurrentDir(wbm->sm_ArgList->wa_Lock);

		/* Get the information stored inside the project's '.info'. */

		dobj = GetDiskObject(wbm->sm_ArgList->wa_Name);
		if (dobj)
		{	str = FindTT(dobj, "CX_PRI");
			if (str)
				Priority = atoi(str);
			str = FindTT(dobj, "CX_POPUP");
			if (str)
				Popup = (*str == 'Y' || *str == 'y');
			str = FindTT(dobj, "CX_POPKEY");
			if (str)
			{	strncpy( PopKeyStr, str, 31 );
				PopKeyStr[31] = '\0';
			};
			str = FindTT(dobj, "NOBEEP");
			if (str)
				Beep = FALSE;
			str = FindTT(dobj, "WPOS");
			if (str)
				CalcWPos(str);
			str = FindTT(dobj, "SHOW");
			if (str)
			{	mfree(&ShowIDBuf);
				ShowIDBuf = MakeArg(&str, MAXSHOWIDLEN, 0);
			};
			str = FindTT(dobj, "OUTPUT");
			if (str)
				OutFile = MakeArg(&str, MAXSHOWIDLEN, 0);

			FreeDiskObject(dobj);
		};
	};
}



int
OpenMyWindow( void )
{	int x, tc;

	tc = 0;
	x = GD_ShowIDLnGad;
	while (x--)
	{	while (AgendaGTags[tc])
			tc += 2;
		tc++;
	};
	AgendaGTags[tc + 1] = (LONG)ShowIDBuf;

	return OpenAgendaWindow();
}



main(	int argc,
	char *argv[]	)
{	ULONG msk, sig;
	struct FileHandle *handle;
	struct Process *process;
	char WTbuf[256];

	OutFile = DefOutFile;
	AgendaLeft = DEF_XPOS;
	AgendaTop = DEF_YPOS;

	NewList((struct List *)&AgendaList);
	NewList((struct List *)&ListGad0List);
	NewList((struct List *)&LGad2List);

	if (!onexit(TheEnd))
		exit(14);

	ReadConfig();
	GetArgs( argc, argv );

	fout = Open(OutFile, MODE_OLDFILE);
	if (!fout)
	{	fout = Open(DefOutFile, MODE_OLDFILE);
		if (!fout)
		{	EASYREQ( "Couldn't open output file", "Sorry" );
			exit(15);
		};
	};
	handle = (struct FileHandle *) (fout << 2);
	process = (struct Process *) FindTask(0);
	OldConTask = process->pr_ConsoleTask;
	process->pr_ConsoleTask = (APTR) handle->fh_Type;

	GetTOD();	// TOD = Time Of Day :)
	InitTimer();
	SetTimer( FALSE );
	omday = CCD.mday;

	ReadAgenda();

	CurrAE = NULL;
	MakeCurrDateTime( CurrAE );

	InitCx();

	strcpy(WTbuf, PROGRAMID);
	strcat(WTbuf, " : Hot Key = <");
	strncat(WTbuf, PopKeyStr, 253-strlen(WTbuf));
	WTbuf[254] = '\0';		// Prevent buffer overflow
	strcat(WTbuf, ">");
	AgendaWdt = WTbuf;

	if (Popup)
	{	if (SetupScreen())
			exit(16);
		if (OpenMyWindow())
			exit(17);
	};

	while (TRUE)
	{	msk = (1<<CxPort->mp_SigBit);
		if (Active)
			msk |= (1<<TimerPort->mp_SigBit);
		if (AgendaWnd)
			msk |= (1<<AgendaWnd->UserPort->mp_SigBit);
		sig = Wait(msk);
		if (AgendaWnd && sig & 1<<AgendaWnd->UserPort->mp_SigBit)
			if (!HandleAgendaIDCMP())
				break;
		if (sig & 1<<TimerPort->mp_SigBit)
			HandleTimer();
		if (sig & 1<<CxPort->mp_SigBit)
			HandleCx();
	};

	exit(0);
}



/************************/
/* Well... just The End */
/************************/

void
TheEnd( void )
{	struct AgendaEntry *ae;
	struct FileHandle *handle;
	struct Process *process;

	if (FileBuf)
		free(&FileBuf);
	if (fout)
	{	Close(fout);
		fout = NULL;
	};

	while (MsgEvCnt--)
	{	mfree(&MsgEvent[MsgEvCnt][0]);
		mfree(&MsgEvent[MsgEvCnt][1]);
	};

	if (Broker)
	{	DeleteCxObjAll(Broker);
		Broker = NULL;
	};

	if (CxPort)
	{	while (GetMsg(CxPort)) ;
		DeletePort(CxPort);
		CxPort = NULL;
	};

	while (ae = (struct AgendaEntry *)RemHead((struct List *)&AgendaList))
		DelAgendaEntry(ae, FALSE);

	CloseAgendaWindow();
	CloseDownScreen();

	if (!TimerDev)
	{	AbortIO((struct IORequest *)TimerReq);
		WaitIO((struct IORequest *)TimerReq);
		CloseDevice((struct IORequest *)TimerReq);
		TimerDev = 1;
	};
	if (TimerReq)
	{	DeleteExtIO((struct IORequest *)TimerReq);
		TimerReq = NULL;
	};
	if (TimerPort)
	{	while (GetMsg(TimerPort)) ;
		DeletePort(TimerPort);
		TimerPort = NULL;
	};

	if (OldConTask)
	{	handle = (struct FileHandle *) (fout << 2);
		process = (struct Process *) FindTask(0);
		process->pr_ConsoleTask = OldConTask;
	};
}
