/* OpenDoors Online Software Programming Toolkit
 * (C) Copyright 1991 - 1999 by Brian Pirie.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *        File: ODEdStr.c
 *
 * Description: Implementation of od_edit_str(). This is the advanced line
 *              editing function which requires ANSI or AVATAR graphics.
 *
 *   Revisions: Date          Ver   Who  Change
 *              ---------------------------------------------------------------
 *              Oct 13, 1994  6.00  BP   New file header format.
 *              Dec 09, 1994  6.00  BP   Standardized coding style.
 *              Dec 31, 1994  6.00  BP   Use ODTimerSleep() instead of loop.
 *              Aug 19, 1995  6.00  BP   32-bit portability.
 *              Nov 11, 1995  6.00  BP   Removed register keyword.
 *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
 *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
 *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
 *              Dec 23, 1995  6.00  BP   Added EDIT_FLAG_SHOW_SIZE.
 *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
 *              Jan 04, 1996  6.00  BP   Use od_get_input().
 *              Jan 12, 1996  6.00  BP   Claim exclusive use of arrow keys.
 *              Jan 31, 1996  6.00  BP   Added timeout for od_get_input().
 *              Feb 10, 1996  6.00  BP   Fixed ...SHOW_SIZE /w ...PERMALITERAL.
 *              Feb 13, 1996  6.00  BP   Added od_get_input() flags parameter.
 *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
 *              Mar 03, 1996  6.10  BP   Begin version 6.10.
 *              Apr 08, 1996  6.10  BP   Make 'C' use word capitalization.
 *              Aug 10, 2003  6.23  SH   *nix support
 */

#define BUILDING_OPENDOORS

#include <ctype.h>
#include <stddef.h>
#include <string.h>

#include "OpenDoor.h"
#include "ODCore.h"
#include "ODGen.h"
#include "ODPlat.h"
#include "ODKrnl.h"
#include "ODStat.h"


/* Current od_edit_str() state and settings. */
static INT anCurrentFormatOffset[80];
static BOOL abCurrentFormatLiteral[80];
static char szCurrentOriginalString[81];
static char *pszCurrentInput;
static char *pszCurrentFormat;
static unsigned char nCurrentStringLength;
static char chCurrentBlank;


/* Private helper functions used by od_edit_str(). */
static BOOL ODEditIsCharValidForPos(char chEntered, INT nPosition);
static char ODEditAsCharForPos(char chEntered, INT nPosition);
static void ODEditDisplayPermaliteral(WORD nFlags);


/* ----------------------------------------------------------------------------
 * od_edit_str()
 *
 * Provides more advanced editing capabilities than od_get_str(), requiring
 * ANSI, AVATAR or RIP modes.
 *
 * Parameters: pszInput          - Pointer to string where inputted text is
 *                                 stored.
 *
 *             pszFormat         - Pointer to format string, which specifies
 *                                 the format of inputted text.
 *
 *             nRow              - The row number where the input field should
 *                                 begin.
 *
 *             nColumn           - The column number where the input field
 *                                 should begin.
 *
 *             btNormalColour    - Color of normal text.
 *
 *             btHighlightColour - Color of highlighted text.
 *
 *             chBlank           - Character to display blanks with.
 *
 *             nFlags            - Specifies one or more flags, combined with
 *                                 the bitwise-or operator.
 *
 *     Return: One of a number of possible EDIT_RETURN_ values, which indicate
 *             why the function returned.
 */
