/*****************************************************************
 * clock - A flexible, efficient digital clock with date and memory watching
 *
 * Copyright 1994 by Peter Schachte
 * 
 * This is an effecient titlebar digital clock, with date, day of week,
 * free chip/fast/total memory, largest free chip/fast/either memory
 * block, control over formatting, pen color for various parts of the
 * display, and refresh frequency.  The clock can also show the local
 * time in a different time zone.  Clock only updates parts of the
 * display that need updating, so CPU usage is kept low.  This program
 * should work under AmigaOS 1.2 and up.
 *
 * This code is based on Mike Meyer's clock program, though by now
 * very little of his code remains.  In fact, I've changed the
 * spirit of his program, which was quite small and simple; now
 * it's (relatively) large and complex, and probably has altogether
 * too many features.
 *
 * Nevertheless, here's Mike's original copyright notice:
 *================================================================
 * clock - a dumb, digital clock in the upper right-hand corner. Designed
 *	to be small, not flexible!
 *
 * Copyright (c) 1986, Mike Meyer
 *
 * Permission is hereby granted to distribute this program for any purposes
 * whatsoever, so long as this notice, including the above copyright, is
 * included with the distribution. Unlike other people, I don't care if you
 * make money off of this, so long as I get credit for having written it.
 *================================================================
 *
 * I likewise hereby grant the right to distribute this program for
 * any purposes whatsoever, as long as this notice, including my
 * copyright and Mike's, is included with the distribution.
 *
 *****************************************************************/

static char version[] =
	"$VER: " PROGNAME " " VERSION " (" __DATE__ ") © 1994 Peter Schachte";

/* There is one feature that I have #ifdef'ed out of this program.  If
 * CORRECTION is defined when this file is processed, this clock will
 * support the extra command line argument and tooltype "CORRECTION"
 * which should be set to the number of seconds until your system
 * clock gains or loses a second, positive if it loses a second or
 * negative if it gains a second.  If this option is specified, then
 * the clock will automatically correct the system time as you
 * specify.
 *
 * This feature is only useful if your system clock gains or loses
 * time consistently.  Mine doesn't, and I assume the same is true of
 * others, so I've left this feature out by default.  But I may change
 * my mind and put it back sometime.
 */


#include "progargs.h"
#include <stdlib.h>
#include <string.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <devices/timer.h>
#include <libraries/dos.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/rastport.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/graphics.h>

/* In case we're linked with cback.o */
char *__procname = PROGNAME;
long __BackGroundIO = 0;



/*****************************************************************
		       Various Useful Constants
*****************************************************************/

#define MINUTES_PER_DAY 1440
#define SECS_PER_DAY 86400
#define END_OF_TIME ((unsigned long) -1)

/* (approx?) sizes of workbench gadgets for OS >=2.0 and OS <=1.3 */
#define WIDTH_OF_20_CLOSE_GADGET 20
#define WIDTH_OF_20_DEPTH_GADGET 23
#define WIDTH_OF_13_CLOSE_GADGET 27
#define WIDTH_OF_13_DEPTH_GADGET 27

#define INTUITION_REV	1L

#define DEFAULT_FORMAT "%2Chip: %1%G%|%2Fast: %1%F%|%e %b %y%|%A%|%q:%M"


/* An individual part of the display may need any of the following
 * data in order to be displayed.  Some parts may need multiple parts,
 * so we use bit masks to represent them.
 */

#define NEED_CHIP	0
#define NEED_FAST	1
#define NEED_BIG_CHIP	2
#define NEED_BIG_FAST	3
#define NEED_TIME	4
#define MAX_NEED	4

#define NO_NEEDS_MASK		0
#define NEED_CHIP_MASK		(1<<NEED_CHIP)
#define NEED_FAST_MASK		(1<<NEED_FAST)
#define NEED_MEM_MASK	 	(NEED_CHIP_MASK|NEED_FAST_MASK)
#define NEED_BIG_CHIP_MASK	(1<<NEED_BIG_CHIP)
#define NEED_BIG_FAST_MASK	(1<<NEED_BIG_FAST)
#define NEED_BIG_MEM_MASK	(NEED_BIG_CHIP_MASK|NEED_BIG_FAST_MASK)
#define NEED_TIME_MASK		(1<<NEED_TIME)



/*****************************************************************
			Maintaining The Window

We use a lazy redisplay technology that only paints (about) as much of
the window as needs to be displayed.  To do this, we maintain a chain
of display_element structures, each representing one part of the
display.  In addition, we also maintain two separate chains of the
same structures, one for the time- and date-related fields
(time_chain) and one for the available memory fields (mem_chain).
When we wish to update the display, we traverse the time_chain and
mem_chain, updating only those fields.  When the whole window needs to
be updated, we traverse the whole chain drawing all strings and bars.

We are also a bit lazy with respect to updates:  each display_element
contains the value currently displayed in the window, and if we find
that field hasn't changed, we don't do any drawing.  For time/date
fields we take this one step further.  We organize the time_chain in
increasing order of "size" of the units (first seconds, then minutes,
etc.).  When we find an element that hasn't changed, we know that the
following elements won't have changed, either, and so we quit looking.
To make this work, we store the previous value in units since 1 Jan
1976; e.g., the minute will be stored as minutes since midnight, 1 Jan
1976.  The minute shown in the clock is just that number modulo 60.

One further thing: we want the display to look nice in the presence of
alphabetic month and weekday displays, which vary quite a bit in
length.  Of course, we don't want to resize the window every time the
day changes, and we don't even want the individual segments of the
display, separated by vertical bars, to change in size (the bars
should stay put).  This means that when the day changes, we have to
replan the display.  When we start up, we plan the display assuming
the greatest possible space needed by each field.  Then, each time the
day changes, we replan using the actual sizes of the fields, and
centering each display segment between the (unmoving) bars (or the
ends of the display).

*****************************************************************/

