/******************************************************************************
* Copyright 1996-2014 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
******************************************************************************/
/******************************************************************************
*
*  NSSDC/CDF                                            Windowing widgets.
*
*  Version 1.3b, 13-Nov-97, Hughes STX.
*
*  Modification history:
*
*   V1.0  19-Nov-93, J Love     Original version.
*   V1.0a 22-Feb-94, J Love     Spelling lesson.
*   V1.0b 28-Mar-94, J Love     Solaris using GNU C compiler.
*   V1.1  13-Dec-94, J Love     CDF V2.5.
*   V1.1a 23-Jan-95, J Love     Primary/alternate "current" item movement
*                               keys for ItemWindow.
*   V1.1b  7-Mar-95, J Love     Added FieldWindow.
*   V1.2  11-Apr-95, J Love     POSIX/CDFexport.
*   V1.2a 10-May-95, J Love     More CDFexport.
*   V1.2b 13-Jun-95, J Love     catchrX.  Linux.
*   V1.2c 30-Aug-95, J Love     CDFexport-related changes.  Moved online help
*                               widget to this file.  FSI key definitions.
*   V1.2d 18-Sep-95, J Love     Macintosh event handling.
*   V1.3  26-Aug-96, J Love     CDF V2.6.
*   V1.3a 31-Mar-97, J Love     Allowed fields in a FieldWindow to be longer
*                               than their on-screen width.
*   V1.3b 13-Nov-97, J Love	Windows NT/Visual C++.
*   V1.4  11-Jul-05, M Liu      Added MingW port for PC.
*
******************************************************************************/
#include <locale.h>
#include "widgets.h"
#include "cdftools.h"

/******************************************************************************
* Local macros.
******************************************************************************/

#define MAX_PCT_LEN     5

#define DIFF(a,b)       BOO((a < b),(b - a),(a - b))

/******************************************************************************
* Local function prototypes.
******************************************************************************/

static void CalcItemDirections PROTOARGs((
  int nItems, int NiLines, int *iLineNs, int *iCols, int *iLens,
  int *iDownTo, int *iUpTo, int *iLeftTo, int *iRightTo
));
static void DownArrow PROTOARGs((
  int *iLineNs, int *iDownTo, int iRowT, int iRowB, Logical iScroll,
  int *iRowN, int *itemN
));
static void UpArrow PROTOARGs((
  int NiLines, int *iLineNs, int *iUpTo, int iRowT, int iRowB, Logical iScroll,
  int *iRowN, int *itemN
));
static void NextScreen PROTOARGs((
  int NiRows, int NiLines, int *iLineNs, int *iDownTo, int iRowB, int iRowT,
  Logical iScroll, int *itemN, int *iRowN
));
static void PrevScreen PROTOARGs((
  int NiRows, int *iLineNs, int *iUpTo, int iRowT, Logical iScroll, int *iRowN,
  int *itemN
));
static void UpdatePctString PROTOARGs((WINDOWid, int, int, int, int, int));
static void DrawSection PROTOARGs((
  WINDOWid, int, int, int, int, char **, int
));
static void DrawField PROTOARGs((struct FieldWindowStruct *FW));
static void DrawFieldsSection PROTOARGs((struct FieldWindowStruct *FW));
static int IWtopRowLineN PROTOARGs((struct ItemWindowStruct *IW));
static int FWtopRowLineN PROTOARGs((struct FieldWindowStruct *FW));
static void DrawEditSection PROTOARGs((struct EditWindowStruct *EW));
static void SetEditCursor PROTOARGs((struct EditWindowStruct *EW));

/******************************************************************************
* ItemWindow.
*
*       Header section.
*       Items section (scrollable).
*       Trailer section.
*
******************************************************************************/

