/*
	ear.C

	Employee Absence Record

	Copyright (C) 1993, Geoff Friesen B.Sc.
	All rights reserved.

	Borland C++ 3.1

	Maintain a database of employee names and their absence tallys
	for each quarter of the year on a yearly basis.  The database
	is maintained via DBASE III+ compatible data files.  Each year
	is given its own file.  A maximum of 61 files that cover years
	1990-2050 inclusive can be created.

	Each data file is divided into records and each record is div-
	ided into the following fields.

	"NAME       "  C  NAMELEN  0	Employee name
	"PHONE      "  C  PHONLEN  0	Employee phone number
	"QT1DA      "  N  2        0	1st qtr days absent
	"QT2DA      "  N  2        0	2nd qtr days absent
	"QT3DA      "  N  2        0	3rd qtr days absent
	"QT4DA      "  N  2        0	4th qtr days absent

	The configuration file contains settings for choosing printer
	drivers and printer ports.  EMP.CFG contains these settings.

	Virtual Printer Codes
	---------------------

	Virtual printer codes are used to provide printer independence
	allowing EAR to work with a variety of printers.  EAR sends a
	virtual code to the printer manager which translates this code
	into suitable codes for the printer (identified by the current
	printer driver).  The following virtual codes are used and '#'
	is part of this code.

	ESC A # - printer setup
	ESC B # - printer cleanup
	ESC C # - start underlining
	ESC D # - stop underlining

	No spaces are allowed between virtual codes.  The decimal num-
	ber 27 is represented by ESC.

	Currently, there are two printer drivers.

	DMP130.DRV  - Tandy DMP130 printer
	HPLJIII.DRV - Hewlett Packard LaserJet III printer
*/

#if !defined(__LARGE__)
#error Large Memory Model Expected
#endif

#include <dir.H>
#include <dos.H>
#include <io.H>
#include <process.H>
#include <stdio.H>
#include <stdlib.H>
#include <string.H>

#include "ceh.H"
#include "classcvt.H"
#include "edit.H"
#include "kbd.H"
#include "macros.H"
#include "msg.H"
#include "print.H"
#include "str.H"
#include "video.H"

/* =================================================================
   Configuration Data Structure
   ================================================================= */

char *drivers [MAXDRV+1];

char *ports [] =
{
   "LPT1",
   "LPT2",
   "LPT3",
   ""
};

struct _config
{
   char driver [9];
   int portno;
}
config =
{
   "DMP130",
   LPT1
};

/* =================================================================
   Data Entry Form Constants

   1. Form widths and heights (borders not included)
   ================================================================= */

#define CFORMW          24
#define CFORMH          2

#define EFORMW          (NAMELEN+6)
#define EFORMH          6

#define	PFORMW		12
#define	PFORMH		1

#define	SFORMW		EFORMW
#define	SFORMH		(EFORMH+1)

/* =================================================================
   Employee Data Structure
   ================================================================= */

#define	MAXEMP	256
#define	NAMELEN	32
#define	PHONLEN	7
#define	HDRSIZ	(7*32+1)
#define	RECSIZ	(1+NAMELEN+PHONLEN+2+2+2+2)
#define	FD1OFF	1
#define	FD2OFF	(FD1OFF+NAMELEN)
#define	FD3OFF	(FD2OFF+PHONLEN)
#define	FD4OFF	(FD3OFF+2)
#define	FD5OFF	(FD4OFF+2)
#define	FD6OFF	(FD5OFF+2)

struct _employee
{
   char name [NAMELEN+1];	/* employee name */
   char phon [PHONLEN+1];	/* home phone number */
   int q1da;			/* quarter 1 days absent */
   int q2da;			/* ditto */
   int q3da;			/* ditto */
   int q4da;			/* ditto */
}
employee [MAXEMP];

int curemp;			/* current employee (LIGHTBAR selected) */
int numemp = 0;			/* current number of employees */
int modified = 0;		/* delete/edit/insert modifications flag */
int file_year = 0;		/* current .dbf file */
int currow;			/* current lightbar row within list box */

#define	LB_WIDTH	NAMELEN
#define	LB_HEIGHT	(rows-6)
#define	LB_LEFT		center(80, LB_WIDTH+2)
#define	LB_TOP		3

#define	RESET		0
#define	UPDATE		1
#define	DELETE		2

#define	CTX_EDITCFG	CTX_USR
#define	CTX_EDITABS	(CTX_USR+1)
#define	CTX_INSABS	(CTX_USR+2)
#define	CTX_LOADYR	(CTX_USR+3)
#define	CTX_MAIN	(CTX_USR+4)
#define	CTX_PRINQTR	(CTX_USR+5)
#define	CTX_SAVEYR	(CTX_USR+6)
#define	CTX_STAT	(CTX_USR+7)

int bw [] =
{
   (LIGHTGRAY << 4) | BLACK,	/* main screen */
   WHITE,			/* borders */
   LIGHTGRAY << 4,		/* lightbar */
   LIGHTGRAY << 4,		/* data entry field */
   WHITE,			/* titles */
   LIGHTGRAY			/* text */
};

int color [] =
{
   (BLUE << 4) | WHITE,		/* main screen */
   WHITE,			/* borders */
   (BLUE << 4) | WHITE,		/* lightbar */
   (BLUE << 4) | WHITE,		/* data entry field */
   GREEN,			/* titles */
   CYAN				/* text */
};

#define	NATTR		(sizeof(bw)/sizeof(bw [0]))

int attr [NATTR];

int rows = 25;			/* maximum screen rows */

int	alphabetize	(void);
void	config_edit	(void);
void	config_load	(void);
void	config_save	(void);
void	delete_employee	(void);
void	down		(int nlines);
void	ear_help	(int context);
int	editi		(int *da, int width, int operation);
void	edit_employee	(void);
void	end		(void);
int	fexist		(char *filespec);
void	home		(void);
void	insert_employee	(void);
void	load_employees	(void);
void	message		(char *m);
void	pgdn		(void);
void	pgup		(void);
int 	print_header	(int pageno, int quarter);
void	print_report	(void);
int	prompt_absence	(char *title, struct _employee *e);
int	prompt_year	(char *title, int *year);
void	refresh		(int cmd);
void	reset_employees	(void);
void	save_employees	(void);
void	statistics	(void);
void	up		(int nlines);

