Subject: curses problem?
To: None <amiga@NetBSD.ORG>
From: Calvin <diavolo@azure.engin.umich.edu>
List: amiga
Date: 09/20/1995 02:31:36
I'm having oddball keyboard problems with the caps coming on 
unexpectedly.  I'm running 1.0.  I havn't seen this problem occur too 
much however I managed to get hold of source code for a typing tutor 
style program.... it works ok, but apparantly the caps lock key comes on 
sporatically ... and hitting the caps lock key helps sometimes, and 
sometimes it doesn't.  In fact, sometimes the caps lock becomes inverted 
and will type lower case with the light on.  (!)  This is with a low load 
average.  Can somebody else have a look to see if it's just me?

--
 o/ \o/  o  <o   o  o   o    She walks in  beauty, like the night
<|   |  /|\  |> <|><|> <|\       Of cloudless climes and starry skies;
/|  / \  /| / \ //  \\ / \   And all that's best of dark and bright
""""""""""""""""""""""""""""     Meet in her aspect and her eyes.  (Byron)

/*
 *  ttype         By Chris Bertin, 1983      
 * 		  Modified by Paul Hubbard, 1985
 *
 *  Description:  TTYPE allows you to practice terminal or typewriter
 *  		  typing. TTYPE is self-explanatory if you request
 *		  instructions.
 *
 *  Compilation:  cc -o ttype ttype.c -lcurses -ltermlib -lcompat
 *
 *  Invocation:   ttype
 */

static char cpright[] = "(C) Chris Bertin, 1983";
static char cpleft[] = "Paul Hubbard, 1985";

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/timeb.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <curses.h>

#define WORDLIST	"/usr/share/dict/words"
#define LFCR		"\n\r"
#define CHAR	0	/* answer types */
#define NUM	1	/* ... */
#define PRACTICE 0	/* run types */
#define DICT	1	/* ... */
#define FILEIN	2	/* ... */
#define MAX	128	/* std array size as well as max ASCII */
#define SPEED	0	/* screen locations */
#define SCORE	2	/* ... */
#define TYPE	LINES -12	/* ... */
#define ASK	LINES - 3	/* ... */
#define BOTTOM	LINES - 2	/* ... */
#define ALLCHARS "&*()_+|\\{}:\"~<>?-=[];'`,./!#$%^ ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 abcdefghijklmnopqrstuvwxyz"

#define moveto(x,y,str) if(CA)move(x,y),clrtoeol();else pr(0,str);
#define dorefresh()	if(CA){clearok(stdscr,TRUE);refresh();clearok(stdscr,FALSE);}
#define sethelp()	wclear(helpscr);wrefresh(helpscr);moveto(TYPE,0,LFCR);

char what_chars[MAX*2];	/* the characters to practice on */
char try_str[MAX*2];	/* the string to be retyped */
char typed_str[MAX*2];	/* the chars being typed by the user */
char work_str[MAX*2];	/* work space (temp string) */
char badchar[MAX];	/* errors; the mistyped chars are pointers into them */
char gbadchar[MAX];	/* same but session total */
int xcoor,ycoor;        /* temp coordinates */
int isafastscreen;	/* is a fast screen? */
int trials;		/* number of trials */
int npractice;		/* number of characters to practice on (practice opt.)*/
char lasterr[500];	/* last error, the next string will start with it */
int err_num;            /* last error pointer */
int linerrors, toterrors, gtoterrors;	/* errors per line / run / session */
float totlngth, gtotlngth;	/* chars typed per run / session */
float tottime, gtottime;	/* time spent per run / session */

struct timeb tb, ta;
struct stat sbuf;
FILE *fp, *dictfp, *fopen();
WINDOW *helpscr, *subwin();

main()
{
	register option, screenloc, lngth, more = 1;
	int out_size, nwords, instruct;
	char fname[MAX];
	float chkstr();

	setbuf(stdin, (char *)NULL);
	ftime(&ta);
	srandom(getpid() + ta.millitm - getuid()); /* should be unique... */
	initscr();
	scrollok(stdscr, FALSE);
	(void) signal(SIGFPE, SIG_IGN);

	raw(), noecho();
	moveto(TYPE - 6, 0, "");
	pr(2, "\t\t\tP R A C T I C E    T Y P I N G");
	pr(2, "\t\t\t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
	if(instruct = answer(work_str, "Instructions? (y or n)", CHAR) == 1)
		helpscr = subwin(stdscr, 9, COLS, TYPE, 0);
	gtoterrors = gtotlngth = gtottime = 0.;
	zero(gbadchar, sizeof gbadchar);
	while(more) {
		screenloc = TYPE;
		if(instruct)
			help1();
		(void) answer(work_str, "What text type do you want?", CHAR);
		if(! strcmp(work_str, "words")) {
			option = DICT;
			if(dictfp == NULL) /* don't redo it */
				if(((stat(WORDLIST, &sbuf)) < 0) ||
				   ((dictfp = fopen(WORDLIST,"r")) == NULL)){
					note("Can't open %s", WORDLIST);
					continue;
				}
			if(instruct)
				help3();
			nwords = answer(work_str,
				"How many words in each line?", NUM);
			if(nwords > COLS/11)
				note("Set to maximum allowed (%d)",
					(nwords = COLS/11));
		}
		else if(! strcmp(work_str, "file")) {
			option = FILEIN;
			if(instruct)
				help2();
			(void) answer(fname, "Enter file name:", CHAR);
			if(! *fname)
				continue;
			if((fp = fopen(fname, "r")) == NULL) {
				note("Can't open %s", fname);
				continue;
			}
		}
		else {
			option = PRACTICE;
			strcpy(what_chars, work_str[0] ? work_str : ALLCHARS);
			npractice = strlen(what_chars);
			if(instruct)
				help4();
			out_size = answer(work_str,
				"How many characters in each string?", NUM);
		}
		if(option != FILEIN) {
			if(instruct)
				help5();
			trials = answer(work_str, "How many trials?", NUM);
		}
		if(instruct)
			help7();
		zero(badchar, sizeof badchar);
		err_num = toterrors = 0;
		totlngth = tottime = 0.;
		if(CA)
			clear();
		moveto(SCORE, 0, LFCR);
		pr(2, "\tNext line: ^C, Quit: ^D, Redraw: ^L");
		while(makestr(option, out_size, nwords) >= 0) {
			ioctl(0, TIOCFLUSH, 0);
			if(CA)
				note("", "");
			if((lngth = chkstr(screenloc)) == 0)
				continue;
			totlngth += lngth;
			toterrors += linerrors;
			if(isafastscreen)
				score(badchar, toterrors, totlngth, tottime);
			if((screenloc += 3) > (ASK - 2))
				screenloc = TYPE;
		}
		gtotlngth += totlngth;
		gtoterrors += toterrors;
		if( ! CA || ! isafastscreen)
			score(badchar, toterrors, totlngth, tottime);
		more = answer(work_str, "Try again?", CHAR);
	}
	leave();
}

answer(str, msg, type)
register char *str;
char *msg;
{
	register c, i, ret = 0;

	for(;;) {
		moveto(ASK, 10, "\n\r> ");
		pr(1, "%s: ", msg);
		for(i=0; ((c = getch()) != '\n' && c != '\r') ;)
		    switch(c) {
			case '\f':
				dorefresh();
				break;
			case '\10':
				if(i>0)
					--i, pr(1, "\010 \010");
				break;
			case '\04':
			case '\03':
			case 0177:
				leave();
			default:
				pr(1, "%c", (str[i++] = noctrl(c)));
		    }
		if(CA)
			note("", "");
		while(str[--i] == ' ') ;
		str[++i] = 0;
		if(type == CHAR)
			if(*str == 'y')
				return(1);
			else
				return(0);
		if((ret = atoi(str)) <= 0) {
			note("Numeric value (> 0) required", "");
			continue;
		}
		return(ret);
	}
}

makestr(opt, lng, n_of_wrds) /* builds the string to be typed */
{
	register ind;

	if(opt != FILEIN && trials <= 0)
		return(-1);
	zero(try_str, sizeof try_str);
	switch(opt) {
	case FILEIN:
		if(fgets(try_str, sizeof try_str, fp) == NULL)
			return(fclose(fp));
		fixstr(try_str);
		return(1);
	case DICT:
		for(ind = 0; ind < n_of_wrds ; ++ind, strcat(try_str, " ")) {
			if(fseek(dictfp, random()%(long)sbuf.st_size, 0) == -1)
				strcpy(work_str, "fseek failed!!!");
			fgets(work_str, sizeof work_str, dictfp);
			if(fgets(work_str, sizeof work_str, dictfp) == NULL)
				strcpy(work_str, "End of file!!!");
			work_str[strlen(work_str) - 1] = 0;
			strcat(try_str, work_str);
		}
		return(--trials);
	case PRACTICE:
		for(ind = 0 ; ind < lng ; ++ind)
			try_str[ind] = what_chars[(int)random() % npractice];
		try_str[0] = (lasterr[err_num] ? lasterr[err_num] : try_str[0]);
		err_num = 0;
		return(--trials);
	}
	/* NOTREACHED */
}

fixstr(str)
char *str;
{
	register char *in, *out;
	short space = 0;

	for(in = str ; isspace(*in) ; ++in) ;
	for(out = str ; *in ; ++in) {
		if(*in == '\010') {
			if(out > str)
				out--;
			continue;
		}
		if(isspace(*in) && space)
			continue;
		space = isspace(*in);
		*out++ = noctrl(*in);
	}
	*out = 0;
}

float
chkstr(location) /* checks the typing */
{
	register ind, c = 0;
	register char *typed_pnt;
	register float n_of_chars;
	float f, elapsed, starttime, gettime();

	zero(typed_str, sizeof typed_str);
	typed_pnt = typed_str;
	if(strlen(try_str) >= COLS-1) {
		note("Line too long, truncated", "");
		try_str[COLS-1] = 0;
	}
	if((n_of_chars = (float) putline(try_str, location)) == 0.)
		return(0.);
	moveto(location+1, 0, LFCR);
	refresh();
	for(linerrors = ind = 0; ind < n_of_chars ; ++ind) {
		switch(c = getch()) {
		  case '\n':
		  case '\r':
			--ind;
			break;
		  case 0177:
                  case '\b':
			getyx(stdscr,ycoor,xcoor);
			move(ycoor,xcoor-1);
 			delch();
			if (lasterr[err_num-1] == try_str[ind-1])
			{
				--linerrors;
				--badchar[lasterr[err_num-1]];
				--gbadchar[lasterr[err_num-1]];
				--err_num;
			}
			clrtoeol();
			refresh();
			ind -= 2;
			break;
		  case '\f':
			dorefresh();
			--ind;
			break;
		  case '\03':
			if((n_of_chars = ind) == 0) /* force out */
				return(0.);
			break;
		  case '\04':
			gtotlngth += totlngth + ind;
			gtoterrors += toterrors + linerrors;
			leave();
		  default: /* a character... */
			if(ind == 0)
				starttime = gettime(0, "");
			if(c != try_str[ind]) { /* error */
				pr(0, "%c", ch_case(c, try_str[ind]));
				warnerror(location+1, ind+1);
				lasterr[err_num] = try_str[ind];
				++err_num;
				++linerrors;
				++badchar[lasterr[err_num-1]];
				++gbadchar[lasterr[err_num-1]];
			}
			else {
				pr(0, "%c", noctrl(c));
				clrtoeol();
			}
			refresh();
			break;
		}
	}
	*typed_pnt = 0;
	standend();
	clrtoeol();
	elapsed = gettime(12, "/ ") - starttime;
	tottime += elapsed;
	gtottime += elapsed;
	moveto(SPEED, COLS-40, LFCR);
	standout();
	if(! CA)
		printf("\t\t\t\t");
	pr(2, "%.1f secs, %d error(s), %.2f w/min", elapsed, linerrors,
		(f=((n_of_chars / 5.) - linerrors) / (elapsed / 60.))>0.?f:0.0);
	standend();
	refresh();
	return(n_of_chars);
}

float
gettime(where, str)
register char *str;
{
	struct tm *localtime();
	register struct tm *tm;
	register x, y;

	ftime(&tb);
	if(isafastscreen) {
		tm = localtime(&tb.time);
		getyx(stdscr, y, x);
		move(SPEED, where);
		pr(1, "%s%02d:%02d:%02d.%02d", str, tm->tm_hour, tm->tm_min,
			tm->tm_sec, (tb.millitm / 10));
		clrtoeol();
		move(y, x);
		refresh();
	}
	return((float) (tb.time - ta.time) + ((float)tb.millitm / 1000.));
}

putline(outstr, where)
register char *outstr;
{
	register n;
	char *rindex();
	register char *back = rindex(outstr, '\0') - 1;

	while(isspace(*back))
		*back-- = 0;
	moveto(where, 0, "\r");
	for(n = 0 ; *outstr ; ++n)
		pr(0, "%c", noctrl(*outstr++));
	if(n)
		refresh();
	return(n);
}

warnerror(x, y)
{
	if(! isafastscreen || y > COLS-15) {
		putchar('\07');
		return;
	}
	clrtoeol();
	move(x, y+3);
	standout();
	pr(0, " Error");
	standend();
	move(x, y);
	refresh();
}

/* NOSTRICT */
/* VARARGS */
note(str1, str2)
char *str1;
long str2;
{
	moveto(BOTTOM+1, COLS-40, LFCR);
	clrtoeol();
	standout();
	pr(2, str1, str2);
	standend();
}

ch_case(bad, good) /* converts to upper or lower case */
{
	register ret;

	if(isupper(bad))
		return((good - (ret=tolower(bad))) ? ret : bad);
	if(islower(bad))
		return((good - (ret=toupper(bad))) ? ret : bad);
	return(noctrl(bad));
}

noctrl(ch)
register ch;
{
	if(ch == '\t')
		return(' ');
	return(iscntrl(ch) ? (ch - '\01' + 'A') : ch);
}

score(errstr, err, lng, ttime) /* prints the score */
char *errstr;
register float lng, ttime;
{
	register s, i, n=0;
	float f;

	if(lng == 0.)
		return;
	moveto(SCORE, 0, "\n\n\r");
	s = (lng - (float) err) * 100. / lng;
	pr(0,"%d errors on %.0f characters (%d %% error), ", err, lng, (100-s));
	pr(1, "average speed: %.2f words/minute",
		((f = ((lng / 5.) - err) / (ttime / 60.)) >0. ? f : 0.), err);
	if(err) {
		moveto(SCORE + 2, 0, LFCR);
		for(n = i = 0 ; i < MAX ; ++i)
			if(errstr[i] != 0) {
				pr(0, "%4d errors on '%c'", errstr[i], i);
				if(++n > 3) {
					n = 0;
					pr(1, LFCR);
				}
			}
		pr(1, LFCR);
	}
	return;
}

/* NOSTRICT */
/* VARARGS */
pr(mode, fmt, str1, str2, str3, str4, str5) /* mode 2: add LFCR, 1: refresh() */
char *fmt;
long str1, str2, str3, str4, str5;
{
	if( CA) {
		printw(fmt, str1, str2, str3, str4, str5);
		if(mode == 2)
			printw(LFCR);
		if(mode > 0)
			refresh();
	}
	else {
		printf(fmt, str1, str2, str3, str4, str5);
		if(mode == 2)
			printf(LFCR);
		fflush(stdout);
	}
}

help1()
{
	sethelp();
	pr(2,"\tIf you hit <RETURN>, the system will give you a random string");
	pr(2,"\tof characters to practice on.  If you want to limit the range");
        pr(2,"\tof characters, enter a character list.  To practice on random");
        pr(2,"\twords from the  dictionary type  'words'.  To practice on the");
	pr(2,"\ttext in a file, type 'file'.");
}

help2()
{
	sethelp();
	pr(2, "\tThe system will print one by one all the lines from the file");
	pr(2, "\tand wait for you to retype them.   If the file is not in the");
	pr(2, "\tcurrent directory, enter the full path name (Ex:/home/test).");
	pr(2, "\tNote:   trailing spaces and tabs are truncated, tabs in the");
	pr(2, "\tlines are replaced with spaces and empty lines are skipped.");
}

help3()
{
	sethelp();
	pr(2, "\tThe program will  pick random  words from  the dictionary.");
	pr(2, "\tEnter the number of words you want the system to  give you");
	pr(2, "\ton each line.  (More words  produce a more  accurate speed");
        pr(2, "\trating.)");
}

help4()
{
	sethelp();
	pr(2, "\tThe program will  make strings of characters for  you to");
	pr(2, "\tenter. Enter the length of string. (More letters produce");
        pr(2, "\ta more accurate speed rating.)");
}

help5()
{
	sethelp();
	pr(2, "\tHow many lines do you want the system to give you in this");
	pr(2, "\tsession?");
}

help6()
{
	sethelp();
	pr(2,"\t Do you want to see the characters you are typing as you type");
	pr(2,"\tthem?  If you type 'no', only the errors will be printed.");
}

help7()
{
	sethelp();
	pr(2, "\tMiscellaneous Notes: <RETURN>  and  <LINE-FEED> are ignored.");
	pr(2, "\tEach error  subtracts one word from  the speed. You can use ");
	pr(2, "\tbackspaces  to recover from errors.");
	if(CA)
		(void) answer(work_str, "Hit <RETURN> to continue", CHAR);
}

leave()
{
	if(gtotlngth) {
		moveto(SPEED, 0, LFCR);
		pr(2, "\t\t\tS E S S I O N	T O T A L");
		score(gbadchar, gtoterrors, gtotlngth, gtottime);
	}
	clrtobot();
	moveto(BOTTOM, 0, LFCR);
	refresh();
	endwin();
	exit(0);
}

zero(str, n)
register char *str;
register n;
{
	while(n--)
		*str++ = 0;
}