#if defined(STDARG)
int ItemWindow (int opT, ...)
#else
int ItemWindow (va_alist)
va_dcl
#endif
{
   int op;                      /* Operation to perform (eg. create new menu,
				   delete menu, etc. */
   struct ItemWindowStruct *IW; /* Menu configuration. */
   va_list ap;

   /***************************************************************************
   * Start variable-length argument list scanning.
   ***************************************************************************/
#if defined(STDARG)
   va_start (ap, opT);
   op = opT;
#else
   VA_START (ap);
   op = va_arg (ap, int);
#endif
   IW = va_arg (ap, struct ItemWindowStruct *);
   /***************************************************************************
   * Perform desired operation.  Some operations wait for user input (by
   * falling through the `switch' statement).
   ***************************************************************************/
   switch (op) {
     /*************************************************************************
     * Check for new/modified menu.
     *************************************************************************/
     case NEWiw:
     case UPDATEiw: {
       int nSections;           /* Number of sections on window. */
       int nHorizLines = 0;     /* Number of horizontal lines needed.  At
				   most 2 horizontal lines will be needed
				   to separate the 3 sections. */
       int horizRows[2];        /* Rows at which to draw the horizontal
				   lines. */
       int horizLineN;          /* Horizontal line number. */
       int xRow;                /* Used when determining starting rows for
				   the various sections. */
       Logical highlightItem = FALSE;           /* If TRUE, hightlight the
						   current item. */
       /***********************************************************************
       * Get remaining arguments.
       ***********************************************************************/
       IW->itemN = va_arg (ap, int);            /* Initial item number (if any
						   items exist). */
       va_end (ap);
       /***********************************************************************
       * Calculate positions.
       ***********************************************************************/
       if (op == NEWiw) {
	 IW->nColS = IW->nColsTotal - 2;
	 nSections = 0;
	 nHorizLines = 0;
	 /*********************************************************************
	 * Start at row 1 (row 0 is top border line).
	 *********************************************************************/
	 xRow = 1;
	 if (IW->NhLines > 0) {
	   /* Header section */
	   nSections += 1;
	   IW->hRowT = xRow;
	   IW->hRowB = IW->hRowT + IW->NhLines - 1;
	   xRow += IW->NhLines + 1;
	 }
	 if (IW->NiRows > 0) {
	   /* Information section */
	   nSections += 1;
	   IW->iRowT = xRow;
	   IW->iRowB = IW->iRowT + IW->NiRows - 1;
	   xRow += IW->NiRows + 1;
	   if (nSections > 1) horizRows[nHorizLines++] = IW->iRowT - 1;
	 } else
	   return FALSE;                /* An item section must exist. */
	 if (IW->NtLines > 0) {
	   /* Trailer section */
	   nSections += 1;
	   IW->tRowT = xRow;
	   IW->tRowB = IW->tRowT + IW->NtLines - 1;
	   xRow += IW->NtLines + 1;
	   if (nSections > 1) horizRows[nHorizLines++] = IW->tRowT - 1;
	 }
	 IW->nRowsTotal = xRow;
       }
       /***********************************************************************
       * Turn off cursor.
       ***********************************************************************/
       set_cursor_mode (CURSORoff);
       /***********************************************************************
       * Create window?
       ***********************************************************************/
       begin_pasteboard_update ();
       if (op == NEWiw) {
	 create_virtual_display (IW->nRowsTotal, IW->nColsTotal, &(IW->wid),
				 BORDER, NORMAL);
	 paste_virtual_display (IW->wid, IW->ULrow, IW->ULcol);
       }
       /***********************************************************************
       * If specified, write label to window.  If this is an update operation,
       * first overwrite the existing label.
       ***********************************************************************/
       if (op == UPDATEiw) {
	 draw_horizontal_line (IW->wid, 0, 1, IW->nColS, NORMAL, FALSE);
       }
       if (IW->label != NULL) {
	 /* Label line -- above the header section */
	 int len = (int) strlen(IW->label);
	 if (len <= IW->nColS) {
	   int nRemainChars = IW->nColS - len;
	   int nCharsBefore = nRemainChars / 2;
	   put_chars (IW->wid, IW->label, len, 0, nCharsBefore + 1, FALSE,
		      REVERSE1);
	 }
       }
       /***********************************************************************
       * If necessary, draw horizontal lines on window.
       ***********************************************************************/
       if (op == NEWiw) {
	 /* Horizon lines between sections */
	 for (horizLineN = 0; horizLineN < nHorizLines; horizLineN++) {
	    draw_horizontal_line (IW->wid, horizRows[horizLineN], 0,
				  IW->nColsTotal-1, NORMAL, TRUE);
	 }
       }
       /***********************************************************************
       * If specified, write header section to window.  If an update operation,
       * it is assumed that the number of lines has not changed.
       ***********************************************************************/
       if (IW->NhLines > 0)
	 DrawSection (IW->wid, IW->hRowT, IW->hRowB, 0,
		      IW->NhLines, IW->hLines, IW->nColS);
       /***********************************************************************
       * Write items section to window and `highlight' current item.  The
       * current item is set to zero initially for a new menu (if items exist,
       * otherwise it is set to -1).  The item section may contain lines but
       * no items.  If an update operation, note that the number of lines may
       * have changed.
       ***********************************************************************/
       if (op == UPDATEiw) {
	 if (IW->iUpTo != NULL) cdf_FreeMemory (IW->iUpTo, FatalError);
	 if (IW->iDownTo != NULL) cdf_FreeMemory (IW->iDownTo, FatalError);
	 if (IW->iLeftTo != NULL) cdf_FreeMemory (IW->iLeftTo, FatalError);
	 if (IW->iRightTo != NULL) cdf_FreeMemory (IW->iRightTo, FatalError);
       }
       IW->iUpTo = (int *) cdf_AllocateMemory ((size_t)IW->nItems *
					       sizeof(int), FatalError);
       IW->iDownTo = (int *) cdf_AllocateMemory ((size_t)IW->nItems *
						 sizeof(int), FatalError);
       IW->iLeftTo = (int *) cdf_AllocateMemory ((size_t)IW->nItems *
						 sizeof(int), FatalError);
       IW->iRightTo = (int *) cdf_AllocateMemory ((size_t)IW->nItems *
						  sizeof(int), FatalError);
       if (IW->NiLines > 0) {
	 /*******************************************************************
	 * One or more lines in the item section.
	 *******************************************************************/
	 if (IW->nItems > 0) {
	   /*****************************************************************
	   * One or more items in the item section.
	   *****************************************************************/
	   CalcItemDirections (IW->nItems, IW->NiLines, IW->iLineNs,
			       IW->iCols, IW->iLens, IW->iDownTo, IW->iUpTo,
			       IW->iLeftTo, IW->iRightTo);
	   if (IW->NiLines > IW->NiRows)
	     IW->iScroll = TRUE;
	   else
	     IW->iScroll = FALSE;
	   /*******************************************************************
	   * Determine row number for current item.  If this is an UPDATEiw
	   * operation, try to keep the row number the same as it was (or as
	   * close as possible).
	   *******************************************************************/
	   if (op == NEWiw)
	     IW->iRowN = MinInt (IW->iRowT +
				 IW->iLineNs[IW->itemN], IW->iRowB);
	   else
	     if (IW->iScroll) {
	       int nLinesFromTop = IW->iLineNs[IW->itemN];
	       int nRowsFromTop = IW->iRowN - IW->iRowT;
	       if (nLinesFromTop < nRowsFromTop)
		 IW->iRowN -= nRowsFromTop - nLinesFromTop;
	       else {
		 int nLinesFromBot = (IW->NiLines-1) - IW->iLineNs[IW->itemN];
		 int nRowsFromBot = IW->iRowB - IW->iRowN;
		 if (nLinesFromBot < nRowsFromBot)
		   IW->iRowN += nRowsFromBot - nLinesFromBot;
	       }
	     } else
	       IW->iRowN = IW->iRowT + IW->iLineNs[IW->itemN];
	   /* Display the info session, if any. */
	   DrawSection (IW->wid, IW->iRowT, IW->iRowB, IWtopRowLineN(IW),
			IW->NiLines, IW->iLines, IW->nColS);
	   highlightItem = TRUE;
	 }
	 else {
	   /*****************************************************************
	   * No items in the item section (but there are some lines).  Just
	   * redraw lines.
	   *****************************************************************/
	   IW->iRowN = IW->iRowT;
	   DrawSection (IW->wid, IW->iRowT, IW->iRowB, IWtopRowLineN(IW),
			IW->NiLines, IW->iLines, IW->nColS);
	 }
       }
       else {
	 /*******************************************************************
	 * No lines in the item section.  Erase lines in case there had
	 * previously been lines.
	 *******************************************************************/
	 IW->iRowN = IW->iRowT;
	 erase_display (IW->wid, IW->iRowT, 1, IW->iRowB, IW->nColS);
       }
       /*********************************************************************
       * Setup/display initial percentage indicator.
       *********************************************************************/
       if (IW->iPct) {
	 int lineNt = IWtopRowLineN (IW);
	 if (op == NEWiw) {
	   IW->iPctRowN = IW->iRowB + 1;
	   IW->iPctColN = IW->nColsTotal - 7;
	 }
	 UpdatePctString (IW->wid, IW->NiLines, IW->NiRows, lineNt,
			  IW->iPctRowN, IW->iPctColN);
       }
       /***********************************************************************
       * If specified, write trailer section to window.  If an update
       * operation, it is assumed that the number of lines has not changed.
       ***********************************************************************/
       if (IW->NtLines > 0)
	 DrawSection (IW->wid, IW->tRowT, IW->tRowB, 0,
		      IW->NtLines, IW->tLines, IW->nColS);
       /***********************************************************************
       * Update screen and then highlight the current item (if there is one).
       * The current item is highlighted after the screen is updated for those
       * situations where the cursor could not be turned off.  Highlighting
       * the current item last will put the (possible blinking) cursor after
       * the current item which isn't as distracting as when the cursor is at
       * the end of the last line of the trailer section.
       ***********************************************************************/
       end_pasteboard_update ();
       if (highlightItem) change_rendition (IW->wid, IW->iRowN,
					    IW->iCols[IW->itemN] + 1, 1,
					    IW->iLens[IW->itemN], REVERSE2);
       return TRUE;
     }
     /*************************************************************************
     * Check for a beep request.
     *************************************************************************/
     case BEEPiw:
       ring_bell ();
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be deleted.
     *************************************************************************/
     case DELETEiw:
       if (IW->iUpTo != NULL) cdf_FreeMemory (IW->iUpTo, FatalError);
       if (IW->iDownTo != NULL) cdf_FreeMemory (IW->iDownTo, FatalError);
       if (IW->iLeftTo != NULL) cdf_FreeMemory (IW->iLeftTo, FatalError);
       if (IW->iRightTo != NULL) cdf_FreeMemory (IW->iRightTo, FatalError);
       delete_virtual_display (IW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be erasea (but remain in existence).
     *************************************************************************/
     case UNDISPLAYiw:
       unpaste_virtual_display (IW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be redisplayed (but don't wait for input - a call
     * with `op = READiw' should be made next).
     *************************************************************************/
     case REDISPLAYiw:
       set_cursor_mode (CURSORoff);
       paste_virtual_display (IW->wid, IW->ULrow, IW->ULcol);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Get next input.  The cursor is positioned and turned off first.  The
     * cursor is positioned for those cases where the cursor cannot be turned
     * off.  A (possibly) blinking cursor at the end of the highlighted item
     * is less distracting than where it might be.
     *************************************************************************/
     case READiw:
       for (;;) {
	  if (IW->nItems > 0) {
	    char *anItem;
	    int iBytes;
	    int lineN, curCol;
	    lineN = IW->iLineNs[IW->itemN];
	    curCol = IW->iCols[IW->itemN];
	    anItem = (char *) malloc (sizeof(char) *
				      (IW->ibLens[IW->itemN] + 1));
	    strncpy (anItem, IW->iLines[lineN]+curCol,
		     IW->ibLens[IW->itemN]);
	    anItem[IW->ibLens[IW->itemN]] = '\0';
	    set_cursor_abs (IW->wid, IW->iRowN,
			    curCol + UTF8StrWidth(anItem) + 1);
	    free (anItem);
	    anItem = NULL;
	  }

	  set_cursor_mode (CURSORoff);
	  read_input (
#if defined(CURSESui)
		      IW->wid,
#endif
			       &(IW->key), PASSTHRUri, TRUE);
	  /********************************************************************
	  * Check for an exit key.  If no exit keys were specified, any key
	  * causes an exit.
	  ********************************************************************/
	  if (IW->exitChars[0] == NUL) {
	    va_end (ap);
	    return TRUE;
	  }
	  else {
	    int charN;
	    for (charN = 0; IW->exitChars[charN] != NUL; charN++) 
	       if (IW->key == IW->exitChars[charN]) { 
		 va_end (ap);
		 return TRUE;
	       }
	  }
	  /********************************************************************
	  * Check for next-screen key.
	  ********************************************************************/
	  if (IW->key == IW->NSkey) {
	    begin_pasteboard_update ();
	    if (IW->nItems > 0) {
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], NORMAL);
	      NextScreen (IW->NiRows, IW->NiLines, IW->iLineNs, IW->iDownTo,
			  IW->iRowB, IW->iRowT, IW->iScroll, &(IW->itemN),
			  &(IW->iRowN));
	      DrawSection (IW->wid, IW->iRowT, IW->iRowB, IWtopRowLineN(IW),
			   IW->NiLines, IW->iLines, IW->nColS);
	      if (IW->iPct) UpdatePctString (IW->wid, IW->NiLines, IW->NiRows,
					     IWtopRowLineN(IW), IW->iPctRowN,
					     IW->iPctColN);
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], REVERSE2);
	    }
	    else
	      ring_bell ();
	    end_pasteboard_update ();
	    continue;
	  }
	  /********************************************************************
	  * Check for prev-screen key.
	  ********************************************************************/
	  if (IW->key == IW->PSkey) {
	    begin_pasteboard_update ();
	    if (IW->nItems > 0) {
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], NORMAL);
	      PrevScreen (IW->NiRows, IW->iLineNs, IW->iUpTo, IW->iRowT,
			  IW->iScroll, &(IW->iRowN), &(IW->itemN));
	      DrawSection (IW->wid, IW->iRowT, IW->iRowB, IWtopRowLineN(IW),
			   IW->NiLines, IW->iLines, IW->nColS);
	      if (IW->iPct) UpdatePctString (IW->wid, IW->NiLines, IW->NiRows,
					     IWtopRowLineN(IW), IW->iPctRowN,
					     IW->iPctColN);
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], REVERSE2);
	    }
	    else
	      ring_bell ();
	    end_pasteboard_update ();
	    continue;
	  }
	  /********************************************************************
	  * Check for down arrow key.
	  ********************************************************************/
	  if (IW->key == IW_DOWN || IW->key == IW_DOWNx) {
	    /* IW->itemN is the previous item number */
	    begin_pasteboard_update ();
	    if (IW->nItems > 0) {
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], NORMAL);
	      DownArrow (IW->iLineNs, IW->iDownTo, IW->iRowT, IW->iRowB,
			 IW->iScroll, &(IW->iRowN), &(IW->itemN));
	      /* IW->itemN is the current item number */
	      DrawSection (IW->wid, IW->iRowT, IW->iRowB, IWtopRowLineN(IW),
			   IW->NiLines, IW->iLines, IW->nColS);
	      if (IW->iPct) UpdatePctString (IW->wid, IW->NiLines, IW->NiRows,
					     IWtopRowLineN(IW), IW->iPctRowN,
					     IW->iPctColN);
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], REVERSE2);
	    }
	    else
	      ring_bell ();
	    end_pasteboard_update ();
	    continue;
	  }
	  /********************************************************************
	  * Check for up arrow key.
	  ********************************************************************/
	  if (IW->key == IW_UP || IW->key == IW_UPx) {
	    begin_pasteboard_update ();
	    if (IW->nItems > 0) {
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], NORMAL);
	      UpArrow (IW->NiLines, IW->iLineNs, IW->iUpTo, IW->iRowT,
		       IW->iRowB, IW->iScroll, &(IW->iRowN), &(IW->itemN));
	      DrawSection (IW->wid, IW->iRowT, IW->iRowB, IWtopRowLineN(IW),
			   IW->NiLines, IW->iLines, IW->nColS);
	      if (IW->iPct) UpdatePctString (IW->wid, IW->NiLines, IW->NiRows,
					     IWtopRowLineN(IW), IW->iPctRowN,
					     IW->iPctColN);
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], REVERSE2);
	    }
	    else
	      ring_bell ();
	    end_pasteboard_update ();
	    continue;
	  }
	  /********************************************************************
	  * Check for left arrow key. Move left for item selection.
	  ********************************************************************/
	  if (IW->key == IW_LEFT || IW->key == IW_LEFTx) {
	    begin_pasteboard_update ();
	    if (IW->nItems > 0) {
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], NORMAL);
	      IW->itemN = IW->iLeftTo[IW->itemN];
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], REVERSE2);
	    }
	    else
	      ring_bell ();
	    end_pasteboard_update ();
	    continue;
	  }
	  /********************************************************************
	  * Check for right arrow key. Move right for item selection. 
	  ********************************************************************/
	  if (IW->key == IW_RIGHT || IW->key == IW_RIGHTx) {
	    begin_pasteboard_update ();
	    if (IW->nItems > 0) {
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], NORMAL);
	      IW->itemN = IW->iRightTo[IW->itemN];
	      change_rendition (IW->wid, IW->iRowN, IW->iCols[IW->itemN] + 1,
				1, IW->iLens[IW->itemN], REVERSE2);
	    }
	    else
	      ring_bell ();
	    end_pasteboard_update ();
	    continue;
	  }
	  /********************************************************************
	  * Check for the `refresh' key.
	  ********************************************************************/
	  if (IW->key == IW->refreshChar) {
	    repaint_screen ();
	    continue;
	  }
	  /********************************************************************
	  * Illegal key, ring the bell.
	  ********************************************************************/
	  ring_bell ();
       }
   }
   va_end (ap);
   return FALSE;                                      /* Illegal operation. */
}

/******************************************************************************
* PromptWindow.
******************************************************************************/

#if defined(STDARG)
int PromptWindow (int opT, ...)
#else
int PromptWindow (va_alist)
va_dcl
#endif
{
   int op;                      /* Operation to perform (eg. create new
				   prompt window, delete window, etc.). */
   struct PromptWindowStruct *PW;
				/* Prompt window configuration. */
   va_list ap;
   char gotKey[5];
   int ic;

   /***************************************************************************
   * Start variable-length argument list scanning.
   ***************************************************************************/
#if defined(STDARG)
   va_start (ap, opT);
   op = opT;
#else
   VA_START (ap);
   op = va_arg (ap, int);
#endif
   PW = va_arg (ap, struct PromptWindowStruct *);
   /***************************************************************************
   * Perform desired operation.  Some operations wait for user input (by
   * falling through the `switch' statement).
   * PW->curLen: value byte count
   * PW->curChar: current cursor character location
   * PW->curChars: value character count
   * PW->curCharsWidth: column length count till current character 
   ***************************************************************************/
   switch (op) {
     /*************************************************************************
     * Check for new menu or menu to be reset.
     *************************************************************************/
     case NEWpw:
     case RESETpw: {
       /***********************************************************************
       * Get remaining arguments.
       ***********************************************************************/
       PW->curChar = va_arg (ap, int);
       PW->insertMode = va_arg (ap, Logical);
       va_end (ap);
       /***********************************************************************
       * Calculate positions.
       ***********************************************************************/
       if (op == NEWpw) {
	 PW->nColS = PW->nColsTotal - 2;
	 PW->nRowsTotal = (PW->NhLines > 0 ? 1 + PW->NhLines : 0) +
			   3 + (PW->NtLines > 0 ? PW->NtLines + 1 : 0);
	 PW->NvCols = PW->nColsTotal - 6;
	 PW->vRow = (PW->NhLines > 0 ? PW->NhLines + 2 : 1);
	 PW->vLcol = 3;
	 PW->vRcol = PW->vLcol + PW->NvCols - 1;
	 PW->mLcol = 1;
	 PW->mRcol = PW->nColsTotal - 2;
	 if (PW->NhLines > 0) {
	   PW->hRowT = 1;
	   PW->hRowB = PW->hRowT + PW->NhLines - 1;
	 }
	 if (PW->NtLines > 0) {
	   PW->tRowT = (PW->NhLines > 0 ? 1 + PW->NhLines : 0) + 3;
	   PW->tRowB = PW->tRowT + PW->NtLines - 1;
	 }
       }
       /***********************************************************************
       * Create window?
       ***********************************************************************/
       begin_pasteboard_update ();
       if (op == NEWpw) {
	 create_virtual_display (PW->nRowsTotal, PW->nColsTotal, &(PW->wid),
				 BORDER, NORMAL);
	 paste_virtual_display (PW->wid, PW->ULrow, PW->ULcol);
       }
       /***********************************************************************
       * If specified, write label to window.
       ***********************************************************************/
       if (PW->label != NULL) {
	 int len = (int) strlen(PW->label);
	 if (len <= PW->nColS) {
	   int nRemainChars = PW->nColS - len;
	   int nCharsBefore = nRemainChars / 2;
	   put_chars (PW->wid, PW->label, len, 0, nCharsBefore + 1, FALSE,
		      REVERSE1);
	 }
       }
       /***********************************************************************
       * Draw necessary horizontal and vertical lines.
       ***********************************************************************/
       if (op == NEWpw) {
	 if (PW->NhLines > 0) draw_horizontal_line (PW->wid, PW->vRow-1, 0,
						    PW->nColsTotal-1, NORMAL,
						    TRUE);
	 if (PW->NtLines > 0) draw_horizontal_line (PW->wid, PW->vRow+1, 0,
						    PW->nColsTotal-1, NORMAL,
						    TRUE);
	 draw_vertical_line (PW->wid, PW->vRow - 1, PW->vRow + 1,
			     PW->vLcol - 1, NORMAL, TRUE);
	 draw_vertical_line (PW->wid, PW->vRow - 1, PW->vRow + 1,
			     PW->vRcol + 1, NORMAL, TRUE);
       }
       /***********************************************************************
       * If specified, draw header.
       ***********************************************************************/
       if (PW->NhLines > 0)
	 DrawSection (PW->wid, PW->hRowT, PW->hRowB, 0,
		      PW->NhLines, PW->hLines, PW->nColS);
       /***********************************************************************
       * Draw initial value (if one exists) and place cursor.  If a RESETpw
       * operation, first erase existing value.
       ***********************************************************************/
       if (op == RESETpw) {
	 put_chars (PW->wid, " ", 1, PW->vRow, PW->mLcol, FALSE, NORMAL);
	 erase_display (PW->wid, PW->vRow, PW->vLcol, PW->vRow, PW->vRcol);
	 put_chars (PW->wid, " ", 1, PW->vRow, PW->mRcol, FALSE, NORMAL);
       }
       PW->curLen = (int) strlen(PW->value);
       PW->curChars = UTF8StrChars (PW->value, PW->curLen);
       PW->curCharWidth = UTF8CharAtWidth (PW->value, PW->curChar);
       PW->curCharsWidth = UTF8FirstCharsWidth (PW->value, PW->curChar);
       if (PW->curCharsWidth < PW->NvCols) {
	 if (UTF8StrWidth (PW->value) > PW->NvCols) {
	   int lastCharNum = UTF8FrontCharsWithinWidth (PW->value, PW->NvCols);
	   put_chars (PW->wid, PW->value, lastCharNum+1, PW->vRow, PW->vLcol,
		      FALSE, NORMAL);
	   put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE, NORMAL);
	 } else {
	   put_chars (PW->wid, PW->value, PW->curChars, PW->vRow, PW->vLcol,
		      FALSE, NORMAL);
	 }
	 PW->curCol = PW->vLcol + PW->curCharsWidth;
       } else {
	 int cCol, offset;
	 int lastCharNum = UTF8BackCharsWithinWidth (PW->value, PW->NvCols);
	 offset = UTF8CharAtOffset (PW->value, lastCharNum);
	 cCol = UTF8StrWidth (PW->value+offset);
	 put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE, NORMAL);
	 if (PW->curChar < PW->curChars) {
	   put_chars (PW->wid, UTF8CharAtString(PW->value, lastCharNum),
		      PW->curChars-lastCharNum, PW->vRow, PW->vLcol, FALSE, 
		      NORMAL);
	   if (PW->curChar != (PW->curChars - 1))
	     put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE, NORMAL);
	 } else {
	   put_chars (PW->wid, UTF8CharAtString(PW->value,
						PW->curChar - PW->NvCols + 1),
		      PW->NvCols - 1, PW->vRow, PW->vLcol, FALSE, NORMAL);
	 }
	 PW->curCol = PW->vLcol + cCol - 1;
       }
       /***********************************************************************
       * If specified, draw trailer.
       ***********************************************************************/
       if (PW->NtLines > 0)
	 DrawSection (PW->wid, PW->tRowT, PW->tRowB, 0, PW->NtLines,
		      PW->tLines, PW->nColS);
       /***********************************************************************
       * Update screen.
       ***********************************************************************/
       end_pasteboard_update ();
       /***********************************************************************
       * Position cursor (which must occur after the pasteboard batching is
       * ended). Move to the column at curChar + vLocol (3, the front spaces).
       ***********************************************************************/
       set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
       set_cursor_mode (CURSORon);
       return TRUE;
     }
     /*************************************************************************
     * Check for a beep request.
     *************************************************************************/
     case BEEPpw:
       ring_bell ();
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if window should be erased (but remain in existence).
     *************************************************************************/
     case UNDISPLAYpw:
       unpaste_virtual_display (PW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if window should be redisplayed (don't wait for input - a call
     * with `op = READpw' should be made next).
     *************************************************************************/
     case REDISPLAYpw:
       paste_virtual_display (PW->wid, PW->ULrow, PW->ULcol);
       set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
       set_cursor_mode (CURSORon);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if window should be deleted.
     *************************************************************************/
     case DELETEpw:
       delete_virtual_display (PW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Read keystrokes until `exit' key entered.
     *************************************************************************/
     case READpw: {
       int colsToEnd;           /* Number of columns from cursor to the right-
				   most column of the value field (including
				   the cursor position). */
       int offRight;            /* Number of characters to the right of the
				   last column in the value field (the right
				   `more' indicator should be ON).  This can
				   be zero or negative. */
       int offLeft;             /* Number of characters to the left of the
				   first column in the value field (the left
				   `more' indicator should be ON).  This can
				   be zero or negative. */
       int charsToEnd;          /* Number of characters from the cursor to the
				   end of the value (including at the cursor
				   position). */
       int colnsToEnd;          /* Number of columns from the cursor to the
				   end of the value (including at the cursor
				   position). */
       int leftColChar;         /* Character number at left column of value
				   field. */
       int rightColChar;        /* Character number at right column of value
				   field. */
       int charN;               /* Character number. */
       /***********************************************************************
       * Set cursor mode/position.
       ***********************************************************************/
       set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
       set_cursor_mode (CURSORon);
       /***********************************************************************
       * Read/process keys until an `exit' key... This section handles almost
       * all the major interactive operations.
       ***********************************************************************/
       for (;;) {
	  read_input (
#if defined(CURSESui)
		      PW->wid,
#endif
			       &(PW->key), PASSTHRUri, TRUE);
	  /********************************************************************
	  * Check for an `exit' key.
	  ********************************************************************/
	  for (charN = 0; PW->exitChars[charN] != NUL; charN++) { 
	     if (PW->key == PW->exitChars[charN]) { 
	       va_end (ap);
	       return TRUE;
	     }
	  }
	  /********************************************************************
	  * Check for left arrow. Move the cursor to left during editing.
	  ********************************************************************/
	  if (PW->key == KB_LEFTARROW) {
	    int ix, offset;
	    begin_pasteboard_update ();
	    if (PW->curCol > PW->vLcol) {
	      PW->curChar -= 1;
	      PW->curCol -= UTF8CharAtWidth (PW->value, PW->curChar);
	    } else {
	      if (PW->curChar > 0) {
		PW->curChar -= 1;
		charsToEnd = PW->curChars - PW->curChar;
		offset = UTF8CharAtOffset (PW->value, PW->curChar);
		ix = UTF8FrontCharsWithinWidth (PW->value + offset, PW->NvCols);
		put_chars (PW->wid, UTF8CharAtString(PW->value,PW->curChar),
			   ix+1, PW->vRow, PW->vLcol,
			   FALSE, NORMAL);
		if (PW->curChar == 0)
		  put_chars (PW->wid, " ", 1, PW->vRow, PW->mLcol, FALSE,
			     NORMAL);
		colnsToEnd = UTF8StrWidth (PW->value + offset);
		offRight = colnsToEnd - PW->NvCols;
		if (offRight >= 1)
		  put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE,
			     NORMAL);
	      } else
		ring_bell ();
	    }
	    end_pasteboard_update ();
	    set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
	    continue;
	  }
	  /********************************************************************
	  * Check for right arrow. Move the cursor to right during editing.
	  ********************************************************************/
	  if (PW->key == KB_RIGHTARROW) {
	    begin_pasteboard_update ();
	    if (PW->curCol < PW->vRcol) { /* cursor within the rightmost end */ 
	      int iy;
	      if (PW->curChar < PW->curChars) { /* current char not over the */
						/* end -- no change to string */
		PW->curChar += 1;
		iy = UTF8CharAtWidth (PW->value, PW->curChar);
		if (PW->curCol < (PW->vRcol-1)) { /* <= 72 */
		  /* cursor moves one character to the right */
		  if (PW->curChar == PW->curChars)
		    PW->curCol += 1;
		  else
		    PW->curCol += UTF8CharAtWidth(PW->value, PW->curChar-1);
		} else { /* curCol: 73 | 74 (or at the last 2 column */
		  if (iy == 1 && 
		      UTF8CharAtWidth(PW->value, PW->curChar-1) == 1) {
		    /* cursor moves one character to the right */
		    if (PW->curChar == PW->curChars)
		      PW->curCol += 1;
		    else
		      PW->curCol += UTF8CharAtWidth(PW->value, PW->curChar-1);
		  } else { /* At 73 and cursor moves 1 column */
		    int ix, iy;
		    ix = UTF8FindFirstChar (PW->value, PW->curChar, 
					    PW->NvCols);
		    if (PW->curChar == PW->curChars)
		      iy = 0;
		    else 
		      iy = 1;
		    put_chars (PW->wid, UTF8CharAtString(PW->value, ix),
			       PW->curChar-ix+iy, PW->vRow, PW->vLcol, TRUE,
			       NORMAL);
		    PW->curCol = PW->vLcol + 
				 UTF8FirstCharsWidth (PW->value, PW->curChar) - 
				 UTF8FirstCharsWidth (PW->value, ix); 
		  }
		}
	      } else
		ring_bell ();
	    } else { /* cursor reaches the end -- reload string, cursor may */
		     /* remain the same or move 1 char to left if a */
		     /* multi-column char is moved out and replaced by a */
		     /* single char(s). */
	      int ix;
	      if (PW->curChar < PW->curChars) { /* current char not over the */
						/* end */
		PW->curChar += 1;
		if (PW->curChar == PW->curChars) { /* over the char count */
						   /* reach the end of value */
		  /* Find out the starting char to fill the window. */
		  ix = UTF8FindFirstChar (PW->value, PW->curChar-1,
					  PW->NvCols-1);
		  put_chars (PW->wid, UTF8CharAtString(PW->value, ix),
			     PW->curChars-ix, PW->vRow, PW->vLcol, FALSE,
			     NORMAL);
		  put_chars (PW->wid, " ", 1, PW->vRow, PW->vRcol, FALSE,
			     NORMAL);
		  PW->curCol = PW->vLcol +
			       UTF8FirstCharsWidth (PW->value, PW->curChar-1) -
			       UTF8FirstCharsWidth (PW->value, ix) + 1;
		} else { /* move a new section of value to window. */
			 /* cut 1 char at leftmost and move in 1 or 2 char at */
			 /* rightmost window. */ 
		  int iy, it, ie;
		  ix = UTF8FindFirstChar (PW->value, PW->curChar, PW->NvCols);
		  iy = PW->curChar - ix + 1;
		  it = UTF8StrWidth (UTF8CharAtString (PW->value, ix)) - 
		       UTF8StrWidth (UTF8CharAtString (PW->value, PW->curChar));
		  ie = 0;
		  if (it < (PW->vRcol-PW->vLcol)) {
		    ie = UTF8FrontCharsWithinWidth (
				UTF8CharAtString (PW->value, PW->curChar + 1),
				(PW->vRcol-PW->vLcol)-it) + 1;
		  }
		  put_chars (PW->wid, UTF8CharAtString (PW->value, ix),
			     iy+ie, PW->vRow, PW->vLcol, TRUE, NORMAL);
		  PW->curCol = PW->vLcol +
			       UTF8FirstCharsWidth (PW->value, PW->curChar) -
			       UTF8FirstCharsWidth (PW->value, ix);
		}
		put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE,
			   NORMAL);
		charsToEnd = PW->curChars - PW->curChar;
		colsToEnd = PW->vRcol - PW->curCol + 1;
		offRight = charsToEnd - colsToEnd;
		if (charsToEnd == 1)
		  put_chars (PW->wid, " ", 1, PW->vRow, PW->mRcol, FALSE,
			     NORMAL);
	      } else
		ring_bell ();
	    }
	    end_pasteboard_update ();
	    set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
	    continue;
	  }
	  /********************************************************************
	  * Check for delete key.
	  * len: number of bytes for the character at the cursor
	  * width: number of columns for the character at the cursor
	  * ptr: pointer at the cursor character
	  ********************************************************************/
	  if (PW->key == KB_DELETE) {
	    int ix, len, width;
	    char *ptr = UTF8CharAtString (PW->value, PW->curChar);
	    len =  UTF8CharAtBytes (PW->value, PW->curChar-1);
	    width =  UTF8CharAtWidth (PW->value, PW->curChar-1);
	    begin_pasteboard_update ();
	    if (PW->curCol == PW->vLcol) { /* At the beginning of field */
	      if (PW->curChar > 0) { /* Cursor points on 2nd+ char of value */
		memmove (ptr-len, ptr, strlen(ptr)+1);
		PW->curChar -= 1;
		PW->curChars -= 1;
		PW->curLen -= len; 
		PW->curCharsWidth = UTF8StrWidth (PW->value);
		if (PW->curChar == 0)
		  put_chars (PW->wid, " ", 1, PW->vRow, PW->mLcol,
			     FALSE, NORMAL);
	      } else
		ring_bell ();
	    } else { /* anywhere after the first character field */
	      int vColsToEnd;
	      vColsToEnd = UTF8StrWidth (PW->value) - 
			   UTF8FirstCharsWidth (PW->value, PW->curChar);
	      colsToEnd = PW->vRcol - PW->curCol + 1;
	      offRight = vColsToEnd - colsToEnd;
	      if (offRight > 0) { /* char at location where the ">" is on */
		memmove (ptr-len, ptr, strlen(ptr) + 1);
		PW->curChar -= 1;
		PW->curChars -= 1;
		PW->curCol -= width;
		PW->curCharsWidth = UTF8StrWidth (PW->value);
		PW->curLen -= len;
		colsToEnd += width;
		PW->curCharsWidth = UTF8StrWidth (PW->value);
		ix = UTF8FindLastChar (PW->value, PW->curChar, PW->curChars,
				       PW->vRcol-PW->curCol);
		put_chars (PW->wid, UTF8CharAtString(PW->value, PW->curChar),
			   ix-PW->curChar+((ix+1)==PW->curChars?1:2), 
			   PW->vRow, PW->curCol, TRUE, NORMAL);
		offRight -= width;
		if (offRight <= 0) 
		  put_chars (PW->wid, " ", 1, PW->vRow, PW->mRcol,
			     FALSE, NORMAL);
	      } else {
		if (charsToEnd > 0) { /* cousor at or before the last char */
		  memmove (ptr-len, ptr, strlen(ptr) + 1);
		  PW->curChar -= 1;
		  PW->curChars -= 1;
		  PW->curCol -= width;
		  PW->curLen -= len;
		  PW->curCharsWidth = UTF8StrWidth (PW->value);
		  ix = UTF8FindLastChar (PW->value, PW->curChar, PW->curChars,
					 PW->vRcol-PW->curCol);
		  put_chars (PW->wid, UTF8CharAtString(PW->value, PW->curChar),
			     ix-PW->curChar+1, PW->vRow, PW->curCol, TRUE, 
			     NORMAL);
		} else { /* cursor pass the last char - valid for non-string */
		  PW->curChar -= 1;
		  PW->curChars -= 1;
		  *(UTF8CharAtString(PW->value, PW->curChar)) = NUL;
		  PW->curLen -= len;
		  PW->curCol -= width;
		  PW->curCharsWidth = UTF8StrWidth (PW->value);
		  put_chars (PW->wid, " ", 1, PW->vRow,
			     PW->curCol, FALSE, NORMAL);
		}
	      }
	    }
	    end_pasteboard_update ();
	    set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
	    continue;
	  }
	  /********************************************************************
	  * Check for the `refresh' key.
	  ********************************************************************/
	  if (PW->key == PW->refreshChar) {
	    repaint_screen ();
	    continue;
	  }
	  /********************************************************************
	  * Check for the SOL key.  If the current length of the value is
	  * greater than the number of available columns, the right `more'
	  * indicator is drawn (even though it may already be drawn).
	  ********************************************************************/
	  if (PW->key == PW->SOLchar) {
	    int ix;
	    begin_pasteboard_update ();
	    begin_pasteboard_update ();
	    ix = UTF8FrontCharsWithinWidth (PW->value, PW->NvCols);
	    put_chars (PW->wid, PW->value,
		       ix+1, PW->vRow, PW->vLcol, FALSE, NORMAL);
	    put_chars (PW->wid, " ", 1, PW->vRow, PW->mLcol, FALSE, NORMAL);
	    if (PW->curChars > (ix + 1)) 
	      put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE, NORMAL);
	    PW->curCol = PW->vLcol;
	    PW->curChar = 0;
	    end_pasteboard_update ();
	    set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
	    continue;
	  }
	  /********************************************************************
	  * Check for the EOL key.  The `right column character' is used to
	  * determine if moving the cursor to the end of the value will cause
	  * characters to be off the left end of the value field.  The left
	  * and right `more' indicators are updated regardless of how they may
	  * have already been.
	  ********************************************************************/
	  if (PW->key == PW->EOLchar) {
	    int ix;
	    begin_pasteboard_update ();
	    ix = UTF8BackCharsWithinWidth (PW->value, PW->NvCols - 1);
	    put_chars (PW->wid, UTF8CharAtString(PW->value, ix),
		       PW->curChars-ix, PW->vRow, PW->vLcol, FALSE, NORMAL);
	    if (ix > 0) 
	      put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE, NORMAL);
	    put_chars (PW->wid, " ", 1, PW->vRow, PW->vRcol, FALSE, NORMAL);
	    put_chars (PW->wid, " ", 1, PW->vRow, PW->mRcol, FALSE, NORMAL);
	    PW->curCharsWidth = UTF8StrWidth (PW->value);
	    PW->curCol = MinInt (PW->vLcol + PW->curCharsWidth, PW->vRcol);
	    PW->curChar = PW->curChars;
	    end_pasteboard_update ();
	    set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
	    continue;
	  }
	  /********************************************************************
	  * Check for the toggle insert/overstrike mode key.
	  ********************************************************************/
	  if (PW->key == PW->TOGGLEchar) {
	    PW->insertMode = (PW->insertMode ? FALSE : TRUE);
	    continue;
	  }
	  /********************************************************************
	  * Check for a (printable or) UTF8 character to insert/overstrike.
	  ********************************************************************/
          if (Printable(PW->key)) {
            gotKey[0] = (char) PW->key;
            ic = 1;
          } else {
	    ic = wctomb(gotKey, (wchar_t) PW->key);
	    if (ic > 0) {
	      gotKey[ic] = '\0';
	    }
	    if (ic == 0 || IsUTF8Char((char *)gotKey) != 1) {
	      ring_bell ();
	      continue;
	    }
          }
	  if (ic > 0) {
	    int ix;
	    begin_pasteboard_update ();
	    charsToEnd = PW->curChars - PW->curChar;
	    colsToEnd = PW->vRcol - PW->curCol + 1;
	    if (charsToEnd == 0) {
	      /****************************************************************
	      * The cursor passes the end of the value.
	      ****************************************************************/
	      if (PW->curChars < PW->maxChars) {
		catchrY (PW->value, gotKey, ic, PW->maxChars);
		PW->curLen += (int) UTF8CharBytes (gotKey);
		PW->curChar += 1;
		PW->curChars += 1;
		if (colsToEnd > 1) {
		  put_chars (PW->wid, UTF8CharAtString(PW->value,
						       PW->curChar-1),
			     1, PW->vRow, PW->curCol, FALSE, NORMAL);
		  PW->curCol = UTF8FirstCharsWidth(PW->value, PW->curChar) +
			       PW->vLcol;
		} else {
		  ix = UTF8BackCharsWithinWidth (PW->value, PW->NvCols-1);
		  put_chars (PW->wid, UTF8CharAtString(PW->value, ix),
			     PW->curChars-ix, PW->vRow, PW->vLcol, FALSE,
			     NORMAL);
		  if (ix > 0)
		    put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE,
			       NORMAL);
		}
	      }
	      else
		ring_bell ();
	    } else {
	      /****************************************************************
	      * The cursor is somewhere in the middle of the value.
	      ****************************************************************/
	      if (!PW->insertMode ||
		  (PW->insertMode && PW->curChars < PW->maxChars)) {
		if (PW->insertMode) {
		  /************************************************************
		  * Insert mode.
		  ************************************************************/
		  char *ptr = UTF8CharAtString (PW->value, PW->curChar);
		  int len =  ic;
		  int width = UTF8CharWidth (gotKey);
		  int iy;
		  memmove (ptr+len, ptr, strlen(ptr));
		  memmove (ptr, (char *) gotKey, len);
		  PW->curLen += len;
		  PW->curCharsWidth = UTF8StrWidth (PW->value);
		  if (colsToEnd > 1) {
		    int iz;
		    PW->curChars += 1;
		    /**********************************************************
		    * The cursor is not at the last column of the value field.
		    **********************************************************/
		    if ((PW->curCol+width) < PW->vRcol) { /* adding 1 char */
		      iz = UTF8FindLastChar (PW->value, PW->curChar,
						PW->curChars,
						PW->vRcol - PW->curCol + 1);
	  	      iy = PW->vRcol - PW->curCol + 1;
		      for (ix = 0; ix < iy; ++ix)
			put_chars (PW->wid, " ", 1, PW->vRow, PW->curCol+ix,
				   FALSE, NORMAL);
		      put_chars (PW->wid, 
				 UTF8CharAtString (PW->value, PW->curChar),
				 iz-PW->curChar+1, PW->vRow, PW->curCol,
				 FALSE, NORMAL);
		      offRight = PW->curCol - 1 + UTF8StrWidth (ptr) - 
				 PW->vRcol;
		      if (offRight > 0)
			put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE,
				   NORMAL);
		      PW->curCol += width;
		    } else { /* need to move string 1 char to the left */
		      char *tmp1, *tmp2;
		      int ilast;
		      iz = UTF8FindFirstChar (PW->value, PW->curChar+2,
					      PW->NvCols);
		      tmp1 = UTF8CharAtString (PW->value, iz);
		      ilast = UTF8FindLastChar (PW->value, iz, PW->curChars,
						PW->NvCols);
		      put_chars (PW->wid, tmp1, ilast-iz+1, PW->vRow, 
				 PW->vLcol, TRUE, NORMAL);
		      if (PW->curChars > (ilast+1))
			put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE,
				   NORMAL);
		      tmp2 = UTF8CharAtString (PW->value, PW->curChar+2);
		      PW->curCol = PW->vLcol + UTF8StrWidth (tmp1) -
				   UTF8StrWidth (tmp2) - 1;
		    }
		    PW->curChar += 1;
		  } else {
		    /**********************************************************
		    * The cursor is at the last column of the value field.
		    **********************************************************/
		    PW->curChar += 1;
		    PW->curChars += 1;
		    ix = UTF8BackCharsWithinWidth (PW->value, PW->NvCols);
		    put_chars (PW->wid, UTF8CharAtString (PW->value, ix),
			       PW->curChars-ix, PW->vRow, PW->vLcol, FALSE, 
			       NORMAL);
		    if (ix > 0)
		      put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE,
				 NORMAL);
		  }
		} else {
		  /************************************************************
		  * Overstrike mode. - find char's location and byte length
		  ************************************************************/
		  char *p = UTF8CharAtString (PW->value,PW->curChar);
		  int  iy = UTF8CharAtBytes (PW->value,PW->curChar);
		  int width2 = UTF8CharWidth (gotKey);
		  memmove (p+ic, p+iy, strlen(p+iy));
		  memmove (p, (char *) gotKey, ic);
		  PW->curCharsWidth = UTF8StrWidth (PW->value);
		  colsToEnd = PW->vRcol - PW->curCol;
		  if (colsToEnd > 1) {
		    /**********************************************************
		    * The cursor is not at the last column of the value field.
		    **********************************************************/
		    if ((PW->curCol+width2-1) <= PW->vRcol) {
		      /********************************************************
		      * String with replaced character still fits the window
		      ********************************************************/
		      int ilast; char *tmpx;
		      ilast = UTF8FindLastChar (PW->value, PW->curChar,
						PW->curChars,
						PW->vRcol - PW->curCol + 1);
		      put_chars (PW->wid, 
				 UTF8CharAtString (PW->value, PW->curChar),
				 ilast-PW->curChar+1, PW->vRow, PW->curCol,
				 FALSE, NORMAL);
                      PW->curChar += 1;
		      PW->curCol += width2;
		      if (PW->curChars > (PW->curChar+1)) {
		        tmpx = UTF8CharAtString(PW->value, PW->curChar+1);
			offRight = PW->curCol + UTF8StrWidth (tmpx) - PW->vRcol;
		      } else
			offRight = 0;
		      if (offRight >= 1)
			put_chars (PW->wid, ">", 1, PW->vRow, PW->mRcol, FALSE,
				   NORMAL);
		    } else {
		      char *ptr1, *ptr2;
		      ix = UTF8FindFirstChar (PW->value, PW->curChar,
					      PW->NvCols);
		      ptr1 = UTF8CharAtString(PW->value, ix);
		      ptr2 = UTF8CharAtString (PW->value, PW->curChar + 1);
		      put_chars (PW->wid, ptr1,
			         PW->curChar-ix+1, PW->vRow, PW->vLcol,
				 TRUE, NORMAL);
		      PW->curCol = PW->vLcol + UTF8StrWidth (ptr1) -
				   UTF8StrWidth (ptr2);
		      PW->curChar += 1;
		    }
		  } else {
		    /**********************************************************
		    * The cursor is at the last column of the value field.
		    **********************************************************/
		    offRight = PW->curChars - PW->curChar - 1;
		    if (offRight > 0) {
		      char *ptr1, *ptr2; int twidth;
		      /********************************************************
		      * One or more character off the right end.
		      ********************************************************/
		      PW->curChar += 1;
		      ix = UTF8FindFirstChar (PW->value, PW->curChar, 
					      PW->NvCols);
		      ptr1 = UTF8CharAtString (PW->value, ix);
		      put_chars (PW->wid, ptr1,
				 PW->curChar-ix+1, PW->vRow, PW->vLcol, TRUE,
				 NORMAL);
		      if (ix > 0) 
			put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE,
				   NORMAL);
		      ptr2 = UTF8CharAtString (PW->value, PW->curChar);
		      PW->curCol = PW->vLcol + UTF8StrWidth (ptr1) -
				   UTF8StrWidth (ptr2);
		      if (PW->curChars > (PW->curChar+1)) {
			char *tmpy = UTF8CharAtString (PW->value, PW->curChar+1);
		        twidth = UTF8StrWidth (tmpy);
		      } else
		        twidth = 0;
		      if ((PW->curCol + twidth) > PW-> vRcol)
			put_chars (PW->wid, " ", 1, PW->vRow, PW->mRcol, FALSE,
				   NORMAL);
		    } else {
		      /********************************************************
		      * Cursor is on the last character.
		      ********************************************************/
		      char *ptr1 ;
		      PW->curChar += 1;
		      leftColChar = PW->curChar - PW->NvCols + 1;
		      ix = UTF8BackCharsWithinWidth (PW->value, PW->NvCols -1);
		      ptr1 = UTF8CharAtString (PW->value, ix);
		      put_chars (PW->wid, ptr1, PW->curChars - ix, PW->vRow,
				 PW->vLcol, TRUE, NORMAL);
		      if (ix > 0)
			put_chars (PW->wid, "<", 1, PW->vRow, PW->mLcol, FALSE,
				   NORMAL);
		      PW->curCol = PW->vLcol + UTF8StrWidth (ptr1);
		    }
		  }
		}
	      }
	      else
		ring_bell ();
	    }
	    end_pasteboard_update ();
	    set_cursor_abs (PW->wid, PW->vRow, PW->curCol);
	    continue;
	  }
	  /********************************************************************
	  * None of the above, illegal character.
	  ********************************************************************/
	  ring_bell ();
       }
     }
   }
   va_end (ap);
   return FALSE;                                      /* Illegal operation. */
}