void main (int argc, char **argv)
{
   int i, key, vmode;

   if (_osmajor < 3 || _osmajor == 3 && _osminor < 30)
   {
       fprintf (stderr, "ear: invalid DOS version number - needs 3.30+\n");
       exit (ERROR);
   }

   if (argc > 2 ||
       argc == 2 && (rows = atoi (argv [1])) != 43 && rows != 50)
   {
       fprintf (stderr, "ear: invalid command line\n");
       exit (ERROR);
   }

   atexit (v_cleanup);
   vmode = v_setup (rows);
   rows = v_getrows ();

   for (i = 0; i < NATTR; i++)
	attr [i] = (vmode == C80) ? color [i] : bw [i];

   v_cursor (CHIDE);
   v_setattr (attr [0]);
   v_clear(1, 1, 80, rows);

   v_cputs ("Employee Absence Record v1.1");

   v_gotoxy (71, 1);
   v_cputs ("FILE: NONE");

   v_gotoxy (1, 2);
   v_putch ('Ä', 80);
   v_gotoxy (1, rows-1);
   v_putch ('Ä', 80);

   v_gotoxy (1, rows);
   v_cputs ("F1-HELP");
   v_gotoxy (73, rows);
   v_cputs ("F10-EXIT");

   v_setattr (attr [1]);
   v_border (DOUBLE_LINE, LB_LEFT, LB_TOP, LB_WIDTH+2, LB_HEIGHT+2);
   v_shadow (LB_LEFT, LB_TOP, LB_WIDTH+2, LB_HEIGHT+2);
   refresh (RESET);

   installceh ();

   if ((i = p_scandrv (drivers)) < 0)
   {
       message ((i == -1) ? "NO PRINTER DRIVERS FOUND DURING SCAN" :
		"TOO MANY PRINTER DRIVERS FOUND DURING SCAN");
       exit (ERROR);
   }

   if (fexist ("ear.cfg"))
       config_load ();

   for (i = 0; drivers [i]; i++)
	if (!strcmp (drivers [i], config.driver))
	    break;

   if (!drivers [i])
   {
       message ("CONFIGURATION DRIVER NOT FOUND DURING SCAN");
       exit (ERROR);
   }

   if (p_loaddrv (config.driver) < 0)
   {
       message ("UNABLE TO LOAD PRINTER DRIVER");
       exit (ERROR);
   }

   k_setctxfunc (ear_help);

   do
   {
      k_setctx (CTX_MAIN);

      if ((key = upper (k_fetch ())) == F10)
	  break;

      switch (key)
      {
	 case INS : insert_employee ();
		    continue;

	 case 'C' : config_edit ();
		    continue;

	 case 'L' : if (modified && msg (MSG_YN, "SAVE CHANGES?",
					 attr [1], attr [5]) == 'Y')
			save_employees ();

		    load_employees ();
		    continue;
      }

      if (numemp)
	  switch (key)
	  {
	     case DEL  : delete_employee ();
			 continue;

	     case 'P'  : print_report ();
			 continue;

	     case 'R'  : reset_employees ();
			 continue;

	     case RET  : edit_employee ();
			 continue;

	     case 'S'  : save_employees ();
			 continue;

	     case '?'  : statistics ();
			 continue;
	  }

      if (numemp > 1)
	  switch (key)
	  {
	     case 'A'  : if (alphabetize () != ESC)
			     refresh (RESET);
			 break;

	     case DOWN : down (1);
			 break;

	     case END  : end ();
			 break;

	     case HOME : home ();
			 break;

	     case PGDN : pgdn ();
			 break;

	     case PGUP : pgup ();
			 break;

	     case UP   : up (1);
	  }
   }
   while (1);

   if (modified &&
       msg (MSG_YN, "SAVE CHANGES?", attr [1], attr [5]) == 'Y')
       save_employees ();

   exit (OK);
}

/* =================================================================
   int alphabetize (void);

   Entry: none

   Exit : ESC if user aborts else !ESC
   ================================================================= */

int alphabetize (void)
{
   int i, pass;

   if (msg (MSG_YN, "ARE YOU SURE?", attr [1], attr [5]) == 'N')
       return ESC;

   msg_flash (MSG_FON, "Sorting employees by name ...");

   for (pass = 0; pass <= numemp-2; pass++)
	for (i = 0; i <= numemp-pass-2; i++)
	     if (strcmp (employee [i].name, employee [i+1].name) > 0)
		 memswap (&employee [i], &employee [i+1],
			  sizeof(struct _employee));

   msg_flash (MSG_FOFF);

   modified = 1;
   return !ESC;
}

/* =================================================================
   void config_edit (void);

   Entry: none

   Exit : none
   ================================================================= */

void config_edit (void)
{
   struct _config temp;
   int _driver, fieldno = 0, key, tdriver, x, y;
   char *screen_buffer, *title = "CONFIGURATION";

   for (_driver = 0; drivers [_driver]; _driver++)
	if (!strcmp (drivers [_driver], config.driver))
	    break;

   x = center(80, CFORMW+2);
   y = center(rows, CFORMH+2);

   if ((screen_buffer = (char *) malloc ((CFORMW+3)*(CFORMH+3)*2)) == NULL)
   {
       message ("INSUFFICIENT MEMORY");
       return;
   }

   v_screen (SCREEN_SAVE, x, y, CFORMW+3, CFORMH+3, screen_buffer);

   v_setattr (attr [1]);
   v_border (DOUBLE_LINE, x, y, CFORMW+2, CFORMH+2);
   v_shadow (x, y, CFORMW+2, CFORMH+2);
   v_setattr (attr [4]);
   v_gotoxy (x+center(CFORMW, strlen (title)), y);
   v_cputs (title);
   v_setattr (attr [5]);
   v_clear(x+1, y+1, CFORMW, CFORMH);

   v_gotoxy (x+1, y+1);
   v_cputs ("Printer driver:");
   v_gotoxy (x+1, y+2);
   v_cputs ("Printer port:");

   v_setattr (attr [3]);
   v_gotoxy (x+17, y+1);
   (void) _editl (drivers, &_driver, EDIT_DISP);
   v_setattr (attr [5]);
   v_gotoxy (x+15, y+2);
   (void) _editl (ports, &config.portno, EDIT_DISP);

   temp = config;

   do
   {
      v_setattr (attr [3]);

      switch (fieldno)
      {
	 case 0 : v_gotoxy (x+17, y+1);
		  k_setctx (CTX_EDITCFG);
		  tdriver = _driver;
		  key = _editl (drivers, &_driver, EDIT_FETCH);
		  if (key != ESC)
		      if (p_loaddrv (drivers [_driver]) < 0)
		      {
			  message ("UNABLE TO LOAD PRINTER DRIVER");
			  _driver = tdriver;
		      }
		      else
			  strcpy (temp.driver, drivers [_driver]);
		  v_setattr (attr [5]);
		  v_gotoxy (x+17, y+1);
		  (void) _editl (drivers, &_driver, EDIT_DISP);
		  break;

	 case 1 : v_gotoxy (x+15, y+2);
		  k_setctx (CTX_EDITCFG);
		  key = _editl (ports, &temp.portno, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+15, y+2);
		  (void) _editl (ports, &temp.portno, EDIT_DISP);
      }

      if (key == ESC || (key == RET && fieldno == 1))
	  break;

      if (key == UP)
	  if (--fieldno < 0)
	      fieldno = 1;

      if (key == DOWN || key == RET)
	  if (++fieldno > 1)
	      fieldno = 0;
   }
   while (1);

   v_screen (SCREEN_RESTORE, x, y, CFORMW+3, CFORMH+3, screen_buffer);

   if (key == RET)
   {
       config = temp;
       config_save ();
   }
}

/* =================================================================
   void config_load (void);

   Entry: none

   Exit : none
   ================================================================= */

