/*****************************************************************************\
 * $VER: YearPrintQ.c 2.5    DICE/LATTICE C/SAS C/AZTEC C + AmigaOS 2.04/2.1 *
 *                 _                                                         *
 *            _   // (c)1992 by "Quarky" Dieter Temme                        *
 *            \\ //                                                          *
 * :ts=4       \X/ --- Freeware --- ONLY AMIGA MAKES IT POSSIBLE             *
 *                                                                           *
 * produces a printout of one fourth of a calendar on a sheet of paper sized *
 * DIN A5                                                                    *
\*****************************************************************************/

#define PRGNAME "YearPrintQ"
#define VERSION "2.5"
#define PRGDATE "13.9.92"

#include "amigacompq.h"

#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <exec/types.h>
#include <clib/asl_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/graphics_protos.h>
#include <clib/icon_protos.h>
#include <clib/intuition_protos.h>
#include <clib/locale_protos.h>
#include <clib/utility_protos.h>
#include <clib/wb_protos.h>
#include <devices/printer.h>
#include <dos/dos.h>
#include <exec/execbase.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <graphics/text.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <libraries/asl.h>
#include <libraries/gadtools.h>
#include <libraries/locale.h>
#include <utility/date.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>

#ifdef PRAGMAS_
 #include <pragmas/asl_lib.h>
 #include <pragmas/dos_lib.h>
 #include <pragmas/exec_lib.h>
 #include <pragmas/gadtools_lib.h>
 #include <pragmas/graphics_lib.h>
 #include <pragmas/icon_lib.h>
 #include <pragmas/intuition_lib.h>
 #include <pragmas/locale_lib.h>
 #include <pragmas/utility_lib.h>
 #include <pragmas/wb_lib.h>
#endif

#ifdef LATTICE
 int CXBRK(void) { return 0; }  /* Disable Lattice CTRL-C handling */
 int chkabort(void) { return 0; }
#endif

#ifdef AZTEC_C
 void _wb_parse(void) { extern long Enable_Abort; Enable_Abort= FALSE; }
 void _cli_parse(void) { extern long _argc; _argc= ~0; }
 void _abort(void) {}
#endif

/*>> version <<*/
TEXT Version[]= "\0$VER: " PRGNAME " " VERSION " (" PRGDATE ")";

/*>> English display output strings and constants <<*/
#define STRINGARRAY
#include "YearPrintQ.h"

/*>> library bases and locale/catalog variables <<*/
extern struct Library *SysBase;
struct Library *AslBase, *GadToolsBase, *GfxBase, *IconBase, *IntuitionBase,
			   *LocaleBase, *UtilityBase, *WorkbenchBase;
struct Locale *locale;
struct Catalog *cat;

/*>> constants <<*/
#define NUMDAYS		 31		/* number of days in months */
#define NUMMONTHS	 12		/* number of months */
#define NUMWEEKDAYS	 7		/* number of weekdays */
#define MAXWIDTH	 26		/* maximum length of holiday name */
#define DEFCOUNTING	 TRUE	/* default for week numbering */
#define DEFRESTWDAY	 0		/* default for weekly rest-day */
#define DEFFIRSTWDAY 1		/* default for first weekday in a week */
#define DEFQUART	 4		/* default for quarter */
#define DEFGRAPH	 TRUE	/* default for graphics */
#define DEFPITCH	 3		/* default for pitch */

/*>> main variables <<*/
BOOL cli;					/* TRUE if running in CLI */
BPTR lock= ~0;				/* contains original CurrentDir() */
ULONG year;					/* year to print */
BOOL fileflag;				/* TRUE if file was read */

/*>> GUI variables <<*/
TEXT *name;					/* contains last filename */
ULONG signals;				/* signals to wait for */
APTR vi;					/* Intuition's visual information structure */
struct Screen *pubscr;		/* pointer to public screen */
struct Window *win;			/* pointer to YearPrintQ's window */
struct MsgPort *appport;	/* message port for Workbench application window */
struct AppWindow *appwin;	/* Workbench's application window structure */
struct Gadget *congad;		/* pointer to GadTool's anchor gagdet structure */
struct FileRequester *freq;	/* pointer to ASL file requester structure */

/*>> printer device variables <<*/
struct MsgPort *prtport;	/* message port for printer device */
struct IOStdReq *prtreq;	/* Printer's device request structure */

/*>> national calendar variables <<*/
UBYTE counting= DEFCOUNTING; /* TRUE if week numbering is requested */
UBYTE firstwday= DEFFIRSTWDAY; /* weekday number of first weekday in a week */
UBYTE restwday= DEFRESTWDAY; /* weekday number of weekly rest-day */