ODAPIDEF WORD ODCALL od_edit_str(char *pszInput, char *pszFormat, INT nRow,
   INT nColumn, BYTE btNormalColour, BYTE btHighlightColour,
   char chBlank, WORD nFlags)
{
   char chTemp;
   unsigned int nCount;
   unsigned char chCurrentValue;
   char *pchCurrent;
   unsigned int nCursorPos;
   INT nKeysPressed = 0;
   WORD wToReturn;
   BOOL bInsertMode = TRUE;
   char chAddAtEnd = '\0';
   BOOL bNormal = TRUE;
   tODInputEvent InputEvent;

   /* Log function entry if running in trace mode */
   TRACE(TRACE_API, "od_edit_str()");

   /* Verify that OpenDoors has been initialized. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   /* Store pointers to current input string and current format string. */
   pszCurrentInput=(char *)pszInput;
   pszCurrentFormat=(char *)pszFormat;

   /* Check that the parameters passed in are valid. */
   if(pszCurrentInput == NULL || pszCurrentFormat == NULL || nRow < 1
      || nColumn < 1)
   {
      od_control.od_error = ERR_PARAMETER;
      OD_API_EXIT();
      return(EDIT_RETURN_ERROR);
   }

   /* Initially, the maximum length of input string is 0. */
   nCurrentStringLength = 0;

   /* The type that is being examined. */
   chCurrentValue = 0;

   /* Counter of position in format string. */
   nCount = 0;

   /* Loop until we reach the end fo the format string. */
   for(pchCurrent = pszCurrentFormat; *pchCurrent;)
   {
      /* Get next character from format string. */
      chTemp = *pchCurrent++;

      /* If current character is not a literal value. */
      if(chCurrentValue == '\0')
      {
         /* If format string has " or ' characters, then this is the */
         /* beginning of a literal string.                           */
         if(chTemp == 39 || chTemp == 34)
         {
            chCurrentValue = chTemp;
         }

         /* If this is not a literal character, and not a space character... */
         else if(chTemp != 32)
         {
            /* Check that we haven't exceeded the maximum allowable string */
            /* length.                                                     */
            if(nCurrentStringLength >= 80)
            {
               od_control.od_error = ERR_PARAMETER;
               OD_API_EXIT();
               return(EDIT_RETURN_ERROR);
            }

            /* Record format character's position. */
            anCurrentFormatOffset[nCurrentStringLength] = nCount;

            /* Record that this character is not a literal. */
            abCurrentFormatLiteral[nCurrentStringLength] = FALSE;

            /* Increment length of input string. */
            ++nCurrentStringLength;
         }
      }

      /* If this is a literal character. */
      else
      {
         /* Check for end of literal string. */
         if(chTemp == chCurrentValue)
         {
            /* If found, stop literal string processing */
            chCurrentValue = '\0';
         }
         else
         {
            /* Check that we haven't exceeded the maximum allowable string */
            /* length.                                                     */
            if(nCurrentStringLength >= 80)
            {
               od_control.od_error = ERR_PARAMETER;
               OD_API_EXIT();
               return(EDIT_RETURN_ERROR);
            }

            /* Record character's position. */
            anCurrentFormatOffset[nCurrentStringLength] = nCount;

            /* Record that character IS a literal value. */
            abCurrentFormatLiteral[nCurrentStringLength] = TRUE;

            /* Increment length of input string. */
            ++nCurrentStringLength;
         }
      }

      /* Increment format string position. */
      ++nCount;
   }

   /* Check that there is at least one character permitted in the input */
   /* string. If not, return with a parameter error.                    */
   if(nCurrentStringLength==0)
   {
      od_control.od_error = ERR_PARAMETER;
      OD_API_EXIT();
      return(EDIT_RETURN_ERROR);
   }

   /* If editing an existing string. */
   if(nFlags & EDIT_FLAG_EDIT_STRING)
   {
      /* Check for valid existing input string. */
      if(strlen(pszCurrentInput) > nCurrentStringLength)
      {
         pszCurrentInput[nCurrentStringLength] = '\0';
      }

      /* Start with cursor at the end of the string. */
      nCursorPos = strlen(pszCurrentInput);
   }

   /* If we are not editing an existing string. */
   else
   {
      /* Blank-out current string contents. */
      pszCurrentInput[0] = '\0';

      /* Set cursor to beginning of string. */
      nCursorPos = 0;
   }

   /* Store original string, in case user cancels. */
   strcpy(szCurrentOriginalString,pszCurrentInput);

   /* Set appropriate text color. */
   od_set_attrib(btHighlightColour);

   /* Determine appropriate blank character */
   chCurrentBlank = (nFlags & EDIT_FLAG_PASSWORD_MODE) ? ' ' : chBlank;

   /* Turn off insert mode if the strict input or permaliteral flags were */
   /* specified.                                                          */
   if((nFlags & EDIT_FLAG_STRICT_INPUT) || (nFlags & EDIT_FLAG_PERMALITERAL))
   {
      bInsertMode = FALSE;
   }

   /* If the no-initial-redraw flag is not set, then do initial redraw. */
   if(!(nFlags & EDIT_FLAG_NO_REDRAW))
   {
      /* Set to redraw position. */
      od_set_cursor(nRow, nColumn);

      if(nFlags & EDIT_FLAG_PASSWORD_MODE)
      {
         /* If we are in password mode, then just draw password blanks. */
         od_repeat(chBlank, (BYTE)strlen(pszCurrentInput));
      }
      else
      {
         /* Otherwise, display the actual string. */
         od_disp_str(pszCurrentInput);
      }

      if(nFlags & EDIT_FLAG_PERMALITERAL)
      {
         /* If we are in permaliteral mode, then fill the remaining edit */
         /* field with the literal characters.                           */
         ODEditDisplayPermaliteral(nFlags);
      }
      else
      {
         /* Otherwise, fill the remaining edit field with the blank */
         /* character.                                              */
         BYTE btRemaining
            = (BYTE)(nCurrentStringLength - strlen(pszCurrentInput));
         if(!(nFlags & EDIT_FLAG_SHOW_SIZE)) ++btRemaining;
         od_repeat(chCurrentBlank, btRemaining);
      }
   }

   /* Claim exclusive use of arrow keys. */
   ODStatStartArrowUse();

   /* Set the cursor to appropriate position. */
   od_set_cursor(nRow, nColumn + nCursorPos);

   /* Normally, we start the input loop at the keep_going tag. */
   if(bNormal) goto keep_going;

   for(;;)
   {
      /* If auto-accept mode has been specified ... */
      if(nFlags & EDIT_FLAG_AUTO_ENTER)
      {
         /* ... then check whether we have reached the end of the string. */
         if(strlen(pszCurrentInput) == nCurrentStringLength)
         {
            /* Indicate that input has been accepted, rather than cancelled. */
            wToReturn = EDIT_RETURN_ACCEPT;

            /* Return the current string to the caller, if it is valid. */
            goto try_to_accept;
         }
      }

keep_going:
      /* Check whether we have reached a literal character in permaliteral */
      /* mode. If so, we will move past the permanent literal characters   */
      /* automatically.                                                    */
      if((nFlags & EDIT_FLAG_PERMALITERAL)
         && (nCursorPos < nCurrentStringLength))
      {
         if(abCurrentFormatLiteral[nCursorPos])
         {
            if(nCursorPos < strlen(pszCurrentInput))
            {
               goto pressed_right_arrow;
            }
            chTemp = pszCurrentFormat[anCurrentFormatOffset[nCursorPos]];
            ++nKeysPressed;
            goto try_this_character;
         }
      }

get_another_key:
      /* Block, waiting for the next key pressed by the user. */

      od_get_input(&InputEvent, OD_NO_TIMEOUT, GETIN_NORMAL);
      
      /* Increment total number of keystrokes. */
      ++nKeysPressed;

      if(InputEvent.EventType == EVENT_EXTENDED_KEY)
      {
         switch(InputEvent.chKeyPress)
         {
            case OD_KEY_UP:
            case OD_KEY_SHIFTTAB:
               if(nFlags & EDIT_FLAG_FIELD_MODE)
               {
                  wToReturn = EDIT_RETURN_PREVIOUS;
                  goto try_to_accept;
               }
               break;

            case OD_KEY_DOWN:
pressed_down_arrow:
               if(nFlags & EDIT_FLAG_FIELD_MODE)
               {
                  wToReturn = EDIT_RETURN_NEXT;
                  goto try_to_accept;
               }
               break;

            case OD_KEY_RIGHT:
pressed_right_arrow:
               /* If we are not at the end of the string. */
               if(nCursorPos < strlen(pszCurrentInput))
               {
                  /* Move input position right. */
                  nCursorPos++;

                  /* Move the cursor on screen. */
                  od_set_cursor(nRow, nColumn + nCursorPos);
               }
               if(chAddAtEnd)
               {
                  chAddAtEnd = 0;
                  goto add_another_key;
               }
               break;

            case OD_KEY_LEFT:
pressed_left_arrow:
               /* If we are not at the beginning of the string. */
               if(nCursorPos > 0)
               {
                  /* Move input position left. */
                  nCursorPos--;

                  /* Move cursor on screen. */
                  od_set_cursor(nRow, nColumn + nCursorPos);
               }

               /* If we are moving past a permanent literal character, */
               /* then continue moving further left, if possible.      */
               if((nFlags & EDIT_FLAG_PERMALITERAL)
                  && abCurrentFormatLiteral[nCursorPos] && nCursorPos > 0)
               {
                  goto pressed_left_arrow;
               }
               break;

         case OD_KEY_HOME:
            /* If we are not at the beginning of the string. */
            if(nCursorPos != 0)
            {
               /* Move input position to the beginning of the string. */
               nCursorPos = 0;

               /* Move the cursor on the screen. */
               od_set_cursor(nRow, nColumn);
            }
            break;

         case OD_KEY_END:
            /* If we are not at the end of the string .*/
            if(nCursorPos != strlen(pszCurrentInput))
            {
               /* Set the input position to the end of the string. */
               nCursorPos=strlen(pszCurrentInput);

               /* Move cursor on screen. */
               od_set_cursor(nRow,nColumn+nCursorPos);
            }
            break;

         case OD_KEY_DELETE:
pressed_delete:
            /* Check whether delete key is permitted at this time. */
            if(!(nFlags & EDIT_FLAG_STRICT_INPUT)
               && nCursorPos < strlen(pszCurrentInput)
               && !(nFlags & EDIT_FLAG_PERMALITERAL))
            {
               /* Move remaining line, if any, to the left */
               chCurrentValue = strlen(pszCurrentInput) - 1;
               for(nCount = nCursorPos; nCount < chCurrentValue; ++nCount)
               {
                  od_putch(
                     pszCurrentInput[nCount] = pszCurrentInput[nCount + 1]);
               }

               /* Erase the last character. */
               pszCurrentInput[chCurrentValue] = '\0';  

               /* Blank out last character. */
               od_putch(chCurrentBlank);

               /* Move the cursor on the screen. */
               od_set_cursor(nRow, nColumn + nCursorPos);

               /* Update changes to string. */
               goto check_cursor_char;
            }
            break;

         case OD_KEY_INSERT:
            if(!(nFlags & EDIT_FLAG_STRICT_INPUT)
               && !(nFlags & EDIT_FLAG_PERMALITERAL))
            {
               /* Toggle insert setting. */
               bInsertMode = !bInsertMode;
            }
            break;

         }
      }
      else if(InputEvent.EventType == EVENT_CHARACTER)
      {
         chTemp = InputEvent.chKeyPress;
try_this_character:

         if(chTemp == 27)
         {
            /* If cancel key is allowed ... */
            if(nFlags & EDIT_FLAG_ALLOW_CANCEL)
            {
               /* Reset the input string to the original contents. */
               strcpy(pszCurrentInput, szCurrentOriginalString);

               /* Indicate that return reason was due to user cancelling. */
               wToReturn = EDIT_RETURN_CANCEL;

               /* Return after redrawing the original string in the input */
               /* field.                                                  */
               goto exit_and_redraw;
            }
         }


         /* If user pressed [Enter] or [Ctrl]-[Z]. */
         else if(chTemp == 13 || chTemp == 26)
         {
            /* User has accepted input. */
            wToReturn = EDIT_RETURN_ACCEPT;

            /* Return if input string is valid. */
            goto try_to_accept;
         }

         /* If the backspace key has been pressed. */
         else if(chTemp == 8)
         {
   backspace_again:
            /* If we are not already at the beginning of the string. */
            if(nCursorPos > 0)
            {
               if(nFlags & EDIT_FLAG_PERMALITERAL)
               {
                  for(nCount = 0;nCount < nCursorPos; ++nCount)
                  {
                     if(!abCurrentFormatLiteral[nCount]) goto continue_deletion;
                  }
                  goto get_another_key;
               }

   continue_deletion:
               /* If we are at the end of the string. */
               if(nCursorPos == strlen(pszCurrentInput))
               {
                  /* Erase last char in string. */
                  pszCurrentInput[--nCursorPos] = '\0';

                  if((nFlags & EDIT_FLAG_PERMALITERAL)
                     && abCurrentFormatLiteral[nCursorPos])
                  {
                     goto backspace_again;
                  }
                  else
                  {
                     /* Move to new cursor pos. */
                     od_set_cursor(nRow,nColumn+nCursorPos);

                     /* Blank old character. */
                     od_putch(chCurrentBlank);

                     /* Move again to cursor pos. */
                     od_set_cursor(nRow,nColumn+nCursorPos);
                  }
               }

               /* If we are in the middle of the string and we are not in */
               /* string input mode.                                      */
               else if(!(nFlags & EDIT_FLAG_STRICT_INPUT)
                  && !(nFlags & EDIT_FLAG_PERMALITERAL))
               {
                  /* Move cursor left. */
                  --nCursorPos;

                  /* Move cursor on screen. */
                  od_set_cursor(nRow, nColumn + nCursorPos);

                  /* Goto standard delete handler. */
                  goto pressed_delete;
               }
            }
         }

         /* If this is a next field request. */
         else if(chTemp == 9)
         {
            /* Goto down arrow handler. */
            goto pressed_down_arrow;
         }

         /* If Control-Y. */
         else if(chTemp == 25)
         {
            /* Erase entire contents of line. */
            goto kill_whole_line;
         }

         else
         {
            /* If this is the first key pressed, and we are in autodelete mode. */
            if(nKeysPressed == 1 && (nFlags & EDIT_FLAG_AUTO_DELETE))
            {
   kill_whole_line:
               /* If string is not empty. */
               if(strlen(pszCurrentInput) != 0)
               {
                  /* Move to beginning of string. */
                  od_set_cursor(nRow,nColumn);

                  /* Blank out the entire string contents. */
                  od_repeat(chCurrentBlank, (BYTE)strlen(pszCurrentInput));
               }

               /* Move to new cursor position. */
               od_set_cursor(nRow,nColumn);

               /* Update insert position. */
               nCursorPos = 0;

               /* Blank out the current string contents. */
               pszCurrentInput[0] = '\0';
            }

   add_another_key:
            if(!ODEditIsCharValidForPos(chTemp,nCursorPos))
            {
               /* If character is not a valid input char. */
               if(abCurrentFormatLiteral[nCursorPos])
               {
                  if(nCursorPos < strlen(pszCurrentInput))
                  {
                     if(pszCurrentInput[nCursorPos] == 
                        pszCurrentFormat[anCurrentFormatOffset[nCursorPos]])
                     {
                        chAddAtEnd = chTemp;
                        goto pressed_right_arrow;
                     }
                  }
                  chAddAtEnd = chTemp;
                  chTemp = pszCurrentFormat[anCurrentFormatOffset[nCursorPos]];
               }
               else
               {
                  continue;
               }
            }

            /* Convert character to correct value, if applicable. */
            chTemp = ODEditAsCharForPos(chTemp, nCursorPos);

            /* If we are at end of string. */
            if(nCursorPos >= strlen(pszCurrentInput))
            {
               /* Reset original cursor position */
               nCursorPos = strlen(pszCurrentInput);

               /* If there is room to add a char. */
               if(nCursorPos < nCurrentStringLength)
               {
                  /* If password mode */
                  if(nFlags & EDIT_FLAG_PASSWORD_MODE)
                  {
                     /* Display the password character. */
                     od_putch(chBlank);
                  }
                  /* If not in password mode. */
                  else
                  {
                     /* Display the character. */
                     od_putch(chTemp);
                  }

                  /* Store the character. */
                  pszCurrentInput[nCursorPos] = chTemp;

                  /* Add a new string terminator. */
                  pszCurrentInput[++nCursorPos] = '\0';
               }
            }

            /* If in insert mode, but not at end of string. */
            else if(bInsertMode)
            {
               /* If room in string. */
               if(strlen(pszCurrentInput) < nCurrentStringLength)
               {
                  /* If in password mode. */
                  if(nFlags & EDIT_FLAG_PASSWORD_MODE)
                  {
                     /* Move to end. */
                     od_set_cursor(nRow,nColumn+strlen(pszCurrentInput));

                     /* Add another password character. */
                     od_putch(chBlank);
                  }

                  /* If not in password mode. */
                  else
                  {
                     /* Display the new character. */
                     od_putch(chTemp);

                     /* Loop through rest of string. */
                     for(nCount = nCursorPos; nCount < strlen(pszCurrentInput);
                        ++nCount)
                     {
                        /* Display the next remaining character. */
                        od_putch(pszCurrentInput[nCount]);
                     }
                  }

                  pszCurrentInput[(strlen(pszCurrentInput) + 1)] = '\0';

                  /* Sift remaining characters forward. */
                  for(nCount = strlen(pszCurrentInput); nCount > nCursorPos;
                     --nCount)
                  {                     
                     pszCurrentInput[nCount] = pszCurrentInput[nCount-1];
                  }

                  /* Add new char in space. */
                  pszCurrentInput[nCursorPos++] = chTemp;

                  /* Move to new cursor position. */
                  od_set_cursor(nRow, nColumn + nCursorPos);
               }
               else
               {
                  goto get_another_key;
               }
            }

            /* If we are in overwrite mode, but not at end of string. */
            else
            {
               /* If password mode. */
               if(nFlags & EDIT_FLAG_PASSWORD_MODE)
               {
                  /* Display the password character. */
                  od_putch(chBlank);
               }
               /* If not in password mode. */
               else
               {
                  /* Display the character. */
                  od_putch(chTemp);
               }

               /* Add character to string. */
               pszCurrentInput[nCursorPos++] = chTemp;
            }

            /* If not at end of possible string. */
            if(nCursorPos < nCurrentStringLength)
            {
               /* If the next character is literal constant. */
               if(abCurrentFormatLiteral[nCursorPos])
               {
                  chTemp = pszCurrentFormat[anCurrentFormatOffset[nCursorPos]];
                  goto add_another_key;
               }
            }

            if(chAddAtEnd)
            {
               chTemp = chAddAtEnd;
               chAddAtEnd = 0;
               goto add_another_key;
            }


   check_cursor_char:
            /* If there is a character under cursor. */
            if(nCursorPos < strlen(pszCurrentInput))
            {
               /* If character corresponds to the format string. */
               if(ODEditIsCharValidForPos(pszCurrentInput[nCursorPos],
                  nCursorPos))
               {
                  /* Determine correct character for this position. */
                  chTemp = ODEditAsCharForPos(pszCurrentInput[nCursorPos],
                     nCursorPos);

                  /* If actual character is not correct. */
                  if(chTemp != pszCurrentInput[nCursorPos])
                  {                        
                     /* Change character to correct value. */
                     pszCurrentInput[nCursorPos] = chTemp;

                     /* If password mode. */
                     if(nFlags & EDIT_FLAG_PASSWORD_MODE)
                     {
                        /* Display the password character. */
                        od_putch(chBlank);
                     }

                     /* If not in password mode. */
                     else
                     {
                        /* Display the character. */
                        od_putch(chTemp);
                     }

                     /* Reset cursor position. */
                     od_set_cursor(nRow, nColumn + nCursorPos);
                  }
               }
            }
         }
      }
   }

   /* Accept string if it is valid. */
try_to_accept:
   /* If string must be filled. */
   if(nFlags & EDIT_FLAG_FILL_STRING)
   {
      /* If string is not filled, don't return. */
      if(strlen(pszCurrentInput) != nCurrentStringLength) goto keep_going;
   }

   /* Loop through string .... */
   for(nCount = 0; nCount < strlen(pszCurrentInput); ++nCount)
   {
      /* ... testing each character for validity. */
      if(!ODEditIsCharValidForPos(pszCurrentInput[nCount], nCount))
         goto keep_going;
   }

   /* Initially, assume that the string has not been changed. */
   chCurrentValue = FALSE;

   /* Loop through the string. */
   for(nCount = 0; nCount < strlen(pszCurrentInput); ++nCount)
   {
      /* Find correct value for each character. */
      chTemp = ODEditAsCharForPos(pszCurrentInput[nCount], nCount);

      /* If character is not correct. */
      if(chTemp != pszCurrentInput[nCount])           
      {
         /* Change char to correct value */
         pszCurrentInput[nCount] = chTemp;

         /* Remember that string has been changed. */
         chCurrentValue = TRUE;
      }
   }

   /* If permaliteral mode. */
   if(nFlags & EDIT_FLAG_LEAVE_BLANK)
   {
      /* Count # of literal characters. */
      nCount = 0;
      while(nCount<strlen(pszCurrentInput))
      {
         if(abCurrentFormatLiteral[nCount])
         {
            ++nCount;
         }
         else
         {
            break;
         }
      }

      /* If only literals in string. */
      if(strlen(pszCurrentInput) == nCount && nCount > 0)
      {
         /* Then they shouldn't be here. */
         pszCurrentInput[0] = '\0';
         goto exit_and_redraw;
      }
   }

   /* Always redraw if string was changed. */
   if(chCurrentValue) goto exit_and_redraw;

   /* If no-redraw flag not set. */
   if(!(nFlags & EDIT_FLAG_NO_REDRAW))
   {
exit_and_redraw:
      /* Set appropriate text colour. */
      od_set_attrib(btNormalColour);

      /* Set to redraw position. */
      od_set_cursor(nRow,nColumn);

      /* If password mode. */
      if(nFlags & EDIT_FLAG_PASSWORD_MODE)
      {
         /* Display blanked-out string. */
         od_repeat(chBlank, (BYTE)strlen(pszCurrentInput));
      }
      else
      {
         /* Display actual string. */
         od_disp_str(pszCurrentInput);
      }

      /* If we should keep the background. */
      if(nFlags & EDIT_FLAG_KEEP_BLANK)
      {
         /* Then redraw background. */
         if(nFlags & EDIT_FLAG_PERMALITERAL)
         {
            ODEditDisplayPermaliteral(nFlags);
         }
         else
         {
            od_repeat(chCurrentBlank,
               (BYTE)(nCurrentStringLength - strlen(pszCurrentInput) + 1));
         }
      }
      /* If we should erase the background ... */
      else
      {
         /* ... then do it. */
         od_repeat(' ',
            (BYTE)(nCurrentStringLength - strlen(pszCurrentInput) + 1));
      }
   }

   /* Release exclusive use of arrow keys. */
   ODStatEndArrowUse();

   /* Return with appropriate return value. */
   OD_API_EXIT();
   return(wToReturn);
}