void config_load (void)
{
   FILE *f;
   struct _config temp;

   if ((f = fopen ("ear.cfg", "rb")) == NULL)
   {
       message ("UNABLE TO OPEN CONFIGURATION FILE");
       return;
   }

   if (fread (&temp, sizeof(struct _config), 1, f) != 1)
       message ("UNABLE TO LOAD CONFIGURATION");
   else
       config = temp;

   (void) fclose (f);
}

/* =================================================================
   void config_save (void);

   Entry: none

   Exit : none
   ================================================================= */

void config_save (void)
{
   FILE *f;

   if ((f = fopen ("ear.cfg", "wb")) == NULL)
   {
       message ("UNABLE TO CREATE CONFIGURATION FILE");
       return;
   }

   if (fwrite (&config, sizeof(struct _config), 1, f) != 1)
       message ("UNABLE TO SAVE CONFIGURATION");

   (void) fclose (f);
}

/* =================================================================
   void delete_employee (void);

   Entry: none

   Exit : none
   ================================================================= */

void delete_employee (void)
{
   int i;

   if (msg (MSG_YN, "ARE YOU SURE?", attr [1], attr [5]) == 'N')
       return;

   for (i = curemp+1; i < numemp; i++)
	employee [i-1] = employee [i];

   numemp--;
   refresh (DELETE);
   modified = 1;
}

/* =================================================================
   void down (int nlines);

   Entry: nlines - number of lines to scroll down

   Exit : none
   ================================================================= */

void down (int nlines)
{
   int i, dnlines;

   if (curemp == numemp-1)
       return;

   v_setattr (attr [5]);
   v_gotoxy (LB_LEFT+1, LB_TOP+currow);
   v_cputs (employee [curemp].name);

   dnlines = min(numemp-1-curemp, nlines);

   for (i = 1; i <= dnlines; i++)
   {
	if (currow != LB_HEIGHT)
	    currow++;
	else
	    v_scroll (LB_LEFT+1, LB_TOP+1, LB_WIDTH, LB_HEIGHT, SCROLL_UP,
		      1, attr [5]);

	if (i == dnlines)
	    v_setattr (attr [2]);

	v_gotoxy (LB_LEFT+1, LB_TOP+currow);
	v_cputs (employee [++curemp].name);
   }
}

/* =================================================================
   void ear_help (int context);

   Entry: context - current help context

   Exit : none
   ================================================================= */