/*>> calendar variables (for current year) <<*/
BYTE firstday[NUMMONTHS];	/* first weekday of each month */
BYTE firstweek[NUMMONTHS];	/* no. of first week of each month */
UBYTE days[NUMMONTHS]=		/* number of days in the months */
{	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

/*>> structure for saving the holidays read from file <<*/
struct fileday
{	struct fileday *next;	/* pointer to next structure, else NULL */
	TEXT name[MAXWIDTH];	/* name of holiday */
	UBYTE month;			/* month of holiday */
	UBYTE day;				/* day of holiday or weekday number */
	WORD offset;			/* offset of holiday */
	BOOL flags;				/* see below */
} *filedays;				/* pointing to holidays or NULL */

/*>> structure sorted for printing <<*/
struct yearday
{	struct yearday *next;	/* pointer to next structure, else NULL */
	TEXT *name;				/* pointer to name in struct fileday */
	UBYTE day;				/* day of holiday in requested year */
	BOOL flags;				/* flag FREEDAY set or cleared */
} *yeardays[NUMMONTHS];		/* pointing to holidays in month or NULL */

/*>> flags <<*/
#define FIRSTWDAY	0x01	/* first weekday requested (number in .day) */
#define LASTWDAY	0x02	/* last weekday requested (number in .day) */
#define EASTER		0x04	/* offset from Easter Sunday */
#define ADVENT		0x08	/* offset from Advent Sunday */
#define FREEDAY		0x10	/* flag for a rest-day */
#define ALLOCATED	0x20	/* memory for name in yeardays was allocated */

/*>> names for weeks and months <<*/
BOOL wdflag;				/* TRUE if weekdays read from file */
TEXT *weekdays[NUMWEEKDAYS]; /* names of weekdays */
BOOL mnflag;				/* TRUE if month names read from file */
TEXT *months[NUMMONTHS];	/* names of months */

/*>> file requester gadget image <<*/
UWORD fridata[]=			/* must be copied into ChipMem */
{	0xFFF8, 0xE0DC, 0xE0DC, 0xE01C, 0xFFFC,
	0xFFFC, 0xFFFC, 0xFFFC, 0xFFFC, 0xFFFC,
	0x0000, 0x7FF0, 0x7FF8, 0x7FF8, 0x7FF8,
	0x7FF8, 0x7FF8, 0x5FF8, 0x7FF8, 0x0000
};
struct Image frimage=
{	3, 2, 14, 10, 2, fridata, 0x0003, 0x0000, NULL
};

/*>> busy mouse pointer image <<*/
UWORD *pimage;				/* mouse imagedata in chip mem */
UWORD mouseimage[]=			/* a 'simple sprite' */
{	0, 0,
	0x0400, 0x07c0, 0x0000, 0x07c0, 0x0100, 0x0380, 0x0000, 0x07e0,
	0x07c0, 0x1ff8, 0x1ff0, 0x3fec, 0x3ff8, 0x7fde, 0x3ff8, 0x7fbe,
	0x7ffc, 0xff7f, 0x7efc, 0xffff, 0x7ffc, 0xffff, 0x3ff8, 0x7ffe,
	0x3ff8, 0x7ffe, 0x1ff0, 0x3ffc, 0x07c0, 0x1ff8, 0x0000, 0x07e0,
	0, 0
};

/*>> definitions for gadgets <<*/
#define GID_QUART		0
#define GID_YEAR		1
#define GID_FREQ		2
#define GID_FILE		3
#define GID_PITCH		4
#define GID_GRAPH		5
#define GID_PRINT		6
#define GID_STOP		7

/*>> structure for gadgets <<*/
#define LEFT_	0		 /* at left window border for BUTTON_KIND,
						      directly after text else */
#define RIGHT_	128		 /* at right window border */
struct
{	BYTE line;				 /* line in display to appear, last item: -1 */
	UBYTE left;				 /* PLACE_LEFT_ or PLACE_RIGHT_ plus offset */
	BYTE textnum;			 /* number of localized text string */
	ULONG kind;				 /* parameter for CreateGadget() (and ng_Flags),
								IMAGE_KIND_ with internal processing */
	UWORD width;			 /* INTEGER_KIND/STRING_KIND/GENERIC_KIND:
								number of chars, CYCLE_KIND: string array
								is scanned (where &array is second tag in
								taglist).
								ATTENTION: is changed for calculation! */
	UBYTE contents;			 /* actual number for CYCLE_KIND gadgets */
	ULONG misc;				 /* GTST_STRING, GTIN_Number, GTCY_LABELS */
	struct Gadget *gad;		 /* NULL, later result of CreateGadget() */
} gads[]=
{	{	0, LEFT_,	MSG_QUART_GADTXT, CYCLE_KIND, 0, DEFQUART	},
	{	1, LEFT_,	MSG_YEAR_GADTXT,  INTEGER_KIND, 5			},
	{	2, LEFT_,	-1,				  GENERIC_KIND, 1,			},
	{	2, LEFT_+1,	MSG_FILE_GADTXT,  STRING_KIND, 20,			},
	{	3, LEFT_,	MSG_PITCH_GADTXT, CYCLE_KIND, 0, DEFPITCH	},
	{	4, LEFT_,	MSG_GRAPH_GADTXT, CYCLE_KIND, 0, DEFGRAPH	},
	{	5, LEFT_,	MSG_PRINT_GAD,	  BUTTON_KIND, 0			},
	{	5, RIGHT_,	MSG_STOP_GAD,	  BUTTON_KIND, 0			},
	{	-1														}
};

/*==== get string from locale/catalog ====*/
TEXT *GetLocaleStrQ_(UBYTE num)
{	static TEXT *orig_weekdays[NUMWEEKDAYS]=
	{	"Mo", "Tu", "We", "Th", "Fr", "Sa", "So"
	};
	static TEXT *orig_months[NUMMONTHS]=
	{	"January", "February", "March", "April", "May", "June",
		"July", "August", "September", "October", "November", "December"
	};
	static TEXT *orig_abmonths[NUMMONTHS]=
	{	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};

	if (!LocaleBase && (num >= ABMON_1))
		return orig_abmonths[num-ABMON_1];
	if ((!LocaleBase || mnflag) && (num >= MON_1))
		return (mnflag? months : orig_months)[num-MON_1];
	if ((!LocaleBase || wdflag) && (num >= ABDAY_1))
		return (wdflag? weekdays : orig_weekdays)[num-ABDAY_1];
	return GetLocaleStr(locale, (ULONG)num);
}
TEXT *GetCatalogStrQ_(BYTE num)
{	if (num < 0) return "";
	return LocaleBase? GetCatalogStr(cat, num, AppStrings[num].as_Str)
		: AppStrings[num].as_Str;
}

/*==== bring error text to the user ====*/
#define ERR_RETRY_	1	/* bit flags for the requested gadgets to choose */
#define ERR_ABORT_	2
#define ERR_CONTI_	4
#define ERR_QUIT_	8
UBYTE PrintError_(const UBYTE mode, TEXT *str, ...)
{	static struct EasyStruct easystruct=
	{	sizeof(struct EasyStruct), 0, NULL, NULL, NULL
	};

	if (cli)
	{	UWORD i= 0;
		TEXT buf[256];
		va_list ap;
		va_start(ap, str);

		sprintf(buf, PRGNAME ": %s\n", str);
		do
		{	if (buf[i] == '\n') buf[i]= ' ';
		} while (buf[i++]);
		buf[i-2]= '\n';

		VPrintf(buf, &va_arg(ap, LONG));
		va_end(ap);
		return FALSE;
	} else if (IntuitionBase)
	{	TEXT buf[4*30];
		UBYTE i;
		ULONG idcmpflags;
		va_list ap;

		buf[0]= '\0';
#define SETBUF(x, y) if (mode&(x)) strcat(strcat(buf, "|"), GetCatalogStrQ_(y))
		SETBUF(ERR_RETRY_, MSG_RETRY_GAD);
		SETBUF(ERR_QUIT_, MSG_QUIT_GAD);
		SETBUF(ERR_CONTI_, MSG_CONTI_GAD);
		SETBUF(ERR_ABORT_, MSG_ABORT_GAD);
#undef SETBUF
		easystruct.es_TextFormat= str;
		easystruct.es_GadgetFormat= buf+1;
		DisplayBeep(pubscr);
		va_start(ap, str);
		if (pimage && win)
		{	idcmpflags= win->IDCMPFlags;
			ModifyIDCMP(win, IDCMP_MENUPICK);
			SetPointer(win, pimage, 16, 16, -6, 0);
		}
		i= (UBYTE)EasyRequestArgs(win, &easystruct, NULL, &va_arg(ap, TEXT *));
		if (pimage && win)
		{	ClearPointer(win);
			ModifyIDCMP(win, idcmpflags);
		}
		va_end(ap);
		return i;
	} else
		return FALSE;
}

/*==== open library ====*/
struct Library *OpenLibraryQ_(TEXT *str)
{	struct Library *base;

	while (!(base= OpenLibrary(str, 36)))
		if (!PrintError_(ERR_QUIT_, GetCatalogStrQ_(MSG_NOTFOUND),
			GetCatalogStrQ_(MSG_LIBRARY), str))
			exit(RETURN_FAIL);
	return base;
}

/*==== allocate memory ====*/
void *MAllocQ_(const ULONG size)
{	void *ptr;

	while (!(ptr= malloc(size)))
		if (!PrintError_(ERR_RETRY_|ERR_QUIT_, GetCatalogStrQ_(MSG_NOMEMORY)))
			exit(RETURN_FAIL);

	return ptr;
}

/*==== get character for "keyable" gagdets ====*/
TEXT GetGadgetChar_(BYTE num)
{	TEXT *s= strchr((num < 0)? "" : GetCatalogStrQ_(num), '_');
	return s? ToUpper(s[1]) : '\0';
}

/*==== resize an imagedata field ====*/
BOOL ResizeImage_(struct Image *image, UWORD newx, UWORD newy)
{	BYTE oldflag, newflag;
	WORD cntx, cnty;
	UWORD x,y, depth, *newindex, *copyindex, newwidth= (newx+15)>>4;
	UWORD *oldindex= image->ImageData;

	x= image->PlanePick; depth= 0;
	while (x)
	{	depth++;
		do x>>= 1; while (x && !(x&1));
	}
	x= (depth*newy*newwidth<<1)+2;

	while (!(newindex= (UWORD *)AllocMem(x, MEMF_CHIP)))
		if (!(PrintError_(ERR_RETRY_|ERR_ABORT_,
			GetCatalogStrQ_(MSG_NOMEMORY))))
			return FALSE;

	newindex[0]= x;
	image->ImageData= ++newindex;

	while (depth--)
	{	cnty= image->Height; y= newy;
		do
		{	if ((cnty-= image->Height) > 0)
			{	CopyMem(copyindex, newindex, newwidth<<1);
				newindex+= newwidth;
			} else
			{	cnty+= newy;
				oldflag= newflag= 15;
				copyindex= newindex;
				cntx= image->Width; x= newx;
				do
				{	if (newflag == 15) *newindex= 0;
					if (*oldindex&(1<<oldflag)) *newindex|= 1<<newflag;
					if ((cntx-= image->Width) <= 0)
					{	cntx+= newx;
						if (--oldflag < 0) { oldflag= 15; oldindex++; }
					}
					if (--newflag < 0) { newflag= 15; newindex++; }
				} while (--x > 0);
				if (oldflag != 15) oldindex++;
				if (newflag != 15) newindex++;
			}
		} while (--y > 0);
	}

	image->Width= newx;
	image->Height= newy;
	return TRUE;
}

/*==== calculate holidays for given year ====*/
BOOL CalcYear_(BOOL new, LONG newyear)
{	/*- year shall be considered of current year plus one -*/
	if (new && !year)
	{	ULONG secs, micros;
		struct ClockData clockdata;

		CurrentTime(&secs, &micros);
		Amiga2Date(secs, &clockdata);
		newyear= clockdata.year+1;
	}

	/*- number of year not right? -*/
	if ((newyear < 1582) || (newyear > 2199)) return FALSE;

	if (win)
		GT_SetGadgetAttrs(gads[GID_YEAR].gad, win, NULL,
			GTIN_Number, newyear,
			TAG_DONE);

	/*- calculate all dependencies -*/
	{	BYTE adventday;			/* number of day of Easter Sunday */
		BYTE easterday;			/* number of day of 1st Advent */
		UBYTE month;			/* loop variable */
		BOOL leapyear;			/* TRUE if year is leapyear */

		year= newyear;

		/* calculate if year is leapyear */
		leapyear= !(year%4) && (year%100 || !(year%400));
		days[1]= 28+leapyear;

		/* calculate first day and no. of week of every month */
		firstday[0]= (year-1+(year-1)/4-(year-1)/100+(year-1)/400)%NUMWEEKDAYS;
		firstweek[0]=
			(NUMWEEKDAYS+firstday[0]-firstwday)%NUMWEEKDAYS < NUMWEEKDAYS/2;
		for (month= 1; month < NUMMONTHS; month++)
		{	firstweek[month]= firstweek[month-1]+
				(firstday[month-1]+days[month-1])/NUMWEEKDAYS;
			firstday[month]= (firstday[month-1]+days[month-1])%NUMWEEKDAYS;
		}

		/* calculate date of Easter */
		{	UWORD S, A, D, E;	/* temporary variables (excellent music ;-) */

			S= year/100;
			A= year%19;
			D= A*19+22; if (S > 16) D++; if (S > 18) D++; D= D%30;
			E= 6;
			if (S < 21) E--; if (S < 19) E--;
			if (S < 18) E--; if (S < 17) E--;
			E= (2*(year%4)+4*(year%7)+6*D+E)%7;
			easterday= D+E+22;
			if (easterday == 57) easterday-= 7;
			if ((easterday == 56) && (D == 28) && (A > 10)) easterday= 35;
		}

		/* calculate date of 1st Advent */
		adventday= -(firstday[0]+5)%7-leapyear;
		if (adventday <= -4) adventday+= 7;

		/* delete yeardays from memory */
		{	struct yearday *point, *old;

			for (month= 0; month < NUMMONTHS; month++)
			{	point= yeardays[month];
				while (point)
				{	old= point->next;
					if (point->flags&ALLOCATED) free(point->name);
					free(point);
					point= old;
				}
				yeardays[month]= NULL;
			}
		}
		
		/* calculate dates in yeardays[] */
		if (fileflag)
		{	WORD day;
			struct fileday *filepoint;
			struct yearday *yearpoint, *old;

			filepoint= filedays;
			while (filepoint)
			{	if (filepoint->flags&EASTER)
				{	month= 3-1;
					day= easterday;
				} else if (filepoint->flags&ADVENT)
				{	month= 12-1;
					day= adventday;
				} else if (filepoint->flags&(FIRSTWDAY|LASTWDAY))
				{	month= filepoint->month;
					day= filepoint->day-firstday[month];
					if (day <= 0) day+= NUMWEEKDAYS;

					if (filepoint->flags&LASTWDAY)
					{	day= day+days[month]-days[month]%NUMWEEKDAYS;
						if (day > days[month]) day-= NUMWEEKDAYS;
					}
				} else
				{	month= filepoint->month;
					day= filepoint->day;
				}

				day+= filepoint->offset;

				while ((day <= 0) && (month > 0))
					day+= days[--month];
				while ((day > days[month]) && (month < NUMMONTHS))
					day-= days[month++];

				if (month < NUMMONTHS)
				{	old= NULL;
					yearpoint= yeardays[month];
					if ((month == 1) && (day > days[1])) day--; /* leapyear */
					while (yearpoint && ((UBYTE)day > yearpoint->day))
					{	old= yearpoint;
						yearpoint= yearpoint->next;
					}

					if (yearpoint && ((UBYTE)day == yearpoint->day))
					{	UBYTE i;

						if ((i= strlen(yearpoint->name)) < MAXWIDTH-3)
						{	TEXT *str;

							if (!(yearpoint->flags&ALLOCATED))
							{	str= MAllocQ_(MAXWIDTH);
								strcpy(str, yearpoint->name);
								yearpoint->flags|= ALLOCATED;
							} else
								str= yearpoint->name;
							strcat(str+i, ", ");
							strncpy(str+i+2, filepoint->name, MAXWIDTH-3-i);
							str[MAXWIDTH-1]= '\0';
							yearpoint->name= str;
							yearpoint->flags|= filepoint->flags&FREEDAY;
						}
					} else
					{	struct yearday *newday;

						newday= (struct yearday *)
							MAllocQ_(sizeof(struct yearday));

						newday->name= filepoint->name;
						newday->flags= filepoint->flags&FREEDAY;
						newday->day= day;

						if (!old)	yeardays[month]= newday;
						else		old->next= newday;
						newday->next= yearpoint;
					}
				}

				filepoint= filepoint->next;
			}
		}
	}
	return TRUE;
}

/*==== read holiday file ====*/
#define SECT_INCLD	0
#define SECT_MNAMS	1
#define SECT_WDAYS	2
#define SECT_WKCNT	3
#define SECT_FWDAY	4
#define SECT_RWDAY	5
#define SECT_HDAYS	6
BOOL ReadFile_(TEXT *filename)
{	UBYTE count;				/* number of item in section, for limiting */
	UBYTE num;					/* number of error specification string */
	BYTE section= -1;			/* descriptor of section actually read */
	struct fileday *newday;		/* pointer to new newday structure */
	UWORD day, month, end;		/* temporary variables */
	TEXT buf[256], *str, *s;	/* temporary variables */
	BPTR fh;					/* file descriptor for defaults file */
	BOOL forward;				/* TRUE if new include file to scan */

	/*- file structure for subsequent #INCLUDE's -*/
	struct file
	{	struct files *prev;		/* linked list of file structs */
		TEXT *name;				/* pointer to filename (mallocated) */
		LONG pos;				/* current position in file for Seek() */
		UWORD line;				/* current line in file */
	} *current;

	/*- sections' names and counts array -*/
	TEXT *sectnames[]=
	{	"INCLUDE", "MONTHNAMES", "WEEKDAYS", "WEEKCOUNT",
		"FIRSTWDAY", "RESTWDAY", "HOLIDAYS", NULL
	};
	TEXT sectcounts[]=
	{	1, NUMMONTHS, NUMWEEKDAYS, 1, 1, 1, 0
	};

	/*- set filename in file gadget -*/
	if (win)
		GT_SetGadgetAttrs(gads[GID_FILE].gad, win, NULL,
			GTST_String, (ULONG)name,
			TAG_DONE);

	/*- initialize variables -*/
	wdflag= mnflag= FALSE;
	counting= DEFCOUNTING;
	restwday= DEFRESTWDAY;
	firstwday= locale? locale->loc_CalendarType : DEFFIRSTWDAY;
	for (month= 0; month < NUMMONTHS; month++)
		if (months[month]) free(months[month]);
	for (num= 0; num < NUMWEEKDAYS; num++)
		if (weekdays[num]) free(weekdays[num]);

	/*- delete filedays from memory -*/
	{	struct fileday *old;

		while (filedays)
		{	old= filedays->next;
			free(filedays);
			filedays= old;
		}
	}

	/*- fill first file structure -*/
	if (!filename) filename= "";
	current= (struct file *)MAllocQ_(sizeof(struct file));
	current->prev= NULL;
	current->name= strcpy((TEXT *)MAllocQ_(strlen(filename)+1), filename);
	forward= TRUE;

	for (;;)
	{	/* get next file */
		if (!forward)
		{	struct file *old= current->prev;
			free(current->name);
			free(current);
			current= old;
			section= SECT_INCLD;
			count= 1;
		} else
		{	section= -1;
			current->pos= 0;
			current->line= 0;
		}

		/* last file processed */
		if (!current) break;

		/* open file */
		if (*filename)
		{	if (!(fh= Open(current->name, MODE_OLDFILE)))
			{	if (forward && current->prev)
				{	struct file *old= current->prev;
					free(current->name);
					free(current);
					current= old;
					section= SECT_INCLD;
					goto secterror;
				}
				PrintError_(ERR_CONTI_, GetCatalogStrQ_(MSG_NOTFOUND),
					GetCatalogStrQ_(MSG_FILE), current->name);
				goto failed;
			}
			Seek(fh, current->pos, OFFSET_BEGINNING);
		} else
		{	free(current->name);
			free(current);
			return fileflag= TRUE;
		}


		/* read file line by line */
		forward= FALSE;
		while (!forward && FGets(fh, buf, 256))
		{	/* remove comments and leading spaces */
			if (!(str= strchr(buf, ';'))) str= buf+strlen(buf)-1;
			while (isspace(str[-1]) && (str != buf)) str--;
			*str= '\0';
			current->line++;
			if (!*buf) continue;

			str= buf; while (isspace(*str)) str++;

			/* read command */
			if (*str == '#')
			{	if ((section != -1) && sectcounts[section]
					&& (sectcounts[section] != count)) goto secterror;
				num= MSG_SECTION;
				section= 0;
				do str++; while (isspace(*str));
				while (s= sectnames[section])
					if (!Strnicmp(str, s, strlen(s))) break; else section++;
				if (!s) goto lineerror;
				str+= strlen(s);
				count= 0;
				if (!*str) continue;
				if (!isspace(*str)) goto lineerror;
				while (isspace(*str)) str++;
			}

			switch (section)
			{	case SECT_INCLD: /* include command */
					{	UBYTE quotes= 0;
						struct file *temp;

						if (count) goto secterror;
						temp= (struct file *)MAllocQ_(sizeof(struct file));
						temp->prev= current;
						current->pos= Seek(fh, 0, OFFSET_CURRENT);
						current= temp;
						forward= TRUE;

						s= str; if (*s == '"') quotes= 2;
						while (((!quotes && !isspace(*str))
							|| (quotes && (*str == '"'))) && *str) str++;
						if (quotes) str++;
						strncpy(current->name= MAllocQ_(str-s-quotes+1), s,
							str-s-quotes);
						current->name[str-s-quotes]= '\0';
						while (isspace(*str)) str++;
					}
					break;

				case SECT_MNAMS: /* monthnames section */
					mnflag= TRUE;
					while (*str)
					{	UBYTE quotes= 0;

						if (count == NUMMONTHS) goto secterror;
						s= str; if (*s == '"') quotes= 2;
						while (((!quotes && !isspace(*str))
							|| (quotes && (*str == '"'))) && *str) str++;
						if (quotes) str++;
						strncpy(months[count]= MAllocQ_(str-s-quotes+1), s,
							str-s-quotes);
						months[count++][str-s-quotes]= '\0';
						while (isspace(*str)) str++;
					}
					break;

				case SECT_WDAYS: /* weekdays section */
					wdflag= TRUE;
					while (*str)
					{	UBYTE quotes= 0;

						if (count == NUMWEEKDAYS) goto secterror;
						s= str; if (*s == '"') quotes= 2;
						while (((!quotes && !isspace(*str))
							|| (quotes && (*str == '"'))) && *str) str++;
						if (quotes) str++;
						strncpy(weekdays[count]= MAllocQ_(str-s-quotes+1), s,
							str-s-quotes);
						weekdays[count++][str-s-quotes]= '\0';
						while (isspace(*str)) str++;
					}
					break;

				case SECT_WKCNT: /* weekcount section */
					if (!Strnicmp(str, "ON", 2))
					{	counting= TRUE;
						str+= 2;
					} else if (!Strnicmp(str, "OFF", 3))
					{	counting= FALSE;
						str+= 3;
					}
					if (count++ || *str) goto secterror;
					break;

				case SECT_FWDAY:
				case SECT_RWDAY:
					day= 0;
					while (Strnicmp(weekdays[day], str, strlen(weekdays[day])))
						if (++day == NUMWEEKDAYS) goto lineerror;
					str+= strlen(weekdays[day]);
					if (count++ || *str) goto secterror;
					if (section == SECT_FWDAY)	firstwday= day;
					else						restwday= day;
					break;

				case SECT_HDAYS: /* holidays section */
					newday= (struct fileday *)MAllocQ_(sizeof(struct fileday));
					newday->next= filedays;
					filedays= newday;
					newday->flags= 0;
					if (*str == '!')
					{	newday->flags= FREEDAY;
						str++;
					}
					if (*str == '%')
					{	/* Easter or Advent */
						switch (ToUpper(*++str))
						{	case 'E':
								newday->flags|= EASTER;
								break;
							case 'A':
								newday->flags|= ADVENT;
								break;
							default:
								goto secterror;
						}
						str++;
					} else
					{	if (!isdigit(*str))
						{	/* first or last weekday */
							day= 0;
							newday->flags|=
								(*str == '~')? LASTWDAY : FIRSTWDAY;
							if (newday->flags&LASTWDAY) str++;
							while (Strnicmp(weekdays[day], str,
								strlen(weekdays[day])))
								if (++day == NUMWEEKDAYS) goto secterror;
							str+= strlen(weekdays[day]);
							sscanf(str, ".%hu.%hn", &month, &end);
						} else
						{	/* normal date */
							sscanf(str, "%hu.%hu.%hn", &day, &month, &end);
						}
						str+= end;
					}

					/* read offset */
					switch (*str)
					{	case '+':
							str++;
						case '-':
							sscanf(str, "%hd%hn", &newday->offset, &end);
							str+= end;
							break;
						default:
							newday->offset= 0;
					}

					/* put in day, month and name */
					while (isspace(*str)) str++;
					strncpy(newday->name, str, MAXWIDTH-1);
					newday->name[MAXWIDTH-1]= '\0';
					newday->day= day;
					if (((newday->month= month-1) > NUMMONTHS-1)
						|| (day > ((month == 2)? 29 : days[month-1])))
secterror:			{	num= MSG_INSECTION;
lineerror:				Close(fh);
						sprintf(buf, GetCatalogStrQ_(MSG_LINEERROR),
							current->line, current->name,
							GetCatalogStrQ_(num));
						PrintError_(ERR_CONTI_, buf, sectnames[section]);
failed:					/* free whole file list */
						{	struct file *old;

							while (current)
							{	old= current->prev;
								free(current->name);
								free(current);
								current= old;
							}
						}
						return fileflag= FALSE;
					}
					break;

				case -1: /* no section */
					num= MSG_NOSECTION;
					goto lineerror;
			}
		}

		/* close file, abort if error occured */
		{	LONG error;

			error= IoErr();
			Close(fh);
			if (error)
			{	PrintError_(ERR_CONTI_, GetCatalogStrQ_(MSG_FILEERROR),
					current->name);
				goto failed;
			}
		}
	}

	return fileflag= TRUE;
}
#undef SECT_INCLD
#undef SECT_MNAMS
#undef SECT_WDAYS
#undef SECT_WKCNT
#undef SECT_FWDAY
#undef SECT_RWDAY
#undef SECT_HDAYS

/*==== open printer ====*/
BOOL OpenPrinter_(void)
{	prtport= CreateMsgPort();
	prtreq= (struct IOStdReq *)CreateIORequest(prtport,
		sizeof(struct IOStdReq));

	if (OpenDevice("printer.device", 0, (struct IORequest *)prtreq, 0))
	{	PrintError_(ERR_ABORT_, GetCatalogStrQ_(MSG_PRINTERUSED));
		return FALSE;
	} else
		return TRUE;
}

/*==== close printer ====*/
void ClosePrinter_(void)
{	if (prtport)
	{	struct Message *msg;

		while (msg= GetMsg(prtport)) ReplyMsg(msg);
		DeleteMsgPort(prtport);
		prtport= NULL;
	}
	if (prtreq)
	{	CloseDevice((struct IORequest *)prtreq);
		DeleteIORequest(prtreq);
		prtreq= NULL;
	}
}

/*==== do printer.device write command ====*/
BOOL ExecPrtIO_(const TEXT *data)
{	prtreq->io_Data= (APTR)data;
	prtreq->io_Length= -1;
	if (DoIO((struct IORequest *)prtreq))
	{	PrintError_(ERR_ABORT_, GetCatalogStrQ_(MSG_PRINTPROBL));
		return FALSE;
	}
	return TRUE;
}

BOOL ExecChrIO_(const TEXT cdata)
{	static TEXT s[2];

	s[0]= cdata;
	return ExecPrtIO_(s);
}

/*==== main printing program ====*/
#define DoPrtIO_(x) { if (!ExecPrtIO_(x)) goto prtfailed; }
#define DoChrIO_(x) { if (!ExecChrIO_(x)) goto prtfailed; }
#define SetPrtIO_(cmd)	prtreq->io_Command= cmd
#define ESC "\x1b"
#define CSI	"\x9b"
UBYTE PrintCalendar_(UBYTE quart, UBYTE pitch, UBYTE ibmpc)
{	/*- miscellaneous variables -*/
	TEXT buf[256];				/* buffer for sprintf() */
	struct yearday *point[3];	/* pointer to holiday entries of each month */
	UBYTE week[3];				/* current week number of each month */
	UBYTE day, month;			/* loop variables */
	UBYTE colwidth;				/* width of one column according to pitch */
	BOOL allquarts;				/* TRUE if whole year is requested */

	/*- graphical characters [0]= IBMPC, [1]= ASCII -*/
	static TEXT ul[]= "+\xd2", ulm[]= "+\xc2", um[]= "+\xd6", ur[]= "+\xb7",
				ml[]= "+\xc7", mm[]= "+\xd7", mr[]= "+\xb6",
				m1lm[]= "+\xc5", m2lm[]= "+\xd8",
				d1lm[]= "+\xc1", d2lm[]= "+\xcf",
				s1l[]= ":\xb3", s2l[]= "|\xba", h1l[]= "-\xc4", h2l[]= "=\xcd";

	/*- separator lines -*/
	static TEXT t1l[6], t1[MAXWIDTH], t2l[6], t2[MAXWIDTH];

	/*- format strings -*/
	TEXT *form1, *form2, *form3;

	/*- re-initialize parameters -*/
	if (allquarts= quart > 3)	quart= 0; else quart*= 3;
	if (pitch > 3)				pitch= DEFPITCH;

	/*- initialize printer -*/
	if (!OpenPrinter_()) goto prtfailed;
	SetPrtIO_(CMD_WRITE);
	DoPrtIO_(ESC "#1" CSI "0z");

	/*- set formats according to pitch -*/
	switch (pitch)
	{	case 0: /* PICA 10cpi= 58 chars/line= 19 chars/col */
			colwidth= 19;
			DoPrtIO_(CSI "0w");
			form1= "%18s";
			form2= "%-12.12s";
			form3= "%-7.7s " CSI "3m-%2.2ld-" CSI "23m";
			break;
		case 1: /* ELITE 12cpi= 69 chars/line= 22 chars/col */
			colwidth= 22;
			DoPrtIO_(CSI "0w" CSI "2w");
			form1= "%21s";
			form2= "%-15.15s";
			form3= "%-10.10s " CSI "3m-%2.2ld-" CSI "23m";
			break;
		case 2: /* SEMI 15cpi= 87 chars/line= 28 chars/col */
			colwidth= 28;
			DoPrtIO_(CSI "0w");
			form1= "%27s";
			form2= "%-21.21s";
			form3= "%-16.16s " CSI "3m%-2.2ld-" CSI "23m";
			break;
		default: /* FINE 17cpi= 98 chars/line= 32 chars/col */
			colwidth= 32;
			DoPrtIO_(CSI "0w" CSI "4w");
			form1= "%31s";
			form2= "%-25.25s";
			form3= "%-20.20s " CSI "3m-%2.2ld-" CSI "23m";
	}

	/*- set line separators according to ibmpc graphics flag -*/
	{	UBYTE c1= h1l[ibmpc], c2= h2l[ibmpc], i;

		for (i= 0; i < 5; i++)	{	t1l[i]= c1; t2l[i]= c2;	}
		for (i= 0; i < 25; i++)	{	t1[i]= c1; t2[i]= c2;	}
		t1[colwidth-7]= t2[colwidth-7]= '\0';
	}

	/*- print loop for one sheet -*/
	do
	{	/* print number of year */
		sprintf(buf, CSI "1m" CSI "4m" CSI "6w%ld" CSI "5w" CSI "0m\n\n",
			year);
		DoPrtIO_(buf);

		/* print month names */
		{	UBYTE slen, left, i;

			for (month= 0; month < 3; month++)
			{	point[month]= yeardays[quart+month];
				week[month]= firstweek[quart+month];

				slen= strlen(GetLocaleStrQ_(MON_1+quart+month));
				left= (colwidth-slen)/2+((colwidth-slen)&1);
				for (i= 0; i < left; i++) buf[i]= ' ';
				strcpy(&buf[left], GetLocaleStrQ_(MON_1+quart+month));
				DoPrtIO_(buf);
				buf[left-((colwidth-slen)&1)]= '\0';
				DoPrtIO_(buf);
			}
			DoChrIO_('\n');
		}

		/* print first separator line */
		if (ibmpc) SetPrtIO_(PRD_RAWWRITE);
		for (month= 0; month < 3; month++)
		{	DoChrIO_((month? ul : um)[ibmpc]);
			DoPrtIO_(t1l);
			DoChrIO_(ulm[ibmpc]);
			DoPrtIO_(t1);
		}
		DoChrIO_(ur[ibmpc]);
		SetPrtIO_(CMD_WRITE);
		DoChrIO_('\n');

		/* print all days line by line */
		for (day= 1; day <= NUMDAYS; day++)
		{	/* print days of a line */
			if (ibmpc) SetPrtIO_(PRD_RAWWRITE);
			for (month= 0; month < 3; month++)
			{	DoChrIO_(s2l[ibmpc]);
				if (day > days[quart+month]) /* empty line */
				{	sprintf(buf, form1, "");
					DoPrtIO_(buf);
				} else
				{	/* day with weekday, date number, holiday text */
					TEXT *str= "";
					BOOL bold;

					bold= FALSE;
					if ((firstday[quart+month]+day)%NUMWEEKDAYS == restwday)
						bold= TRUE;
					if (point[month] && (point[month]->day == day))
					{	str= point[month]->name;
						if (point[month]->flags&FREEDAY) bold= TRUE;
						point[month]= point[month]->next;
					}
					SetPrtIO_(CMD_WRITE);
					sprintf(buf, "%s%2.2s %2ld%s",
						bold? CSI "1m" : "",
						GetLocaleStrQ_(ABDAY_1+(firstday[quart+month]+day)
							%NUMWEEKDAYS),
						day,
						bold? CSI "0m" : "");
					DoPrtIO_(buf);
					if (ibmpc) SetPrtIO_(PRD_RAWWRITE);
					DoChrIO_(s1l[ibmpc]);
					SetPrtIO_(CMD_WRITE);
					if (!counting || ((firstday[quart+month]+day)
						%NUMWEEKDAYS != firstwday))
						sprintf(buf, form2, str);
					else
					{	if ((quart+month == NUMMONTHS-1) && (day >= 29))
							week[month]= 1;
						else if (day != 1) week[month]++;
						sprintf(buf, form3, str, week[month]);
					}
					DoPrtIO_(buf);
					if (ibmpc) SetPrtIO_(PRD_RAWWRITE);
				}
			}
			DoChrIO_(s2l[ibmpc]);
			SetPrtIO_(CMD_WRITE);
			DoChrIO_('\n');

			/* print separator line */
			for (month= 0; month < 3; month++)
			{	if (ibmpc) SetPrtIO_(PRD_RAWWRITE);
				if (day > days[quart+month])
				{	DoChrIO_((((month < 3) && (day > days[quart+month+1]))?
						mr : s2l)[ibmpc]);
					sprintf(buf, form1, ""); /* expand */
					DoPrtIO_(buf);
				} else
				{	DoChrIO_(((month && (day <= days[quart+month-1]))? mm : ml)
						[ibmpc]);
					if ((firstday[quart+month]+day+1)%NUMWEEKDAYS != firstwday)
					{	DoPrtIO_(t1l);
						DoChrIO_(((day == days[quart+month])? d1lm : m1lm)
							[ibmpc]);
						DoPrtIO_(t1);
					} else
					{	DoPrtIO_(t2l);
						DoChrIO_(((day == days[quart+month])? d2lm : m2lm)
							[ibmpc]);
						DoPrtIO_(t2);
					}
				}
			}
			DoChrIO_(mr[ibmpc]);
			SetPrtIO_(CMD_WRITE);
			DoChrIO_('\n');

			/* interrupt printing if user presses CTRL-C */
			{	struct IntuiMessage *imsg= NULL;
				BOOL brk;

				brk= ((SetSignal(0, 0)&SIGBREAKF_CTRL_C)
					|| (win && (imsg= GT_GetIMsg(win->UserPort))
					&& ((imsg->Class == IDCMP_CLOSEWINDOW)
					|| (imsg->Class == IDCMP_GADGETUP))));
				if (imsg) GT_ReplyIMsg(imsg);
				if (brk)
					if(!PrintError_(ERR_CONTI_|ERR_ABORT_, 
						GetCatalogStrQ_(MSG_USERBREAK)))
						goto prtfailed;
			}
		}

		/* print form feed */
		DoChrIO_('\f');
	} while (allquarts && ((quart+= 3) < 12));

	/*- reset and close printer -*/
	DoPrtIO_(ESC "#1");
	ClosePrinter_();
	return RETURN_OK;

prtfailed:
	/*- close printer -*/
	ClosePrinter_();
	return RETURN_ERROR;
}
#undef DoPrtIO_
#undef DoChrIO_
#undef SetPrtIO_
#undef ESC
#undef CSI

/*=== open window with all gadgets ====*/
void OpenWinAndGadgets_(void)
{	struct RastPort *rp;	/* pointer to public screen's rastport */
	UWORD xsize;			/* font's width */
	UWORD winwidth;			/* window width */
	UWORD textwidth= 0;		/* maximum width of gadgets' text */
	UWORD fieldwidth= 0;	/* maximum width of gagdets */
	UWORD u;				/* width of underscore character */
	UWORD n;				/* temporary width variable */
	TEXT *str;				/* temporary string variable */
	BYTE i;					/* loop variable */
	static struct NewGadget newgad; /* used with CreateGadget() */

	/*- my newwindow structure -*/
	static struct NewWindow newwin=
	{	0, 0, 0, 0, 0, 1,
		CYCLEIDCMP|NUMBERIDCMP|STRINGIDCMP|BUTTONIDCMP|IDCMP_GADGETUP|
			IDCMP_GADGETDOWN|IDCMP_CLOSEWINDOW|IDCMP_RAWKEY|IDCMP_VANILLAKEY,
		WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_CLOSEGADGET|WFLG_ACTIVATE|
			WFLG_SMART_REFRESH|WFLG_GIMMEZEROZERO,
		NULL, NULL, PRGNAME, NULL, NULL, 0, 0, 0, 0, PUBLICSCREEN
	};

	/*- get width of system font -*/
	rp= &pubscr->RastPort;
	if (rp->Font->tf_Flags&FPF_PROPORTIONAL)
	{	i= max(rp->Font->tf_LoChar, ' ');

		xsize= 0;
		do
		{	if ((n= TextLength(rp, &i, 1)) > xsize) xsize= n;
		} while ((i != (BYTE)rp->Font->tf_HiChar) && (i++ != (BYTE)'~'));
	} else
		xsize= rp->Font->tf_XSize;

	/*- resize file requester gadget image -*/
	if (!ResizeImage_(&frimage, 2*xsize+2, 2+rp->Font->tf_YSize))
		goto failed;

	/*- calculate all gadgets' widths -*/
	u= TextLength(rp, "_", 1);
	i= -1; while (gads[++i].line != -1)
	{	switch (gads[i].kind)
		{	case CYCLE_KIND:
				{	BYTE j= -1;

					TEXT **s= (TEXT **)gads[i].misc;
					gads[i].width= 0;
					while (s[++j])
					{	n= TextLength(rp, s[j], strlen(s[j]));
						if (n > gads[i].width) gads[i].width= n;
					}
				}
				gads[i].width+= 4*INTERWIDTH;
				if (gads[i].width > fieldwidth) fieldwidth= gads[i].width;
calctext:		str= GetCatalogStrQ_(gads[i].textnum);
				n= TextLength(rp, str, strlen(str));
				if (strchr(str, '_')) n-= u;
				if (n > textwidth) textwidth= n;
				break;
			case BUTTON_KIND:
				str= GetCatalogStrQ_(gads[i].textnum);
				gads[i].width=
					2*INTERWIDTH+TextLength(rp, str, strlen(str));
				if (strchr(str, '_')) gads[i].width-= u;
				break;
			default: /* STRING_KIND, INTEGER_KIND or GENERIC_KIND */
				n= xsize*(gads[i].width+1)+INTERWIDTH;
				gads[i].width= n;
				if ((gads[i].left != RIGHT_) && (gads[i].left > LEFT_))
					n+= xsize*gads[i].left+INTERWIDTH+2;
				if (n > fieldwidth) fieldwidth= n;
				goto calctext;
		}
	}

	/*- set window width correctly -*/
	winwidth= textwidth+fieldwidth+INTERWIDTH;
	i= -1; while (gads[++i].line != -1)
	{	if ((gads[i].left == LEFT_) && (gads[i+1].left == RIGHT_)
			&& (gads[i].line == gads[i+1].line)
			&& (gads[i].kind == BUTTON_KIND)
			&& (gads[i+1].kind == BUTTON_KIND))
		{	n= gads[i].width+gads[i+1].width;
			if (n > winwidth) winwidth= n;
		}
	}
	winwidth+= 3*INTERWIDTH;

	/*- bring gadgets to screen -*/
	do
	{	CreateContext(&congad);
		newgad.ng_VisualInfo= vi;
		newgad.ng_TextAttr= pubscr->Font;
		newgad.ng_Height= 6+rp->Font->tf_YSize;
		newgad.ng_TopEdge= INTERHEIGHT;
		newgad.ng_GadgetID= 0;
		i= -1; while (gads[++i].line != -1)
		{	if (gads[i].kind == BUTTON_KIND)
			{	newgad.ng_LeftEdge= (gads[i].left == RIGHT_)?
					winwidth-INTERWIDTH-gads[i].width : INTERWIDTH;
				newgad.ng_Flags= PLACETEXT_IN;
			} else
			{	newgad.ng_LeftEdge= 2*INTERWIDTH+textwidth;
				newgad.ng_Flags= PLACETEXT_LEFT;				
			}
			newgad.ng_Width= gads[i].width;
			newgad.ng_GadgetText= GetCatalogStrQ_(gads[i].textnum);
			gads[i].gad= CreateGadget(gads[i].kind,
				i? gads[i-1].gad : congad, &newgad,
				GT_Underscore, (ULONG)'_',
				STRINGA_ExitHelp, TRUE,
				GTST_MaxChars, 256,
				GTCY_Active, gads[i].contents,
				(gads[i].kind == STRING_KIND)? GTST_String
				: (gads[i].kind == NUMBER_KIND)? GTIN_Number
				: GTCY_Labels, gads[i].misc,
				GA_Disabled, i == GID_STOP,
				TAG_DONE);
			if ((gads[i].left != RIGHT_) && (gads[i].left > LEFT_))
			{	struct IntuiText *it= gads[i].gad->GadgetText;

				n= xsize*(gads[i].left+1)+INTERWIDTH+2;
				gads[i].gad->LeftEdge+= n;
				while (it)
				{	it->LeftEdge-= n;
					it= it->NextText;
				}
			}
			if (gads[i+1].line != -1)
			{	n= gads[i+1].line-gads[i].line;
				while (n-- > 0)
					newgad.ng_TopEdge+= newgad.ng_Height+INTERHEIGHT;
			}				
			newgad.ng_GadgetID++;
		}

		if (!gads[i-1].gad)
		{	if (!PrintError_(ERR_RETRY_|ERR_QUIT_,
				GetCatalogStrQ_(MSG_NOMEMORY)))
				goto failed;
			FreeGadgets(congad);
			congad= NULL;
		} else
			break;
	} while (TRUE);

	/*- open window -*/
	while (!(win= OpenWindowTags(&newwin,
		WA_ScreenTitle, (ULONG)PRGNAME " - (c)1991 by Dieter Temme",
		WA_InnerWidth, winwidth,
		WA_InnerHeight, newgad.ng_TopEdge+newgad.ng_Height+INTERHEIGHT,
		TAG_DONE)))
		if (!PrintError_(ERR_RETRY_|ERR_QUIT_,
			GetCatalogStrQ_(MSG_NOWINDOW)))
failed:		exit(RETURN_FAIL);

	/*- unlock public default screen and set signals variable -*/
	UnlockPubScreen(NULL, pubscr); pubscr= NULL;
	signals= 1<<win->UserPort->mp_SigBit;

	/*- define file requester gadget -*/
	gads[GID_FREQ].gad->Flags= GFLG_GADGIMAGE;
	gads[GID_FREQ].gad->Activation= GACT_RELVERIFY;
	gads[GID_FREQ].gad->GadgetType|= GTYP_BOOLGADGET;
	gads[GID_FREQ].gad->GadgetRender= (APTR)&frimage;
	DrawBevelBox(win->RPort, gads[GID_FREQ].gad->LeftEdge,
		gads[GID_FREQ].gad->TopEdge,
		gads[GID_FREQ].gad->Width, gads[GID_FREQ].gad->Height,
		GT_VisualInfo, (ULONG)vi,
		TAG_DONE);

	/*- add gadgets to window -*/
	AddGList(win, congad, ~0, -1, NULL);
	RefreshGList(congad, win, NULL, -1);
	GT_RefreshWindow(win, NULL);
}

/*==== process filename ====*/
#define NOREAD	0
#define READ	1
#define FORCED	2
void ProcessFilename_(UBYTE readflag, TEXT *newname)
{	if ((readflag == FORCED) || Stricmp(newname, name))
	{	if (name) free(name);
		name= strcpy((TEXT *)MAllocQ_(strlen(newname)+1), newname);
		fileflag= FALSE;
	}

	if (readflag && !fileflag)
	{	ReadFile_(name);
		CalcYear_(FALSE, year);
	}

	GT_SetGadgetAttrs(gads[GID_PRINT].gad, win, NULL,
		GA_Disabled, TRUE-fileflag,
		TAG_DONE);
}

/*==== read icon's tool types or bundled file ====*/
void ReadToolTypes_(struct WBStartup *wbarg)
{	struct DiskObject *obj;
	TEXT *s, **tt;
	ULONG i;

	if (IconBase= OpenLibrary("icon.library", 0))
	{	if (obj= GetDiskObject(wbarg->sm_ArgList->wa_Name))
		{	/* scan tool types */
			if ((s= FindToolType(tt, "YEAR")))
				CalcYear_(FALSE, atol(s));
			if (s= FindToolType(tt, "QUARTER"))
			{	if (!Stricmp(s, "ALL"))
					gads[GID_QUART].contents= 4;
				else if ((i= (ULONG)atol(s)) <= 3)
					gads[GID_QUART].contents= (UBYTE)i;
			}
			if ((s= FindToolType(tt= obj->do_ToolTypes, "FILE")) && *s)
				ProcessFilename_(READ, s);
			if (s= FindToolType(tt, "PITCH"))
			{	if (!Stricmp(s, "PICA")) gads[GID_PITCH].contents= 0;
				else if (!Stricmp(s, "ELITE")) gads[GID_PITCH].contents= 1;
				else if (!Stricmp(s, "SEMI")) gads[GID_PITCH].contents= 2;
				else if (!Stricmp(s, "FINE")) gads[GID_PITCH].contents= 3;
			}
			if (s= FindToolType(tt, "GRAPHICS"))
				if (!Stricmp(s, "ASCII")) gads[GID_GRAPH].contents= FALSE;
				else if (!Stricmp(s, "IBMPC")) gads[GID_GRAPH].contents= TRUE;
				FreeDiskObject(obj);

			/* get bundled defaults file icon */
			if (wbarg->sm_NumArgs > 1)
			{	struct WBArg *argptr= &wbarg->sm_ArgList[1];
				ULONG x, y;
				TEXT *str;
				BPTR lock;

				y= strlen(argptr->wa_Name)+2;
				CurrentDir(lock= CurrentDir((BPTR)NULL));
				if (SameLock(lock, argptr->wa_Lock) == LOCK_SAME)
					str= strcpy((TEXT *)MAllocQ_(y), argptr->wa_Name);
				else
				{	x= 256;
					do
					{	if (str) free(str);
						str= MAllocQ_(x+y);
					} while (!NameFromLock(argptr->wa_Lock, str, x));
					AddPart(str, argptr->wa_Name, x+y);
				}
				ProcessFilename_(READ, str);
			}
		}
		CloseLibrary(IconBase);
	}
}

/*==== process application window messages ====*/
void ProcessAppMessages_(void)
{	struct AppMessage *amsg;
	struct WBArg *argptr;
	ULONG x, y;
	TEXT *str;
	BPTR lock;

	while (amsg= (struct AppMessage *)GetMsg(appport))
	{	str= NULL;
		argptr= &amsg->am_ArgList[amsg->am_NumArgs-1];
		y= strlen(argptr->wa_Name)+2;
		CurrentDir(lock= CurrentDir((BPTR)NULL));
		if (SameLock(lock, argptr->wa_Lock) == LOCK_SAME)
			str= strcpy((TEXT *)MAllocQ_(y), argptr->wa_Name);
		else
		{	x= 256;
			do
			{	if (str) free(str);
				str= MAllocQ_(x+y);
			} while (!NameFromLock(argptr->wa_Lock, str, x));
			AddPart(str, argptr->wa_Name, x+y);
		}
		ProcessFilename_(READ, str);

		ReplyMsg((struct Message *)amsg);
	}
}

/*==== process window messages ====*/
#define STRINGINFO(gid) ((struct StringInfo *)gads[gid].gad->SpecialInfo)
#define REQPRINT	1
#define REQEXIT		2
UBYTE ProcessWindowMessages_(void)
{	struct IntuiMessage *imsg;		/* pointer to window message */
	TEXT c;							/* character of VANILLAKEY message */
	TEXT fname[31];					/* filename without path */
	UBYTE rc= 0;					/* return code */

	while (!rc && (imsg= GT_GetIMsg(win->UserPort)))
	{	switch (imsg->Class)
		{	case IDCMP_GADGETUP:
				switch (c= ((struct Gadget *)imsg->IAddress)->GadgetID)
				{	case GID_YEAR:
						CalcYear_(FALSE, STRINGINFO(GID_YEAR)->LongInt);
						break;

					case GID_FILE:
						if (imsg->Code == 0x5f) goto outhelp;
						ProcessFilename_(FORCED, STRINGINFO(GID_FILE)->Buffer);
						break;

					case GID_FREQ:
					{	ULONG idcmpflags;

						ProcessFilename_(NOREAD, STRINGINFO(GID_FILE)->Buffer);
						strncpy(fname, FilePart(name), 30);
						fname[30]= '\0';
						*PathPart(name)= '\0';
						if (pimage)
						{	idcmpflags= win->IDCMPFlags;
							ModifyIDCMP(win, IDCMP_MENUPICK);
							SetPointer(win, pimage, 16, 16, -6, 0);
						}
						if (AslRequestTags((APTR)freq,
							ASLFR_InitialDrawer, (ULONG)name,
							ASLFR_InitialFile, (ULONG)fname,
							TAG_DONE))
						{	UWORD i;
							TEXT *tmpstr;

							tmpstr= strcpy(MAllocQ_(i= strlen(freq->fr_Drawer)
								+strlen(freq->fr_File)+3), freq->fr_Drawer);
							AddPart(tmpstr, freq->fr_File, i);
							ProcessFilename_(FORCED, tmpstr);
						}
						if (pimage)
						{	ClearPointer(win);
							ModifyIDCMP(win, idcmpflags);
						}
						break;
					}

					case GID_PRINT:
print:					ProcessFilename_(READ, STRINGINFO(GID_FILE)->Buffer);
						rc= REQPRINT;
						break;

					default:
						gads[c].contents= (UBYTE)imsg->Code;
				}
				break;
			case IDCMP_VANILLAKEY:
				c= (TEXT)ToUpper(imsg->Code);
				if (c == GetGadgetChar_(MSG_YEAR_GADTXT))
					ActivateGadget(gads[GID_YEAR].gad, win, NULL);
				else if (c == GetGadgetChar_(MSG_FILE_GADTXT))
					ActivateGadget(gads[GID_FILE].gad, win, NULL);
				else if (c == GetGadgetChar_(MSG_QUART_GADTXT))
				{	gads[GID_QUART].contents=
						(gads[GID_QUART].contents+1)%5;
					c= GID_QUART;
					goto setattr;
				} else if (c == GetGadgetChar_(MSG_PITCH_GADTXT))
				{	gads[GID_PITCH].contents=
						(gads[GID_PITCH].contents+1)&3;
					c= GID_PITCH;
					goto setattr;
				} else if (c == GetGadgetChar_(MSG_GRAPH_GADTXT))
				{	gads[GID_GRAPH].contents^= TRUE;
					c= GID_GRAPH;
setattr:			GT_SetGadgetAttrs(gads[c].gad, win, NULL,
						GTCY_Active, gads[c].contents,
						TAG_DONE);
				} else if (c == GetGadgetChar_(MSG_PRINT_GAD))
						goto print;
				else if (c == '\x1b')
						goto quit;
				break;
			case IDCMP_RAWKEY:
				if (imsg->Code == 0x5f)
outhelp:			PrintError_(ERR_CONTI_, "*** " PRGNAME VERSION " ***"
						" - (c)1991 by Dieter Temme\n\n%s",
						GetCatalogStrQ_(MSG_HELP));
				break;
			case IDCMP_CLOSEWINDOW:
quit:			rc= REQEXIT;
		}
		GT_ReplyIMsg(imsg);
	}
	return rc;
}
#undef STRINGINFO

/*==== main program (Workbench) ====*/
int guimain(struct WBStartup *wbarg)
{	/*- miscellaneous variables -*/
	TEXT *name= NULL;		/* pointer to filename including path */
	struct RastPort *rp;	/* pointer to public screen's rastport */
	UWORD xsize;			/* font's width */
	UBYTE flag;				/* contains flags REQPRINT and REQEXIT */

	/*- cycle gadget lists -*/
	static TEXT *quartlist[]= /* is localized, see below */
	{	"a      ", "b      ", "c      ", "d      ", NULL, NULL
	};
	static TEXT *pitchlist[5]; /* is localized, see below */
	static TEXT *graphlist[]=
	{	"+-=:|", "IBM PC", NULL
	};

	/*- copy busy mouse pointer image into ChipMem -*/
	while (!(pimage= AllocMem(sizeof(mouseimage), MEMF_CHIP)))
		if (!PrintError_(ERR_RETRY_|ERR_QUIT_, GetCatalogStrQ_(MSG_NOMEMORY)))
			return RETURN_FAIL;
	CopyMem(mouseimage, pimage, sizeof(mouseimage));

	/*- open additional libraries -*/
	AslBase= OpenLibraryQ_("asl.library");
	GadToolsBase= OpenLibraryQ_("gadtools.library");
	GfxBase= OpenLibraryQ_("graphics.library");
	WorkbenchBase= OpenLibraryQ_("workbench.library");

	/*- set current directory to program's directory -*/
	lock= CurrentDir(wbarg->sm_ArgList->wa_Lock);

	/*- fill in gads[].misc and cycle gadget lists -*/
	gads[GID_QUART].misc= (ULONG)quartlist;
	gads[GID_PITCH].misc= (ULONG)pitchlist;
	gads[GID_GRAPH].misc= (ULONG)graphlist;
	{	UBYTE i;

		for (i= 0; i <= 3; i++)
		{	pitchlist[i]= GetCatalogStrQ_(MSG_PICA_GAD+i);
			sprintf(quartlist[i], "%3.3s-%3.3s",
				GetLocaleStrQ_(ABMON_1+3*i), GetLocaleStrQ_(ABMON_3+3*i));
		}
	}
	quartlist[4]= GetCatalogStrQ_(MSG_ALL_GAD);
	pitchlist[4]= NULL;

	/*- lock default public screen, get visual information -*/
	pubscr= LockPubScreen(NULL);
	if (!(vi= GetVisualInfo(pubscr, TAG_DONE)))
		return RETURN_FAIL;

	/*- open window with gadgets -*/
	OpenWinAndGadgets_();

	/*- add window to application window list -*/
	if (appport= CreateMsgPort())
	{	appwin= AddAppWindowA(1, 0, win, appport, NULL);
		signals|= 1<<appport->mp_SigBit;
	}

	/*- initialize a file requester structure -*/
	freq= (struct FileRequest *)AllocAslRequestTags(ASL_FileRequest,
		ASLFR_Window, (ULONG)win,
		ASLFR_TitleText, (ULONG)GetCatalogStrQ_(MSG_CHOOSEFILE),
		ASLFR_Flags1, FRF_DOPATTERNS,
		ASLFR_InitialPattern, (ULONG)((AslBase->lib_Version < 38)?
			"YPQ~(#?.info)" : "YPQ#?"),
		ASLFR_RejectIcons, TRUE,
		TAG_DONE);

	/*- get number of next year -*/
	CalcYear_(TRUE, 0);

	/*- read icon's tool types or bundled file -*/
	ReadToolTypes_(wbarg);

	/*- wait for and process messages -*/
	do
	{	do
		{	Wait(signals);

			/* process application messages */
			ProcessAppMessages_();

			/* process window messages */
			flag= ProcessWindowMessages_();
		} while (!flag);

		/* calendar printing requested */
		if (flag&REQPRINT)
		{	static struct TagItem abletag[]=
			{	GA_Disabled, FALSE,
				TAG_DONE
			};
			struct Gadget *gad;
			
			/* disable gadgets */
			GT_SetGadgetAttrsA(gads[GID_STOP].gad, win, NULL, abletag);
			abletag[0].ti_Data= TRUE;
			gad= gads[0].gad;
			do
			{	if (gad == gads[GID_FREQ].gad)
					OffGadget(gad, win, NULL);
				else
					GT_SetGadgetAttrsA(gad, win, NULL, abletag);
			} while ((gad= gad->NextGadget) != gads[GID_STOP].gad);

			/* print calendar, increment quarter and year */
			if (!PrintCalendar_(gads[GID_QUART].contents,
				gads[GID_PITCH].contents, gads[GID_GRAPH].contents))
			{	if (gads[GID_QUART].contents != 4)
					GT_SetGadgetAttrs(gads[GID_QUART].gad, win, NULL,
						GTCY_Active, gads[GID_QUART].contents=
							(gads[GID_QUART].contents+1)&3,
						TAG_DONE);
				if (!(gads[GID_QUART].contents&3))
					CalcYear_(FALSE, (year == 2199)? 1582 : year+1);
			}

			/* re-enable gadgets */
			GT_SetGadgetAttrsA(gads[GID_STOP].gad, win, NULL, abletag);
			abletag[0].ti_Data= FALSE;
			gad= gads[GID_QUART].gad;
			do
			{	if (gad == gads[GID_FREQ].gad)
				{	UWORD x= gad->LeftEdge;
					UWORD y= gad->TopEdge;

					SetDrMd(win->RPort, JAM1);
					SetAPen(win->RPort, 0);
					RectFill(win->RPort, x, y,
						x+gad->Width-1, y+gad->Height-1);
					DrawBevelBox(win->RPort, x, y, gad->Width, gad->Height,
						GT_VisualInfo, (ULONG)vi,
						TAG_DONE);
					OnGadget(gad, win, NULL);
				} else
					GT_SetGadgetAttrsA(gad, win, NULL, abletag);
			} while ((gad= gad->NextGadget) != gads[GID_STOP].gad);
		}
	} while (!(flag&REQEXIT));

	return RETURN_OK;
}

/*==== clean up all at exit ====*/
void CleanUp_(void)
{	ClosePrinter_();
	if (!cli)
	{	if (appwin) RemoveAppWindow(appwin);
		if (appport)
		{	struct Message *msg;

			while (msg= GetMsg(appport)) ReplyMsg(msg);
			DeleteMsgPort(appport);
		}
		if (freq) FreeAslRequest((APTR)freq);
		if (win) CloseWindow(win);
		if (frimage.ImageData != fridata)
			FreeMem(&frimage.ImageData[-1], frimage.ImageData[-1]);
		if (pimage) FreeMem(pimage, sizeof(mouseimage));
		if (congad) FreeGadgets(congad);
		if (lock != ~0) CurrentDir(lock);
		if (vi) FreeVisualInfo(vi);
		if (pubscr) UnlockPubScreen(NULL, pubscr);

		if (AslBase) CloseLibrary(AslBase);
		if (GadToolsBase) CloseLibrary(GadToolsBase);
		if (GfxBase) CloseLibrary(GfxBase);
		if (WorkbenchBase) CloseLibrary(WorkbenchBase);
	}

	if (LocaleBase)
	{	CloseCatalog(cat);
		CloseLocale(locale);
		CloseLibrary(LocaleBase);
	}
	if (IconBase) CloseLibrary(IconBase);
	if (UtilityBase) CloseLibrary(UtilityBase);
	if (IntuitionBase) CloseLibrary(IntuitionBase);
}

/*==== main program ====*/
int main(int argc, TEXT *argv[])
/* ATTENTION: argv isn't set if called from CLI in Aztec-C */
{	static LONG argray[8]=	/* argument array */
 	{ 0, 0, 0, 0, 0, 0, 0, DEFGRAPH };
	BYTE num;				/* number of error message */
	UBYTE pitch;			/* self-explaining */

	/*- only allow AmigaOS 2.04 and up -*/
	if (SysBase->lib_Version < 37)
	{	BPTR file;
		if (argc) file= Output();
		else	  file= Open("CON:0/0/350/30/" PRGNAME, MODE_NEWFILE);
		Write(file, "Sorry, you'll need at least AmigaOS 2.04!\n", 42);
		Delay(100);
		Close(file);
		return RETURN_FAIL;
	}

	/*- open libraries -*/
	IntuitionBase= OpenLibraryQ_("intuition.library");
	if (LocaleBase= OpenLibrary("locale.library", 38))
	{	locale= OpenLocale(NULL);
		cat= OpenCatalogA(locale, PRGNAME ".catalog", NULL);
	}
	UtilityBase= OpenLibraryQ_("utility.library");

	/*- set cleanup routine -*/
	atexit(CleanUp_);

	/*- debugging feature: start GUI from CLI -*/
	#ifdef DEBUG
	if (argc)
	{	struct WBStartup wbstartup;
		struct WBArg wbarg;
		BPTR lock;
		int rc;

		wbstartup.sm_NumArgs= 1;
		wbstartup.sm_ArgList= &wbarg;
		lock= CurrentDir((BPTR)0);
		wbarg.wa_Lock= DupLock(lock);
		CurrentDir(lock);
		wbarg.wa_Name= PRGNAME;
		argv= (TEXT **)&wbstartup;
		argc= 0;
	}
	#endif

	/*- jump to GUI main routine -*/
	if (!argc) return guimain((struct WBStartup *)argv);

	/*- set CLI flag, print copyright message -*/
	cli= TRUE;
	Printf("*** " PRGNAME " *** " VERSION " - (c)1991 by Dieter Temme\n",
		(ULONG)NULL, (ULONG)NULL);

	/*- read parameters -*/
	FreeArgs(ReadArgs("File/A,Year/N,Quarter/N,"
		"PICA/S,ELITE/S,SEMI/S,FINE/S,IBMPC/S", (TEXT **)argray, NULL));

	/*- read pitch -*/
	{	UBYTE i;

		for (pitch= 0; pitch <= 3; pitch++) if (argray[pitch+3]) break;
		for (i= pitch+1; i <= 3; i++) if (argray[i+3])
		{	PrintError_(0, GetCatalogStrQ_(MSG_PITCHERR));
			return RETURN_FAIL;
		}
	}

	/*- read quarter -*/
	if (argray[2] && (*(ULONG *)argray[2] > 3))
	{	PrintError_(0, GetCatalogStrQ_(MSG_QUARTERR));
		return RETURN_FAIL;
	}

	/*- read filename and file and create filedays -*/
	if (!ReadFile_((TEXT *)argray[7])) return RETURN_FAIL;

	/*- read number of year and create yeardays -*/
	if (!CalcYear_(!argray[1], argray[1]))
	{	PrintError_(0, GetCatalogStrQ_(MSG_YEARERR));
		return RETURN_FAIL;
	}

	/*- print calendar and exit -*/
	return PrintCalendar_(argray[2]? (UBYTE)*(ULONG *)argray[2] : 0,
		pitch, argray[7]);
}

#ifdef _DCC
int wbmain(struct WBStartup *wbstart)
{	return main(0, (TEXT **)wbstart);
}
#endif
