
static char rcsid[] = "@(#)$Id: in_utils.c,v 4.1 90/04/28 22:43:13 syd Exp $";

/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 4.1 $   $State: Exp $
 *
 * 			Copyright (c) 1986, 1987 Dave Taylor
 * 			Copyright (c) 1988, 1989, 1990 USENET Community Trust
 *******************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 *
 *	Syd Weinstein, Elm Coordinator
 *	elm@DSI.COM			dsinc!elm
 *
 *******************************************************************************
 * $Log:	in_utils.c,v $
 * Revision 4.1  90/04/28  22:43:13  syd
 * checkin of Elm 2.3 as of Release PL0
 * 
 *
 ******************************************************************************/

/** Mindless I/O routines for ELM 
	
**/

#include "headers.h"
#include <errno.h>
#include <ctype.h>

#ifdef BSD
#  undef tolower
#endif

extern int errno;		/* system error number */

unsigned alarm();

#define isstopchar(c)		(c == ' ' || c == '\t' || c == '/')
#define isslash(c)		(c == '/')
#define erase_a_char()		{ Writechar(BACKSPACE); Writechar(' '); \
			          Writechar(BACKSPACE); fflush(stdout); }

int
want_to(question, dflt)
char *question, dflt;
{
	/** Ask 'question' on LINES-2 left enough to just leave room for an
	    answer, returning the answer in lower case.
	    Echo answer as full "Yes" or "No".  'dflt' is the 
	    default answer if <return> is pressed. (Note: 'dflt' is also what 
	    will be returned if <return> is pressed!)
	**/
	register int ch, cols;

	cols = COLUMNS - (strlen(question) + 5 );	/* 5 for "Yes." + 1 */

	MoveCursor(LINES-3, cols);
	CleartoEOLN();
	PutLine3(LINES-3, cols,"%s%c%c", question, dflt, BACKSPACE);
	fflush(stdout);
	fflush(stdin);

	ch = ReadCh();
	ch = tolower(ch);

	while (!( ch == 'y' || ch == 'n' || ch == '\n' || ch == '\r')) {
	  ch = ReadCh();
	  ch = tolower(ch);
	}
	if(ch == '\n' || ch == '\r')
	  ch = dflt;

	if(ch == 'y')
	  Write_to_screen("Yes.", 0);
	else
	  Write_to_screen("No.", 0);

	return(ch);
}

int
read_number(ch)
char ch;
{
	/** Read a number, where 'ch' is the leading digit! **/
	
	char buff[NLEN];
	int  num;

	buff[0] = ch;
	buff[1] = '\0';

	PutLine0(LINES-3, COLUMNS-40,"Set current message to :");
	if (optionally_enter(buff, LINES-3, COLUMNS-15, TRUE, FALSE) == -1)
	  return(current);

	sscanf(buff,"%d", &num);
	return(num);
}