void ear_help (int context)
{
   static char buffer [4000], helphelp [160];
   static int cshape, height, left, oldattr, oldx, oldy, top, width;

   if (context == CTX_MSG)	/* no context help for message boxes */
       return;

   switch (context)
   {
      case CTX_CEH     : width = 48;
			 height = 20;
			 break;

      case CTX_EDITCFG :
      case CTX_EDITABS :
      case CTX_INSABS  : width = 51;
			 height = 23;
			 break;

      case CTX_LOADYR  : width = 47;
			 height = 23;
			 break;

      case CTX_MAIN    : width = 53;
			 if (!numemp)
			     height = 9;
			 else
			 if (numemp == 1)
			     height = 15;
			 else
			     height = 22;
			 break;

      case CTX_PRINQTR : width = 45;
			 height = 17;
			 break;

      case CTX_SAVEYR  : width = 47;
			 height = 23;
			 break;

      case CTX_STAT    : width = 39;
			 height = 6;
   }

   left = center(80, width);
   top = center(rows, height);

   v_cursor (CSAVE, &cshape);
   oldattr = v_getattr ();
   oldx = v_wherex ();
   oldy = v_wherey ();

   v_cursor (CHIDE);
   v_screen (SCREEN_SAVE, left, top, width+1, height+1, buffer);
   v_setattr (attr [1]);
   v_border (SINGLE_LINE, left, top, width, height);
   v_shadow (left, top, width, height);
   v_setattr (attr [5]);
   v_clear(left+1, top+1, width-2, height-2);

   if (context == CTX_CEH)
   {
       v_gotoxy (left+1, top+1);
       v_cputs ("A critical error has occurred but don't worry.");
       v_gotoxy (left+1, top+2);
       v_cputs ("You might have left the floppy disk drive door");
       v_gotoxy (left+1, top+3);
       v_cputs ("open or are not using a formatted diskette.");
       v_gotoxy (left+1, top+4);
       v_cputs ("Close the door, get a formatted diskette, or");
       v_gotoxy (left+1, top+5);
       v_cputs ("contact your computer support person.         ");

       v_gotoxy (left+1, top+7);
       v_cputs ("The RETRY option repeats the operation respon-");
       v_gotoxy (left+1, top+8);
       v_cputs ("sible for the critical error (this assumes you");
       v_gotoxy (left+1, top+9);
       v_cputs ("have fixed the problem) and gives you another");
       v_gotoxy (left+1, top+10);
       v_cputs ("chance.  ABORT cancels the operation that was");
       v_gotoxy (left+1, top+11);
       v_cputs ("taking place.  A message will appear informing");
       v_gotoxy (left+1, top+12);
       v_cputs ("you as to what operation was cancelled.");

       v_gotoxy (left+1, top+14);
       v_cputs ("You select either ABORT or RETRY by using the");
       v_gotoxy (left+1, top+15);
       v_cputs ("left or right arrow keys to move the lightbar");
       v_gotoxy (left+1, top+16);
       v_cputs ("(the block border surrounding ABORT or RETRY)");
       v_gotoxy (left+1, top+17);
       v_cputs ("to the appropriate option and press the RETURN");
       v_gotoxy (left+1, top+18);
       v_cputs ("(also known as ENTER on some keyboards) key.");
   }

   if (context == CTX_EDITCFG || context == CTX_EDITABS ||
       context == CTX_INSABS)
   {
       v_gotoxy (left+1, top+1);

       if (context == CTX_EDITCFG)
       {
	   v_cputs ("You can tell EAR how to work with the printer by");
	   v_gotoxy (left+1, top+2);
	   v_cputs ("changing the configuration.  The number of lines");
	   v_gotoxy (left+1, top+3);
	   v_cputs ("per printed page, printer port (LPT1 or 0 is the");
	   v_gotoxy (left+1, top+4);
	   v_cputs ("default) where characters are sent, a variety of");
	   v_gotoxy (left+1, top+5);
	   v_cputs ("printer control strings, and the report author's");
	   v_gotoxy (left+1, top+6);
	   v_cputs ("name can be changed.  The following keys can be");
	   v_gotoxy (left+1, top+7);
	   v_cputs ("used to make these changes.");
       }

       if (context == CTX_EDITABS)
       {
	   v_cputs ("You can edit the highlighted employee.  EAR does");
	   v_gotoxy (left+1, top+2);
	   v_cputs ("not check to see if you have already entered the");
	   v_gotoxy (left+1, top+3);
	   v_cputs ("name (if there is a duplicate).  If you find that");
	   v_gotoxy (left+1, top+4);
	   v_cputs ("you have duplicated a name then move the lightbar");
	   v_gotoxy (left+1, top+5);
	   v_cputs ("to the duplicate name and press the RETURN (ENTER");
	   v_gotoxy (left+1, top+6);
	   v_cputs ("may be the name on your keyboard) key to edit the");
	   v_gotoxy (left+1, top+7);
	   v_cputs ("name.  Use the following keys.");
       }

       if (context == CTX_INSABS)
       {
	   v_cputs ("Position lightbar to appropriate spot in list of");
	   v_gotoxy (left+1, top+2);
	   v_cputs ("names.  If topmost name highlighted then you will");
	   v_gotoxy (left+1, top+3);
	   v_cputs ("be asked if you want the new name inserted either");
	   v_gotoxy (left+1, top+4);
	   v_cputs ("above or below this name.  Otherwise, the name is");
	   v_gotoxy (left+1, top+5);
	   v_cputs ("inserted below the highlighted name.  Blank lines");
	   v_gotoxy (left+1, top+6);
	   v_cputs ("refers to the number of blank lines printed after");
	   v_gotoxy (left+1, top+7);
	   v_cputs ("an employee name.  Use the following keys.");
       }

       v_gotoxy (left+1, top+9);
       v_cputs ("RET   (move down one field - last field exits)");
       v_gotoxy (left+1, top+10);
       v_cputs ("\x18     (move up to previous field)");
       v_gotoxy (left+1, top+11);
       v_cputs ("\x19     (move down to next field)");
       v_gotoxy (left+1, top+12);
       v_cputs ("ESC   (press once to restore original field)");
       v_gotoxy (left+1, top+13);
       v_cputs ("      (press again to cancel data entry)");
       v_gotoxy (left+1, top+14);
       v_cputs ("0-9 . (for numeric fields only)");
       v_gotoxy (left+1, top+15);
       v_cputs ("HOME  (move to leftmost field character)");
       v_gotoxy (left+1, top+16);
       v_cputs ("END   (move to rightmost field character)");
       v_gotoxy (left+1, top+17);
       v_cputs ("\x1b     (move one field character left)");
       v_gotoxy (left+1, top+18);
       v_cputs ("\x1a     (move one field character right)");
       v_gotoxy (left+1, top+19);
       v_cputs ("INS   (toggle between insert/overwrite modes)");
       v_gotoxy (left+1, top+20);
       v_cputs ("DEL   (delete character under cursor)");
       v_gotoxy (left+1, top+21);
       v_cputs ("BS    (backspace over character left of cursor)");
   }

   if (context == CTX_MAIN)
   {
       v_gotoxy (left+1, top+1);
       if (!numemp)
	   v_cputs ("No employees are displayed in the listbox.");
       else
       if (numemp == 1)
	   v_cputs ("Only one employee is displayed in the listbox.");
       else
	   v_cputs ("More than one employee is displayed in the listbox.");

       v_gotoxy (left+1, top+2);
       v_cputs ("You may press any of the following keys.");

       v_gotoxy (left+1, top+4);
       v_cputs ("C or c (view or change configuration)");
       v_gotoxy (left+1, top+5);
       v_cputs ("L or l (load employee file)");
       v_gotoxy (left+1, top+6);
       v_cputs ("INS    (insert new employee)");
       v_gotoxy (left+1, top+7);
       v_cputs ("F10    (exit EAR)");

       if (numemp)
       {
	   v_gotoxy (left+1, top+8);
	   v_cputs ("P or p (print a quarterly absence report)");
	   v_gotoxy (left+1, top+9);
	   v_cputs ("R or r (reset all absences to NIL)");
	   v_gotoxy (left+1, top+10);
	   v_cputs ("S or s (save your work)");
	   v_gotoxy (left+1, top+11);
	   v_cputs ("?      (get statistics on highlighted employee)");
	   v_gotoxy (left+1, top+12);
	   v_cputs ("RET    (edit highlighted employee)");
	   v_gotoxy (left+1, top+13);
	   v_cputs ("DEL    (delete highlighted employee)");
       }

       if (numemp > 1)
       {
	   v_gotoxy (left+1, top+14);
	   v_cputs ("A or a (alphabetize employees by name)");
	   v_gotoxy (left+1, top+15);
	   v_cputs ("\x18      (highlight previous employee)");
	   v_gotoxy (left+1, top+16);
	   v_cputs ("\x19      (highlight next employee)");
	   v_gotoxy (left+1, top+17);
	   v_cputs ("HOME   (highlight first employee)");
	   v_gotoxy (left+1, top+18);
	   v_cputs ("END    (highlight last employee)");
	   v_gotoxy (left+1, top+19);
	   v_cputs ("PGUP   (highlight employee several lines up)");
	   v_gotoxy (left+1, top+20);
	   v_cputs ("PGDN   (highlight employee several lines down)");
       }
   }

   if (context == CTX_LOADYR || context == CTX_SAVEYR)
   {
       v_gotoxy (left+1, top+1);
       v_cputs ("You need to enter the name of the file where");
       v_gotoxy (left+1, top+2);
       v_cputs ("you are going to ");
       if (context == CTX_LOADYR)
	   v_cputs ("load");
       else
	   v_cputs ("save");
       v_cputs (" employee information.");

       v_gotoxy (left+1, top+4);
       v_cputs ("The name consists of four digits that repre-");
       v_gotoxy (left+1, top+5);
       v_cputs ("sent a year between 1990 and 2050 inclusive.");
       v_gotoxy (left+1, top+6);
       v_cputs ("The following keys can be used when entering");
       v_gotoxy (left+1, top+7);
       v_cputs ("this year.");

       v_gotoxy (left+1, top+9);
       v_cputs ("0-9   (the digits making up the year)");
       v_gotoxy (left+1, top+10);
       v_cputs ("ESC   (press once to restore original year)");
       v_gotoxy (left+1, top+11);
       v_cputs ("      (press a second time to exit)");
       v_gotoxy (left+1, top+12);
       v_cputs ("RET   (exit with selected year)");
       v_gotoxy (left+1, top+13);
       v_cputs ("\x18     (exit with selected year)");
       v_gotoxy (left+1, top+14);
       v_cputs ("\x19     (exit with selected year)");
       v_gotoxy (left+1, top+15);
       v_cputs ("HOME  (move to leftmost digit)");
       v_gotoxy (left+1, top+16);
       v_cputs ("END   (move to rightmost digit)");
       v_gotoxy (left+1, top+17);
       v_cputs ("\x1b     (move one digit left)");
       v_gotoxy (left+1, top+18);
       v_cputs ("\x1a     (move one digit right)");
       v_gotoxy (left+1, top+19);
       v_cputs ("INS   (toggle between insert/overwrite modes)");
       v_gotoxy (left+1, top+20);
       v_cputs ("DEL   (delete digit under cursor)");
       v_gotoxy (left+1, top+21);
       v_cputs ("BS    (backspace over digit left of cursor)");
   }

   if (context == CTX_PRINQTR)
   {
       v_gotoxy (left+1, top+1);
       v_cputs ("You need to enter the number of the quarter");
       v_gotoxy (left+1, top+2);
       v_cputs ("that you wish to print.  The number lies in");
       v_gotoxy (left+1, top+3);
       v_cputs ("the range 1 to 4 inclusive.  Keys to use in");
       v_gotoxy (left+1, top+4);
       v_cputs ("entering this number include:");

       v_gotoxy (left+1, top+6);
       v_cputs ("1-4   (the quarter number)");
       v_gotoxy (left+1, top+7);
       v_cputs ("ESC   (press once to restore default)");
       v_gotoxy (left+1, top+8);
       v_cputs ("      (press a second time to exit)");
       v_gotoxy (left+1, top+9);
       v_cputs ("RET   (exit with selected quarter)");
       v_gotoxy (left+1, top+10);
       v_cputs ("\x18     (exit with selected quarter)");
       v_gotoxy (left+1, top+11);
       v_cputs ("\x19     (exit with selected quarter)");

       v_gotoxy (left+1, top+13);
       v_cputs ("You can also use the \x1b, \x1a, HOME, END, DEL,");
       v_gotoxy (left+1, top+14);
       v_cputs ("INS, and backspace keys but they probably");
       v_gotoxy (left+1, top+15);
       v_cputs ("will not be as useful.");
   }

   if (context == CTX_STAT)
   {
       v_gotoxy (left+1, top+1);
       v_cputs ("A summary of employee absence info is");
       v_gotoxy (left+1, top+2);
       v_cputs ("shown for each quarter along with the");
       v_gotoxy (left+1, top+3);
       v_cputs ("home telephone number.  Press any key");
       v_gotoxy (left+1, top+4);
       v_cputs ("except F1 to continue.");
   }

   v_screen (SCREEN_SAVE, 1, rows, 80, 1, helphelp);

   v_setattr (LIGHTGRAY);
   v_clear(1, rows, 80, 1);

   v_gotoxy (27, rows);
   v_cputs ("Press any key to exit help.");

   (void) k_fetch ();

   v_screen (SCREEN_RESTORE, 1, rows, 80, 1, helphelp);

   v_screen (SCREEN_RESTORE, left, top, width+1, height+1, buffer);
   v_gotoxy (oldx, oldy);
   v_setattr (oldattr);
   v_cursor (CRESTORE, cshape);
}