/* these are the various kinds of data that can be displayed */
enum display_kind {
    invalid, space, vbar, string,
    chip_mem, chip_mem_K, fast_mem, fast_mem_K, total_mem, total_mem_K,
    largest_chip, largest_chip_K, largest_fast, largest_fast_K,
    largest_mem, largest_mem_K,

    /* NB:  this group is carefully ordered so that if one of these
     * hasn't changed since the last update, then later ones haven't either
     */
    second, minute, hour12, hour12_0, hour24, hour24_0, am_pm,

    /* this group is similarly ordered */
    julian, day, day0, week_num_sun, week_num_mon, weekday_abbrev,
    weekday_full, weekday_num, monthnum0, monthnum, month_abbrev,
    month_full, year, year100
};

/* for each part of the display, we have one of these */
struct display_element {
    struct display_element *next;		/* all elements in order */
    struct display_element *next_of_kind;	/* elements of similar kind */
    short left;					/* pixel left of element */
    short max_width;				/* max width of element */
    short curr_width;				/* current width of elt */
    short pen;					/* pen to draw in */
    enum display_kind kind;			/* what is to be shown */
    union {
	struct string {				/* for strings: */
	    char *text;				/* the string to show */
	    int length;				/* char length of string */
	} string;
	struct data {				/* for most other kinds: */
	    int curr_value;			/* value currently shown */
	} data;
    };
};


/*****************************************************************
		 Command Line Args and Icon Tooltypes
*****************************************************************/

int background_pen = 0;			/* default background is pen 0 */
char *format_string = DEFAULT_FORMAT;
int interval_tenth_secs = 10;		/* default to one update per second */
int offset_minutes = 0;			/* default to no time zone offset */
int priority = 1;			/* default priority */
int win_left;				/* default left:  relative to screen */
					/* width and window width.  Don't */
					/* know default till we know OS ver */
int win_top = 0;			/* default top */
int horiz_padding = 5;			/* pixels of padding on either side */
					/* of clock */
#ifdef CORRECTION
int correction_ratio = 0;		/* add one second to the time */
					/* every correction_ratio */
					/* seconds; subtract if negative */
#endif

struct arg_descriptor arg_desc[] = {
    INT_ARG("BACKGROUND", background_pen),
    STRING_ARG("FORMAT", format_string),
    INT_ARG("INTERVAL", interval_tenth_secs),
    INT_ARG("OFFSET", offset_minutes),
    INT_ARG("PRIORITY", priority),
    INT_ARG("LEFT", win_left),
    INT_ARG("TOP", win_top),
    INT_ARG("PADDING", horiz_padding),
#ifdef CORRECTION
    INT_ARG("CORRECTION", correction_ratio)
#endif
};



/*****************************************************************
			      Constants
*****************************************************************/

static struct NewWindow	New_Window = {
	0, 0, 0, 0,		/* Fill these in later */
	255, 255,		/* Default pens */
	IDCMP_ACTIVEWINDOW | IDCMP_INACTIVEWINDOW |
	IDCMP_CHANGEWINDOW |
	IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW,
	WINDOWCLOSE | WINDOWDRAG | NOCAREREFRESH,
	(struct Gadget *) NULL,
	(struct Image *) NULL,
	(UBYTE *) NULL,		/* No title */
	(struct Screen *) NULL,
	(struct BitMap *) NULL,
	0, 0, 0, 0,
	WBENCHSCREEN
};

static char *str_am_pm[2] = {"AM", "PM"};
static char *str_weekday[7] = {"Sunday", "Monday", "Tuesday", "Wednesday",
			       "Thursday", "Friday", "Saturday"};
static char *str_month[12] = {"January", "February", "March", "April", "May",
			      "June", "July", "August", "September",
			      "October", "November", "December"};

/* I'm using tables for quite a few things here to keep the code
 * small.  It's fairly clean, too, in most places.  In just about all
 * of these cases, there are exceptions and special cases that are
 * treated separately in the code.
 */


/* display_kind for each upper case format letter */
static char upper_codes[26] = {
	  /* A */  (char) weekday_full,
	  /* B */  (char) month_full,
	  /* C */  (char) invalid, /* short for "%a %b %e %T %Z %Y" */
	  /* D */  (char) invalid, /* short for "%m/%d/%y" */
	  /* E */  (char) invalid,
	  /* F */  (char) fast_mem_K,
	  /* G */  (char) chip_mem_K,
	  /* H */  (char) hour24_0,
	  /* I */  (char) hour12_0,
	  /* J */  (char) monthnum,
	  /* K */  (char) total_mem,
	  /* L */  (char) invalid,
	  /* M */  (char) minute,
	  /* N */  (char) invalid,
	  /* O */  (char) largest_chip_K,
	  /* P */  (char) invalid,
	  /* Q */  (char) hour12,
	  /* R */  (char) invalid, /* short for "%H:%M" */
	  /* S */  (char) second,
	  /* T */  (char) invalid, /* short for "%H:%M:%S" */
	  /* U */  (char) week_num_sun,
	  /* V */  (char) largest_fast_K,
	  /* W */  (char) week_num_mon,
	  /* X */  (char) invalid, /* short for "%H:%M:%S" */
	  /* Y */  (char) year,
	  /* Z */  (char) largest_mem_K,
};