/* ----------------------------------------------------------------------------
 * ODEditIsCharValidForPos()                           *** PRIVATE FUNCTION ***
 *
 * Determines whether or not the entered character can be accepted as a valid
 * character (after any possible conversion by ODEditAsCharForPos() is applied)
 * for the specified position in the string.
 *
 * Parameters: chEntered   - The character entered by the user.
 *
 *             nPosition   - The position in the string where this character
 *                           would be inserted.
 *
 *     Return: TRUE if this character should be accepted, FALSE if not.
 */
static BOOL ODEditIsCharValidForPos(char chEntered, INT nPosition)
{
   /* If this character is a literal. */
   if(abCurrentFormatLiteral[nPosition])
   {                                     
      /* Check required literal character. */
      if(chEntered != pszCurrentFormat[anCurrentFormatOffset[nPosition]])
      {                                          
         /* If this is not the correct literal character, then do not */
         /* permit it to be entered in this position. */
         return(FALSE);
      }
      return(TRUE);
   }

   /* If this position has a corresponding format control character, */
   /* then check that control character. The execution path will     */
   /* continue out of this switch statement (rather than returning   */
   /* to the calling function) if and only if the entered character  */
   /* is valid for the format character specified.                   */
   switch(pszCurrentFormat[anCurrentFormatOffset[nPosition]])
   {
      /* Only numerical characters are to be permitted. */
      case '#':
         if(chEntered < '0' || chEntered > '9') return(FALSE);
         break;

      /* Only numerical and space characters are to be permitted. */
      case '%':
         if((chEntered < '0' || chEntered > '9') && chEntered != ' ')
         {
            return(FALSE);
         }
         break;

      /* Only floating point number characters are to be permitted. */
      case '9':
         if(chEntered >= '0' && chEntered <= '9') break;
         if(chEntered == '.' || chEntered == '+' || chEntered == '-') break;
         return(FALSE);

      /* Only "printable" characters are to be permitted. */
      case '*':
         if(chEntered < 32) return(FALSE);
         break;

      /* City name characters are to be permitted. */
      case 'C':
      case 'c':
         if(chEntered >= 'A' && chEntered <= 'Z') break;
         if(chEntered >= 'a' && chEntered <= 'z') break;
         if(chEntered == ' ' || chEntered == ',' || chEntered == '.') break;
         if(chEntered == '*' || chEntered == '?') break;
         return(FALSE);

      /* If only alphabetic characters are to be permitted. */
      case 'A':
      case 'a':
      case 'L':
      case 'l':
      case 'M':
      case 'm':
      case 'U':
      case 'u':
         if(chEntered>='A' && chEntered<='Z') break;
         if(chEntered>='a' && chEntered<='z') break;
         if(chEntered==' ') break;
         return(FALSE);

      /* If only date characters are to be permitted. */
      case 'D':
      case 'd':
         if(chEntered>='0' && chEntered<='9') break;
         if(chEntered=='-' || chEntered=='/') break;
         return(FALSE);

      /* If only MS-DOS filename characters are to be permitted. */
      case 'F':
      case 'f':
         if(chEntered >= 'A' && chEntered <= 'Z') break;
         if(chEntered >= '0' && chEntered <= '9') break;
         if(chEntered >= 'a' && chEntered <= 'z') break;
         switch(chEntered)
         {
            /* Filename separators. */
            case ':':
            case '.':
            case DIRSEP:

            /* Wildcard characters. */
            case '?':
            case '*':

            /* Other valid symbols in filenames */
            case '#':
            case '$':
            case '&':
            case '\'':
            case '(':
            case '>':
            case '-':
            case '@':
            case '_':
            case '!':
            case '{':
            case '}':
            case '~':
               return(TRUE);
         }

         return(FALSE);

      /* If only hexidecimal characters are to be permitted. */
      case 'H':
      case 'h':
         if(chEntered>='0' && chEntered<='9') break;
         if(chEntered>='A' && chEntered<='F') break;
         if(chEntered>='a' && chEntered<='f') break;
         return(FALSE);

      /* If only telephone number characters are to be permitted. */
      case 'T':                                          
      case 't':
         if(chEntered >= '0' && chEntered <= '9') break;
         if(chEntered == '-' || chEntered == '(' || chEntered == ')'
            || chEntered == ' ' || chEntered == '+')
         {
            break;
         }
         return(FALSE);

      /* If filenames with wildcards are to be permitted. */
      case 'W':
      case 'w':
         if(chEntered >= 'A' && chEntered <= 'Z') break;
         if(chEntered >= 'a' && chEntered <= 'z') break;
         if(chEntered == ':' || chEntered == '.' || chEntered == DIRSEP
            || chEntered == '*' || chEntered == '?')
         {
            break;
         }
         return(FALSE);

      /* If alpha-numeric characters are to be permitted. */
      case 'X':
      case 'x':
         if(chEntered >= 'A' && chEntered <= 'Z') break;
         if(chEntered >= 'a' && chEntered <= 'z') break;
         if(chEntered >= '0' && chEntered <= '9') break;
         if(chEntered == ' ') break;
         return(FALSE);

      /* If this is a Yes/No field. */
      case 'Y':
      case 'y':
         if(chEntered == 'y' || chEntered == 'n' || chEntered == 'Y'
            || chEntered == 'N')
         {
            break;
         }
         return(FALSE);
   }

   /* If execution gets to this point, then the character has been approved. */
   return(TRUE);
}