/* =================================================================
   int editi (int *da, int width, int operation);

   Entry: da  - number of days absent
	  width - number of decimal digits to display (max. width=2)
	  operation - editting operation (EDIT_DISP or EDIT_FETCH)

   Exit : RET or ESC
   ================================================================= */

int editi (int *da, int width, int operation)
{
   int key;
   double value;

   value = *da;
   key = _editn (&value, width, 0, operation);
   *da = value;
   return key;
}

/* =================================================================
   void edit_employee (void);

   Entry: none

   Exit : none
   ================================================================= */

void edit_employee (void)
{
   struct _employee temp = employee [curemp];

   k_setctx (CTX_EDITABS);
   if (prompt_absence (" EDIT ", &temp) == ESC)
       return;

   employee [curemp] = temp;

   refresh (UPDATE);
   modified = 1;
}

/* =================================================================
   void end (void);

   Entry: none

   Exit : none
   ================================================================= */

void end (void)
{
   int limit;

   if (curemp == numemp-1)
       return;

   v_setattr (attr [5]);
   v_clear(LB_LEFT+1, LB_TOP+1, LB_WIDTH, LB_HEIGHT);

   if (numemp < LB_HEIGHT)
   {
       limit = numemp;
       curemp = 0;
   }
   else
   {
       limit = LB_HEIGHT;
       curemp = numemp-LB_HEIGHT;
   }

   for (currow = 1; currow <= limit; currow++)
   {
	v_setattr (attr [(currow == limit) ? 2 : 5]);
	v_gotoxy (LB_LEFT+1, LB_TOP+currow);
	v_cputs (employee [curemp++].name);
   }

   currow = limit;
   curemp--;
}

/* =================================================================
   int fexist (char *filespec);

   Entry: filespec - specification of file for existance check

   Exit : zero if file does not exist
   ================================================================= */

int fexist (char *filespec)
{
   struct ffblk f;

   return !findfirst (filespec, &f, 0) ? 1 : 0;
}

/* =================================================================
   void home (void);

   Entry: none

   Exit : none
   ================================================================= */

void home (void)
{
   int minimum;

   if (!curemp)
       return;

   v_setattr (attr [5]);
   v_clear(LB_LEFT+1, LB_TOP+1, LB_WIDTH, LB_HEIGHT);

   curemp = 0;
   minimum = min(numemp, LB_HEIGHT);

   for (currow = 1; currow <= minimum; currow++)
   {
	v_setattr (attr [(currow == 1) ? 2 : 5]);
	v_gotoxy (LB_LEFT+1, LB_TOP+currow);
	v_cputs (employee [curemp++].name);
   }

   curemp = 0;
   currow = 1;
}

/* =================================================================
   void insert_employee (void);

   Entry: none

   Exit : none
   ================================================================= */

void insert_employee (void)
{
   struct _employee temp;
   int i, key = 'B', limit;

   if (numemp == MAXEMP)
   {
       message ("DATABASE LIMIT REACHED");
       return;
   }

   temp.name [0] = '\0';
   temp.phon [0] = '\0';
   temp.q1da = 0;
   temp.q2da = 0;
   temp.q3da = 0;
   temp.q4da = 0;

   k_setctx (CTX_INSABS);
   if (prompt_absence (" INSERT ", &temp) == ESC)
       return;

   if (!numemp)
   {
       employee [0] = temp;
       numemp = 1;
   }
   else
   {
       if (!curemp)
	   key = msg (MSG_AB, "INSERT ABOVE OR BELOW", attr [1], attr [5]);

       limit = (key == 'A') ? curemp : curemp+1;

       for (i = numemp-1; i >= limit; i--)
	    employee [i+1] = employee [i];

       employee [limit] = temp;
       numemp++;
   }

   refresh (UPDATE);
   if (key == 'B')
       down (1);
   modified = 1;
}

/* =================================================================
   void load_employees (void);

   Entry: none

   Exit : none
   ================================================================= */

void load_employees (void)
{
   FILE *f;
   long i, nrecs;
   struct date d;
   int headersize, recsize, year = file_year;
   char buffer [5], filespec [9], header [HDRSIZ], record [RECSIZ];

   if (!file_year)
   {
       getdate (&d);
       year = d.da_year;
   }

   k_setctx (CTX_LOADYR);

   do
   {
      if (prompt_year (" LOAD ", &year) == ESC)
	  return;
   }
   while (year < 1990 || year > 2050);

   sprintf (filespec, "%4d.dbf", year);

   if ((f = fopen (filespec, "rb")) == NULL)
   {
       message ("FILE NOT FOUND");
       return;
   }

   if (fread (header, HDRSIZ, 1, f) != 1)
   {
       (void) fclose (f);
       message ("UNABLE TO READ FROM FILE");
       return;
   }

   nrecs = *(long *) (header+4);
   headersize = *(int *) (header+8);
   recsize = *(int *) (header+10);

   if (header [0] != 0x03 || nrecs > MAXEMP ||
       nrecs*recsize+headersize+1 != filelength (fileno(f)))
   {
       (void) fclose (f);
       message ("FILE CORRUPT - UNABLE TO READ");
       return;
   }

   for (i = 0; i < nrecs; i++)
   {
	if (fread (record, RECSIZ, 1, f) != 1)
	{
	    message ("UNABLE TO READ FROM FILE");
	    break;
	}

	memcpy (employee [i].name, record+FD1OFF, NAMELEN);
	employee [i].name [NAMELEN] = '\0';
	memcpy (employee [i].phon, record+FD2OFF, PHONLEN);
	employee [i].phon [PHONLEN] = '\0';
	buffer [2] = '\0';
	memcpy (buffer, record+FD3OFF, 2);
	employee [i].q1da = atoi (buffer);
	memcpy (buffer, record+FD4OFF, 2);
	employee [i].q2da = atoi (buffer);
	memcpy (buffer, record+FD5OFF, 2);
	employee [i].q3da = atoi (buffer);
	memcpy (buffer, record+FD6OFF, 2);
	employee [i].q4da = atoi (buffer);
   }

   (void) fclose (f);

   v_setattr (attr [0]);
   v_gotoxy (77, 1);
   sprintf (buffer, "%4d", file_year = year);
   v_cputs (buffer);
   numemp = nrecs;
   refresh (RESET);
   modified = 0;
}