int
optionally_enter(string, x, y, append_current, passwd)
char *string;
int  x,y, append_current, passwd;
{
	/** This will display the string on the screen and allow the user to
	    either accept it (by pressing RETURN) or alter it according to
	    what the user types.   The various flags are:
	         string    is the buffer to use (with optional initial value)
	 	 x,y	   is the location we're at on the screen (-1,-1 means
			   that we can't use this info and need to find out
			   the current location)
		 append_current  means that we have an initial string and that
			   the cursor should be placed at the END of the line,
			   not the beginning (the default).
		 passwd	   accept non-printing characters and do not echo
			   entered characters.
	      
	    If we hit an interrupt or EOF we'll return non-zero.
	**/

	int ch;
	register int ch_count = 0, iindex = 0, escaped = OFF;

	clearerr(stdin);

	if(!passwd) {
	  if(!(x >=0 && y >= 0))
	    GetXYLocation(&x, &y);
	  PutLine1(x, y, "%s", string);	
	}

	CleartoEOLN();

	if (! append_current) {
	  MoveCursor(x,y);
	}
	else
	  iindex = strlen(string);

	if (cursor_control)
	  transmit_functions(OFF);

	/** now we have the screen as we want it and the cursor in the 
	    right place, we can loop around on the input and return the
	    string as soon as the user presses <RETURN>
	**/

	do {
	  ch = getchar();

	  if (ch == ctrl('D') || ch == EOF) {		/* we've hit EOF */
	    if (cursor_control)
	      transmit_functions(ON);
	    return(1);
	  }

	  if (ch_count++ == 0) {
	    if (ch == '\n' || ch == '\r') {
	      if (cursor_control)
	        transmit_functions(ON);
	      return(0);
	    }
	    else if (! append_current) {
	      CleartoEOLN();
	      iindex = (append_current? strlen(string) : 0);
	    }
	  }

	  /* the following is converted from a case statement to
	     allow the variable characters (backspace, kill_line
	     and break) to be processed.  Case statements in
	     C require constants as labels, so it failed ...
	  */

	  if (ch == backspace &&
	    (!escaped || (!isprint(ch) && !passwd))) {
	    /* This is tricky. Here we are dealing with all situations
	     * under which a backspace (really whatever erase char is
	     * set to, not necessarily \b) erases the previous character.
	     * It will erase unless escaped, because if it's escaped
	     * it is taken literally. There is one exception to that --
	     * if backspace would be rejected (we don't accept non-printing
	     * characters in non-passwd mode), we accept it here as an
	     * erasing character, for it if got rejected there would
	     * be no way of erasing a preceding backslash. */
	    escaped = OFF;
	    if (iindex > 0) {
	      if(!passwd)
		Writechar(BACKSPACE);
  	      iindex--;
	    }
	    if(!passwd) {
	      Writechar(' ');
	      Writechar(BACKSPACE);
	      fflush(stdout);
	    }
	  }
	  else if (ch == EOF || ch == '\n' || ch == '\r') {
	    escaped = OFF;
	    string[iindex] = '\0';
	    if (cursor_control)
	      transmit_functions(ON);
	    return(0);
	  }
	  else if (!passwd && ch == ctrl('W')) {	/* back up a word! */
	    escaped = OFF;
	    if (iindex == 0)
	      continue;		/* no point staying here.. */
	    iindex--;
	    if (isslash(string[iindex])) {
	      erase_a_char();
	    }
	    else {
	      while (iindex >= 0 && isspace(string[iindex])) {
	        iindex--;
	        erase_a_char();
	      }

	      while (iindex >= 0 && ! isstopchar(string[iindex])) {
	        iindex--;
	        erase_a_char();
	      }
	      iindex++;	/* and make sure we point at the first AVAILABLE slot */
	    }
	  }
	  else if (!passwd && ch == ctrl('R')) {
	    escaped = OFF;
	    string[iindex] = '\0';
	    PutLine1(x,y, "%s", string);	
	    CleartoEOLN();
	  }
	  else if (!escaped && ch == kill_line) {
	    /* needed to test if escaped since kill_line character could
	     * be a desired valid printing character */
	    escaped = OFF;
	    if(!passwd) {
	      MoveCursor(x,y);
	      CleartoEOLN();
	    }
	    iindex = 0;
	  }
	  else if (ch == '\0') {
	    escaped = OFF;
	    if (cursor_control)
	      transmit_functions(ON);
	    fflush(stdin); 	/* remove extraneous chars, if any */
	    string[0] = '\0'; /* clean up string, and... */
	    return(-1);
	  }
	  else if (!passwd && !isprint(ch)) {
	    /* non-printing character - warn with bell*/
	    /* don't turn off escaping backslash since current character
	     * doesn't "use it up".
	     */
	    Writechar('\007');
	  }
	  else {  /* default case */
	      if(escaped && (ch == backspace || ch == kill_line)) {
		/* if last character was a backslash,
		 * and if this character is escapable
		 * simply write this character over it even if
		 * this character is a backslash.
		 */
		if(!passwd)
		  Writechar(BACKSPACE);
		iindex--;
		string[iindex++] = ch;
		if(!passwd)
		  Writechar(ch);
	        escaped = OFF;
	      } else {
		string[iindex++] = ch;
		if(!passwd)
		  Writechar(ch);
		escaped = ( ch == '\\' ? ON : OFF);
	      }
	  }
	} while (iindex < SLEN);

	string[iindex] = '\0';

	if (cursor_control)
	  transmit_functions(ON);

	return(0);
}