/* display_kind for each lower case format letter */
static char lower_codes[26] = {
	  /* a */  (char) weekday_abbrev,
	  /* b */  (char) month_abbrev,
	  /* c */  (char) invalid, /* short for "%a %b %d %H:%M:%S %Y" */
	  /* d */  (char) day0,
	  /* e */  (char) day,
	  /* f */  (char) fast_mem,
	  /* g */  (char) chip_mem,
	  /* h */  (char) month_abbrev,
	  /* i */  (char) invalid,
	  /* j */  (char) julian,
	  /* k */  (char) total_mem_K,
	  /* l */  (char) invalid,
	  /* m */  (char) monthnum0,
	  /* n */  (char) invalid, /* ignore newlines */
	  /* o */  (char) largest_chip,
	  /* p */  (char) am_pm,
	  /* q */  (char) hour24,
	  /* r */  (char) invalid, /* short for "%I:%M:%S %p" */
	  /* s */  (char) invalid,
	  /* t */  (char) invalid, /* ignore tabs */
	  /* u */  (char) invalid,
	  /* v */  (char) largest_fast,
	  /* w */  (char) weekday_num,
	  /* x */  (char) invalid, /* short for "%m/%d/%y" */
	  /* y */  (char) year100,
	  /* z */  (char) largest_mem,
};


static char hms_fmt[] = "%H:%M:%S";

/* cases where a format letter is an alias for a longer format */
static char aliases_C[] = "%a %b %e %T %Z %Y";
static char aliases_D[] = "%m/%d/%y";

char *aliases_RX[7] = {
	  /* R */  "%H:%M",
	  /* S */  NULL,
	  /* T */  hms_fmt,
	  /* U */  NULL,
	  /* V */  NULL,
	  /* W */  NULL,
	  /* X */  hms_fmt
};

static char aliases_c[] = "%a %b %d %H:%M:%S %Y";
static char aliases_r[] = "%I:%M:%S %p";
static char aliases_x[] = "%m/%d/%y";


/* the (max) number of digits in each format kind.  0 means there's a
 * special case for that kind.
 */
char field_digit_width[] = {
    0, /* invalid */
    0, /* space */
    0, /* vbar */
    0, /* string */
    7, /* chip_mem */
    4, /* chip_mem_K */
    8, /* fast_mem */
    5, /* fast_mem_K */
    8, /* total_mem */
    5, /* total_mem_K */
    7, /* largest_chip */
    4, /* largest_chip_K */
    8, /* largest_fast */
    5, /* largest_fast_K */
    8, /* largest_mem */
    5, /* largest_mem_K */
    2, /* second */
    2, /* minute */
    2, /* hour12 */
    2, /* hour12_0 */
    2, /* hour24 */
    2, /* hour24_0 */
    0, /* am_pm */
    3, /* julian */
    2, /* day */
    2, /* day0 */
    2, /* week_num_sun */
    2, /* week_num_mon */
    0, /* weekday_abbrev */
    0, /* weekday_full */
    1, /* weekday_num */
    2, /* monthnum0 */
    2, /* monthnum */
    0, /* month_abbrev */
    0, /* month_full */
    4, /* year */
    2  /* year100 */
};


/* Which data must be inquired of the OS for each display_kind */
char field_requirements[] = {
    NO_NEEDS_MASK,	/* invalid */
    NO_NEEDS_MASK,	/* space */
    NO_NEEDS_MASK,	/* vbar */
    NO_NEEDS_MASK,	/* string */
    NEED_CHIP_MASK,	/* chip_mem */
    NEED_CHIP_MASK,	/* chip_mem_K */
    NEED_FAST_MASK,	/* fast_mem */
    NEED_FAST_MASK,	/* fast_mem_K */
    NEED_MEM_MASK,	/* total_mem */
    NEED_MEM_MASK,	/* total_mem_K */
    NEED_BIG_CHIP_MASK,	/* largest_chip */
    NEED_BIG_CHIP_MASK,	/* largest_chip_K */
    NEED_BIG_FAST_MASK,	/* largest_fast */
    NEED_BIG_FAST_MASK,	/* largest_fast_K */
    NEED_BIG_MEM_MASK,	/* largest_mem */
    NEED_BIG_MEM_MASK,	/* largest_mem_K */
    NEED_TIME_MASK,	/* second */
    NEED_TIME_MASK,	/* minute */
    NEED_TIME_MASK,	/* hour12 */
    NEED_TIME_MASK,	/* hour12_0 */
    NEED_TIME_MASK,	/* hour24 */
    NEED_TIME_MASK,	/* hour24_0 */
    NEED_TIME_MASK,	/* am_pm */
    NEED_TIME_MASK,	/* julian */
    NEED_TIME_MASK,	/* day */
    NEED_TIME_MASK,	/* day0 */
    NEED_TIME_MASK,	/* week_num_sun */
    NEED_TIME_MASK,	/* week_num_mon */
    NEED_TIME_MASK,	/* weekday_abbrev */
    NEED_TIME_MASK,	/* weekday_full */
    NEED_TIME_MASK,	/* weekday_num */
    NEED_TIME_MASK,	/* monthnum0 */
    NEED_TIME_MASK,	/* monthnum */
    NEED_TIME_MASK,	/* month_abbrev */
    NEED_TIME_MASK,	/* month_full */
    NEED_TIME_MASK, 	/* year */
    NEED_TIME_MASK	/* year100 */
};