/* ----------------------------------------------------------------------------
 * ODEditAsCharForPos()                                *** PRIVATE FUNCTION ***
 *
 * Converts the character entered by the user to a valid character for this
 * position in the string. For example, for fields that are set to all
 * upper case, this function converts the entered characte to its upper case
 * equivalent.
 *
 * Parameters: chEntered   - Character that was entered by the user.
 *
 *             nPosition   - Position in the string where the character is to
 *                           be entered.
 *
 *     Return: The actual character to add to the input string at this
 *             position.
 */
static char ODEditAsCharForPos(char chEntered, INT nPosition)
{
   /* If this character is a literal. */
   if(abCurrentFormatLiteral[nPosition])
   {                                      
      /* Return the only valid char for this position. */
      return(pszCurrentFormat[anCurrentFormatOffset[nPosition]]);
   }

   /* If this position has a corresponding format control character, */
   /* then check that control character. */
   switch(pszCurrentFormat[anCurrentFormatOffset[nPosition]])
   {
      /* If Yes/No characters are required. */
      case 'Y':
      case 'y':
         return(toupper(chEntered));

      /* If filename characters are required. */
      case 'F':                                       
      case 'f':
         return(toupper(chEntered));

      /* If lower case characters are required. */
      case 'L':
      case 'l':
         return(tolower(chEntered));

      /* If upper case characters are required. */
      case 'U':
      case 'u':
         return(toupper(chEntered));

      /* If automatic capitalization is required. */
      case 'M':
      case 'm':
      case 'C':
      case 'c':
         /* First character is always upper case. */
         if(nPosition == 0) return(toupper(chEntered));

         /* Check for other base cases. */
         if(abCurrentFormatLiteral[nPosition-1]) return(toupper(chEntered));
         if(toupper(pszCurrentFormat[anCurrentFormatOffset[nPosition]]) != 'M'
            && toupper(pszCurrentFormat[anCurrentFormatOffset[nPosition]])
            != 'C')
         {
            return(toupper(chEntered));
         }

         /* If previous character is a word delimiter, then this character */
         /* should be uppper case.                                         */
         if(pszCurrentInput[nPosition-1] == ' '
            || pszCurrentInput[nPosition-1] == '.'
            || pszCurrentInput[nPosition-1] == ','
            || pszCurrentInput[nPosition-1] == '-')
         {
            return(toupper(chEntered));                                             /* Otherwise, this should be lower */
         }

         /* Otherwise, this character should be lower-case. */
         return(tolower(chEntered));
   }

   return(chEntered);
}


/* ----------------------------------------------------------------------------
 * ODEditDisplayPermaliteral()                         *** PRIVATE FUNCTION ***
 *
 * Displays permaliterals (characters specified in the format string that
 * should be returned in the input string, but which the user may never
 * change).
 *
 * Parameters: nFlags - Flags parameter that was passed into od_edit_str().
 *
 *     Return: void
 */
static void ODEditDisplayPermaliteral(WORD nFlags)
{
   INT nCount;
   BYTE btRepeat = 0;

   for(nCount = strlen(pszCurrentInput); nCount <= nCurrentStringLength;
      ++nCount)
   {
      if(nCount != nCurrentStringLength)
      {
         if(abCurrentFormatLiteral[nCount])
         {
            if(btRepeat > 0)
            {
               od_repeat(chCurrentBlank, btRepeat);
               btRepeat = 0;
            }
            od_putch(pszCurrentFormat[anCurrentFormatOffset[nCount]]);
         }
         else
         {
            ++btRepeat;
         }
      }
      else
      {
         if(!(nFlags & EDIT_FLAG_SHOW_SIZE))
         {
            ++btRepeat;
         }
      }
   }

   if(btRepeat > 0) od_repeat(chCurrentBlank, btRepeat);
}