int
pattern_enter(string, alt_string, x, y, alternate_prompt)
char *string, *alt_string, *alternate_prompt;
int  x,y;
{
	/** This function is functionally similar to the routine
	    optionally-enter, but if the first character pressed
	    is a '/' character, then the alternate prompt and string
	    are used rather than the normal one.  This routine 
	    returns 1 if alternate was used, 0 if not
	**/

	int ch;
	register iindex = 0, escaped = OFF;

	PutLine1(x, y, "%s", string);	
	CleartoEOLN();
	MoveCursor(x,y);

	if (cursor_control)
	  transmit_functions(OFF);

	ch = getchar();

	if (ch == '\n' || ch == '\r') {
	  if (cursor_control)
	    transmit_functions(ON);
	  return(0);	/* we're done.  No change needed */
	}
	
	if (ch == '/') {
	  PutLine1(x, 0, "%s", alternate_prompt);
	  CleartoEOLN();
	  (void) optionally_enter(alt_string, x, strlen(alternate_prompt)+1,
		 FALSE, FALSE);
	  return(1);
	}

	CleartoEOLN();

	iindex = 0;

	if (ch == kill_line) {
	  MoveCursor(x,y);
          CleartoEOLN();
	  iindex = 0;
	}
	else if (ch != backspace) {
	  if(ch == '\\') escaped = ON;
	  Writechar(ch);
	  string[iindex++] = ch;
	}
	else if (iindex > 0) {
	  iindex--;
	  erase_a_char();
	}
	else {
	  Writechar(' ');
	  Writechar(BACKSPACE);
	}

	do {
	  fflush(stdout);
	  ch = getchar();

	  /* the following is converted from a case statement to
	     allow the variable characters (backspace, kill_line
	     and break) to be processed.  Case statements in
	     C require constants as labels, so it failed ...
	  */

	    if (ch == backspace &&
	      (!escaped || !isprint(ch)) ) {
	      /* This is tricky. Here we are dealing with all situations
	       * under which a backspace (really whatever erase char is
	       * set to, not necessarily \b) erases the previous character.
	       * It will erase unless escaped, because if it's escaped
	       * it is taken literally. There is one exception to that --
	       * if backspace would be rejected (we don't accept non-printing
	       * characters in non-passwd mode), we accept it here as an
	       * erasing character, for it if got rejected there would
	       * be no way of erasing a preceding backslash. */
	      escaped = OFF;
              if (iindex > 0) {
		iindex--;
		erase_a_char();
	      }
	      else {
		Writechar(' ');
		Writechar(BACKSPACE);
	      }
	    }
	    else if (ch == '\n' || ch == '\r') {
	      escaped = OFF;
	      string[iindex] = '\0';
	      if (cursor_control)
	        transmit_functions(ON);
	      return(0);
	    }
	    else if (ch == ctrl('W')) {
	      escaped = OFF;
	      if (iindex == 0)
	        continue;		/* no point staying here.. */
	      iindex--;
	      if (isslash(string[iindex])) {
	        erase_a_char();
	      }
	      else {
	        while (iindex >= 0 && isspace(string[iindex])) {
	          iindex--;
	          erase_a_char();
	        }

	        while (iindex >= 0 && ! isstopchar(string[iindex])) {
	          iindex--;
	          erase_a_char();
	        }
	        iindex++;/* and make sure we point at the first AVAILABLE slot */
	      }
	    }
	    else if (ch == ctrl('R')) {
	      escaped = OFF;
	      string[iindex] = '\0';
	      PutLine1(x,y, "%s", string);	
	      CleartoEOLN();
	    }
	    else if (!escaped && ch == kill_line) {
	      /* needed to test if escaped since kill_line character could
	       * be a desired valid printing character */
	      escaped = OFF;
	      MoveCursor(x,y);
              CleartoEOLN();
	      iindex = 0;
	    }
	    else if (ch == '\0') {
	      escaped = OFF;
	      if (cursor_control)
	        transmit_functions(ON);
	      fflush(stdin); 	/* remove extraneous chars, if any */
	      string[0] = '\0'; /* clean up string, and... */
	      return(-1);
	    }
	    else if (!isprint(ch)) {
	      /* non-printing character - warn with bell*/
	      /* don't turn off escaping backslash since current character
	       * doesn't "use it up".
	       */
	      Writechar('\007');
	    }
	    else {  /* default case */
		if(escaped && (ch == backspace || ch == kill_line)) {
		  /* if last character was a backslash,
		   * and if this character is escapable
		   * simply write this character over it even if
		   * this character is a backslash.
		   */
		  Writechar(BACKSPACE);
		  iindex--;
		  string[iindex++] = ch;
		  Writechar(ch);
		  escaped = OFF;
		} else {
		  string[iindex++] = ch;
		  Writechar(ch);
		  escaped = ( ch == '\\' ? ON : OFF);
		}
	    }
	} while (iindex < SLEN);

	string[iindex] = '\0';

	if (cursor_control)
	  transmit_functions(ON);
	return(0);
}

int
GetPrompt()
{
	/** This routine does a read/timeout for a single character.
	    The way that this is determined is that the routine to
	    read a character is called, then the "errno" is checked
	    against EINTR (interrupted call).  If they match, this
	    returns NO_OP_COMMAND otherwise it returns the normal
	    command.  On BSD systems, the EINTR will never be returned
	    so we instead longjmp from the signal handler.
	**/

	int ch;

	if (timeout > 0) {
	  alarm((unsigned) timeout);
#ifdef	BSD
	  if (setjmp(GetPromptBuf)) {
	    InGetPrompt = 0;
	    ch = NO_OP_COMMAND;
	    alarm((unsigned) 0);
	  }
	  else {
	    InGetPrompt = 1;
	    ch = ReadCh();
	    InGetPrompt = 0;
	    alarm((unsigned) 0);
	  }
#else
	    errno = 0;	/* we actually have to do this.  *sigh*  */
	    ch = ReadCh();
	    if (errno == EINTR) ch = NO_OP_COMMAND;
	    alarm((unsigned) 0);
#endif
	}
	else
	  ch = ReadCh();

	return(ch);
}