/* =================================================================
   void message (char *m);

   Entry: m points to an ASCIZ string to be displayed which must not
	  exceed 77 characters (not counting the \0 terminator)

   Exit : none
   ================================================================= */

void message (char *m)
{
   (void) msg (MSG_ANY, m, attr [1], attr [5]);
}

/* =================================================================
   void pgdn (void);

   Entry: none

   Exit : none
   ================================================================= */

void pgdn (void)
{
   down (min(numemp, LB_HEIGHT));
}

/* =================================================================
   void pgup (void);

   Entry: none

   Exit : none
   ================================================================= */

void pgup (void)
{
   up (min(numemp, LB_HEIGHT));
}

/* =================================================================
   int print_header (int pageno, int quarter);

   Entry: pageno - current page number
	  quarter - report quarter id

   Exit : ESC on printer error else !ESC
   ================================================================= */

int print_header (int pageno, int quarter)
{
   char buffer [20];
   char *months [] =
   {
      "Jan. 1 to Mar. 31",
      "Apr. 1 to Jun. 30",
      "Jul. 1 to Sep. 30",
      "Oct. 1 to Dec. 31"
   };

   if (p_xchar (' ', 29) == ESC)
       return ESC;

   if (p_str ("^27C#") == ESC)
       return ESC;

   if (p_str ("Employee Absence Record") == ESC)
       return ESC;

   if (p_str ("^27D#") == ESC)
       return ESC;

   if (p_xchar (' ', 19) == ESC || p_str ("Page: ") == ESC)
       return ESC;

   sprintf (buffer, "%03d", pageno);

   if (p_str (buffer) == ESC)
       return ESC;

   if (p_str ("^13^10^10^10") == ESC)
       return ESC;

   strcpy (buffer, "Year: ");
   (void) itoa (file_year, buffer+6, 10);
   strcat (buffer, "^13^10^10");

   if (p_str (buffer) == ESC)
       return ESC;

   if (p_xchar (' ', NAMELEN+4) == ESC)
       return ESC;

   if (p_str ("^27C#") == ESC)
       return ESC;

   if (p_str (months [quarter]) == ESC)
       return ESC;

   if (p_str ("^27D#") == ESC)
       return ESC;

   if (p_xchar (' ', 4) == ESC)
       return ESC;

   if (p_str ("^27C#Year to Date^27D#") == ESC)
       return ESC;

   if (p_str ("^13^10^10") == ESC)
       return ESC;

   return !ESC;
}

/* =================================================================
   void print_report (void);

   Entry: none

   Exit : none
   ================================================================= */

void print_report (void)
{
   int i, key, pageno = 1, quarter = 1, x, y;
   char buffer [4], *screen_buffer, *title = "PRINT";

   x = center(80, PFORMW+2);
   y = center(rows, PFORMH+2);

   if ((screen_buffer = (char *) malloc ((PFORMW+3)*(PFORMH+3)*2)) == NULL)
   {
       message ("INSUFFICIENT MEMORY");
       return;
   }

   v_screen (SCREEN_SAVE, x, y, PFORMW+3, PFORMH+3, screen_buffer);

   v_setattr (attr [1]);
   v_border (DOUBLE_LINE, x, y, PFORMW+2, PFORMH+2);
   v_shadow (x, y, PFORMW+2, PFORMH+2);
   v_setattr (attr [4]);
   v_gotoxy (x+center(PFORMW, strlen (title)), y);
   v_cputs (title);
   v_setattr (attr [5]);
   v_clear(x+1, y+1, PFORMW, PFORMH);

   v_setattr (attr [3]);
   k_setctx (CTX_PRINQTR);

   do
   {
      key = editi (&quarter, 1, EDIT_FETCH);
      v_gotoxy (x+1, y+1);
   }
   while (key != ESC && (quarter < 1 || quarter > 4));

   v_screen (SCREEN_RESTORE, x, y, PFORMW+3, PFORMH+3, screen_buffer);

   if (key == ESC)
       return;

   msg_flash (MSG_FON, "Printing - press ESC to abort ...");

   p_init (65, config.portno);

   if (p_str ("^27A#") == ESC)
       goto failure;

   for (i = 0; i < numemp; i++)
   {
	if (!p_lc ())
	    if (print_header (pageno++, quarter-1) == ESC)
		break;

	if (p_str (employee [i].name) == ESC)
	    break;

	if (p_xchar (' ', 4) == ESC)
	    break;

	if (quarter == 1)
	    sprintf (buffer, "%2d", employee [i].q1da);
	else
	if (quarter == 2)
	    sprintf (buffer, "%2d", employee [i].q2da);
	else
	if (quarter == 3)
	    sprintf (buffer, "%2d", employee [i].q3da);
	else
	    sprintf (buffer, "%2d", employee [i].q4da);

	if (p_str (buffer) == ESC)
	    break;

	if (p_xchar (' ', 19) == ESC)
	    break;

	sprintf (buffer, "%3d", employee [i].q1da+employee [i].q2da+
				employee [i].q3da+employee [i].q4da);

	if (p_str (buffer) == ESC)
	    break;

	if (p_str ("^13^10") == ESC)
	    break;
   }

   if (p_lc () && p_eject () == ESC)
       goto failure;

   (void) p_str ("^27B#");

failure:

   msg_flash (MSG_FOFF);
}

/* =================================================================
   int prompt_absence (char *title, struct _employee *e);

   Entry: none

   Exit : RET or ESC
   ================================================================= */