/*****************************************************************
		Data Shared By Procedures In This File
*****************************************************************/
/*
 * Some things that need to be shared with initialize() and done().
 */
static struct Window		*Window = NULL;
static struct RastPort		*win_rast;
static int			win_baseline;
static int			inleft, intop, inright, inbottom;
static struct timerequest	delay_req;
static struct timerequest	time_req;
static struct MsgPort		*delay_port = NULL;
static struct MsgPort		*time_port = NULL;
static struct Screen		screen_info;
static struct Screen		*my_screen;
static struct RastPort		*scrn_rast;
static int 			digit_width; /* pixel width of a digit */
static int			space_width; /* pixel width of space */

/* booleans indicating what info we need to query the OS for */
static int			need_time=0, need_chip=0,
				need_fast=0, need_big_chip=0, need_big_fast=0;

static int			new_os;	     /* running OS >= 2.0? */
int 				offset_secs; /* computed from offset_minutes */

#ifdef CORRECTION
int				correction = 1;	/* secs to add every */
						/* correction_interval */
#endif
	
static struct display_element	*win_content;   /* all display elements */
/* separate chains (though next_of_kind member) for memory and time displays */
static struct display_element 	*mem_chain = NULL;
static struct display_element	*time_chain = NULL;


struct GfxBase		*GfxBase;
struct IntuitionBase	*IntuitionBase;





/*****************************************************************
			      Prototypes
*****************************************************************/

static void plan_display(char *format, int *pixelpos, int *pen,
			 struct display_element ***tailptr);
static void add_display_space(int width, int *pixelpos,
			 struct display_element ***tailptr);
static void add_display_string(char *string, int len, int *pixelpos, int *pen,
			       struct display_element ***tailptr);
static void add_display_format(char code, int *pixelpos, int *pen,
			       struct display_element ***tailptr);
static struct display_element *add_element(int width, int *pixelpos, int pen,
					   enum display_kind kind,
					   struct display_element ***tailptr);
static void insert_in_kind(struct display_element *elt,
			   struct display_element **chainptr);
static int widest(char **ptr, int count, int charcount);
static void replan_display(int curr_day, int curr_wkday, int curr_month);
static void display_strings(void);
static void draw_bar(long x);
static void display_data(void);
static void draw_datum(struct display_element *ptr, int val,
		       char **string_array, int len);
static char *int_string(int n, int *minwidth);
static void initialize(void);
static void done(int code);

/* this is defined in compute_date.c: */

void compute_date(long n, int *y, int *m, int *d, int *w, int *jul);




/*****************************************************************
				 Code
*****************************************************************/

void main(int argc, char *argv[]) {
	int 			win_width;

	struct Layer		*MyLayer;
	struct ClipRect		*MyClip;
	struct IntuiMessage	*Msg;
	struct Task		*FindTask();
	struct TextFont		*font;
	unsigned long		timersig, windowsig, sigmask, sig;
	unsigned long		interval_micros;
	int			pen;
	struct display_element	**tail = &win_content;

	/* set up */
	initialize();
	handle_args(argc, argv, arg_desc);

	offset_secs = 60 * offset_minutes;
	pen = (background_pen==1 ? 0 : 1);
	inleft = (new_os ? WIDTH_OF_20_CLOSE_GADGET : WIDTH_OF_13_CLOSE_GADGET);
	win_width = inleft + horiz_padding;
	plan_display(format_string, &win_width, &pen, &tail);
	win_width += horiz_padding;

	New_Window.LeftEdge = (win_left<0 ?
			       screen_info.Width-win_width+win_left :
			       win_left);
	New_Window.Width = win_width;
	New_Window.Height = screen_info.Font->ta_YSize
	  + screen_info.WBorTop + 1;
	New_Window.TopEdge = (win_top<0 ?
			      screen_info.Height-New_Window.Height+win_top :
			      win_top);

	Window = (struct Window *) OpenWindow(&New_Window);
	if (Window == NULL) done(20);
	/* set screen title to version string without leading "$VER: " */
	SetWindowTitles(Window, NULL, (UBYTE*) &version[5]);

	/* We've opened the window, now store away some info about it */
	win_rast = Window->RPort;
	my_screen = Window->WScreen;

	font = OpenFont(screen_info.Font);
	win_baseline = font->tf_Baseline+screen_info.WBorTop;
	SetAPen(win_rast, 1L);
	SetBPen(win_rast, background_pen);
	SetDrMd(win_rast, JAM2);
	SetFont(win_rast, font);
	
	intop = screen_info.WBorTop - 1;
	inright = win_width - 2;
	inbottom = New_Window.Height - 2;
	MyLayer = win_rast->Layer;

	timersig = 1L << delay_port->mp_SigBit;
	windowsig = 1L << Window->UserPort->mp_SigBit;
	sigmask = timersig | windowsig | SIGBREAKF_CTRL_C;
	interval_micros = interval_tenth_secs*100000;

	{
	    int yr, month, dy, weekday, jul;

	    /* display clock the first time.  First get the current time */
	    DoIO((struct IORequest *) &time_req.tr_node);
	    compute_date((time_req.tr_time.tv_secs+offset_secs)/SECS_PER_DAY,
			 &yr, &month, &dy, &weekday, &jul);
	    replan_display(dy, weekday, month);
	    display_strings();
	    display_data();
	}


	display_strings();
	display_data();

	(void) SetTaskPri(FindTask(NULL), priority);

#ifdef CORRECTION
	if (correction_ratio != 0) {
	    if (correction_ratio < 0) {
		correction = -1;
		correction_ratio = -correction_ratio;
	    }
	    need_time = 1;
	}
#endif

	for (;;) {
	    int strings_need_display = 0;

	    sig = Wait(sigmask);
	    
	    if (sig & windowsig) {
		while (Msg=(struct IntuiMessage *)GetMsg(Window->UserPort)) {
		    switch (Msg->Class) {
		      case IDCMP_CLOSEWINDOW:
			ReplyMsg((struct Message *)Msg);
			done(0);
			break;
		      default:
			strings_need_display = 1;
			break;
		    }
		    ReplyMsg((struct Message *)Msg);
		}
	    }

	    if ((sig & timersig) && GetMsg(delay_port)) {
		delay_req.tr_time.tv_secs = 0;
		delay_req.tr_time.tv_micro = interval_micros;
		SendIO((struct IORequest *) &delay_req.tr_node);
	    }

	    if (sig & SIGBREAKF_CTRL_C) done(20);

	    /* no point doing anything if our screen is not in front */
	    if (my_screen != IntuitionBase->FirstScreen) continue;

	    /* check if somone is on top of us */
	    MyClip = MyLayer->ClipRect;
	    if (MyClip && (MyClip->Next || MyClip->lobs)) {
		WindowToFront(Window);	/* keep us on top */
		strings_need_display = 1;
	    }

	    if (strings_need_display) display_strings();
	    display_data();
	}
    }