/******************************************************************************
* EditWindow.
******************************************************************************/

#if defined(STDARG)
int EditWindow (int opT, ...)
#else
int EditWindow (va_alist)
va_dcl
#endif
{
   int op;                       /* Operation to perform (eg. create new
				    edit window, delete window, etc.). */
   struct EditWindowStruct *EW;  /* Edit window configuration. */
   va_list ap;
   char gotKey[5];
int ic;

   /***************************************************************************
   * Start variable-length argument list scanning.
   ***************************************************************************/
#if defined(STDARG)
   va_start (ap, opT);
   op = opT;
#else
   VA_START (ap);
   op = va_arg (ap, int);
#endif
   EW = va_arg (ap, struct EditWindowStruct *);
   /***************************************************************************
   * Perform desired operation.  Some operations wait for user input (by
   * falling through the `switch' statement).
   ***************************************************************************/
   switch (op) {
     /*************************************************************************
     * Check for new menu.
     *************************************************************************/
     case NEWew:
     case UPDATEew: {
       /***********************************************************************
       * Get remaining arguments.
       ***********************************************************************/
       EW->insertMode = va_arg (ap, Logical);
       va_end (ap);
       /***********************************************************************
       * Create window?
       ***********************************************************************/
       begin_pasteboard_update ();
       if (op == NEWew) {
	 EW->nRowsTotal = BOO(EW->NhLines > 0,1,0) + EW->NhLines +
			  BOO(EW->NeRows > 0,1,0) + EW->NeRows +
			  BOO(EW->NtLines > 0,1,0) + EW->NtLines + 1;
	 create_virtual_display (EW->nRowsTotal, EW->nColsTotal, &(EW->wid),
				 BORDER, NORMAL);
	 paste_virtual_display (EW->wid, EW->ULrow, EW->ULcol);
	 EW->nColS = EW->nColsTotal - 2;
       }
       /***********************************************************************
       * If specified, write label to window.  If this is an update operation,
       * first overwrite the existing label.
       ***********************************************************************/
       if (op == UPDATEew) {
	 draw_horizontal_line (EW->wid, 0, 1, EW->nColS, NORMAL, FALSE);
       }
       if (EW->label != NULL) {
	 int len = (int) strlen(EW->label);
	 if (len <= EW->nColS) {
	   int nRemainChars = EW->nColS - len;
	   int nCharsBefore = nRemainChars / 2;
	   put_chars (EW->wid, EW->label, len, 0, nCharsBefore + 1, FALSE,
		      REVERSE1);
	 }
       }
       /***********************************************************************
       * If necessary, calculate positions and draw horizontal lines on window.
       ***********************************************************************/
       if (op == NEWew) {
	 int nSections = 0;    /* Number of sections in the window. */
	 int xRow = 1;         /* Start at row 1 (row 0 is top border line). */
	 if (EW->NhLines > 0) {
	   nSections += 1;
	   EW->hRowT = xRow;
	   EW->hRowB = EW->hRowT + EW->NhLines - 1;
	   xRow += EW->NhLines + 1;
	 }
	 if (EW->NeRows > 0) {
	   nSections += 1;
	   EW->eRowT = xRow;
	   EW->eRowB = EW->eRowT + EW->NeRows - 1;
	   xRow += EW->NeRows + 1;
	   if (nSections > 1) draw_horizontal_line (EW->wid, EW->eRowT - 1, 0,
						    EW->nColsTotal - 1, NORMAL,
						    TRUE);
	 }
	 if (EW->NtLines > 0) {
	   nSections += 1;
	   EW->tRowT = xRow;
	   EW->tRowB = EW->tRowT + EW->NtLines - 1;
	   xRow += EW->NtLines + 1;
	   if (nSections > 1)
	     draw_horizontal_line (EW->wid, EW->tRowT - 1, 0,
				   EW->nColsTotal - 1, NORMAL, TRUE);
	 }
       }
       /***********************************************************************
       * If specified, write header section to window.  If an update operation,
       * it is assumed that the number of lines has not changed.
       ***********************************************************************/
       if (EW->NhLines > 0)
	 DrawSection (EW->wid, EW->hRowT, EW->hRowB, 0,
		      EW->NhLines, EW->hLines, EW->nColS);
       /***********************************************************************
       * Write edit section to window.  If an update operation, note that the
       * text may have changed (to more or less lines) but not the number of
       * rows.
       ***********************************************************************/
       EW->nChars = UTF8StrChars (EW->eText, (int) strlen(EW->eText));
       EW->xChars = EW->nChars;
       EW->cursorRow = EW->eRowT;
       EW->cursorCol = 1;
       EW->firstChar = 0;
       EW->cursorChar = 0;
       DrawEditSection (EW);
       /*********************************************************************
       * Setup/display initial percentage indicator.
       *********************************************************************/
       if (EW->ePct) {
	 if (op == NEWew) {
	   EW->ePctRowN = EW->eRowB + 1;
	   EW->ePctColN = EW->nColsTotal - 7;
	 }
	 UpdatePctString (EW->wid, EW->nChars + 1, 1, EW->cursorChar,
			  EW->ePctRowN, EW->ePctColN);
       }
       /***********************************************************************
       * If specified, write trailer section to window.  If an update
       * operation, it is assumed that the number of lines has not changed.
       ***********************************************************************/
       if (EW->NtLines > 0)
	 DrawSection (EW->wid, EW->tRowT, EW->tRowB, 0,
		      EW->NtLines, EW->tLines, EW->nColS);
       /***********************************************************************
       * Update screen.
       ***********************************************************************/
       end_pasteboard_update ();
       /***********************************************************************
       * Position cursor (which must occur after the pasteboard batching is
       * ended).
       ***********************************************************************/
       set_cursor_abs (EW->wid, EW->cursorRow, EW->cursorCol);
       set_cursor_mode (CURSORon);
       return TRUE;
     }
     /*************************************************************************
     * Check for a beep request.
     *************************************************************************/
     case BEEPew:
       ring_bell ();
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be deleted.
     *************************************************************************/
     case DELETEew:
       delete_virtual_display (EW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Get next input (exit key).
     *************************************************************************/
     case READew: {
       int length, i, count;
       set_cursor_abs (EW->wid, EW->cursorRow, EW->cursorCol);
       set_cursor_mode (CURSORon);
       for (;;) {
	  read_input (
#if defined(CURSESui)
		      EW->wid,
#endif
			       &(EW->key), PASSTHRUri, TRUE);
	  /********************************************************************
	  * Check for an exit key.  If no exit keys were specified, any key
	  * causes an exit.
	  ********************************************************************/
	  if (EW->exitChars[0] == NUL) {
	    va_end (ap);
	    return TRUE;
	  }
	  else {
	    int charN;
	    for (charN = 0; EW->exitChars[charN] != NUL; charN++) {
	       if (EW->key == EW->exitChars[charN]) {
		 va_end (ap);
		 return TRUE;
	       }
	    }
	  }
	  /********************************************************************
	  * Check for start-of-line key.
	  ********************************************************************/
	  if (EW->key == EW->SOLkey) {
	    ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for end-of-line key.
	  ********************************************************************/
	  if (EW->key == EW->EOLkey) {
	    ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for start-of-text key.
	  ********************************************************************/
	  if (EW->key == EW->SOTkey) {
	    ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for end-of-text key.
	  ********************************************************************/
	  if (EW->key == EW->EOTkey) {
	    ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for next-word key.
	  ********************************************************************/
	  if (EW->key == EW->NWkey) {
	    ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for delete-line key.
	  ********************************************************************/
	  if (EW->key == EW->DLkey) {
	    if (EW->readOnly)
	      ring_bell ();
	    else {
	      ring_bell ();
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for next-screen key.
	  ********************************************************************/
	  if (EW->key == EW->NSkey) {
	    if (EW->eText[EW->cursorChar] == NUL) ring_bell ();
	    else {
	      int count;       /* Number of lines that cursor is moved down. */
	      /****************************************************************
	      * Move the cursor to the beginning of the line.
	      ****************************************************************/
	      while (EW->cursorChar > 0) {
		if (EW->eText[EW->cursorChar-1] == Nl) break;
		EW->cursorChar -= 1;
	      }
	      /****************************************************************
	      * Move the cursor down the number of rows displayed (or less).
	      ****************************************************************/
	      for (count = 0;
		   EW->eText[EW->cursorChar] != NUL;
		   EW->cursorChar++) {
		 if (EW->eText[EW->cursorChar] == Nl) {
		   count += 1;
		   if (count == EW->NeRows) {
		     EW->cursorChar += 1;
		     break;
		   }
		 }
	      }
	      /****************************************************************
	      * Move the `first character displayed' down the same number of
	      * rows (if necessary).
	      ****************************************************************/
	      if (EW->cursorRow + count > EW->eRowB) {
		int count1;    /* Number of lines after the first displayed. */
		int charN;
		for (count1 = 0, charN = EW->firstChar;
		     EW->eText[charN] != NUL; charN++) {
		   if (EW->eText[charN] == Nl) count1 += 1;
		}
		count1 -= (EW->NeRows - 1);
		count1 = MINIMUM(count1,count);
		while (count1 > 0) {
		  if (EW->eText[EW->firstChar] == Nl) count1 -= 1;
		  EW->firstChar += 1;
		}
		DrawEditSection (EW);
	      }
	      /****************************************************************
	      * Update the percentage and cursor position.
	      ****************************************************************/
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for prev-screen key.
	  ********************************************************************/
	  if (EW->key == EW->PSkey) {
	    if (EW->cursorChar == 0)
	      ring_bell ();
	    else {
	      int count;       /* Number of lines that cursor is moved up. */
	      /****************************************************************
	      * Move the cursor to the beginning of the line.
	      ****************************************************************/
	      while (EW->cursorChar > 0) {
		if (EW->eText[EW->cursorChar-1] == Nl) break;
		EW->cursorChar -= 1;
	      }
	      /****************************************************************
	      * Move the cursor up the number of rows displayed (or less).
	      ****************************************************************/
	      for (count = 0; EW->cursorChar > 0; EW->cursorChar--) {
		 if (EW->eText[EW->cursorChar] == Nl) {
		   count += 1;
		   if (count == EW->NeRows) {
		     if (EW->cursorChar > 0) {
		       EW->cursorChar -= 1;
		       while (EW->cursorChar > 0) {
			 if (EW->eText[EW->cursorChar-1] == Nl) break;
			 EW->cursorChar -= 1;
		       }
		     }
		     break;
		   }
		 }
	      }
	      /****************************************************************
	      * Move the `first character displayed' up the same number of
	      * rows (if necessary).
	      ****************************************************************/
	      if (EW->cursorRow - count < EW->eRowT) {
		while (EW->firstChar > 0) {
		  if (EW->eText[EW->firstChar] == Nl) {
		    count -= 1;
		    if (count == 0) {
		      if (EW->firstChar > 0) {
			EW->firstChar -= 1;
			while (EW->firstChar > 0) {
			  if (EW->eText[EW->firstChar-1] ==  Nl) break;
			  EW->firstChar -= 1;
			}
		      }
		      break;
		    }
		  }
		  EW->firstChar -= 1;
		}
		DrawEditSection (EW);
	      }
	      /****************************************************************
	      * Update the percentage and cursor position.
	      ****************************************************************/
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for down arrow key.
	  ********************************************************************/
	  if (EW->key == KB_DOWNARROW) {
	    char *nextNL = strchr (UTF8CharAtString(EW->eText,EW->cursorChar),
				   Nl);
	    if (nextNL == NULL)
	      ring_bell ();
	    else {
	      int toNL = (int) (nextNL - UTF8CharAtString(EW->eText,
							  EW->cursorChar));
	      EW->cursorChar += (toNL + 1);
	      for (i = 1; i < EW->cursorCol; i++) {
		 if (*(UTF8CharAtString(EW->eText,EW->cursorChar)) == Nl)
		   break;
		 if (*(UTF8CharAtString(EW->eText,EW->cursorChar)) == NUL)
		   break;
		 EW->cursorChar += 1;
	      }
	      if (EW->cursorRow == EW->eRowB) {
		while (*(UTF8CharAtString(EW->eText,EW->firstChar)) != Nl)
		  EW->firstChar += 1;
		EW->firstChar += 1;
		DrawEditSection (EW);
	      }
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for up arrow key.
	  ********************************************************************/
	  if (EW->key == KB_UPARROW) {
	    if (EW->firstChar == 0 && EW->cursorRow == EW->eRowT)
	      ring_bell ();
	    else {
	      /****************************************************************
	      * If the cursor is on the top line then scroll up one line.
	      ****************************************************************/
	      if (EW->cursorRow == EW->eRowT) {
		for (EW->firstChar--; EW->firstChar > 0; EW->firstChar--) {
		   if (*(UTF8CharAtString(EW->eText,EW->firstChar-1)) == Nl)
		     break;
		}
		DrawEditSection (EW);
	      }
	      /****************************************************************
	      * Set the cursor character as follows...
	      * 1. Move backward to the newline character of the preceeding
	      *    line while counting the number of characters moved.
	      * 2. Move backward to the first character of the preceeding
	      *    line.
	      * 3. Move forward to position the cursor character at the same
	      *    column or at the newline character (which ever occurs
	      *    first).
	      ****************************************************************/
	      for (count = 1, EW->cursorChar--;
		   *(UTF8CharAtString(EW->eText,EW->cursorChar)) != Nl;
		   EW->cursorChar--) count += 1;
	      while (EW->cursorChar > 0) {
		if (*(UTF8CharAtString(EW->eText,EW->cursorChar-1)) == Nl)
		  break;
		EW->cursorChar -= 1;
	      }
	      for (i = 1; i < count; i++) {
		 if (*(UTF8CharAtString(EW->eText,EW->cursorChar)) == Nl)
		   break;
		 EW->cursorChar += 1;
	      }
	      /****************************************************************
	      * Update the percentage and cursor position.
	      ****************************************************************/
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for left arrow key.
	  ********************************************************************/
	  if (EW->key == KB_LEFTARROW) {
	    if (EW->cursorChar == 0)
	      ring_bell ();
	    else {
	      if (EW->cursorChar == EW->firstChar) {
		for (EW->firstChar--; EW->firstChar > 0; EW->firstChar--) {
		   if (*(UTF8CharAtString(EW->eText,EW->firstChar-1)) == Nl)
		     break;
		}
		DrawEditSection (EW);
	      }
	      EW->cursorChar -= 1;
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for right arrow key.
	  ********************************************************************/
	  if (EW->key == KB_RIGHTARROW) {
	    if (*(UTF8CharAtString(EW->eText, EW->cursorChar)) == NUL)
	      ring_bell ();
	    else {
	      if (EW->cursorRow == EW->eRowB &&
		  *(UTF8CharAtString(EW->eText, EW->cursorChar)) == Nl) {
		while (*(UTF8CharAtString(EW->eText, EW->firstChar))
		       != Nl) EW->firstChar += 1;
		EW->firstChar += 1;
		DrawEditSection (EW);
	      }
	      EW->cursorChar += 1;
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for the RETURN key.
	  ********************************************************************/
	  if (EW->key == KB_RETURN) {
	    if (EW->readOnly)
	      ring_bell ();
	    else {
	      char *p1;
	      if (EW->nChars == EW->xChars) {
		EW->xChars += EW->nColS;
		EW->eText = cdf_ReallocateMemory (EW->eText,
						  (size_t) (EW->xChars + 1),
						  FatalError);
	      }
	      p1 = UTF8CharAtString (EW->eText, EW->cursorChar);
	      memmove (p1+1, p1, strlen(p1) + 1); 
	      *(p1) = Nl;
	      EW->nChars += 1;
	      EW->cursorChar += 1;
	      if (EW->cursorRow == EW->eRowB) {
		while (*(UTF8CharAtString(EW->eText,EW->firstChar)) != Nl)
		  EW->firstChar += 1;
		EW->firstChar += 1;
	      }
	      DrawEditSection (EW);
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for the DELETE key.
	  ********************************************************************/
	  if (EW->key == KB_DELETE) {
	    if (EW->readOnly)
	      ring_bell ();
	    else {
	      if (EW->cursorChar == 0)
		ring_bell ();
	      else {
		int ilen; char *p1, *p2;
		p1 = UTF8CharAtString (EW->eText, EW->cursorChar);
		p2 = UTF8CharAtString (EW->eText, EW->cursorChar-1);
		memmove (p2, p1, strlen(p1) + 1); 
		EW->nChars -= 1;
		if (EW->cursorChar == EW->firstChar) {
		  for (EW->firstChar--; EW->firstChar > 0; EW->firstChar--) {
		     if (*(UTF8CharAtString(EW->eText,EW->firstChar-1)) == Nl)
		       break;
		  }
		}
		EW->cursorChar -= 1;
		DrawEditSection (EW);
		if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					       EW->cursorChar, EW->ePctRowN,
					       EW->ePctColN);
		SetEditCursor (EW);
	      }
	    }
	    continue;
	  }
	  /********************************************************************
	  * Check for the toggle insert/overstrike mode key.
	  ********************************************************************/
	  if (EW->key == EW->TOGGLEkey) {
	    if (EW->readOnly)
	      ring_bell ();
	    else
	      EW->insertMode = BOO(EW->insertMode,FALSE,TRUE);
	    continue;
	  }
	  /********************************************************************
	  * Check for the `refresh' key.
	  ********************************************************************/
	  if (EW->key == EW->REFRESHkey) {
	    repaint_screen ();
	    continue;
	  }
	  /********************************************************************
	  * Check for a (printable character or) UTF8 character.
	  ********************************************************************/
          if (Printable(EW->key)) {
            gotKey[0] = (char) EW->key;
            ic = 1;
          } else {
	    ic = wctomb(gotKey, (wchar_t) EW->key);
	    if (ic > 0) gotKey[ic] = '\0';
	    if (ic == 0 || IsUTF8Char((char *)gotKey) == 0) {
	      ring_bell ();
	      continue;
	    }
          }
	  if (ic > 0) {
	    if (EW->readOnly)
	      ring_bell ();
	    else {
	      char *p1;
	      p1 = UTF8CharAtString (EW->eText, EW->cursorChar); 
	      if (EW->insertMode ||
		  /* If over-strike mode. */
		  *p1 == Nl || *p1 == NUL) {
		if (EW->nChars == EW->xChars) {
		  EW->xChars += EW->nColS;
		  EW->eText = cdf_ReallocateMemory (EW->eText,
						    (size_t) (EW->xChars + 1),
						    FatalError);
		}
		memmove (p1+ic, p1, strlen(p1) + 1); 
		memmove (p1, (char *) gotKey, ic);
		EW->nChars += 1;
	      } else {
		char *p2 = UTF8CharAtString (EW->eText, EW->cursorChar+1);
		if (strlen(p2) != 0) {
/*		  length = (int) strlen (&(EW->eText[EW->cursorChar])); */
		  memmove (p1+ic, p2, strlen(p2));
		}
		memmove (p1, (char *) gotKey, ic);
	      }
	      DrawEditSection (EW);
	      if (EW->cursorCol < EW->nColS) EW->cursorChar += 1;
	      if (EW->ePct) UpdatePctString (EW->wid, EW->nChars + 1, 1,
					     EW->cursorChar, EW->ePctRowN,
					     EW->ePctColN);
	      SetEditCursor (EW);
	    }
	    continue;
	  }
	  /********************************************************************
	  * Illegal key, ring the bell.
	  ********************************************************************/
	  ring_bell ();
       }
     }
   }
   va_end (ap);
   return FALSE;                                      /* Illegal operation. */
}

/******************************************************************************
* DrawEditSection.
******************************************************************************/

static void DrawEditSection (EW)
struct EditWindowStruct *EW;
{
  char tmp2[312];
  char *charPtr = UTF8CharAtString(EW->eText,EW->firstChar);
  int rowN = EW->eRowT, length, length2, width;
  for (;;) {
     char *nextNL = strchr (charPtr, Nl);
     if (nextNL == NULL) {
       length = UTF8StrChars (charPtr, (int) strlen(charPtr));
       width = UTF8StrWidth (charPtr);
       if (length > 0) {
	 put_chars (EW->wid, charPtr, MINIMUM(length,EW->nColS), rowN, 1,
		    FALSE, NORMAL);
	 if (length < EW->nColS) erase_display (EW->wid, rowN, width+1,
						rowN, EW->nColS);
	 rowN += 1;
       }
       break;
     }
     length = (int) (nextNL - charPtr);
     memcpy (tmp2, charPtr, (size_t) length);
     tmp2[length] = (char) '\0';
     length2 = UTF8StrChars (tmp2, (int) strlen(tmp2));
     put_chars (EW->wid, tmp2, MINIMUM(length2,EW->nColS), rowN, 1, FALSE,
		NORMAL);
     width = UTF8StrWidth (tmp2);
     if (length2 < EW->nColS) erase_display (EW->wid, rowN, width+1,
					     rowN, EW->nColS);
     rowN += 1;
     if (rowN > EW->eRowB) break;
     charPtr = nextNL + 1;
  }
  if (rowN <= EW->eRowB) erase_display (EW->wid, rowN, 1,
					EW->eRowB, EW->nColS);
  return;
}

/******************************************************************************
* SetEditCursor.
******************************************************************************/

static void SetEditCursor (EW)
struct EditWindowStruct *EW;
{
  int charN;
  EW->cursorRow = EW->eRowT;
  EW->cursorCol = 0;
  for (charN = EW->firstChar; charN <= EW->cursorChar; charN++) {
     EW->cursorCol += 1;
     if (*(UTF8CharAtString(EW->eText,charN)) == Nl &&
	 charN < EW->cursorChar) {
       EW->cursorCol = 0;
       EW->cursorRow += 1;
     }
  }
  set_cursor_abs (EW->wid, EW->cursorRow, EW->cursorCol);
  return;
}

/******************************************************************************
* FieldWindow.
*
*       Header section (optional).
*       Fields section (scrollable) containing one or more fields.
*       Trailer section (optional).
*
******************************************************************************/

#if defined(STDARG)
int FieldWindow (int opT, ...)
#else
int FieldWindow (va_alist)
va_dcl
#endif
{
   int op;              /* Operation to perform (eg. create new menu). */
   struct FieldWindowStruct *FW;
			/* Menu configuration. */
   va_list ap;
   char gotKey[5];
   int ic;

   /***************************************************************************
   * Start variable-length argument list scanning.
   ***************************************************************************/
#if defined(STDARG)
   va_start (ap, opT);
   op = opT;
#else
   VA_START (ap);
   op = va_arg (ap, int);
#endif
   FW = va_arg (ap, struct FieldWindowStruct *);
   /***************************************************************************
   * Perform desired operation.  Some operations wait for user input (by
   * falling through the `switch' statement).
   ***************************************************************************/
   switch (op) {
     /*************************************************************************
     * Check for new/modified menu.
     *************************************************************************/
     case NEWfw:
     case UPDATEfw: {
       int nSections;           /* Number of sections on window. */
       int nHorizLines = 0;     /* Number of horizontal lines needed.  At
				   most 2 horizontal lines will be needed
				   to separate the 3 sections. */
       int horizRows[2];        /* Rows at which to draw the horizontal
				   lines. */
       int horizLineN;          /* Horizontal line number. */
       int xRow;                /* Used when determining starting rows for
				   the various sections. */
       /***********************************************************************
       * Get remaining arguments.
       ***********************************************************************/
       FW->fieldN = va_arg (ap, int);
       FW->insert = va_arg (ap, Logical);
       va_end (ap);
       /***********************************************************************
       * Validate menu.
       ***********************************************************************/
       if (FW->NfRows < 1) return FALSE;
       /***********************************************************************
       * Turn on cursor.
       ***********************************************************************/
       set_cursor_mode (CURSORon);
       /***********************************************************************
       * Calculate positions.
       ***********************************************************************/
       if (op == NEWfw) {
	 FW->nColS = FW->nColsTotal - 2;
	 nSections = 0;
	 nHorizLines = 0;
	 /*********************************************************************
	 * Start at row 1 (row 0 is top border line).
	 *********************************************************************/
	 xRow = 1;
	 if (FW->NhLines > 0) {
	   nSections += 1;
	   FW->hRowT = xRow;
	   FW->hRowB = FW->hRowT + FW->NhLines - 1;
	   xRow += FW->NhLines + 1;
	 }
	 nSections += 1;
	 FW->fRowT = xRow;
	 FW->fRowB = FW->fRowT + FW->NfRows - 1;
	 xRow += FW->NfRows + 1;
	 if (nSections > 1) horizRows[nHorizLines++] = FW->fRowT - 1;
	 if (FW->NtLines > 0) {
	   nSections += 1;
	   FW->tRowT = xRow;
	   FW->tRowB = FW->tRowT + FW->NtLines - 1;
	   xRow += FW->NtLines + 1;
	   if (nSections > 1) horizRows[nHorizLines++] = FW->tRowT - 1;
	 }
	 FW->nRowsTotal = xRow;
       }
       /***********************************************************************
       * Create window?
       ***********************************************************************/
       begin_pasteboard_update ();
       if (op == NEWfw) {
	 create_virtual_display (FW->nRowsTotal, FW->nColsTotal, &(FW->wid),
				 BORDER, NORMAL);
	 paste_virtual_display (FW->wid, FW->ULrow, FW->ULcol);
       }
       /***********************************************************************
       * If specified, write label to window.  If this is an update operation,
       * first overwrite the existing label.
       ***********************************************************************/
       if (op == UPDATEfw) {
	 draw_horizontal_line (FW->wid, 0, 1, FW->nColS, NORMAL, FALSE);
       }
       if (FW->label != NULL) {
	 int len = (int) strlen(FW->label);
	 if (len <= FW->nColS) {
	   int nRemainChars = FW->nColS - len;
	   int nCharsBefore = nRemainChars / 2;
	   put_chars (FW->wid, FW->label, len, 0, nCharsBefore + 1, FALSE,
		      REVERSE1);
	 }
       }
       /***********************************************************************
       * If necessary, draw horizontal lines on window.
       ***********************************************************************/
       if (op == NEWfw) {
	 for (horizLineN = 0; horizLineN < nHorizLines; horizLineN++) {
	    draw_horizontal_line (FW->wid, horizRows[horizLineN], 0,
				  FW->nColsTotal-1, NORMAL, TRUE);
	 }
       }
       /***********************************************************************
       * If specified, write header section to window.  If an update operation,
       * it is assumed that the number of lines has not changed.
       ***********************************************************************/
       if (FW->NhLines > 0)
	 DrawSection (FW->wid, FW->hRowT, FW->hRowB, 0,
		      FW->NhLines, FW->hLines, FW->nColS);
       /***********************************************************************
       * Write fields section to window.  If an update operation, note that
       * the number of lines may have changed.
       ***********************************************************************/
       if (op == UPDATEfw) {
	 if (FW->fUpTo != NULL) cdf_FreeMemory (FW->fUpTo, FatalError);
	 if (FW->fDownTo != NULL) cdf_FreeMemory (FW->fDownTo, FatalError);
	 if (FW->fLeftTo != NULL) cdf_FreeMemory (FW->fLeftTo, FatalError);
	 if (FW->fRightTo != NULL) cdf_FreeMemory (FW->fRightTo, FatalError);
       }
       if (FW->nFields > 0) {
	 size_t nBytes = FW->nFields * sizeof(int);
	 FW->fUpTo = (int *) cdf_AllocateMemory (nBytes, FatalError);
	 FW->fDownTo = (int *) cdf_AllocateMemory (nBytes, FatalError);
	 FW->fLeftTo = (int *) cdf_AllocateMemory (nBytes, FatalError);
	 FW->fRightTo = (int *) cdf_AllocateMemory (nBytes, FatalError);
	 CalcItemDirections (FW->nFields, FW->NfLines, FW->fLineNs, FW->fCols,
			     FW->fLens, FW->fDownTo, FW->fUpTo, FW->fLeftTo,
			     FW->fRightTo);
       }
       else {
	 FW->fUpTo = NULL;
	 FW->fDownTo = NULL;
	 FW->fLeftTo = NULL;
	 FW->fRightTo = NULL;
       }
       if (FW->NfLines > FW->NfRows)
	 FW->fScroll = TRUE;
       else
	 FW->fScroll = FALSE;
       /***********************************************************************
       * Determine row number for current field.  If this is an UPDATEfw
       * operation, try to keep the row number the same as it was (or as
       * close as possible).
       ***********************************************************************/
       if (FW->nFields > 0) {
	 if (op == NEWfw)
	   FW->fRowN = MinInt (FW->fRowT + FW->fLineNs[FW->fieldN], FW->fRowB);
	 else {
	   if (FW->fScroll) {
	     int nLinesFromTop = FW->fLineNs[FW->fieldN];
	     int nRowsFromTop = FW->fRowN - FW->fRowT;
	     if (nLinesFromTop < nRowsFromTop)
	       FW->fRowN -= nRowsFromTop - nLinesFromTop;
	     else {
	       int nLinesFromBot = (FW->NfLines-1) - FW->fLineNs[FW->fieldN];
	       int nRowsFromBot = FW->fRowB - FW->fRowN;
	       if (nLinesFromBot < nRowsFromBot)
		 FW->fRowN += nRowsFromBot - nLinesFromBot;
	     }
	   }
	   else
	     FW->fRowN = FW->fRowT + FW->fLineNs[FW->fieldN];
	 }
       }
       else
	 FW->fRowN = FW->fRowT;
       /*********************************************************************
       * Draw fields section.
       *********************************************************************/
       DrawFieldsSection (FW);
       /*********************************************************************
       * Setup/display initial percentage indicator.
       *********************************************************************/
       if (FW->fPct) {
	 int lineNt = FWtopRowLineN (FW);
	 if (op == NEWfw) {
	   FW->fPctRowN = FW->fRowB + 1;
	   FW->fPctColN = FW->nColsTotal - 7;
	 }
	 UpdatePctString (FW->wid, FW->NfLines, FW->NfRows, lineNt,
			  FW->fPctRowN, FW->fPctColN);
       }
       /***********************************************************************
       * If specified, write trailer section to window.  If an update
       * operation, it is assumed that the number of lines has not changed.
       ***********************************************************************/
       if (FW->NtLines > 0)
	 DrawSection (FW->wid, FW->tRowT, FW->tRowB, 0,
		      FW->NtLines, FW->tLines, FW->nColS);
       /***********************************************************************
       * Update screen and place the cursor in the current field.
       ***********************************************************************/
       end_pasteboard_update ();
       FW->charN = 0;
       FW->leftCharN = 0;
       if (FW->nFields > 0) {
	 set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
       }
       return TRUE;
     }
     /*************************************************************************
     * Check for a beep request.
     *************************************************************************/
     case BEEPfw:
       ring_bell ();
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be deleted.
     *************************************************************************/
     case DELETEfw:
       if (FW->fUpTo != NULL) cdf_FreeMemory (FW->fUpTo, FatalError);
       if (FW->fDownTo != NULL) cdf_FreeMemory (FW->fDownTo, FatalError);
       if (FW->fLeftTo != NULL) cdf_FreeMemory (FW->fLeftTo, FatalError);
       if (FW->fRightTo != NULL) cdf_FreeMemory (FW->fRightTo, FatalError);
       delete_virtual_display (FW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be erased (but remain in existence).
     *************************************************************************/
     case UNDISPLAYfw:
       unpaste_virtual_display (FW->wid);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Check if menu should be redisplayed (but don't wait for input - a call
     * with `op = READfw' should be made next).
     *************************************************************************/
     case REDISPLAYfw:
       set_cursor_mode (CURSORon);
       paste_virtual_display (FW->wid, FW->ULrow, FW->ULcol);
       va_end (ap);
       return TRUE;
     /*************************************************************************
     * Get next input.  The cursor is positioned first in case it had been
     * moved.
     *************************************************************************/
     case READfw: {
       int colN;
       if (FW->nFields < 1) return FALSE;
       set_cursor_mode (CURSORon);
       colN = FW->fCols[FW->fieldN] + (FW->charN - FW->leftCharN) + 1;
       set_cursor_abs (FW->wid, FW->fRowN, colN);
       for (;;) {
	  read_input (
#if defined(CURSESui)
		      FW->wid,
#endif
			       &(FW->key), PASSTHRUri, TRUE);
	  /********************************************************************
	  * Check for an exit key.  If no exit keys were specified, any key
	  * causes an exit.
	  ********************************************************************/
	  if (FW->exitChars[0] == NUL) {
	    va_end (ap);
	    return TRUE;
	  }
	  else {
	    int charN;
	    for (charN = 0; FW->exitChars[charN] != NUL; charN++)
	       if (FW->key == FW->exitChars[charN]) {
		 va_end (ap);
		 return TRUE;
	       }
	  }
	  /********************************************************************
	  * Check for next-screen key.
	  ********************************************************************/
	  if (FW->key == FW->NSkey) {
	    begin_pasteboard_update ();
	    NextScreen (FW->NfRows, FW->NfLines, FW->fLineNs, FW->fDownTo,
			FW->fRowB, FW->fRowT, FW->fScroll, &(FW->fieldN),
			&(FW->fRowN));
	    DrawFieldsSection (FW);
	    if (FW->fPct) UpdatePctString (FW->wid, FW->NfLines, FW->NfRows,
					   FWtopRowLineN(FW), FW->fPctRowN,
					   FW->fPctColN);
	    end_pasteboard_update ();
	    FW->charN = 0;
	    FW->leftCharN = 0;
	    set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
	    continue;
	  }
	  /********************************************************************
	  * Check for prev-screen key.
	  ********************************************************************/
	  if (FW->key == FW->PSkey) {
	    begin_pasteboard_update ();
	    PrevScreen (FW->NfRows, FW->fLineNs, FW->fUpTo, FW->fRowT,
			FW->fScroll, &(FW->fRowN), &(FW->fieldN));
	    DrawFieldsSection (FW);
	    if (FW->fPct) UpdatePctString (FW->wid, FW->NfLines, FW->NfRows,
					   FWtopRowLineN(FW), FW->fPctRowN,
					   FW->fPctColN);
	    end_pasteboard_update ();
	    FW->charN = 0;
	    FW->leftCharN = 0;
	    set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
	    continue;
	  }
	  /********************************************************************
	  * Check for down field key.
	  ********************************************************************/
	  if (FW->key == FW_DOWN_FIELD || FW->key == FW_DOWN_FIELDx) {
	    begin_pasteboard_update ();
	    DownArrow (FW->fLineNs, FW->fDownTo, FW->fRowT, FW->fRowB,
		       FW->fScroll, &(FW->fRowN), &(FW->fieldN));
	    DrawFieldsSection (FW);
	    if (FW->fPct) UpdatePctString (FW->wid, FW->NfLines, FW->NfRows,
					   FWtopRowLineN(FW), FW->fPctRowN,
					   FW->fPctColN);
	    end_pasteboard_update ();
	    FW->charN = 0;
	    FW->leftCharN = 0;
	    set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
	    continue;
	  }
	  /********************************************************************
	  * Check for up field key.
	  ********************************************************************/
	  if (FW->key == FW_UP_FIELD || FW->key == FW_UP_FIELDx) {
	    begin_pasteboard_update ();
	    UpArrow (FW->NfLines, FW->fLineNs, FW->fUpTo, FW->fRowT,
		     FW->fRowB, FW->fScroll, &(FW->fRowN), &(FW->fieldN));
	    DrawFieldsSection (FW);
	    if (FW->fPct) UpdatePctString (FW->wid, FW->NfLines, FW->NfRows,
					   FWtopRowLineN(FW), FW->fPctRowN,
					   FW->fPctColN);
	    end_pasteboard_update ();
	    FW->charN = 0;
	    FW->leftCharN = 0;
	    set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
	    continue;
	  }
	  /********************************************************************
	  * Check for left field key.
	  ********************************************************************/
	  if (FW->key == FW_LEFT_FIELD || FW->key == FW_LEFT_FIELDx) {
	    FW->fieldN = FW->fLeftTo[FW->fieldN];
	    FW->charN = 0;
	    FW->leftCharN = 0;
	    set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
	    continue;
	  }
	  /********************************************************************
	  * Check for right field key.
	  ********************************************************************/
	  if (FW->key == FW_RIGHT_FIELD || FW->key == FW_RIGHT_FIELDx) {
	    FW->fieldN = FW->fRightTo[FW->fieldN];
	    FW->charN = 0;
	    FW->leftCharN = 0;
	    set_cursor_abs (FW->wid, FW->fRowN, FW->fCols[FW->fieldN] + 1);
	    continue;
	  }
	  /********************************************************************
	  * Check for left character key.
	  ********************************************************************/
	  if (FW->key == FW_LEFT_CHAR || FW->key == FW_LEFT_CHARx) {
	    if (FW->charN > 0) {
	      int colN, len; char *ptr;
	      if (FW->charN == FW->leftCharN) {
		FW->leftCharN -= 1;
		ptr = UTF8CharAtString (FW->fields[FW->fieldN], FW->leftCharN);
		len = UTF8StrChars (ptr, (int) strlen(ptr));
		put_chars (FW->wid, ptr, MINIMUM(len,FW->fLens[FW->fieldN]),
			   FW->fRowN, FW->fCols[FW->fieldN] + 1, FALSE, NORMAL);
	      }
	      FW->charN -= 1;
	      colN = FW->fCols[FW->fieldN] + (FW->charN - FW->leftCharN);
	      set_cursor_abs (FW->wid, FW->fRowN, colN + 1);
	    }
	    else
	      ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for right character key.
	  ********************************************************************/
	  if (FW->key == FW_RIGHT_CHAR || FW->key == FW_RIGHT_CHARx) {
	    int fieldLen;
	    char *pFieldN = FW->fields[FW->fieldN];
	    fieldLen = UTF8StrChars (pFieldN, (int) strlen(pFieldN));
	    if (FW->charN < fieldLen) {
	      int colN, cursorPos = FW->charN - FW->leftCharN + 1;
	      if (cursorPos == FW->fLens[FW->fieldN]) {
		char *ptr = UTF8CharAtString(pFieldN, FW->leftCharN+1);
		if (FW->charN == (fieldLen - 1)) {
		  int count = FW->fLens[FW->fieldN] - 1;
		  put_chars (FW->wid, ptr, count, FW->fRowN,
			     FW->fCols[FW->fieldN] + 1, FALSE, NORMAL);
		  colN = FW->fCols[FW->fieldN] + count;
		  put_chars (FW->wid, " ", 1, FW->fRowN, colN + 1,
			     FALSE, NORMAL);
		}
		else
		  put_chars (FW->wid, ptr, FW->fLens[FW->fieldN], FW->fRowN,
			     FW->fCols[FW->fieldN] + 1, FALSE, NORMAL);
		FW->leftCharN += 1;
	      }
	      FW->charN += 1;
	      colN = FW->fCols[FW->fieldN] + (FW->charN - FW->leftCharN);
	      set_cursor_abs (FW->wid, FW->fRowN, colN + 1);
	    }
	    else
	      ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for delete key.
	  ********************************************************************/
	  if (FW->key == KB_DELETE) {
	    char *ptr0 = FW->fields[FW->fieldN];
	    if (FW->charN > 0) {
	      int ix;
	      ix = UTF8StrChars (ptr0, (int) strlen(ptr0));
	      if (FW->charN == FW->leftCharN) {
		char *ptr = UTF8CharAtString (ptr0, FW->charN);
		int len =  UTF8CharAtBytes (ptr0, FW->charN-1);
		memmove (ptr-len, ptr, strlen(ptr) + 1);
		FW->charN -= 1;
		FW->leftCharN -= 1;
	      }
	      else {
		int len;
		char *ptr = UTF8CharAtString (ptr0, FW->charN);
		len =  UTF8CharAtBytes (ptr0, FW->charN-1);
		memmove (ptr-len, ptr, strlen(ptr) + 1);
		FW->charN -= 1;
		DrawField (FW);
	      }
	    }
	    else
	      ring_bell ();
	    continue;
	  }
	  /********************************************************************
	  * Check for the `refresh' key.
	  ********************************************************************/
	  if (FW->key == FW->refreshChar) {
	    repaint_screen ();
	    continue;
	  }
	  /********************************************************************
	  * Check for the toggle insert/overstrike key.
	  ********************************************************************/
	  if (FW->key == FW->toggleKey) {
	    FW->insert = BOO(FW->insert,FALSE,TRUE);
	    continue;
	  }
	  /********************************************************************
	  * A (printable character or) UTF8 character to insert/overstrike.
	  ********************************************************************/
	  if (Printable(FW->key)) {
             gotKey[0] = (char) FW->key;
            ic = 1;
          } else {
	    if (IsUTF8Char((char *)&(FW->key)) == 0) {
	      ring_bell ();
	      continue;
	    }
	    ic = wctomb(gotKey, (wchar_t) FW->key);
	    if (ic > 0) gotKey[ic] = '\0';
	  }
	  if (ic > 0) {
	    if (FW->insert) {
	      char *ptr0 = FW->fields[FW->fieldN];
	      int len = UTF8StrChars (ptr0, (int) strlen(ptr0)); 
	      if (len < FW->fMaxs[FW->fieldN]) {
		char *ptr = UTF8CharAtString(ptr0, FW->charN);
		memmove (ptr + ic, ptr, strlen(ptr) + 1);
		memmove (ptr, (char *)gotKey, ic);
		if (FW->charN - FW->leftCharN == FW->fLens[FW->fieldN] - 1) {
		  FW->leftCharN += 1;
		}
		FW->charN += 1;
		DrawField (FW);
	      }
	      else
		ring_bell ();
	    } else {
	      if (FW->charN < FW->fMaxs[FW->fieldN]) {
	        char *ptr0 = FW->fields[FW->fieldN];
		int len2;
		int len = UTF8StrChars (ptr0, (int) strlen(ptr0)); /* char # */
		char *ptr = UTF8CharAtString(ptr0,FW->charN); /* char loc */
		char *ptr2 = UTF8CharAtString(ptr0, FW->charN+1);/*char+1 loc*/
		memmove (ptr + ic, ptr2, strlen(ptr2) + 1);
		memmove (ptr, (char *)gotKey, ic);
		if (FW->charN == len) *(ptr+1) = NUL;
		if (FW->charN - FW->leftCharN == FW->fLens[FW->fieldN] - 1) {
		  FW->leftCharN += 1;
		}
		FW->charN += 1;
		DrawField (FW);
	      }
	      else
		ring_bell ();
	    }
	    continue;
	  }
	  /********************************************************************
	  * Illegal key, ring the bell.
	  ********************************************************************/
	  ring_bell ();
       }
     }
   }
   va_end (ap);
   return FALSE;                                      /* Illegal operation. */
}

/******************************************************************************
* DrawField.
******************************************************************************/

static void DrawField (FW)
struct FieldWindowStruct *FW;
{
  char *ptr = UTF8CharAtString(FW->fields[FW->fieldN],FW->leftCharN);
  int i, colN, len;
  len = UTF8StrChars (ptr, (int) strlen(ptr));
  put_chars (FW->wid, ptr, MINIMUM(len,FW->fLens[FW->fieldN]),
	     FW->fRowN, FW->fCols[FW->fieldN] + 1, FALSE, NORMAL);
  for (i = len; i < FW->fLens[FW->fieldN]; i++) {
     put_chars (FW->wid, " ", 1, FW->fRowN,
		FW->fCols[FW->fieldN] + i + 1, FALSE, NORMAL);
  }
  colN = FW->fCols[FW->fieldN] + (FW->charN - FW->leftCharN);
  set_cursor_abs (FW->wid, FW->fRowN, colN + 1);
  return;
}

/******************************************************************************
* DrawFieldsSection.
******************************************************************************/

static void DrawFieldsSection (FW)
struct FieldWindowStruct *FW; /* Pointer to FieldWindow structure. */
{
  int len;
  int i, rowN, lineN, fieldN; char line[SCREEN_WIDTH+1];
  int charLen, fColN, width; char *aField, *charLoc;
  for (rowN = FW->fRowT, lineN = FWtopRowLineN(FW);
       rowN <= FW->fRowB && lineN < FW->NfLines; rowN++, lineN++) {
     strcpyX (line, FW->fLines[lineN], SCREEN_WIDTH);
     for (fieldN = 0; fieldN < FW->nFields; fieldN++) {
	if (FW->fLineNs[fieldN] == lineN) {
	  aField = (char *) FW->fields[fieldN];
	  len = UTF8StrChars (aField, (int) strlen(aField));
	  fColN = FW->fCols[fieldN];
	  for (i = 0; i < FW->fLens[fieldN]; i++) {
	     if (i < (int) len) {
	       charLen = UTF8CharAtBytes (aField, i);
	       charLoc = UTF8CharAtString (aField, i);
	       memmove (line+fColN, charLoc, charLen);
	     } else {
	       *(line+fColN) = ' ';
	       charLen = 1;
	     }
	     fColN += charLen;
	  }
	}
     }
     len = UTF8StrChars (line, (int) strlen(line));
     width = UTF8StrWidth (line);
     put_chars (FW->wid, line, MINIMUM(FW->nColS,len), rowN, 1,
		FALSE, NORMAL);
     if (width < FW->nColS)
	erase_display (FW->wid, rowN, width+1, rowN, FW->nColS);
  }
  if (rowN <= FW->fRowB)
    erase_display (FW->wid, rowN, 1, FW->fRowB, FW->nColS);
  return;
}

/******************************************************************************
* CalcItemDirections.
******************************************************************************/

static void CalcItemDirections (nItems, NiLines, iLineNs, iCols, iLens,
				iDownTo, iUpTo, iLeftTo, iRightTo)
int nItems;
int NiLines;
int *iLineNs;
int *iCols;
int *iLens;
int *iDownTo;
int *iUpTo;
int *iLeftTo;
int *iRightTo;
{
   int i, j, k, ix, ir, ii;
   int *lineItems, *itemLineI;
   /* This keeps the number of items in each line. */
   lineItems = malloc(sizeof(int) * NiLines);
   /* This keeps each item's position in its line. */ 
   itemLineI = malloc(sizeof(int) * nItems);
   j = iLineNs[0];
   k = 1;
   ix = 0;
   itemLineI[0] = 0;
   for (i = 1; i < nItems; i++) {
     if (j == iLineNs[i]) {
       ++k;
       itemLineI[i] = k - 1;
     } else {
       lineItems[ix] = k;
       ++ix;
       j = iLineNs[i];
       k = 1;
       itemLineI[i] = k - 1;
     }
   }
   lineItems[NiLines-1] = k;
   /***************************************************************************
   * Allocate and calculate center point for each item.
   ***************************************************************************/
   for (i = 0; i < nItems; i++) {
      /************************************************************************
      * Calculate down direction.
      ************************************************************************/
      ir = iLineNs[i] + 1;
      if (ir == NiLines) ir = 0;
      ii = itemLineI[i];
      if (ii >= lineItems[ir]) {
        ii = lineItems[ir] - 1;
      }
      k = 0;
      for (j = 0; j < ir; ++j) 
         k += lineItems[j];
      iDownTo[i] = k + ii;
    }

    for (i = 0; i < nItems; i++) {
      /************************************************************************
      * Calculate up direction.
      ************************************************************************/
      ir = iLineNs[i] - 1;
      if (ir < 0) ir = NiLines - 1;
      ii = itemLineI[i];
      if (ii >= lineItems[ir]) {
        ii = lineItems[ir] - 1;
      }
      k = 0;
      for (j = 0; j < ir; ++j) 
         k += lineItems[j];
      iUpTo[i] = k + ii;
    } 


   /***************************************************************************
   * Calculate directions for each item.
   ***************************************************************************/
   for (i = 0; i < nItems; i++) {
      /************************************************************************
      * Calculate left direction.  First check for the nearest item to the
      * left.  If none found, check for the farthest item to the right (if
      * none found, going left stays at the same item).
      ************************************************************************/
      iLeftTo[i] = i;
      if (nItems > 1) {
	int toLeftLineN = (i > 0 ? iLineNs[i-1] : -1), j;
	if (toLeftLineN == iLineNs[i])
	  iLeftTo[i] = i - 1;
	else
	  for (j = i+1; j < nItems; j++)
	     if (iLineNs[j] == iLineNs[i])
	       iLeftTo[i] = j;
	     else
	       break;
      }
      /************************************************************************
      * Calculate right direction.  First check for the nearest item to the
      * right.  If none found, check for the farthest item to the left (if
      * none found, going right stays at the same item).
      ************************************************************************/
      iRightTo[i] = i;
      if (nItems > 1) {
	int toRightLineN = (i < nItems-1 ? iLineNs[i+1] : -1), j;
	if (toRightLineN == iLineNs[i])
	  iRightTo[i] = i + 1;
	else
	  for (j = i-1; j >= 0; j--)
	     if (iLineNs[j] == iLineNs[i])
	       iRightTo[i] = j;
	     else
	       break;
      }
   }
   free (lineItems);
   free (itemLineI);
   lineItems = NULL;
   itemLineI = NULL;
   return;
}

/******************************************************************************
* UpdatePctString.
******************************************************************************/

static void UpdatePctString (wid, nLines, nRows, topRowLineN, rowN, colN)
WINDOWid wid;
int nLines;
int nRows;
int topRowLineN;
int rowN;
int colN;
{
  char pct[MAX_PCT_LEN+1];
  pct[0] = (char) '\0';
  if (nLines <= nRows)
    strcpyX (pct, " All ", MAX_PCT_LEN);
  else
    if (topRowLineN == 0)
      strcpyX (pct, " Top ", MAX_PCT_LEN);
    else
      if (topRowLineN == nLines - nRows)
	strcpyX (pct, " End ", MAX_PCT_LEN);
      else {
	snprintf (pct, (size_t) sizeof(pct), "%3d%% ", (int)
		  ((100.0 * (((float) topRowLineN) / (nLines - nRows))) + 0.5));
      }
  put_chars (wid, pct, (int) strlen(pct), rowN, colN, FALSE, NORMAL);
  return;
}

/******************************************************************************
* DrawSection.
* Each section starts after "+-----...----+" line in the first cdfedit window.
******************************************************************************/

static void DrawSection (wid, rowT, rowB, topRowLineN, nLines, lineS, nColS)
WINDOWid wid;
int rowT;               /* Top row number. */
int rowB;               /* Bottom row number. */
int topRowLineN;        /* Line number to be displayed in top row. */
int nLines;             /* Number of lines (may be greater than the number of
			   available rows). */
char **lineS;           /* Lines to be displayed.  Capital `S' because of the
			   IBM RS6000. */
int nColS;              /* Number of available columns (first character of a
			   line is displayed in column one [1]). */
{
  int len;
  int rowN, lineN, width;
  for (rowN = rowT, lineN = topRowLineN;
       rowN <= rowB && lineN < nLines; rowN++, lineN++) {
     len = UTF8StrChars (lineS[lineN], (int) strlen(lineS[lineN]));
     put_chars (wid, lineS[lineN], MINIMUM(nColS,len), rowN, 1,
		FALSE, NORMAL);
     width = UTF8StrWidth (lineS[lineN]);
     if (width < nColS)
	erase_display (wid, rowN, width+1, rowN, nColS);
  }
  if (rowN <= rowB) erase_display (wid, rowN, 1, rowB, nColS);
  return;
}

/******************************************************************************
* NextScreen.
******************************************************************************/

static void NextScreen (NiRows, NiLines, iLineNs, iDownTo, iRowB, iRowT,
			iScroll, itemN, iRowN)
int NiRows;
int NiLines;
int *iLineNs;
int *iDownTo;
int iRowB;
int iRowT;
Logical iScroll;
int *itemN;
int *iRowN;
{
  int oldLineN = iLineNs[*itemN];
  int maxLineN = MinInt (oldLineN + NiRows, NiLines - 1);
  for (;;) {
     int itemNt = iDownTo[*itemN];
     int lineNt = iLineNs[itemNt];
     if (lineNt <= maxLineN && lineNt > oldLineN)
       *itemN = itemNt;
     else
       break;
  }
  if (iScroll) {
    int linesToEnd = (NiLines - 1) - iLineNs[*itemN];
    if (*iRowN + linesToEnd < iRowB) *iRowN = iRowB - linesToEnd;
  }
  else
    *iRowN = iRowT + iLineNs[*itemN];
  return;
}

/******************************************************************************
* PrevScreen.
******************************************************************************/

static void PrevScreen (NiRows, iLineNs, iUpTo, iRowT, iScroll, iRowN, itemN)
int NiRows;
int *iLineNs;
int *iUpTo;
int iRowT;
Logical iScroll;
int *iRowN;
int *itemN;
{
  int oldLineN = iLineNs[*itemN];
  int minLineN = MaxInt (oldLineN - NiRows, 0);
  for (;;) {
     int itemNt = iUpTo[*itemN];
     int lineNt = iLineNs[itemNt];
     if (lineNt >= minLineN && lineNt < oldLineN)
       *itemN = itemNt;
     else
       break;
  }
  if (iScroll) {
    int linesToBeg = iLineNs[*itemN];
    if (*iRowN - linesToBeg > iRowT) *iRowN = iRowT + linesToBeg;
  }
  else
    *iRowN = iRowT + iLineNs[*itemN];
  return;
}

/******************************************************************************
* DownArrow.
******************************************************************************/

static void DownArrow (iLineNs, iDownTo, iRowT, iRowB, iScroll, iRowN, itemN)
int *iLineNs;
int *iDownTo;
int iRowT;
int iRowB;
Logical iScroll;
int *iRowN;
int *itemN;
{
  int oldLineN = iLineNs[*itemN];
  *itemN = iDownTo[*itemN];
  if (iScroll) {
    int nLinesDown = iLineNs[*itemN] - oldLineN;
    if (nLinesDown < 0)
      *iRowN = iRowT + iLineNs[*itemN];
    else
      *iRowN = MINIMUM (*iRowN + nLinesDown, iRowB);
  }
  else
    *iRowN = iRowT + iLineNs[*itemN];
  return;
}

/******************************************************************************
* UpArrow.
******************************************************************************/

static void UpArrow (NiLines, iLineNs, iUpTo, iRowT, iRowB, iScroll, iRowN,
		     itemN)
int NiLines;
int *iLineNs;
int *iUpTo;
int iRowT;
int iRowB;
Logical iScroll;
int *iRowN;
int *itemN;
{
  int oldLineN = iLineNs[*itemN];
  *itemN = iUpTo[*itemN];
  if (iScroll) {
    int nLinesUp = oldLineN - iLineNs[*itemN];
    if (nLinesUp < 0) {
      int nLinesFromBot = (NiLines - 1) - iLineNs[*itemN];
      *iRowN = iRowB - nLinesFromBot;
    }
    else
      *iRowN = MaxInt (*iRowN - nLinesUp, iRowT);
  }
  else
    *iRowN = iRowT + iLineNs[*itemN];
  return;
}

/******************************************************************************
* IWtopRowLineN.
******************************************************************************/

static int IWtopRowLineN (IW)
struct ItemWindowStruct *IW;
{
  return BOO(IW->nItems > 0,
	     IW->iLineNs[IW->itemN] - (IW->iRowN - IW->iRowT), 0);
}

/******************************************************************************
* FWtopRowLineN.
******************************************************************************/

static int FWtopRowLineN (FW)
struct FieldWindowStruct *FW;
{
  return BOO(FW->nFields > 0,
	     FW->fLineNs[FW->fieldN] - (FW->fRowN - FW->fRowT), 0);
}

/******************************************************************************
* AllocIW.
******************************************************************************/

void AllocIW (IW, nItems, NiLines, iLineNchars, fatalFnc)
struct ItemWindowStruct *IW;
int nItems;
int NiLines;
int iLineNchars;
void (*fatalFnc) PROTOARGs((char *msg));
{
   IW->nItems = nItems;
   if (IW->nItems > 0) {
     IW->iLineNs = (int *) cdf_AllocateMemory ((size_t)IW->nItems *
					       sizeof(int), fatalFnc);
     IW->iCols = (int *) cdf_AllocateMemory ((size_t)IW->nItems * sizeof(int),
					     fatalFnc);
     IW->iLens = (int *) cdf_AllocateMemory ((size_t)IW->nItems * sizeof(int),
					     fatalFnc);
     IW->ibLens = (int *) cdf_AllocateMemory ((size_t)IW->nItems * sizeof(int),
					      fatalFnc);
   }
   IW->NiLines = NiLines;
   if (IW->NiLines > 0) {
     int lineN, i;
     IW->iLines = (char **) cdf_AllocateMemory ((size_t)IW->NiLines *
						sizeof(char *), fatalFnc);
     for (lineN = 0; lineN < IW->NiLines; lineN++) {
	IW->iLines[lineN] = 
		(char *) cdf_AllocateMemory ((size_t)4*iLineNchars+1,
					     fatalFnc);
	memset (IW->iLines[lineN], (char) ' ', (size_t) 4*iLineNchars);
	IW->iLines[lineN][4*iLineNchars] = NUL;
     }
   }
   return;
}

/******************************************************************************
* AllocFW.
******************************************************************************/

void AllocFW (FW, nFields, NfLines, fLineNchars, fatalFnc)
struct FieldWindowStruct *FW;
int nFields;
int NfLines;
int fLineNchars;
void (*fatalFnc) PROTOARGs((char *msg));
{
   FW->nFields = nFields;
   if (FW->nFields > 0) {
     FW->fLineNs = (int *) cdf_AllocateMemory ((size_t)FW->nFields *
					       sizeof(int), fatalFnc);
     FW->fCols = (int *) cdf_AllocateMemory ((size_t)FW->nFields * sizeof(int),
					     fatalFnc);
     FW->fLens = (int *) cdf_AllocateMemory ((size_t)FW->nFields * sizeof(int),
					     fatalFnc);
   }
   FW->NfLines = NfLines;
   if (FW->NfLines > 0) {
     int lineN, i;
     FW->fLines = (char **) cdf_AllocateMemory ((size_t)FW->NfLines *
						sizeof(char *), fatalFnc);
     for (lineN = 0; lineN < FW->NfLines; lineN++) {
	FW->fLines[lineN] =
		 (char *) cdf_AllocateMemory ((size_t)4*fLineNchars+1,
					      fatalFnc);
	memset (FW->fLines[lineN], (char) ' ', (size_t) 4*fLineNchars);
	FW->fLines[lineN][4*fLineNchars] = NUL;
     }
   }
   return;
}

/******************************************************************************
* FreeIW.
******************************************************************************/

void FreeIW (IW, fatalFnc)
struct ItemWindowStruct *IW;
void (*fatalFnc) PROTOARGs((char *msg));
{
   if (IW->NiLines > 0) {
     int lineN;
     for (lineN = IW->NiLines - 1; lineN >= 0; lineN--) {
	cdf_FreeMemory (IW->iLines[lineN], fatalFnc);
     }
     cdf_FreeMemory (IW->iLines, fatalFnc);
   }
   if (IW->nItems > 0) {
     cdf_FreeMemory (IW->iLens, fatalFnc);
     cdf_FreeMemory (IW->ibLens, fatalFnc);
     cdf_FreeMemory (IW->iCols, fatalFnc);
     cdf_FreeMemory (IW->iLineNs, fatalFnc);
   }
   IW->NiLines = 0;
   IW->nItems = 0;
   IW->iLines = NULL;
   IW->iLineNs = NULL;
   IW->iCols = NULL;
   IW->iLens = NULL;
   IW->ibLens = NULL;
   return;
}

/******************************************************************************
* FreeFW.
******************************************************************************/

void FreeFW (FW, fatalFnc)
struct FieldWindowStruct *FW;
void (*fatalFnc) PROTOARGs((char *msg));
{
   if (FW->NfLines > 0) {
     int lineN;
     for (lineN = FW->NfLines - 1; lineN >= 0; lineN--) {
	cdf_FreeMemory (FW->fLines[lineN], fatalFnc);
     }
     cdf_FreeMemory (FW->fLines, fatalFnc);
   }
   if (FW->nFields > 0) {
     cdf_FreeMemory (FW->fLens, fatalFnc);
     cdf_FreeMemory (FW->fCols, fatalFnc);
     cdf_FreeMemory (FW->fLineNs, fatalFnc);
   }
   FW->NfLines = 0;
   FW->nFields = 0;
   FW->fLines = NULL;
   FW->fLineNs = NULL;
   FW->fCols = NULL;
   FW->fLens = NULL;
   return;
}


/******************************************************************************
* OnlineHelpWindow.
******************************************************************************/

Logical OnlineHelpWindow (ilhFile, helpId)
char *ilhFile;
int helpId;
{
  AOSs1A (header, BLANKs78)
  AOSs1B (trailer,
	  "Exit: ________  NextScreen: ________  PrevScreen: ________")
  static char errorLines[] = "Online help not available.\n";
  static int exitChars[] = { EXITkey_FSI, NUL };
  static char label[] = { BLANKs78 };
  static Logical first = TRUE;
  static struct EditWindowStruct EW = {
    label, 0, 0, SCREEN_WIDTH, 1, header, NULL, 18, 1, trailer, TRUE, TRUE,
    exitChars, REFRESHkey_FSI, NUL, NUL, NUL, NUL, NSkey_FSI, PSkey_FSI, NUL,
    NUL, NUL
  };
  /****************************************************************************
  * Encode label and key definitions the first time.
  ****************************************************************************/
  if (first) {
    snprintf (EW.label, sizeof(BLANKs78), " %s Online Help ", pgmName);
    EncodeKeyDefinitions (1, EW.tLines, EXITkey_FSI, NSkey_FSI, PSkey_FSI);
    first = FALSE;
  }
  /****************************************************************************
  * Load online help.
  ****************************************************************************/
  if (!LoadOnlineHelp(ilhFile,helpId,EW.hLines[0],&EW.eText)) {
    cdf_FreeMemory (EW.eText, FatalError);
    strcpyX (EW.hLines[0], "Error!", 0);
    EW.eText = errorLines;
    EditWindow (NEWew, &EW, TRUE);
    EditWindow (READew, &EW);
    EditWindow (DELETEew, &EW);
    return FALSE;
  }
  /****************************************************************************
  * Display help window/wait for exit key.
  ****************************************************************************/
  EditWindow (NEWew, &EW, TRUE);
  EditWindow (READew, &EW);
  EditWindow (DELETEew, &EW);
  /****************************************************************************
  * Cleanup and return.
  ****************************************************************************/
  cdf_FreeMemory (EW.eText, FatalError);
  return TRUE;
}

/******************************************************************************
* LoadOnlineHelp.
*     You'll notice that the buffers used to hold lines of online help are
* allocated as (SCREEN_WIDTH-2)+1+1.  This is for the number of characters,
* the newline (as returned by `fgets'), and the terminating NUL character.
* When `fgets' is called, the number of characters to read is specified as
* (SCREEN_WIDTH-2)+1+1.  This is for the actual characters plus the newline
* plus one more since `fgets' subtracts one from this value and uses that as
* the maximum number of characters to read (which includes the newline).
******************************************************************************/

Logical LoadOnlineHelp (ilhFile, helpId, header, eText)
char *ilhFile;
int helpId;
char *header;
char **eText;
{
  FILE *fp; int i; size_t length;
  char beginStr[15+1], line[(SCREEN_WIDTH-2)+1+1], helpIdStr[15+1];
  enum { ITEMw, PROMPTw, EDITw } windowType;
  int nBlanks;
  int nestLevel = 0;    /* Depth into `#ifos's. */
  int osMask = 0;       /* When 0, display line.  Note that bit 0 is not used
			   (eg. nesting depth of 1 uses bit 1, etc.). */
#if defined(vms)
  static char thisOS[] = "vms";
#endif
#if defined(unix) || defined(posixSHELL)
  static char thisOS[] = "unix";
#endif
#if defined(dos)
  static char thisOS[] = "dos";
#endif
#if defined(mac)
  static char thisOS[] = "mac";
#endif
#if defined(win32)
  static char thisOS[] = "win";
#endif
  /****************************************************************************
  * Initialize.
  ****************************************************************************/
  *eText = cdf_AllocateMemory ((size_t) 1, FatalError);
  MakeNUL (*eText);
  /****************************************************************************
  * Open help file.
  ****************************************************************************/
  fp = OnlineHelpFP (ilhFile, NULL);
  if (fp == NULL) return FALSE;
  /****************************************************************************
  * Read through help file looking for proper help section.
  ****************************************************************************/
  snprintf (beginStr, (size_t) sizeof(beginStr), "#section %d", helpId);
  while (fgets(line,(SCREEN_WIDTH-2)+1+1,fp) != NULL) {
    /**************************************************************************
    * Strip trailing newline character and check to see if the help section
    * has been found.
    **************************************************************************/
    line[strlen(line)-1] = NUL;
    if (!strcmp(line,beginStr)) {
      /************************************************************************
      * Determine window type.
      ************************************************************************/
      if (fgets(line,(SCREEN_WIDTH-2)+1+1,fp) == NULL) {
	fclose (fp);
	return FALSE;
      }
      line[strlen(line)-1] = NUL;
      if (!strcmp(line,"#item"))
	windowType = ITEMw;
      else
	if (!strcmp(line,"#prompt"))
	  windowType = PROMPTw;
	else
	  if (!strcmp(line,"#edit"))
	    windowType = EDITw;
	  else {
	    fclose (fp);
	    return FALSE;
	  }
      /************************************************************************
      * Build header.
      ************************************************************************/
      if (fgets(line,(SCREEN_WIDTH-2)+1+1,fp) == NULL) {
	fclose (fp);
	return FALSE;
      }
      line[strlen(line)-1] = NUL;
      if (strncmp(line,"#title ",7) != 0) {
	fclose (fp);
	return FALSE;
      }
      strcpyX (header, "Help for ", 0);
      strcatX (header, &line[7], 0);
      snprintf (helpIdStr, (size_t) sizeof(helpIdStr), "[%d%c]", helpId,
	        (windowType == ITEMw ? 'i' : (windowType == PROMPTw ? 'p' :
		 (windowType == EDITw ? 'e' : '?'))));
      nBlanks = (SCREEN_WIDTH-2) - strlen(header) - strlen(helpIdStr);
      CatNcharacters (header, nBlanks, (int) ' ');
      strcatX (header, helpIdStr, 0);
      /************************************************************************
      * Read and save lines until "#endsection" is found.
      ************************************************************************/
      while (fgets(line,(SCREEN_WIDTH-2)+1+1,fp) != NULL) {
	line[strlen(line)-1] = NUL;
	/**********************************************************************
	* Check if at end of this section of help.  If so, delete trailing
	* newline characters, encode key definitions based on window type,
	* close help file, and return.
	**********************************************************************/
	if (!strcmp(line,"#endsection")) {
	  if (nestLevel != 0) {
	    fclose (fp);
	    return FALSE;
	  }
	  length = strlen (*eText);
	  if (length > 0) {
	    for (i = length - 1; i >= 0; i--) {
	       if ((*eText)[i] != Nl) break;
	       (*eText)[i] = NUL;
	    }
	  }
	  switch (windowType) {
	    case ITEMw:
	      EncodeKeyDefinitions (1, eText, NSkey_FSI, PSkey_FSI);
	      break;
	    case PROMPTw:
	      EncodeKeyDefinitions (1, eText, SOLkey_FSI, EOLkey_FSI,
				    INSERTorOVERkey_FSI);
	      break;
	    case EDITw:
	      EncodeKeyDefinitions (1, eText, NSkey_FSI, PSkey_FSI);
	      break;
	  }
	  fclose (fp);
	  return TRUE;
	}
	/**********************************************************************
	* Not at end yet.  Check if an operating system directive.  If not,
	* include the line if all of the bits in the operating system mask
	* are clear.
	**********************************************************************/
	if (line[0] == '#') {
	  /********************************************************************
	  * Check for an `#ifos' directive.  If this operating system is not
	  * specified, then set the bit in the operating system mask for this
	  * nesting level.
	  ********************************************************************/
	  if (!strncmp(line,"#ifos",5)) {
	    nestLevel += 1;
	    if (strstr(line,thisOS) == NULL) SETBIT (osMask, nestLevel);
	    continue;
	  }
	  /********************************************************************
	  * Check for an `#else' directive.  Simply flip the bit in the
	  * operating system mask for this nesting level.
	  ********************************************************************/
	  if (!strcmp(line,"#else")) {
	    if (nestLevel < 1) {
	      fclose (fp);
	      return FALSE;
	    }
	    FLPBIT (osMask, nestLevel);
	    continue;
	  }
	  /********************************************************************
	  * Check for an `#endos' directive.  Clear the bit in the operating
	  * system mask for this nesting level and decrement the nesting level.
	  ********************************************************************/
	  if (!strcmp(line,"#endos")) {
	    if (nestLevel < 1) {
	      fclose (fp);
	      return FALSE;
	    }
	    CLRBIT (osMask, nestLevel);
	    nestLevel -= 1;
	    continue;
	  }
	  /********************************************************************
	  * An unknown directive has been encountered.
	  ********************************************************************/
	  fclose (fp);
	  return FALSE;
	}
	else {
	  if (osMask == 0) {
	    size_t length, newLength;
#if defined(dos)
	    int tabCount, i;
#endif
	    length = strlen (line);
#if defined(dos)
	    for (tabCount = 0, i = 0; i < length; i++) {
	       if (line[i] == Ht) tabCount += 1;
	    }
	    length += (7 * tabCount);
#endif
	    newLength = strlen(*eText) + (length + 1) + 1;
	    *eText = cdf_ReallocateMemory (*eText, newLength,
				       FatalError);
#if defined(dos)
	    for (i = 0; i < tabCount; i++) {
	       strcatX (*eText, "        ", 0);
	    }
	    strcatX (*eText, &line[i], 0);
#else
	    strcatX (*eText, line, 0);
#endif
	    strcatX (*eText, "\n", 0);
	  }
	}
      }
      /************************************************************************
      * `#endsection' not found - error return.
      ************************************************************************/
      fclose (fp);
      return FALSE;
    }
  }
  /****************************************************************************
  * `#section x' not found - error return.
  ****************************************************************************/
  fclose (fp);
  return FALSE;
}

/******************************************************************************
* InfoWindow.
******************************************************************************/

void InfoWindow (message1, message2, message3, center, beep, wait)
char *message1;         /* This message line must exist. */
char *message2;         /* This message line is optional (NULL if absent). */
char *message3;         /* This message line is optional (NULL if absent).
			   If `message2' is NULL, this must also be NULL. */
Logical center;         /* TRUE if window should be in center of screen. */
Logical beep;           /* TRUE if window should beep after being displayed. */
int wait;               /* 0: Read a key before deleting window.
			   >0: Wait `wait' seconds and then delete window. */
{
   static int exitChars[] = { NUL };
   static char eText[INFOtextMAX+1];
   static char ackLabel[] = " Enter any key to acknowledge. ";
   static struct EditWindowStruct EW = {
     NULL, 0, 0, 0, 0, NULL, eText, 0, 0, NULL, FALSE, TRUE, exitChars,
     REFRESHkey_FSI, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL
   };
   EW.label = BOO(wait > 0,NULL,ackLabel);
   if (center) {
     EW.nColsTotal = BOO(EW.label == NULL,0,(int)(strlen(EW.label) + 4));
     EW.nColsTotal = MaxInt (EW.nColsTotal, (int) (strlen(message1) + 2));
     strcpyX (EW.eText, message1, INFOtextMAX);
     strcatX (EW.eText, "\n", INFOtextMAX);
     EW.ULrow = (SCREEN_HEIGHT - 3) / 2;
     EW.NeRows = 1;
     if (message2 != NULL) {
       EW.nColsTotal = MaxInt (EW.nColsTotal, (int) (strlen(message2) + 2));
       strcatX (EW.eText, message2, INFOtextMAX);
       strcatX (EW.eText, "\n", INFOtextMAX);
       EW.ULrow = (SCREEN_HEIGHT - 4) / 2;
       EW.NeRows = 2;
     }
     if (message3 != NULL) {
       EW.nColsTotal = MaxInt (EW.nColsTotal, (int) (strlen(message3) + 2));
       strcatX (EW.eText, message3, INFOtextMAX);
       strcatX (EW.eText, "\n", INFOtextMAX);
       EW.ULrow = (SCREEN_HEIGHT - 5) / 2;
       EW.NeRows = 3;
     }
     EW.ULcol = (SCREEN_WIDTH - EW.nColsTotal) / 2;
   }
   else {
     strcpyX (EW.eText, message1, INFOtextMAX);
     strcatX (EW.eText, "\n", INFOtextMAX);
     EW.ULrow = SCREEN_HEIGHT - 3;
     EW.NeRows = 1;
     if (message2 != NULL) {
       strcatX (EW.eText, message2, INFOtextMAX);
       strcatX (EW.eText, "\n", INFOtextMAX);
       EW.ULrow = SCREEN_HEIGHT - 4;
       EW.NeRows = 2;
     }
     if (message3 != NULL) {
       strcatX (EW.eText, message3, INFOtextMAX);
       strcatX (EW.eText, "\n", INFOtextMAX);
       EW.ULrow = SCREEN_HEIGHT - 5;
       EW.NeRows = 3;
     }
     EW.nColsTotal = SCREEN_WIDTH;
     EW.ULcol = 0;
   }
   EditWindow (NEWew, &EW, TRUE);
   if (beep) EditWindow (BEEPew, &EW);
   if (wait > 0)
     zzzzz ((double) wait);
   else
     EditWindow (READew, &EW);
   EditWindow (DELETEew, &EW);
   return;
}