int prompt_absence (char *title, struct _employee *e)
{
   double value;
   int fieldno = 0, i, key, x, y;
   char buffer [11], *screen_buffer;

   x = center(80, EFORMW+2);
   y = center(rows, EFORMH+2);

   if ((screen_buffer = (char *) malloc ((EFORMW+3)*(EFORMH+3)*2)) == NULL)
   {
       message ("INSUFFICIENT MEMORY");
       return ESC;
   }

   v_screen (SCREEN_SAVE, x, y, EFORMW+3, EFORMH+3, screen_buffer);

   v_setattr (attr [1]);
   v_border (DOUBLE_LINE, x, y, EFORMW+2, EFORMH+2);
   v_shadow (x, y, EFORMW+2, EFORMH+2);
   v_setattr (attr [4]);
   v_gotoxy (x+center(EFORMW, strlen (title)), y);
   v_cputs (title);
   v_setattr (attr [5]);
   v_clear(x+1, y+1, EFORMW, EFORMH);

   v_gotoxy (x+1, y+1);
   v_cputs ("Name:");
   v_gotoxy (x+1, y+2);
   v_cputs ("Phone:");
   for (i = 1; i <= 4; i++)
   {
	sprintf (buffer, "Quarter %d:", i);
	v_gotoxy (x+1, y+2+i);
	v_cputs (buffer);
   }

   v_setattr (attr [3]);
   v_gotoxy (x+7, y+1);
   (void) _editc (e->name, NAMELEN, EDIT_DISP);
   v_setattr (attr [5]);
   v_gotoxy (x+8, y+2);
   (void) _editp (e->phon, PHONPIC, EDIT_DISP);
   v_gotoxy (x+12, y+3);
   (void) editi (&e->q1da, 2, EDIT_DISP);
   v_gotoxy (x+12, y+4);
   (void) editi (&e->q2da, 2, EDIT_DISP);
   v_gotoxy (x+12, y+5);
   (void) editi (&e->q3da, 2, EDIT_DISP);
   v_gotoxy (x+12, y+6);
   (void) editi (&e->q4da, 2, EDIT_DISP);

   do
   {
      v_setattr (attr [3]);

      switch (fieldno)
      {
	 case 0 : v_gotoxy (x+7, y+1);
		  key = _editc (e->name, NAMELEN, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+7, y+1);
		  (void) _editc (e->name, NAMELEN, EDIT_DISP);
		  break;

	 case 1 : v_gotoxy (x+8, y+2);
		  key = _editp (e->phon, PHONPIC, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+8, y+2);
		  (void) _editp (e->phon, PHONPIC, EDIT_DISP);
		  break;

	 case 2 : v_gotoxy (x+12, y+3);
		  key = editi (&e->q1da, 2, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+12, y+3);
		  (void) editi (&e->q1da, 2, EDIT_DISP);
		  break;

	 case 3 : v_gotoxy (x+12, y+4);
		  key = editi (&e->q2da, 2, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+12, y+4);
		  (void) editi (&e->q2da, 2, EDIT_DISP);
		  break;

	 case 4 : v_gotoxy (x+12, y+5);
		  key = editi (&e->q3da, 2, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+12, y+5);
		  (void) editi (&e->q3da, 2, EDIT_DISP);
		  break;

	 case 5 : v_gotoxy (x+12, y+6);
		  key = editi (&e->q4da, 2, EDIT_FETCH);
		  v_setattr (attr [5]);
		  v_gotoxy (x+12, y+6);
		  (void) editi (&e->q4da, 2, EDIT_DISP);
      }

      if (key == ESC || (key == RET && fieldno == 5))
	  break;

      if (key == UP)
	  if (--fieldno < 0)
	      fieldno = 5;

      if (key == DOWN || key == RET)
	  if (++fieldno > 5)
	      fieldno = 0;
   }
   while (1);

   v_screen (SCREEN_RESTORE, x, y, EFORMW+3, EFORMH+3, screen_buffer);

   return key;
}

/* =================================================================
   int prompt_year (char *title, int *year);

   Entry: title - form title
	  year  - resultant year

   Exit : RET or ESC
   ================================================================= */

int prompt_year (char *title, int *year)
{
   int key, temp, x, y;
   char screen_buffer [13*4*2];

   x = center(80, 12);
   y = center(rows, 3);

   v_screen (SCREEN_SAVE, x, y, 13, 4, screen_buffer);

   v_setattr (attr [1]);
   v_border (DOUBLE_LINE, x, y, 12, 3);
   v_shadow (x, y, 12, 3);

   v_setattr (attr [4]);
   v_gotoxy (x+3, y);
   v_cputs (title);

   v_setattr (attr [5]);
   v_clear(x+1, y+1, 10, 1);

   v_gotoxy (x+1, y+1);
   v_cputs ("YEAR: ");

   temp = *year;
   v_setattr (attr [3]);
   if ((key = editi (&temp, 4, EDIT_FETCH)) != ESC)
       *year = temp;

   v_screen (SCREEN_RESTORE, x, y, 13, 4, screen_buffer);

   return key;
}

/* =================================================================
   void refresh (int cmd);

   Entry: cmd - method of refreshing listbox

   Exit : none
   ================================================================= */

void refresh (int cmd)
{
   char buffer [4];
   int i, lastrow, row;

   v_setattr (attr [5]);
   v_clear(LB_LEFT+1, LB_TOP+1, LB_WIDTH, LB_HEIGHT);

   sprintf (buffer, "%3d", numemp);
   v_setattr (attr [0]);
   v_gotoxy (39, rows);
   v_cputs (buffer);

   if (!numemp && cmd != RESET)
       return;

   lastrow = min(numemp, LB_HEIGHT);

   switch (cmd)
   {
      case RESET   : i = 0;
		     currow = 1;
		     curemp = 0;
		     break;

      case UPDATE  : i = curemp-currow+1;
		     break;

      case DELETE  : numemp++;
		     i = curemp-currow+1;
		     if (i > 0 && curemp+LB_HEIGHT-currow == numemp-1)
			 i--;

		     if (numemp <= LB_HEIGHT && curemp == numemp-1)
		     {
			 currow--;
			 curemp--;
			 numemp--;
			 break;
		     }

		     if (curemp-currow+1 > 0 &&
			 curemp+LB_HEIGHT-currow == numemp-1)
			 if (currow == LB_HEIGHT)
			     curemp--;
			 else
			     currow++;

		     numemp--;
   }

   for (row = 1; row <= lastrow; row++)
   {
	v_setattr (attr [(row == currow) ? 2 : 5]);
	v_gotoxy (LB_LEFT+1, LB_TOP+row);
	v_cputs (employee [i++].name);
   }
}

/* =================================================================
   void reset_employees (void);

   Entry: none

   Exit : none
   ================================================================= */

void reset_employees (void)
{
   int i;

   if (msg (MSG_YN, "ARE YOU SURE?", attr [1], attr [5]) == 'Y')
       for (i = 0; i < numemp; i++)
       {
	    employee [i].q1da = 0;
	    employee [i].q2da = 0;
	    employee [i].q3da = 0;
	    employee [i].q4da = 0;
       }

   message ("ALL ABSENCES RESET TO NIL");

   modified = 1;
}

/* =================================================================
   void save_employees (void);

   Entry: none

   Exit : none
   ================================================================= */