/* Create a chain of display_elements describing the clock display,
 * and return the interior width of the clock window.  format is the
 * format string.  *pixelpos is the position at which to start the
 * next part of the display; on completion, it is the width of the
 * (inside of) the window.  Pen is the pen color in which to draw the
 * next part of the display.  tailptr points to a pointer to a (NULL)
 * pointer to the next display element, allowing us to keep adding to
 * the end of the chain.  On return, *tailptr will point to the new
 * NULL next pointer.
 */
static void plan_display(char *format, int *pixelpos, int *pen,
			 struct display_element ***tailptr)
    {
	char *ptr = format;

	do {
	    if (*ptr==' ') {
		int width = 0;

		do {width += space_width;} while (*++ptr==' ');
		add_display_space(width, pixelpos, tailptr);
	    } else if (*ptr == '%') {
		add_display_format(*++ptr, pixelpos, pen, tailptr);
		++ptr;
	    } else if (*ptr != '\0') {
		char *strstart = ptr;
		
		do {++ptr;} while (*ptr!='%' && *ptr!='\0');
		/* we'll handle spaces in next iteration, so trim them */
		while (*--ptr == ' ');		/* trim trailing blanks */
		++ptr;				/* point after string */
		add_display_string(strstart, ptr-strstart, pixelpos, pen,
				   tailptr);
	    }
	} while (*ptr != '\0');
    }


static void add_display_space(int width, int *pixelpos,
			 struct display_element ***tailptr)
    {
	add_element(width, pixelpos, 0, space, tailptr);
    }


/* create a display_element for the string starting at string and
 * whose length is len.  Add the width of this string to pixelpos.
 */
static void add_display_string(char *str, int len, int *pixelpos, int *pen,
			       struct display_element ***tailptr)
    {
	struct display_element *elt =
	  add_element(TextLength(scrn_rast,str,len), pixelpos, *pen, string,
		      tailptr);

	elt->string.text = str;
	elt->string.length = len;
    }


/* create a new display_element record or records for format character
 * code.  I.e., we've seen a '%' followed by code, and now want to
 * handle it.
 */
static void add_display_format(char code, int *pixelpos, int *pen,
			       struct display_element ***tailptr)
    {
	struct display_element *elt;
	enum display_kind kind = invalid;
	char *alias = NULL;
	int bits;
	int width;

	if (code >= 'A' && code <= 'Z') {
	    if (code == 'C') alias = aliases_C;
	    else if (code == 'D') alias = aliases_D;
	    else if (code >= 'R' && code <= 'X') alias = aliases_RX[code-'R'];
	    if (alias != NULL) {
		plan_display(alias, pixelpos, pen, tailptr);
		return;
	    } else {
		kind = upper_codes[code-'A'];
	    }
	} else if (code >= 'a' && code <= 'z') {
	    if (code == 'c') alias = aliases_c;
	    else if (code == 'r') alias = aliases_r;
	    else if (code == 'x') alias = aliases_x;
	    if (alias != NULL) {
		plan_display(alias, pixelpos, pen, tailptr);
		return;
	    } else {
		kind = lower_codes[code-'a'];
	    }
	} else if (code >= '0' && code <= '9') {
	    *pen = code-'0';
	    return;
	} else if (code == '|') {
	    kind = vbar;
	} else if (code == '%') {
	    add_display_string("%", 1, pixelpos, pen, tailptr);
	    return;
	}

	switch (kind) {
	  case weekday_abbrev: width = widest(str_weekday, 7, 3); break;
	  case weekday_full:   width = widest(str_weekday, 7, 0); break;
	  case month_abbrev:   width = widest(str_month, 12, 3); break;
	  case month_full:     width = widest(str_month, 12, 0); break;
	  case am_pm: 	       width = widest(str_am_pm, 2, 0); break;
	  case vbar:	       width = 2*horiz_padding + 1 + (new_os!=0); break;
	  default:	       width = digit_width *
				       field_digit_width[(int)kind]; break;
	}

	elt = add_element(width, pixelpos, *pen, kind, tailptr);
	elt->data.curr_value = -1;		/* universal bogus value */

	bits = field_requirements[(int)kind];

	if (bits & NEED_CHIP_MASK) need_chip = 1;
	if (bits & NEED_FAST_MASK) need_fast = 1;
	if (bits & NEED_BIG_CHIP_MASK) need_big_chip = 1;
	if (bits & NEED_BIG_FAST_MASK) need_big_fast = 1;

	if (bits & NEED_TIME_MASK) {
	    need_time = 1;
	    insert_in_kind(elt, &time_chain);
	} else if (bits != NO_NEEDS_MASK) {
	    /* must have been one of the memory items */
	    insert_in_kind(elt, &mem_chain);
	}
    }

/* Create a new generic display_element, fill it in and return it. */
static struct display_element *add_element(int width, int *pixelpos, int pen,
					   enum display_kind kind,
					   struct display_element ***tailptr)
    {
	struct display_element *new = AllocMem(sizeof(struct display_element),
					       MEMF_ANY);

	new->next = NULL;
	new->next_of_kind = NULL;
	new->left = *pixelpos;
	new->max_width = width;
	new->curr_width = width;
	*pixelpos += width;
	new->pen = pen;
	new->kind = kind;
	**tailptr = new;
	*tailptr = &(new->next);
	return new;
    }


/* Insert a display_element into an existing chain (through the
 * next_of_kind field) in sorted order.
 */
static void insert_in_kind(struct display_element *elt,
			   struct display_element **chainptr)
    {
	enum display_kind kind = elt->kind;
	struct display_element *ptr = *chainptr;

	while (ptr!=NULL && ptr->kind<kind) {
	    chainptr = &(ptr->next_of_kind);
	    ptr = *chainptr;
	}
	elt->next_of_kind = ptr;
	*chainptr = elt;
    }


/* Return the width of the widest of the count strings in the array
 * ptr of string pointers.  If charcount is >0, then consider only the
 * first charcount characters of each string.
 */
static int widest(char **ptr, int count, int charcount)
    {
	int max = 0;
	int width;

	for (; count>0; --count, ++ptr) {
	    width = TextLength(scrn_rast, *ptr, (charcount!=0 ? charcount :
						 strlen(*ptr)));
	    if (width > max) max = width;
	}
	return max;
    }



/* Walk down the full display list recentering data in their part of
 * the display.  A part of the display is delimited by vertical bars
 * (or the ends of the window).  For each part, we traverse it once,
 * adding up the actual sizes of everything, and then traverse it
 * again placing everything where it should go.
 */

static void replan_display(int curr_day, int curr_wkday, int curr_month)
    {
	struct display_element *ptr = win_content;
	struct display_element *this_part;
	int left = inleft + horiz_padding;

	while(ptr != NULL) {
	    int changed = 0;			/* has this part changed? */
	    int total_width = 0;		/* width of this part */
	    int total_max_width = 0;		/* max width of this part */

	    this_part = ptr;
	    for (; ptr!=NULL && ptr->kind!=vbar; ptr=ptr->next) {
		int width;

		switch (ptr->kind) {
		  case day:
		    /* remember:  day is 0 origin, so '9' is the 10th */
		    width = digit_width * (curr_day<9 ? 1 : 2);
		    break;
		  case weekday_abbrev:
		    width = TextLength(Window->RPort,str_weekday[curr_wkday],3);
		    break;
		  case weekday_full:
		    width = TextLength(Window->RPort,str_weekday[curr_wkday],
				       strlen(str_weekday[curr_wkday]));
		    break;
		  case month_abbrev:
		    width = TextLength(Window->RPort,str_month[curr_month],3);
		    break;
		  case month_full:
		    width = TextLength(Window->RPort,str_month[curr_month],
				       strlen(str_month[curr_month]));
		    break;
		  default:
		    width = ptr->max_width;	/* width never changes */
		    break;
		}
		if (width != ptr->curr_width) {
		    ptr->curr_width = width;
		    changed = 1;
		}
		total_width += width;
		total_max_width += ptr->max_width;
	    }
	    if (changed) {
		left += (total_max_width-total_width)/2;
		for (ptr=this_part;
		     ptr!=NULL && ptr->kind!=vbar;
		     ptr=ptr->next) {
		    ptr->left = left;
		    left += ptr->curr_width;
		}
	    }
	    if (ptr != NULL) {
		left = ptr->left + ptr->max_width;
		ptr = ptr->next;
	    }
	}
    }




/* Clear the window and display all strings and bars.  Also
 * invalidate all previous values for all update records, since after
 * this no values will be up-to-date.
 */
static void display_strings(void)
    {
	struct display_element *ptr;

	SetAPen(win_rast, background_pen);
	RectFill(win_rast, inleft, intop, inright, inbottom);
	for (ptr=win_content; ptr!=NULL; ptr=ptr->next) {
	    switch(ptr->kind) {
	      case invalid: case space:
		break;				/* nothing to do */
	      case vbar:
		draw_bar(ptr->left+horiz_padding);
		break;
	      case string:
		Move(Window->RPort, ptr->left, win_baseline);
		SetAPen(win_rast, ptr->pen);
		Text(Window->RPort, ptr->string.text, ptr->string.length);
		break;
	      default:
		ptr->data.curr_value = -1;	/* force later redisplay */
	    }
	}
    }