void save_employees (void)
{
   FILE *f;
   int i, year;
   struct date d;
   char buffer [5], filespec [9], header [HDRSIZ], record [RECSIZ+1];

   getdate (&d);
   year = (!file_year) ? d.da_year : file_year;
   k_setctx (CTX_SAVEYR);

   do
   {
      if (prompt_year (" SAVE ", &year) == ESC)
	  return;
   }
   while (year < 1990 || year > 2050);

   header [0] = 0x03;
   header [1] = d.da_year-1900;
   header [2] = d.da_mon;
   header [3] = d.da_day;
   *(long *) (header+4) = (long) numemp;
   *(int *) (header+8) = HDRSIZ;
   *(int *) (header+10) = RECSIZ;
   memset (header+12, 0, 20);

   memcpy (header+32, "NAME\0\0\0\0\0\0\0", 11);
   header [32+11] = 'C';
   *(long *) (header+32+12) = 0;
   header [32+16] = NAMELEN;
   header [32+17] = 0;
   memset (header+32+18, 0, 14);

   memcpy (header+32*2, "PHONE\0\0\0\0\0\0", 11);
   header [32*2+11] = 'C';
   *(long *) (header+32*2+12) = 0;
   header [32*2+16] = PHONLEN;
   header [32*2+17] = 0;
   memset (header+32*2+18, 0, 14);

   memcpy (header+32*3, "QT1DA\0\0\0\0\0\0", 11);
   header [32*3+11] = 'N';
   *(long *) (header+32*3+12) = 0;
   header [32*3+16] = 2;
   header [32*3+17] = 0;
   memset (header+32*3+18, 0, 14);

   memcpy (header+32*4, "QT2DA\0\0\0\0\0\0", 11);
   header [32*4+11] = 'N';
   *(long *) (header+32*4+12) = 0;
   header [32*4+16] = 2;
   header [32*4+17] = 0;
   memset (header+32*4+18, 0, 14);

   memcpy (header+32*5, "QT3DA\0\0\0\0\0\0", 11);
   header [32*5+11] = 'N';
   *(long *) (header+32*5+12) = 0;
   header [32*5+16] = 2;
   header [32*5+17] = 0;
   memset (header+32*5+18, 0, 14);

   memcpy (header+32*6, "QT4DA\0\0\0\0\0\0", 11);
   header [32*6+11] = 'N';
   *(long *) (header+32*6+12) = 0;
   header [32*6+16] = 2;
   header [32*6+17] = 0;
   memset (header+32*6+18, 0, 14);

   header [32*7] = CR;

   sprintf (filespec, "%4d.dbf", year);

   if ((f = fopen (filespec, "wb")) == NULL)
   {
       message ("UNABLE TO CREATE FILE");
       return;
   }

   if (fwrite (header, HDRSIZ, 1, f) != 1)
   {
       (void) fclose (f);
       message ("UNABLE TO WRITE TO FILE");
       return;
   }

   for (i = 0; i < numemp; i++)
   {
	*record = ' ';
	memcpy (record+FD1OFF, employee [i].name, NAMELEN);
	memcpy (record+FD2OFF, employee [i].phon, PHONLEN);
	sprintf (record+FD3OFF, "%2d", employee [i].q1da);
	sprintf (record+FD4OFF, "%2d", employee [i].q2da);
	sprintf (record+FD5OFF, "%2d", employee [i].q3da);
	sprintf (record+FD6OFF, "%2d", employee [i].q4da);

	if (fwrite (record, RECSIZ, 1, f) != 1)
	{
	    (void) fclose (f);
	    message ("UNABLE TO WRITE TO FILE");
	    return;
	}
   }

   record [0] = '\x1a';
   if (fwrite (record, 1, 1, f) != 1)
   {
       (void) fclose (f);
       message ("UNABLE TO WRITE TO FILE");
       return;
   }

   (void) fclose (f);

   v_setattr (attr [0]);
   v_gotoxy (77, 1);
   sprintf (buffer, "%4d", file_year = year);
   v_cputs (buffer);
   modified = 0;
}

/* =================================================================
   void statistics (void);

   Entry: none

   Exit : none
   ================================================================= */

void statistics (void)
{
   int i, total, x, y;
   char buffer [11], *screen_buffer, *title = "STATISTICS";

   x = center(80, SFORMW+2);
   y = center(rows, SFORMH+2);

   if ((screen_buffer = (char *) malloc ((SFORMW+3)*(SFORMH+3)*2)) == NULL)
   {
       message ("INSUFFICIENT MEMORY");
       return;
   }

   v_screen (SCREEN_SAVE, x, y, SFORMW+3, SFORMH+3, screen_buffer);

   v_setattr (attr [1]);
   v_border (DOUBLE_LINE, x, y, SFORMW+2, SFORMH+2);
   v_shadow (x, y, SFORMW+2, SFORMH+2);
   v_setattr (attr [4]);
   v_gotoxy (x+center(SFORMW, strlen (title)), y);
   v_cputs (title);
   v_setattr (attr [5]);
   v_clear(x+1, y+1, SFORMW, SFORMH);

   v_gotoxy (x+1, y+1);
   v_cputs ("Name:");
   v_gotoxy (x+1, y+2);
   v_cputs ("Phone:");
   for (i = 1; i <= 4; i++)
   {
	sprintf (buffer, "Quarter %d:", i);
	v_gotoxy (x+1, y+i+2);
	v_cputs (buffer);
   }
   v_gotoxy (x+1, y+7);
   v_cputs ("Year total:");

   v_setattr (attr [3]);
   v_gotoxy (x+7, y+1);
   (void) _editc (employee [curemp].name, NAMELEN, EDIT_DISP);
   v_gotoxy (x+8, y+2);
   (void) _editp (employee [curemp].phon, PHONPIC, EDIT_DISP);
   v_gotoxy (x+12, y+3);
   (void) editi (&employee [curemp].q1da, 2, EDIT_DISP);
   v_gotoxy (x+12, y+4);
   (void) editi (&employee [curemp].q2da, 2, EDIT_DISP);
   v_gotoxy (x+12, y+5);
   (void) editi (&employee [curemp].q3da, 2, EDIT_DISP);
   v_gotoxy (x+12, y+6);
   (void) editi (&employee [curemp].q4da, 2, EDIT_DISP);

   total = employee [curemp].q1da+employee [curemp].q2da+
	   employee [curemp].q3da+employee [curemp].q4da;

   v_gotoxy (x+13, y+7);
   (void) editi (&total, 3, EDIT_DISP);

   k_setctx (CTX_STAT);
   (void) k_fetch ();

   v_screen (SCREEN_RESTORE, x, y, SFORMW+3, SFORMH+3, screen_buffer);
}

/* =================================================================
   void up (int nlines);

   Entry: nlines - number of lines to scroll up

   Exit : none
   ================================================================= */

void up (int nlines)
{
   int i, uplines;

   if (!curemp)
       return;

   v_setattr (attr [5]);
   v_gotoxy (LB_LEFT+1, LB_TOP+currow);
   v_cputs (employee [curemp].name);

   uplines = min(curemp, nlines);

   for (i = 1; i <= uplines; i++)
   {
	if (currow != 1)
	    currow--;
	else
	    v_scroll (LB_LEFT+1, LB_TOP+1, LB_WIDTH, LB_HEIGHT, SCROLL_DN,
		      1, attr [5]);

	if (i == uplines)
	    v_setattr (attr [2]);

	v_gotoxy (LB_LEFT+1, LB_TOP+currow);
	v_cputs (employee [--curemp].name);
   }
}