/* draw a top-to-bottom bar in window at position x */
static void draw_bar(long x)
    {
	long bottom = Window->BorderTop-2;
	
	if (new_os) {
	    /* a nice 3d bar */
	    SetAPen(win_rast, 1L);
	    Move(Window->RPort, x, 1L);
	    Draw(Window->RPort, x, bottom);
	    SetAPen(win_rast, 2L);
	    Move(Window->RPort, x+1, 1L);
	    Draw(Window->RPort, x+1, bottom);
	} else {
	    /* boring old single line for pre-2.0 systems*/
	    SetAPen(win_rast, 1L);
	    Move(Window->RPort, x+1, 1L);
	    Draw(Window->RPort, x+1, bottom);
	}
    }
    



/* display all data in clock that have changed since last display. */
static void display_data(void)
    {
	struct display_element *ptr;
	unsigned long secs;
	int chip_free, fast_free, big_chip_free, big_fast_free;
	/* yr < 0 means we haven't computed yr, month, dy, weekday yet */
	int yr=-1, month, dy, weekday, jul;

	if (need_time) {
#ifdef CORRECTION
	    static unsigned long next_correction = 0;
#endif
	    DoIO((struct IORequest *) &time_req.tr_node);
	    secs = time_req.tr_time.tv_secs + offset_secs;
#ifdef CORRECTION
	    if (secs >= next_correction) {
		if (next_correction == 0) {
		    /* need to set up next_correction */
		    next_correction = (correction_ratio==0 ?
				       END_OF_TIME :
				       secs + correction_ratio);
		} else {
		    /* really need to make a one second correction now */
		    time_req.tr_node.io_Command = TR_SETSYSTIME;
		    time_req.tr_time.tv_secs += correction;
		    DoIO((struct IORequest *) &time_req.tr_node);
		    time_req.tr_node.io_Command = TR_GETSYSTIME;
		    next_correction += correction_ratio + correction;
		}
	    }
#endif
	    for (ptr=time_chain; ptr!=NULL; ptr=ptr->next_of_kind) {
		int is_date = 0;
		int modulus;
		unsigned long val;		/* the numeric value to show */
		int len = 0;			/* minimum int width, max string
						 * width (0 means unlimited)
						 */
		char **string_array = NULL;	/* the array whose val-th elt
						 * to show, or NULL, to show
						 * val as a number
						 */
		
		switch (ptr->kind) {
		  case   hour24:  val=secs/3600; modulus=24;        break;
		  case   hour12:  val=secs/3600; modulus=12;        break;
		  case hour24_0:  val=secs/3600; modulus=24; len=2; break;
		  case hour12_0:  val=secs/3600; modulus=12; len=2; break;
		  case   minute:  val=secs/60;   modulus=60; len=2; break;
		  case   second:  val=secs;      modulus=60; len=2; break;
		  case    am_pm:  val=secs/43200;modulus= 2;
				  string_array=str_am_pm;           break;
		  default      :  val=secs/SECS_PER_DAY; is_date=1; break;
		}
		/* chain is sorted, so when we get to an element that hasn't
		 * changed, we know nothing later will have changed.
		 */
		if (val == ptr->data.curr_value) break;

		if (is_date) {
		    if (yr < 0) {
			/* we haven't figured out the date yet, so do it now. */
			compute_date(val, &yr, &month, &dy, &weekday, &jul);
			if (ptr->data.curr_value != -1) {
			    replan_display(dy, weekday, month);
			    display_strings();
			    display_data();
			    return;
			}
		    }
		    ptr->data.curr_value = val;
		    switch (ptr->kind) {
		      case           year:  val=yr; break;
		      case        year100:  val=yr%100; len=2; break;
		      case      monthnum0:  val=month+1; len=2; break;
		      case       monthnum:  val=month+1; break;
		      case   month_abbrev:  val=month; len=3;
					    string_array=str_month; break;
		      case     month_full:  val=month;
					    string_array=str_month; break;
		      case   week_num_sun:  val=(14+jul-((7+jul-weekday)%7))/7;
					    break;
		      case   week_num_mon:  val=(14+jul-((8+jul-weekday)%7))/7;
					    break;
		      case weekday_abbrev:  val=weekday; len=3;
					    string_array=str_weekday; break;
		      case   weekday_full:  val=weekday;
					    string_array=str_weekday; break;
		      case    weekday_num:  val=1+weekday; break;
		      case           day0:  val=dy+1; len=2; break;
		      case            day:  val=dy+1; break;
		      case         julian:  val=jul+1; break;
		    }
		} else {
		    val %= modulus;
		    ptr->data.curr_value = val;
		}
		draw_datum(ptr, val, string_array, len);
	    }
	}
	if (need_chip) chip_free = AvailMem(MEMF_CHIP);
	if (need_fast) fast_free = AvailMem(MEMF_FAST);
	if (need_big_chip) big_chip_free = AvailMem(MEMF_CHIP|MEMF_LARGEST);
	if (need_big_fast) big_fast_free = AvailMem(MEMF_FAST|MEMF_LARGEST);

	for (ptr=mem_chain; ptr!=NULL; ptr=ptr->next_of_kind) {
	    int val;				/* the numeric value to show */

	    switch (ptr->kind) {
	      case       chip_mem:  val=chip_free; break;
	      case     chip_mem_K:  val=(chip_free+512)>>10; break;
	      case       fast_mem:  val=fast_free; break;
	      case     fast_mem_K:  val=(fast_free+512)>>10; break;
	      case      total_mem:  val=chip_free+fast_free; break;
	      case    total_mem_K:  val=(chip_free+fast_free+512)>>10; break;
	      case   largest_chip:  val=big_chip_free; break;
	      case largest_chip_K:  val=(big_chip_free+512)>>10; break;
	      case   largest_fast:  val=big_fast_free; break;
	      case largest_fast_K:  val=(big_fast_free+512)>>10; break;
	      case    largest_mem:  val=big_chip_free+big_fast_free; break;
	      case  largest_mem_K:  val=(big_chip_free+big_fast_free+512)>>10;
				    break;
	    }
	    draw_datum(ptr, val, NULL, 0);
	}
    }


/* display a single datum in the window.  *ptr describes where the
 * datum should be displayed, val is the value to display.  If
 * string_array is non-NULL, then show its val-th element as a string,
 * rather than showing a number.  If len is not 0, show only the first
 * len characters of the specified string, or show exactly len digits,
 * 0-filled if necessary, of val if string_array is NULL.
 */
static void draw_datum(struct display_element *ptr, int val,
		       char **string_array, int len)
    {
	char *todraw;
	int pixelleft;

	if (string_array != NULL) {
	    todraw = string_array[val];
	    if (len==0) len=strlen(todraw);
	} else {
	    todraw = int_string(val, &len);
	}
	pixelleft = ptr->left + ptr->curr_width -
	  TextLength(Window->RPort,todraw,len);
	if (ptr->left < pixelleft) {
	    SetAPen(win_rast, background_pen);
	    RectFill(win_rast, ptr->left, intop, pixelleft-1, inbottom);
	}
	Move(Window->RPort, pixelleft, win_baseline);
	SetAPen(win_rast, ptr->pen);
	Text(Window->RPort, todraw, len);
    }


/* cheap int->string conversion.  Doesn't handle negative numbers, and
 * always fills to the specified minimum width with 0's.  It returns a
 * pointer to a static buffer, so one call's value is destroyed by the
 * next call to this function.  But that's all we need for this clock.
 * Note that on completion we set *minwidth to the actual width.
 */
#define INT_STRING_BUFF_LEN 8
static char *int_string(int n, int *minwidth)
    {
	static char buffer[INT_STRING_BUFF_LEN];
	char *ptr = &buffer[INT_STRING_BUFF_LEN-1];
	int len=0, mw=*minwidth;

	*ptr = '\0';
	do {
	    *--ptr = '0'+n%10;
	    n/=10;
	} while ((++len)<mw || n > 0);
	*minwidth = len;
	return ptr;
    }



/* open libraries and initialize the timer device. */
static void initialize(void)
    {
	if ((IntuitionBase = (struct IntuitionBase *)
	     OpenLibrary("intuition.library", INTUITION_REV)) == NULL)
	  done(20);

	if ((GfxBase = (struct GfxBase *)
	     OpenLibrary("graphics.library", 0L)) == NULL)
	  done(20);

	/* set up delay clock, which will wake us every second */
	if ((delay_port = CreatePort(NULL, 0L)) == NULL)
	  done(20);
	if (OpenDevice(TIMERNAME, UNIT_VBLANK,
		       (struct IORequest *) &delay_req, 0L) != NULL)
	  done(20);
	delay_req.tr_node.io_Message.mn_ReplyPort = delay_port;
	delay_req.tr_node.io_Command = TR_ADDREQUEST;
	delay_req.tr_node.io_Flags = 0;
	delay_req.tr_node.io_Error = 0;
	delay_req.tr_time.tv_secs = 0;
	delay_req.tr_time.tv_micro = 1000000;
	/* send first request, to get us started */
	SendIO((struct IORequest *) &delay_req.tr_node);

	/* set up time clock, which will get us system time */
	if ((time_port = CreatePort(NULL, 0L)) == NULL)
	  done(20);
	if (OpenDevice(TIMERNAME, UNIT_VBLANK /* does this matter? */,
		       (struct IORequest *) &time_req, 0L) != NULL)
	  done(20);
	time_req.tr_node.io_Message.mn_ReplyPort = time_port;
	time_req.tr_node.io_Command = TR_GETSYSTIME;
	time_req.tr_node.io_Flags = 0;
	time_req.tr_node.io_Error = 0;

	if (!GetScreenData(&screen_info, sizeof(screen_info), WBENCHSCREEN,
			   NULL))
	  done(20);
	scrn_rast = &screen_info.RastPort;
	digit_width = TextLength(scrn_rast, "9", 1);
	space_width = TextLength(scrn_rast, " ", 1);
	new_os = ((struct Library *)IntuitionBase)->lib_Version >= 37;
	/* now we can set the default window left */
	win_left = -2*(new_os ? WIDTH_OF_20_DEPTH_GADGET
			: WIDTH_OF_13_DEPTH_GADGET);
    }	

/* just clean up that which is open, and then leave. */
static void done(int code)
    {
	handle_args_finish();		/* cleanup arg handling */
	if (Window) CloseWindow(Window);
	if (delay_req.tr_node.io_Message.mn_ReplyPort) {
	    AbortIO((struct IORequest *) &delay_req.tr_node);
	    CloseDevice((struct IORequest *) &delay_req);
	}
	if (delay_port) DeletePort(delay_port);
	if (time_req.tr_node.io_Message.mn_ReplyPort)
	    CloseDevice((struct IORequest *) &time_req);
	if (time_port) DeletePort(time_port);
	if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
	if (GfxBase) CloseLibrary((struct Library *)GfxBase);

	/* free allocated memory */
	while (win_content != NULL) {
	    struct display_element *next = win_content->next;
	    FreeMem(win_content, sizeof(*win_content));
	    win_content = next;
	}
	/* __exit() doesn't try to close files, so it makes a much
	 * smaller executable than using exit().  This is the correct
	 * thing to do, since we compile with the nostdio option.
	 */
	__exit((long)code);
    }

