1 /*
2  * GNU Typist  - interactive typing tutor program for UNIX systems
3  *
4  * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
5  *               Simon Baldwin (simonb@sco.com)
6  * Copyright (C) 2003, 2004, 2008, 2009, 2011
7  *               GNU Typist Development Team <bug-gtypist@gnu.org>
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "config.h"
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <stdio.h>
27 #include <ctype.h>
28 #include <signal.h>
29 #include <sys/param.h>
30 #include <sys/time.h>
31 
32 #ifdef HAVE_PDCURSES
33 #include <curses.h>
34 #else
35 #include <ncurses.h>
36 #endif
37 
38 #include <time.h>
39 #include <errno.h>
40 #include <string.h>
41 #include <getopt.h>
42 #include <assert.h>
43 #include <locale.h>
44 #include <wctype.h>
45 #ifndef MINGW
46 #include <langinfo.h>
47 #endif
48 
49 #include "cursmenu.h"
50 #include "script.h"
51 #include "error.h"
52 #include "gtypist.h"
53 #include "utf8.h"
54 
55 #include "gettext.h"
56 #define _(String) gettext (String)
57 
58 /* VERSION and PACKAGE defined in config.h */
59 
60 char *COPYRIGHT;
61 
62 char* locale_encoding; /* current locale's encoding */
63 int isUTF8Locale; /* does the current locale have a UTF-8 encoding? */
64 
65 /* character to be display to represent "enter key" */
66 /* TODO: this requires beginner mode!
67 #define RETURN_CHARACTER 0x000023CE */
68 #define RETURN_CHARACTER 0x00000020
69 
70 /* a definition of a boolean type */
71 #ifndef bool
72 #define bool			int
73 #endif
74 
75 /* some screen postions */
76 #define	MESSAGE_LINE		(LINES - 1)
77 #define B_TOP_LINE		0
78 #define T_TOP_LINE		(B_TOP_LINE + 1)
79 #define I_TOP_LINE		(T_TOP_LINE)
80 #define DP_TOP_LINE		(I_TOP_LINE + 2)
81 #define	SPEED_LINE		(LINES - 5)
82 
83 /* mode indicator strings */
84 char *MODE_TUTORIAL;
85 char *MODE_QUERY;
86 char *MODE_DRILL;
87 char *MODE_SPEEDTEST;
88 
89 /* yes/no responses and miscellanea */
90 #define	QUERY_Y			'Y'
91 #define	QUERY_N			'N'
92 #define	DRILL_CH_ERR		'^'
93 #define	DRILL_NL_ERR		'^'
94 char *WAIT_MESSAGE;
95 char *ERROR_TOO_HIGH_MSG;
96 char *SKIPBACK_VIA_F_MSG;
97 char *REPEAT_NEXT_EXIT_MSG;
98 char *REPEAT_EXIT_MSG;
99 char *CONFIRM_EXIT_LESSON_MSG;
100 char *NO_SKIP_MSG;
101 char *SPEED_RAW_WPM;
102 char *SPEED_RAW_CPM;
103 char *SPEED_ADJ_WPM;
104 char *SPEED_ADJ_CPM;
105 char *SPEED_PCT_ERROR;
106 char *SPEED_BEST_WPM;
107 char *SPEED_BEST_CPM;
108 char *SPEED_BEST_NEW_MSG;
109 wchar_t *YN;
110 wchar_t *RNE;
111 
112 #ifdef MINGW
113 #define DATADIR "lessons"
114 #else
115 #ifndef DATADIR
116 #define DATADIR "."
117 #endif
118 #endif
119 
120 #define	DEFAULT_SCRIPT      "gtypist.typ"
121 
122 #ifdef MINGW
123 #define BESTLOG_FILENAME    "gtypist-bestlog"
124 #else
125 #define BESTLOG_FILENAME    ".gtypist-bestlog"
126 #endif
127 
128 
129 /* some colour definitions */
130 static short	colour_array[] = {
131   COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
132   COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE };
133 #define	NUM_COLOURS		(sizeof( colour_array ) / sizeof( short ))
134 
135 /* shortcuts for reverse/normal mode strings */
136 #define ADDSTR(X) wideaddstr(X)
137 #define ADDSTR_REV(X) wideaddstr_rev(X)
138 #define ADDCH(X) wideaddch(X)
139 #define ADDCH_REV(X) wideaddch_rev(X)
140 
141 
142 #ifdef MINGW
143 #define MIN( a, b ) ( ( a ) < ( b )? ( a ) : ( b ) )
144 #define MAX( a, b ) ( ( a ) > ( b )? ( a ) : ( b ) )
145 #endif
146 
147 /* command line options/values */
148 static bool     cl_error_max_specified = FALSE; /* is --error-max specified? */
149 static float	cl_default_error_max = 3.0;	/* maximum error percentage */
150 static bool	cl_notimer = FALSE;		/* no timings in drills */
151 static bool	cl_term_cursor = FALSE;		/* don't do s/w cursor */
152 static int	cl_curs_flash = 10;		/* cursor flash period */
153 static bool	cl_silent = FALSE;		/* no beep on errors */
154 static char	*cl_start_label = NULL;		/* initial lesson start point*/
155 static bool	cl_colour = FALSE;		/* set if -c given */
156 static int	cl_fgcolour = 7;		/* fg colour */
157 static int	cl_bgcolour = 0;		/* bg colour */
158 static int	cl_banner_bg_colour = 0;	// since we display them in
159 static int	cl_banner_fg_colour = 6;	// inverse video, fg is bg
160 static int	cl_prog_name_colour = 5;	// and vice versa.
161 static int 	cl_prog_version_colour = 1;
162 static int	cl_menu_title_colour = 7;
163 static bool	cl_wp_emu = FALSE;		/* do wp-like stuff */
164 static bool     cl_no_skip = FALSE;             /* forbid the user to */
165 static bool	cl_rev_video_errors = FALSE;    /* reverse video for errors */
166 static bool	cl_scoring_cpm = FALSE;		/* Chars-per-minute scoring */
167 static bool	cl_personal_best = FALSE;	/* track personal best speeds */
168 
169 /* a few global variables */
170 static bool	global_resp_flag = TRUE;
171 static char	global_prior_command = C_CONT;
172 
173 static float	global_error_max = -1.0;
174 static bool	global_error_max_persistent = FALSE;
175 
176 static struct	label_entry *global_on_failure_label = NULL;
177 static bool	global_on_failure_label_persistent = FALSE;
178 
179 static char 	*global_script_filename = NULL;
180 
181 static char	*global_home_env = NULL;
182 
183 /* a global area for associating function keys with labels */
184 #define NFKEYS			12		/* num of function keys */
185 static char	*fkey_bindings[ NFKEYS ] =
186   { NULL, NULL, NULL, NULL, NULL, NULL,
187     NULL, NULL, NULL, NULL, NULL, NULL };
188 /* table of pseudo-function keys, to allow ^Q to double as Fkey1, etc */
189 #define	CTRL_OFFSET		0100		/* ctrl keys are 'X' - 0100 */
190 static	char	pfkeys[ NFKEYS ] =
191   { 'Q'-CTRL_OFFSET, 'W'-CTRL_OFFSET, 'E'-CTRL_OFFSET, 'R'-CTRL_OFFSET,
192     'T'-CTRL_OFFSET, 'Y'-CTRL_OFFSET, 'U'-CTRL_OFFSET, 'I'-CTRL_OFFSET,
193     'O'-CTRL_OFFSET, 'P'-CTRL_OFFSET, 'A'-CTRL_OFFSET, 'S'-CTRL_OFFSET };
194 
195 static bool user_is_always_sure = FALSE;
196 
197 /* prototypes */
198 
199 static int getch_fl( int cursor_char );
200 static bool wait_user (FILE *script, char *message, char *mode );
201 static void display_speed( int total_chars, double elapsed_time, int errcount );
202 static void do_keybind( FILE *script, char *line );
203 static void do_tutorial( FILE *script, char *line );
204 static void do_instruction( FILE *script, char *line );
205 static int is_error_too_high( int chars_typed, int errors );
206 static void do_drill( FILE *script, char *line );
207 static void do_speedtest( FILE *script, char *line );
208 static void do_clear( FILE *script, char *line );
209 static void do_goto( FILE *script, char *line, bool flag );
210 static char do_query_repeat( FILE *script, bool allow_next );
211 static bool do_query_simple( char *text );
212 static bool do_query( FILE *script, char *line );
213 static void do_error_max_set( FILE *script, char *line );
214 static void do_on_failure_label_set( FILE *script, char *line );
215 static void parse_file( FILE *script, char *label );
216 static void indent_to( int n );
217 static void print_usage_item( char *op, char *lop, char *help,
218 			      int col_op, int col_lop, int col_help,
219 			      int last_col );
220 static void print_help();
221 static void parse_cmdline( int argc, char **argv );
222 static void catcher( int signal );
223 static FILE *open_script( const char *filename );
224 static void do_bell();
225 static bool get_best_speed( const char *script_filename,
226 			    const char *excersise_label, double *adjusted_cpm );
227 static void put_best_speed( const char *script_filename,
228 			    const char *excersise_label, double adjusted_cpm );
229 void get_bestlog_filename( char *filename );
230 
231 // Display the top banner with the given text
banner(const char * text)232 void banner (const char *text)
233 {
234    int colnum, brand_length, brand_position, text_length, text_position;
235 
236    // Get rid of spaces at the edges of the text
237    while (isspace (*text))
238       text ++;
239    text_length = strlen (text);
240    if (text_length > 0)
241    {
242       while (isspace (text [text_length - 1]))
243       {
244          text_length --;
245 	 if (! text_length)
246 	    break;
247       }
248    }
249 
250    brand_length = utf8len (PACKAGE) + utf8len (VERSION) + 3,
251    brand_position = COLS - brand_length,
252    text_position = ((COLS - brand_length) > text_length) ?
253 		(COLS - brand_length - text_length) / 2 : 0;
254 
255 // TODO:  much of redundant output here...
256 
257    move (B_TOP_LINE , 0);
258    attron (COLOR_PAIR (C_BANNER));
259    for (colnum = 0; colnum < COLS; colnum++)
260       ADDCH_REV (ASCII_SPACE);
261 
262    move (B_TOP_LINE, text_position);
263    {
264      wchar_t* wideText = convertFromUTF8(text);
265      int numChars = wcslen(wideText);
266 
267      int i;
268      for (i = 0; i < numChars; i++)
269        wideaddch_rev(wideText[i]);
270      free(wideText);
271    }
272 
273    move (B_TOP_LINE, brand_position);
274    attron (COLOR_PAIR (C_PROG_NAME));
275    ADDCH_REV (' ');
276    ADDSTR_REV (PACKAGE);
277    ADDCH_REV (' ');
278    attron (COLOR_PAIR (C_PROG_VERSION));
279    ADDSTR_REV (VERSION);
280    refresh ();
281    attron (COLOR_PAIR (C_NORMAL));
282 }
283 
bind_F12(const char * label)284 void bind_F12 (const char *label)
285 {
286   if (!label)
287      return;
288 
289   if (fkey_bindings [11])
290      free (fkey_bindings [11]);
291   fkey_bindings [11] = strdup (label);
292   if (! fkey_bindings [11])
293   {
294        perror ("strdup");
295        fatal_error (_("internal error in strdup"), label);
296   }
297 }
298 
299 /*
300   getch() that does a flashing cursor; some xterms seem to be
301   unwilling to do A_BLINK, however, the program needs some
302   way to separate out the inverse char cursor from the inverse
303   char mistyping indications.  And to complicate things, some
304   xterms seem not make the cursor invisible either.
305 */
306 static int
getch_fl(int cursor_char)307 getch_fl( int cursor_char )
308 {
309   int	y, x;				/* saved cursor posn */
310   int	return_char;			/* return value */
311   bool	alternate = FALSE;		/* flashes control */
312 
313   /* save the cursor position - we're going to need it */
314   getyx( stdscr, y, x );
315 
316   /* if no cursor then do our best not to show one */
317   if ( cursor_char == ASCII_NULL )
318     {
319       /* degrade to cursor-less getch */
320       curs_set( 0 ); refresh();
321       move( LINES - 1, COLS - 1 );
322       cbreak();
323       get_widech(&return_char);
324       move( y, x );
325     }
326   else
327     {
328       /* produce a flashing cursor, or not, as requested */
329       if ( ! cl_term_cursor ) {
330         /* go for the flashing block here */
331         wideaddch_rev(cursor_char);
332         curs_set( 0 ); refresh();
333         move( LINES - 1, COLS - 1 );
334         if ( ( cl_curs_flash / 2 ) > 0 )
335           {
336             halfdelay( cl_curs_flash / 2 );
337             while ( get_widech(&return_char) == ERR )
338               {
339                 move( y, x );
340                 if ( alternate )
341                   wideaddch_rev(cursor_char);
342                 else
343                   wideaddch(cursor_char);
344                 move( LINES - 1, COLS - 1 );
345                 alternate = !alternate;
346               }
347           }
348         else
349           {
350             cbreak();
351             get_widech(&return_char);
352           }
353         move( y, x );
354         wideaddch(cursor_char);
355         move( y, x );
356       }
357       else
358         {
359           /* just use the terminal's cursor - this is easy */
360           curs_set( 1 ); refresh();
361           cbreak(); //return_char = getch();
362           get_widech(&return_char);
363           curs_set( 0 ); refresh();
364         }
365     }
366 
367   /* return what key was pressed */
368   return ( return_char );
369 }
370 
371 /*
372   wait for a nod from the user before continuing. In MODE_TUTORIAL only, TRUE is
373   returned if the user pressed escape to indicate that seek_label was called
374 */
wait_user(FILE * script,char * message,char * mode)375 static bool wait_user (FILE *script, char *message, char *mode)
376 {
377   int	resp;			/* response character */
378   bool	seek_done = FALSE;	/* was seek_label called? */
379 
380   /* move to the message line print a prompt */
381   move( MESSAGE_LINE, 0 ); clrtoeol();
382   move( MESSAGE_LINE, COLS - utf8len( mode ) - 2 );
383   wideaddstr_rev(mode);
384   move( MESSAGE_LINE, 0 );
385   wideaddstr_rev(message);
386 
387   do {
388     resp = getch_fl (ASCII_NULL);
389 
390     /* in tutorial mode only, escape has the special purpose that we exit to a
391        menu (or quit if there is none) */
392     if (resp == ASCII_ESC && mode == MODE_TUTORIAL)
393     {
394       // Return to the last F12-binded location
395       if( fkey_bindings[ 11 ] && *( fkey_bindings[ 11 ] ) )
396 	{
397           seek_label( script, fkey_bindings[ 11 ], NULL );
398           seek_done = TRUE;
399 	}
400       else
401 	do_exit( script );
402       break;
403     }
404   } while (resp != ASCII_NL && resp != ASCII_SPACE && resp != ASCII_ESC);
405 
406   /* clear the message line */
407   move( MESSAGE_LINE, 0 ); clrtoeol();
408 
409   return seek_done;
410 }
411 
412 
413 /*
414   display speed and accuracy from a drill or speed test
415 */
display_speed(int total_chars,double elapsed_time,int errcount)416 static void display_speed( int total_chars, double elapsed_time, int errcount ) {
417   double	test_time;			/* time in minutes */
418   double	cpm, adjusted_cpm;		/* speeds in CPM */
419   char		message[MAX_WIN_LINE];		/* buffer */
420   int		line = SPEED_LINE;		/* current line no. */
421   bool		had_best_speed = FALSE;		/* already had a p.best? */
422   bool		new_best_speed = FALSE;		/* have we beaten it? */
423   double	best_cpm;			/* personal best speed in CPM */
424   char		*raw_speed_str, *adj_speed_str, *best_speed_str;
425 
426   /* calculate the speeds */
427   test_time = elapsed_time / 60.0;
428   if( elapsed_time > 0 )
429     {
430       /* calculate speed values */
431       cpm = (double)total_chars / test_time;
432       adjusted_cpm = (double)( total_chars - ( errcount * 5 ) ) / test_time;
433 
434       /* limit speed values */
435       cpm = MIN( cpm, 9999.99 );
436       adjusted_cpm = MAX( MIN( adjusted_cpm, 9999.99 ), 0 );
437 
438       /* remove errors in adjusted speed */
439       if( adjusted_cpm < 0.01 )
440 	adjusted_cpm = 0;
441     }
442   else
443     /* unmeasurable elapsed time - use big numbers */
444     cpm = adjusted_cpm = (double)9999.99;
445 
446   /* obtain (and update?) a personal best speed */
447   if( cl_personal_best )
448     {
449       had_best_speed =
450 	get_best_speed( global_script_filename, __last_label, &best_cpm );
451       new_best_speed = ( !had_best_speed || adjusted_cpm > best_cpm ) &&
452 	!is_error_too_high( total_chars, errcount );
453       if( new_best_speed )
454 	put_best_speed( global_script_filename, __last_label, adjusted_cpm );
455     }
456 
457   /* adjust display position/height */
458   line -=
459     ( had_best_speed? 1 : 0 ) +
460     ( new_best_speed? 1 : 0 );
461 
462   /* display everything */
463   if( cl_scoring_cpm )
464     sprintf( message, SPEED_RAW_CPM, cpm );
465   else
466     sprintf( message, SPEED_RAW_WPM, cpm / (double)5.0 );
467   move( line++, COLS - utf8len( message ) - 1 );
468   ADDSTR_REV( message );
469   if( cl_scoring_cpm )
470     sprintf( message, SPEED_ADJ_CPM, adjusted_cpm );
471   else
472     sprintf( message, SPEED_ADJ_WPM, adjusted_cpm / (double)5.0 );
473   move( line++, COLS - utf8len( message ) - 1 );
474   ADDSTR_REV( message );
475   sprintf( message, SPEED_PCT_ERROR,
476            (double)100.0 * (double)errcount / (double)total_chars );
477   move( line++, COLS - utf8len( message ) - 1 );
478   ADDSTR_REV( message );
479   if( had_best_speed )
480     {
481       if( cl_scoring_cpm )
482 	sprintf( message, SPEED_BEST_CPM, best_cpm );
483       else
484 	sprintf( message, SPEED_BEST_WPM, best_cpm / (double)5.0 );
485       move( line++, COLS - utf8len( message ) - 1 );
486       ADDSTR_REV( message );
487     }
488   if( new_best_speed )
489     {
490       move( line++, COLS - utf8len( SPEED_BEST_NEW_MSG ) - 1 );
491       ADDSTR_REV( SPEED_BEST_NEW_MSG );
492     }
493 }
494 
495 /*
496   bind a function key to a label
497 */
498 static void
do_keybind(FILE * script,char * line)499 do_keybind( FILE *script, char *line ) {
500   int	fkey;				/* function key number */
501   char	*label;				/* associated label */
502 
503   /* extract the fkey number and label string, and check
504      the syntax and correctness of the mappings */
505   label = (char*)malloc( strlen(SCR_DATA( line )) + 1 );
506   if ( sscanf( SCR_DATA( line ), "%d:%s", &fkey, label ) != 2 )
507     fatal_error( _("invalid key binding"), line );
508   if ( fkey < 1 || fkey > NFKEYS )
509     fatal_error( _("invalid function key number"), line );
510 
511   /* free the previous binding malloced data */
512   if ( fkey_bindings[ fkey - 1 ] != NULL )
513     {
514       free( fkey_bindings[ fkey - 1 ] );
515       fkey_bindings[ fkey - 1 ] = NULL;
516     }
517 
518   /* add the association to the array, or unbind the association
519      if the target is the special label "NULL" (ugh - hacky) */
520   if ( strcmp( label, "NULL" ) != 0 && strcmp( label, "null" ) != 0 )
521     fkey_bindings[ fkey - 1 ] = label;
522   else
523     free( label );
524 
525   /* get the next command */
526   get_script_line( script, line );
527   global_prior_command = C_KEYBIND;
528 }
529 
530 
531 /*
532   print the given text onto the screen
533 */
534 static void
do_tutorial(FILE * script,char * line)535 do_tutorial( FILE *script, char *line ) {
536   int	linenum;		/* line counter */
537   bool  seek_done = FALSE;      /* was there a seek_label before exit? */
538 
539   /* start at the top of the screen, and clear it */
540   linenum = T_TOP_LINE;
541   move( linenum, 0 ); clrtobot();
542 
543   /* output this line, and each continuation line read */
544   do
545     {
546       if ( linenum >= LINES - 1 )
547 	fatal_error( _("data exceeds screen length"), line );
548       move( linenum, 0 );
549       /* ADDSTR( SCR_DATA( line )); */
550       wideaddstr(SCR_DATA( line ));
551       get_script_line( script, line );
552       linenum++;
553     }
554   while ( SCR_COMMAND( line ) == C_CONT && ! feof( script ));
555 
556   /* wait for a return, unless the next command is a query,
557      when we can skip it to save the user keystrokes */
558   if ( SCR_COMMAND( line ) != C_QUERY )
559     seek_done = wait_user (script, WAIT_MESSAGE, MODE_TUTORIAL);
560   global_prior_command = C_TUTORIAL;
561 
562   /* if seek_label has been called (in wait_user) then we need to read in the
563      next line of the script in to `line` */
564   if (seek_done)
565     get_script_line( script, line );
566 }
567 
568 
569 /*
570   print up a line, at most two, usually followed by a drill or a speed test
571 */
572 static void
do_instruction(FILE * script,char * line)573 do_instruction( FILE *script, char *line ) {
574 
575   /* move to the instruction line and output the first bit */
576   move( I_TOP_LINE, 0 ); clrtobot();
577   ADDSTR( SCR_DATA( line ));
578   get_script_line( script, line );
579 
580   /* if there is a second line then output that too */
581   if ( SCR_COMMAND( line ) == C_CONT && ! feof( script ))
582     {
583       move( I_TOP_LINE + 1, 0 );
584       ADDSTR( SCR_DATA( line ) );
585       get_script_line( script, line );
586     }
587 
588   /* if there is a third line then complain */
589   if ( SCR_COMMAND( line ) == C_CONT && ! feof( script ))
590     fatal_error( _("instructions are limited to two lines"), line );
591   global_prior_command = C_INSTRUCTION;
592 }
593 
594 /*
595   Calculate whether a drill's error rate is too high, keeping in mind
596   rounding of output to a single decimal place.
597 */
598 static int
is_error_too_high(int chars_typed,int errors)599 is_error_too_high( int chars_typed, int errors ) {
600   /* (double)100.0*(double)errcount / (double)total_chars ) */
601   double err_max, err;
602   char buf[BUFSIZ];
603 
604   /* Calculate error rates */
605   err_max = (double)global_error_max;
606   err = (double)100.0*(double)errors / (double)chars_typed;
607 
608   /* We need to use the same kind of rounding used by printf. */
609   sprintf(buf, "%.1f", err_max);
610   sscanf(buf, "%lf", &err_max);
611   sprintf(buf, "%.1f", err);
612   sscanf(buf, "%lf", &err);
613   return err > err_max;
614 }
615 
616 /*
617   execute a typing drill
618 */
619 static void
do_drill(FILE * script,char * line)620 do_drill( FILE *script, char *line ) {
621 
622   int	errors = 0;		 /* error count */
623   int	linenum;		 /* line counter */
624   char	*data = NULL;		 /* data string */
625   int	lines_count = 0;	 /* measures drill length */
626   int	rc;			 /* curses char typed */
627   wchar_t  *widep, *wideData;
628   double start_time=0, end_time; /* timing variables */
629   char	message[MAX_WIN_LINE];	 /* message buffer */
630   char	drill_type;		 /* note of the drill type */
631   int	chars_typed;		 /* count of chars typed */
632   int	chars_in_the_line_typed;
633   bool  seek_done = FALSE;       /* was there a seek_label before exit? */
634   int	error_sync;		 /* error resync state */
635   struct timeval tv;
636 
637   /* note the drill type to see if we need to make the user repeat */
638   drill_type = SCR_COMMAND( line );
639 
640   /* get the complete exercise into a single string */
641   data = buffer_command( script, line );
642 
643   /* get the exercise as a wide string */
644   wideData = convertFromUTF8(data);
645   int numChars = wcslen(wideData);
646 
647   /* count the lines in this exercise, and check the result
648      against the screen length */
649   for ( widep = wideData, lines_count = 0; *widep != ASCII_NULL; widep++ )
650     {
651       if ( *widep == ASCII_NL)
652         lines_count++;
653     }
654   if ( DP_TOP_LINE + lines_count * 2 > LINES )
655     fatal_error( _("data exceeds screen length"), line );
656 
657   /* if the last command was a tutorial, ensure we have
658      the complete screen */
659   if ( global_prior_command == C_TUTORIAL )
660     {
661       move( T_TOP_LINE, 0 ); clrtobot();
662     }
663 
664   while (1)
665     {
666 
667       /* display drill pattern */
668       linenum = DP_TOP_LINE;
669       move( linenum, 0 ); clrtobot();
670       for ( widep = wideData; *widep != ASCII_NULL; widep++ )
671         {
672           if ( *widep != ASCII_NL )
673             wideaddch(*widep);
674           else
675           {
676               /* emit return character */
677               wideaddch(RETURN_CHARACTER);
678 
679               /* newline - move down the screen */
680               linenum++; linenum++;	/* alternate lines */
681               move( linenum, 0 );
682           }
683         }
684       move( MESSAGE_LINE, COLS - utf8len( MODE_DRILL ) - 2 );
685       ADDSTR_REV( MODE_DRILL );
686 
687       /* run the drill */
688       linenum = DP_TOP_LINE + 1;
689       move( linenum, 0 );
690       for ( widep = wideData; *widep == ASCII_SPACE && *widep != ASCII_NULL; widep++ )
691         wideaddch(*widep);
692 
693       for ( chars_typed = 0, errors = 0, error_sync = 0,
694               chars_in_the_line_typed = 0;
695             *widep != ASCII_NULL; widep++ )
696         {
697           do
698             {
699               rc = getch_fl (chars_in_the_line_typed >= COLS ? *(widep + 1) :
700                              (*widep == ASCII_TAB ? ASCII_TAB : ASCII_SPACE));
701             }
702           while ( rc == GTYPIST_KEY_BACKSPACE || rc == ASCII_BS || rc == ASCII_DEL );
703 
704           /* start timer on first char entered */
705           if ( chars_typed == 0 )
706             {
707               gettimeofday(&tv, NULL);
708               start_time = tv.tv_sec + tv.tv_usec / 1000000.0;
709             }
710           chars_typed++;
711           error_sync--;
712 
713           /* ESC is "give up"; ESC at beginning of exercise is "skip lesson"
714              (this is handled outside the for loop) */
715           if ( rc == ASCII_ESC )
716             break;
717 
718           /* check that the character was correct */
719           if ( rc == *widep ||
720                ( cl_wp_emu && rc == ASCII_SPACE && *widep == ASCII_NL ))
721             {
722               if (cl_wp_emu && rc == ASCII_SPACE && *widep == ASCII_NL)
723                 chars_in_the_line_typed = 0;
724               else
725                 {
726                   if (rc != ASCII_NL)
727                     {
728                       wideaddch(rc);
729                       chars_in_the_line_typed ++;
730                     }
731                   else
732                     {
733                       wideaddch(RETURN_CHARACTER);
734                       chars_in_the_line_typed = 0;
735                     }
736                 }
737             }
738           else
739             {
740               /* try to sync with typist behind */
741               if ( error_sync >= 0 && widep > wideData && rc == *(widep-1) )
742                 {
743                   widep--;
744                   continue;
745                 }
746 
747               if (chars_in_the_line_typed < COLS)
748                 {
749                   wideaddch_rev( *widep == ASCII_NL ? DRILL_NL_ERR :
750                                  (*widep == ASCII_TAB ?
751                                   ASCII_TAB : (cl_rev_video_errors ?
752                                                rc : DRILL_CH_ERR)));
753                   chars_in_the_line_typed ++;
754                 }
755 
756               if (*widep == ASCII_NL)
757                 chars_in_the_line_typed = 0;
758 
759               if ( ! cl_silent )
760                 {
761                   do_bell();
762                 }
763               errors++;
764               error_sync = 1;
765 
766               /* try to sync with typist ahead */
767               if ( rc == *(widep+1) )
768                 {
769                   ungetch( rc );
770                   error_sync++;
771                 }
772             }
773 
774           /* move screen location if newline */
775           if ( *widep == ASCII_NL )
776             {
777               linenum++; linenum++;
778               move( linenum, 0 );
779             }
780 
781           /* perform any other word processor like adjustments */
782           if ( cl_wp_emu )
783             {
784               if ( rc == ASCII_SPACE )
785                 {
786                   while ( *(widep+1) == ASCII_SPACE
787                           && *(widep+1) != ASCII_NULL )
788                     {
789                       widep++;
790                       wideaddch(*widep);
791                       chars_in_the_line_typed ++;
792                     }
793                 }
794               else if ( rc == ASCII_NL )
795                 {
796                   while ( ( *(widep+1) == ASCII_SPACE
797                             || *(widep+1) == ASCII_NL )
798                           && *(widep+1) != ASCII_NULL )
799                     {
800                       widep++;
801                       wideaddch(*widep);
802                       chars_in_the_line_typed ++;
803                       if ( *widep == ASCII_NL ) {
804                         linenum++; linenum++;
805                         move( linenum, 0 );
806                         chars_in_the_line_typed = 0;
807                       }
808                     }
809                 }
810               else if ( isalpha(*widep) && *(widep+1) == ASCII_DASH
811                         && *(widep+2) == ASCII_NL )
812                 {
813                   widep++;
814                   wideaddch(*widep);
815                   widep++;
816                   wideaddch(*widep);
817                   linenum++; linenum++;
818                   move( linenum, 0 );
819                   chars_in_the_line_typed = 0;
820                 }
821             }
822         }
823 
824       /* ESC not at the beginning of the lesson: "give up" */
825       if ( rc == ASCII_ESC && chars_typed != 1)
826         continue; /* repeat */
827 
828       /* skip timings and don't check error-pct if exit was through ESC */
829       if ( rc != ASCII_ESC )
830         {
831           /* display timings */
832           gettimeofday(&tv, NULL);
833           end_time = tv.tv_sec + tv.tv_usec / 1000000.0;
834           if ( ! cl_notimer )
835             {
836               display_speed( chars_typed, end_time - start_time,
837                              errors );
838             }
839 
840           /* check whether the error-percentage is too high (unless in d:) */
841           if (drill_type != C_DRILL_PRACTICE_ONLY &&
842               is_error_too_high(chars_typed, errors))
843             {
844               sprintf( message, ERROR_TOO_HIGH_MSG, global_error_max );
845               wait_user (script, message, MODE_DRILL);
846 
847               /* check for F-command */
848               if (global_on_failure_label != NULL)
849                 {
850                   /* move to the label position in the file */
851                   if (fseek(script, global_on_failure_label->offset, SEEK_SET )
852                       == -1)
853                     fatal_error( _("internal error: fseek"), NULL );
854                   global_line_counter = global_on_failure_label->line_count;
855                   /* tell the user about the misery :) */
856                   sprintf(message,SKIPBACK_VIA_F_MSG,
857                           global_on_failure_label->label);
858                   /* reset value unless persistent */
859                   if (!global_on_failure_label_persistent)
860                     global_on_failure_label = NULL;
861                   wait_user (script, message, MODE_DRILL);
862                   seek_done = TRUE;
863                   break;
864                 }
865 
866               continue;
867             }
868         }
869 
870       /* ask the user whether he/she wants to repeat or exit */
871       if ( rc == ASCII_ESC && cl_no_skip ) /* honor --no-skip */
872         rc = do_query_repeat (script, FALSE);
873       else
874         rc = do_query_repeat (script, TRUE);
875       if (rc == 'E') {
876         seek_done = TRUE;
877         break;
878       }
879       if (rc == 'N')
880         break;
881 
882     }
883 
884   /* free the malloced memory */
885   free( data );
886   free( wideData );
887 
888   /* reset global_error_max */
889   if (!global_error_max_persistent)
890     global_error_max = cl_default_error_max;
891 
892   /* buffer_command takes care of advancing `script' (and setting
893      `line'), so we only do if seek_label had been called (in
894      do_query_repeat or due to a failure and an F: command) */
895   if (seek_done)
896     get_script_line( script, line );
897   global_prior_command = drill_type;
898 }
899 
900 
901 /*
902   execute a timed speed test
903 */
904 static void
do_speedtest(FILE * script,char * line)905 do_speedtest( FILE *script, char *line ) {
906   int	errors = 0;		 /* error count */
907   int	linenum;		 /* line counter */
908   char	*data = NULL;		 /* data string */
909   int	lines_count = 0;	 /* measures exercise length */
910   int	rc;			 /* curses char typed */
911   wchar_t  *widep, *wideData;
912   double start_time=0, end_time; /* timing variables */
913   char	message[MAX_WIN_LINE];	 /* message buffer */
914   char	drill_type;		 /* note of the drill type */
915   int	chars_typed;		 /* count of chars typed */
916   bool  seek_done = FALSE;       /* was there a seek_label before exit? */
917   int	error_sync;		 /* error resync state */
918   struct timeval tv;
919 
920   /* note the drill type to see if we need to make the user repeat */
921   drill_type = SCR_COMMAND( line );
922 
923   /* get the complete exercise into a single string */
924   data = buffer_command( script, line );
925 
926   wideData = convertFromUTF8(data);
927   int numChars = wcslen(wideData);
928 
929   /*
930     fprintf(F, "convresult=%d\n", convresult);
931     fprintf(F, "wideData='%ls'\n", wideData);
932     int i;
933     for (i = 0; i <= numChars; i++) {
934     fprintf(F, "wideData[%d]=%d\n", i, wideData[i]);
935     }
936   */
937 
938   /* count the lines in this exercise, and check the result
939      against the screen length */
940   for ( widep = wideData, lines_count = 0; *widep != ASCII_NULL; widep++ )
941     {
942       if ( *widep == ASCII_NL)
943         lines_count++;
944     }
945   if ( DP_TOP_LINE + lines_count > LINES )
946     fatal_error( _("data exceeds screen length"), line );
947 
948   /* if the last command was a tutorial, ensure we have
949      the complete screen */
950   if ( global_prior_command == C_TUTORIAL )
951     {
952       move( T_TOP_LINE, 0 ); clrtobot();
953     }
954 
955   while (1)
956     {
957       /* display speed test pattern */
958       linenum = DP_TOP_LINE;
959       move( linenum, 0 ); clrtobot();
960       for ( widep = wideData; *widep != ASCII_NULL; widep++ )
961         {
962           if ( *widep != ASCII_NL )
963             {
964               wideaddch(*widep);
965             }
966           else
967             {
968               /* emit return character */
969               wideaddch(RETURN_CHARACTER);
970 
971               /* newline - move down the screen */
972               linenum++;
973               move( linenum, 0 );
974             }
975         }
976       move( MESSAGE_LINE, COLS - utf8len( MODE_SPEEDTEST ) - 2 );
977       ADDSTR_REV( MODE_SPEEDTEST );
978 
979       /* run the data */
980       linenum = DP_TOP_LINE;
981       move( linenum, 0 );
982       for ( widep = wideData; *widep == ASCII_SPACE && *widep != ASCII_NULL; widep++ )
983         wideaddch(*widep);
984 
985       for ( chars_typed = 0, errors = 0, error_sync = 0;
986             *widep != ASCII_NULL; widep++ )
987         {
988           rc = getch_fl( (*widep != ASCII_NL) ? *widep : RETURN_CHARACTER );
989 
990           /* start timer on first char entered */
991           if ( chars_typed == 0 )
992             {
993               gettimeofday(&tv, NULL);
994               start_time = tv.tv_sec + tv.tv_usec / 1000000.0;
995             }
996           chars_typed++;
997           error_sync--;
998 
999           /* check for delete keys if not at line start or
1000              speed test start */
1001           if ( rc == GTYPIST_KEY_BACKSPACE || rc == ASCII_BS || rc == ASCII_DEL )
1002             {
1003               /* just ignore deletes where it's impossible or hard */
1004               if ( widep > wideData && *(widep-1) != ASCII_NL && *(widep-1) != ASCII_TAB ) {
1005                 /* back up one character */
1006                 ADDCH( ASCII_BS ); widep--;
1007               }
1008               widep--;		/* defeat widep++ coming up */
1009               continue;
1010             }
1011 
1012           /* ESC is "give up"; ESC at beginning of exercise is "skip lesson"
1013              (this is handled outside the for loop) */
1014           if ( rc == ASCII_ESC )
1015             break;
1016 
1017           /* check that the character was correct */
1018           if ( rc == *widep
1019                || ( cl_wp_emu && rc == ASCII_SPACE && *widep == ASCII_NL ))
1020           { /* character is correct */
1021             if (*widep == ASCII_NL)
1022             {
1023                 wideaddch(RETURN_CHARACTER);
1024             }
1025             else
1026             {
1027                 wideaddch(rc);
1028             }
1029           }
1030           else
1031             { /* character is incorrect */
1032               /* try to sync with typist behind */
1033               if ( error_sync >= 0 && widep > wideData && rc == *(widep-1) )
1034                 {
1035                   widep--;
1036                   continue;
1037                 }
1038 
1039               wideaddch_rev(*widep == ASCII_NL ? RETURN_CHARACTER : *widep);
1040 
1041               if ( ! cl_silent ) {
1042                 do_bell();
1043               }
1044               errors++;
1045               error_sync = 1;
1046 
1047               /* try to sync with typist ahead */
1048               if ( rc == *(widep+1) )
1049                 {
1050                   ungetch( rc );
1051                   error_sync++;
1052                 }
1053             }
1054 
1055           /* move screen location if newline */
1056           if ( *widep == ASCII_NL )
1057             {
1058               linenum++;
1059               move( linenum, 0 );
1060             }
1061 
1062           /* perform any other word processor like adjustments */
1063           if ( cl_wp_emu )
1064             {
1065               if ( rc == ASCII_SPACE )
1066                 {
1067                   while ( *(widep+1) == ASCII_SPACE
1068                           && *(widep+1) != ASCII_NULL )
1069                     {
1070                       widep++;
1071                       wideaddch(*widep);
1072                     }
1073                 }
1074               else if ( rc == ASCII_NL )
1075                 {
1076                   while ( ( *(widep+1) == ASCII_SPACE
1077                             || *(widep+1) == ASCII_NL )
1078                           && *(widep+1) != ASCII_NULL )
1079                     {
1080                       widep++;
1081                       wideaddch(*widep);
1082                       if ( *widep == ASCII_NL )
1083                         {
1084                           linenum++;
1085                           move( linenum, 0 );
1086                         }
1087                     }
1088                 }
1089               else if ( isalpha(*widep) && *(widep+1) == ASCII_DASH
1090                         && *(widep+2) == ASCII_NL )
1091                 {
1092                   widep++;
1093                   wideaddch(*widep);
1094                   widep++;
1095                   wideaddch(*widep);
1096                   linenum++;
1097                   move( linenum, 0 );
1098                 }
1099             }
1100         }
1101 
1102 
1103       /* ESC not at the beginning of the lesson: "give up" */
1104       if ( rc == ASCII_ESC && chars_typed != 1)
1105         continue; /* repeat */
1106 
1107       /* skip timings and don't check error-pct if exit was through ESC */
1108       if ( rc != ASCII_ESC )
1109         {
1110           /* display timings */
1111           gettimeofday(&tv, NULL);
1112           end_time = tv.tv_sec + tv.tv_usec / 1000000.0;
1113           display_speed( chars_typed, end_time - start_time,
1114                          errors );
1115 
1116           /* check whether the error-percentage is too high (unless in s:) */
1117           if (drill_type != C_SPEEDTEST_PRACTICE_ONLY &&
1118               is_error_too_high(chars_typed, errors))
1119             {
1120               sprintf( message, ERROR_TOO_HIGH_MSG, global_error_max );
1121               wait_user (script, message, MODE_SPEEDTEST);
1122 
1123               /* check for F-command */
1124               if (global_on_failure_label != NULL)
1125                 {
1126                   /* move to the label position in the file */
1127                   if (fseek(script, global_on_failure_label->offset, SEEK_SET )
1128                       == -1)
1129                     fatal_error( _("internal error: fseek"), NULL );
1130                   global_line_counter = global_on_failure_label->line_count;
1131                   /* tell the user about the misery :) */
1132                   sprintf(message,SKIPBACK_VIA_F_MSG,
1133                           global_on_failure_label->label);
1134                   /* reset value unless persistent */
1135                   if (!global_on_failure_label_persistent)
1136                     global_on_failure_label = NULL;
1137                   wait_user (script, message, MODE_SPEEDTEST);
1138                   seek_done = TRUE;
1139                   break;
1140                 }
1141 
1142               continue;
1143             }
1144         }
1145 
1146       /* ask the user whether he/she wants to repeat or exit */
1147       if ( rc == ASCII_ESC && cl_no_skip ) /* honor --no-skip */
1148         rc = do_query_repeat (script, FALSE);
1149       else
1150         rc = do_query_repeat (script, TRUE);
1151       if (rc == 'E') {
1152         seek_done = TRUE;
1153         break;
1154       }
1155       if (rc == 'N')
1156         break;
1157 
1158     }
1159 
1160   /* free the malloced memory */
1161   free( data );
1162   free( wideData );
1163 
1164   /* reset global_error_max */
1165   if (!global_error_max_persistent)
1166     global_error_max = cl_default_error_max;
1167 
1168   /* buffer_command takes care of advancing `script' (and setting
1169      `line'), so we only do if seek_label had been called (in
1170      do_query_repeat or due to a failure and an F: command) */
1171   if (seek_done)
1172     get_script_line( script, line );
1173   global_prior_command = C_SPEEDTEST;
1174 }
1175 
1176 
1177 /*
1178  * clear the complete screen, maybe leaving a header behind
1179  */
do_clear(FILE * script,char * line)1180 static void do_clear (FILE *script, char *line)
1181 {
1182   /* clear the complete screen */
1183   move( B_TOP_LINE , 0 ); clrtobot();
1184 
1185   banner (SCR_DATA (line));
1186 
1187   /* finally, get the next script command */
1188   get_script_line( script, line );
1189   global_prior_command = C_CLEAR;
1190 }
1191 
1192 
1193 /*
1194   go to a label - the flag is used to implement conditional goto's
1195 */
1196 static void
do_goto(FILE * script,char * line,bool flag)1197 do_goto( FILE *script, char *line, bool flag )
1198 {
1199   char *line_iterator;
1200 
1201   /* reposition only if flag set - otherwise a noop */
1202   if ( flag )
1203     {
1204       /* remove trailing whitespace from line */
1205       line_iterator = line + strlen(line) - 1;
1206       while (line_iterator != line && isspace(*line_iterator))
1207 	{
1208 	  *line_iterator = '\0';
1209 	  --line_iterator;
1210 	}
1211 
1212       seek_label( script, SCR_DATA( line ), line );
1213     }
1214   get_script_line( script, line );
1215 }
1216 
1217 
1218 /*
1219   Ask the user whether he/she wants to repeat, continue or exit
1220   (this is used at the end of an exercise (drill/speedtest))
1221   The second argument is FALSE if skipping a lesson isn't allowed (--no-skip).
1222 */
1223 static char
do_query_repeat(FILE * script,bool allow_next)1224 do_query_repeat ( FILE *script, bool allow_next )
1225 {
1226   int resp;
1227 
1228   /* display the prompt */
1229   move( MESSAGE_LINE, 0 ); clrtoeol();
1230   move( MESSAGE_LINE, COLS - utf8len( MODE_QUERY ) - 2 );
1231   ADDSTR_REV( MODE_QUERY );
1232   move( MESSAGE_LINE, 0 );
1233   if (allow_next)
1234     ADDSTR_REV( REPEAT_NEXT_EXIT_MSG );
1235   else
1236     ADDSTR_REV( REPEAT_EXIT_MSG );
1237 
1238   /* wait for [RrNnEe] (or translation of these) */
1239   while (TRUE)
1240     {
1241       resp = getch_fl( ASCII_NULL );
1242 
1243       if (towideupper (resp) == 'R' ||
1244 	  towideupper (resp) == RNE [0]) {
1245 	resp = 'R';
1246 	break;
1247       }
1248       if (allow_next && (towideupper (resp) == 'N' ||
1249 			 towideupper (resp) == RNE [2])) {
1250 	resp = 'N';
1251 	break;
1252       }
1253       if (towideupper (resp) == 'E' || towideupper (resp) == RNE [4]) {
1254 	if (do_query_simple (CONFIRM_EXIT_LESSON_MSG))
1255 	  {
1256 	    seek_label (script, fkey_bindings [11], NULL);
1257 	    resp = 'E';
1258 	    break;
1259 	  }
1260 	/* redisplay the prompt */
1261 	move( MESSAGE_LINE, 0 ); clrtoeol();
1262 	move( MESSAGE_LINE, COLS - utf8len( MODE_QUERY ) - 2 );
1263 	ADDSTR_REV( MODE_QUERY );
1264 	move( MESSAGE_LINE, 0 );
1265 	if (allow_next)
1266 	  ADDSTR_REV( REPEAT_NEXT_EXIT_MSG );
1267 	else
1268 	  ADDSTR_REV( REPEAT_EXIT_MSG );
1269       }
1270     }
1271 
1272   /* clear out the message line */
1273   move( MESSAGE_LINE, 0 ); clrtoeol();
1274 
1275   return (char)resp;
1276 }
1277 
1278 
1279 /*
1280   Same as do_query, but only used internally (doesn't set global_resp_flag,
1281   returns the value instead) and doesn't accept Fkeys.
1282   This is used to let the user confirm (E)xit.
1283 */
1284 static bool
do_query_simple(char * text)1285 do_query_simple ( char *text )
1286 {
1287   int resp;
1288 
1289   if (user_is_always_sure)
1290      return TRUE;
1291 
1292   /* display the prompt */
1293   move( MESSAGE_LINE, 0 ); clrtoeol();
1294   move( MESSAGE_LINE, COLS - strlen( MODE_QUERY ) - 2 );
1295   ADDSTR_REV( MODE_QUERY );
1296   move( MESSAGE_LINE, 0 );
1297   ADDSTR_REV( text );
1298 
1299   /* wait for Y/N or translation of Y/N */
1300   do
1301     {
1302       resp = getch_fl( ASCII_NULL );
1303 
1304       if (towideupper (resp) == 'Y' || towideupper (resp) == YN[0])
1305 	resp = 0;
1306       else if (towideupper (resp) == 'N' || towideupper (resp) == YN[2])
1307 	resp = -1;
1308     /* Some PDCURSES implementations return -1 when no key is pressed
1309        for a second or so.  So, unless resp is explicitly set to Y/N,
1310        don't exit! */
1311       else
1312 	resp = 2;
1313     }  while (resp != 0 && resp != -1);
1314 
1315   /* clear out the message line */
1316   move( MESSAGE_LINE, 0 ); clrtoeol();
1317 
1318   return resp == 0 ? TRUE : FALSE;
1319 }
1320 
1321 
1322 /*
1323   get a Y/N response from the user: returns true if we just got the
1324   expected Y/N, false if exit was by a function key
1325 */
1326 static bool
do_query(FILE * script,char * line)1327 do_query( FILE *script, char *line )
1328 {
1329   int	resp;			/* response character */
1330   int	fkey;			/* function key iterator */
1331   bool ret_code;
1332 
1333   /* display the prompt */
1334   move( MESSAGE_LINE, 0 ); clrtoeol();
1335   move( MESSAGE_LINE, COLS - strlen( MODE_QUERY ) - 2 );
1336   ADDSTR_REV( MODE_QUERY );
1337   move( MESSAGE_LINE, 0 );
1338   ADDSTR_REV( SCR_DATA( line ) );
1339 
1340   /* wait for a Y/N response, translation of Y/N or matching FKEY */
1341   while ( TRUE )
1342     {
1343       resp = getch_fl( ASCII_NULL );
1344 
1345       /* translate pseudo Fkeys into real ones if applicable
1346 	 The pseudo keys are defined in array pfkeys and are also:
1347 	 F1 - 1, F2 - 2, F3 - 3,.... F10 - 0, F11 - A, F12 - S */
1348       for ( fkey = 1; fkey <= NFKEYS; fkey++ )
1349 	{
1350 	  if ( resp == pfkeys[ fkey - 1 ] || (fkey<11 && resp == (fkey+'0'))
1351 	       || (fkey==10 && (resp =='0'))
1352 	       || (fkey==11 && (resp =='a' || resp=='A'))
1353 	       || (fkey==12 && (resp =='s' || resp=='S')))
1354 	    {
1355 	      resp = KEY_F( fkey );
1356 	      break;
1357 	    }
1358 	}
1359 
1360       /* search the key bindings for a matching key */
1361       for ( fkey = 1; fkey <= NFKEYS; fkey++ )
1362 	{
1363 	  if ( resp == KEY_F( fkey )
1364 	       && fkey_bindings[ fkey - 1 ] != NULL )
1365 	    {
1366 	      seek_label( script, fkey_bindings[ fkey - 1 ],
1367 			  NULL );
1368 	      break;
1369 	    }
1370 	}
1371       if ( fkey <= NFKEYS ) {
1372 	ret_code = FALSE;
1373 	break;
1374       }
1375 
1376       /* no FKEY binding - check for Y or N */
1377       if ( towideupper( resp ) == QUERY_Y ||
1378 	   towideupper( resp ) == YN[0] )
1379 	{
1380 	  ret_code = TRUE;
1381 	  global_resp_flag = TRUE;
1382 	  break;
1383 	}
1384       if ( towideupper( resp ) == QUERY_N ||
1385 	   towideupper( resp ) == YN[2] )
1386 	{
1387 	  ret_code = TRUE;
1388 	  global_resp_flag = FALSE;
1389 	  break;
1390 	}
1391     }
1392 
1393   /* clear out the message line */
1394   move( MESSAGE_LINE, 0 ); clrtoeol();
1395 
1396   /* get the next command */
1397   get_script_line( script, line );
1398 
1399   /* tell the caller whether we got Y/N or a function key */
1400   return ( ret_code );
1401 }
1402 
1403 /*
1404   execute a E:-command: either "E: <value>%" (only applies to the next drill)
1405   or "E: <value>%*" (applies until the next E:-command)
1406 */
1407 static void
do_error_max_set(FILE * script,char * line)1408 do_error_max_set( FILE *script, char *line )
1409 {
1410   char copy_of_line[MAX_SCR_LINE];
1411   char *data;
1412   bool star = FALSE;
1413   char *tail;
1414   double temp_value;
1415 
1416   /* we need to make a copy for a potential error-message */
1417   strcpy( copy_of_line, line );
1418 
1419   /* hide whitespace (and '*') */
1420   data = SCR_DATA( line ) + strlen( SCR_DATA( line ) ) - 1;
1421   while (data != SCR_DATA(line) && !star && (isspace( *data ) || *data == '*'))
1422     {
1423       if (*data == '*')
1424 	star = TRUE;
1425       *data = '\0';
1426       --data;
1427     }
1428   data = SCR_DATA( line );
1429   while (isspace( *data ))
1430     ++data;
1431 
1432   /* set the state variables */
1433   global_error_max_persistent = star;
1434   if (strcmp( data, "default" ) == 0 || strcmp( data, "Default" ) == 0)
1435     global_error_max = cl_default_error_max;
1436   else {
1437     /* value is not a special keyword */
1438     /* check for incorrect (not so readable) syntax */
1439     data = data + strlen( data ) - 1;
1440     if (*data != '%') {
1441       /* find out what's wrong */
1442       if (star && isspace( *data )) {
1443 	/* find out whether `line' contains '%' */
1444 	while (data != SCR_DATA( line ) && isspace( *data ))
1445 	  {
1446 	    *data = '\0';
1447 	    --data;
1448 	  }
1449 	if (*data == '%')
1450 	  /* xgettext: no-c-format */
1451 	  fatal_error( _("'*' must immediately follow '%'"), copy_of_line );
1452 	else
1453 	  /* xgettext: no-c-format */
1454 	  fatal_error( _("missing '%'"), copy_of_line );
1455       } else
1456 	/* xgettext: no-c-format */
1457 	fatal_error( _("missing '%'"), copy_of_line );
1458     }
1459     if (isspace( *(data - 1) ))
1460       /* xgettext: no-c-format */
1461       fatal_error( _("'%' must immediately follow value"), copy_of_line );
1462     /* remove '%' */
1463     *data = '\0';
1464     /* convert value: SCR_DATA(line) may contain whitespace at the
1465        beginning, but strtod ignores this */
1466     data = SCR_DATA( line );
1467     errno = 0;
1468     temp_value = (float)strtod( data, &tail );
1469     if (errno)
1470       fatal_error( _("overflow in do_error_max_set"), copy_of_line );
1471     /* TODO: if line="E:-1.0%", then tail will be ".0 "...
1472        if (*tail != '\0')
1473        fatal_error( _("can't parse value"), tail );*/
1474     /*
1475       If --error-max is specified (but *not* if the default value is used),
1476       an E:-command will only be applied if its level is more
1477       difficult (smaller) than the one specified via --error-max/-e
1478     */
1479     if (cl_error_max_specified) {
1480       if (temp_value < cl_default_error_max)
1481 	global_error_max = temp_value;
1482       else
1483 	global_error_max = cl_default_error_max;
1484     } else
1485       global_error_max = temp_value;
1486   }
1487 
1488   /* sanity checks */
1489   if (global_error_max < 0.0 || global_error_max > 100.0)
1490     fatal_error( _("Invalid value for \"E:\" (out of range)"), copy_of_line );
1491 
1492   /* get the next command */
1493   get_script_line( script, line );
1494 }
1495 
1496 /*
1497 
1498 */
1499 static void
do_on_failure_label_set(FILE * script,char * line)1500 do_on_failure_label_set( FILE *script, char *line )
1501 {
1502   char copy_of_line[MAX_SCR_LINE];
1503   char *line_iterator;
1504   bool star = FALSE;
1505   int i;
1506   char	message[MAX_SCR_LINE];
1507 
1508   /* we need to make a copy for a potential error-message */
1509   strcpy( copy_of_line, line );
1510 
1511   /* remove trailing whitespace (and '*') */
1512   line_iterator = line + strlen( line ) - 1;
1513   while (line_iterator != line && !star &&
1514 	 (isspace( *line_iterator ) || *line_iterator == '*'))
1515     {
1516       if (*line_iterator == '*')
1517 	star = TRUE;
1518       *line_iterator = '\0';
1519       --line_iterator;
1520     }
1521 
1522   global_on_failure_label_persistent = star;
1523 
1524   /* check for special value "NULL" */
1525   if (strcmp(SCR_DATA(line), "NULL") == 0)
1526     global_on_failure_label = NULL;
1527   else
1528     {
1529       /* find the right hash list for the label */
1530       i = hash_label( SCR_DATA(line) );
1531 
1532       /* search the linked list for the label */
1533       for ( global_on_failure_label = global_label_list[i];
1534 	    global_on_failure_label != NULL;
1535 	    global_on_failure_label = global_on_failure_label->next )
1536 	{
1537 	  /* see if this is our label */
1538 	  if ( strcmp( global_on_failure_label->label, SCR_DATA(line) ) == 0 )
1539 	    break;
1540 	}
1541 
1542       /* see if the label was not found in the file */
1543       if ( global_on_failure_label == NULL )
1544 	{
1545 	  sprintf( message, _("label '%s' not found"), SCR_DATA(line) );
1546 	  fatal_error( message, copy_of_line );
1547 	}
1548     }
1549 
1550   /* get the next command */
1551   get_script_line( script, line );
1552 }
1553 
1554 /*
1555   execute the directives in the script file
1556 */
1557 static void
parse_file(FILE * script,char * label)1558 parse_file( FILE *script, char *label ) {
1559 
1560   char	line[MAX_SCR_LINE];		/* line buffer */
1561   char	command;			/* current command */
1562 
1563   /* if label given then start running there */
1564   if ( label != NULL )
1565     {
1566       /* find the label we want to start at */
1567       seek_label( script, label, NULL );
1568     }
1569   else
1570     {
1571       /* start at the very beginning (a very good place to start) */
1572       rewind( script );
1573       global_line_counter = 0;
1574     }
1575   get_script_line( script, line );
1576 
1577   /* just handle lines until the end of the file */
1578   while( ! feof( script ))
1579     {
1580       command = SCR_COMMAND( line );
1581       switch( command )
1582 	{
1583 	case C_TUTORIAL:
1584 	  do_tutorial( script, line ); break;
1585 	case C_INSTRUCTION:
1586 	  do_instruction( script, line ); break;
1587 	case C_CLEAR:	do_clear( script, line ); break;
1588 	case C_GOTO:	do_goto( script, line, TRUE ); break;
1589 	case C_EXIT:	do_exit( script ); break;
1590 	case C_QUERY:	do_query( script, line ); break;
1591 	case C_YGOTO:	do_goto( script, line, global_resp_flag );
1592 	  break;
1593 	case C_NGOTO:	do_goto( script, line, !global_resp_flag );
1594 	  break;
1595 	case C_DRILL:
1596 	case C_DRILL_PRACTICE_ONLY:
1597 	  do_drill( script, line ); break;
1598 	case C_SPEEDTEST:
1599 	case C_SPEEDTEST_PRACTICE_ONLY:
1600 	  do_speedtest( script, line ); break;
1601 	case C_KEYBIND:	do_keybind( script, line ); break;
1602 
1603 	case C_LABEL:
1604 	   __update_last_label (SCR_DATA (line));
1605 	   get_script_line (script, line);
1606 	   break;
1607 	case C_ERROR_MAX_SET: do_error_max_set( script, line ); break;
1608 	case C_ON_FAILURE_SET: do_on_failure_label_set( script, line ); break;
1609 	case C_MENU: do_menu (script, line); break;
1610 	default:
1611 	  fatal_error( _("unknown command"), line );
1612 	  break;
1613 	}
1614     }
1615 }
1616 
1617 /**
1618    indent to column n
1619    @param n is the amount of times to write ' ' (negative or zero means none)
1620 **/
1621 static void
indent_to(int n)1622 indent_to( int n )
1623 {
1624   int i;
1625   for (i=0; i < n; ++i)
1626     fputc(' ', stdout);
1627 }
1628 
1629 /**
1630    Prints one usage option.
1631    The arguments op, lop and help should not have special characters '\n' '\t'
1632    @param op is the short option
1633    @param lop is the long option
1634    @param help is the explanation of the option
1635    @param col_op is the column where the short option will be displayed
1636    @param col_lop is the column where the long option will be displayed
1637    @param col_op is the column where the explanation will be displayed
1638    @param last_col is the last column that can be used
1639 **/
1640 static void
print_usage_item(char * op,char * lop,char * help,int col_op,int col_lop,int col_help,int last_col)1641 print_usage_item( char *op, char *lop, char *help,
1642 		  int col_op, int col_lop, int col_help, int last_col )
1643 {
1644   int col=0;
1645   char help_string[MAX_SCR_LINE];
1646   char *token;
1647   const char delimiters[] = " ";
1648 
1649   assert (op!=NULL && lop!=NULL && help!=NULL);
1650   assert (0<=col_op && col_op<col_lop
1651 	  && col_lop<col_help && col_help<last_col);
1652 
1653   indent_to(col_op);
1654   printf ("%s", op);
1655   col += col_op + strlen (op);
1656 
1657   indent_to(col_lop - col);
1658   printf ("%s", lop);
1659   col += MAX(0, col_lop - col) + strlen (lop);
1660 
1661   indent_to(col_help - col);
1662   col += MAX(0, col_help - col);
1663 
1664   strcpy ( help_string, help );
1665   token = strtok ( help_string, delimiters );
1666   while (token != NULL)
1667     {
1668       if (col + strlen (token) >= last_col) {
1669 	putc ( '\n', stdout );
1670 	indent_to ( col_help );
1671 	fputs ( token, stdout );
1672 	putc ( ' ', stdout );
1673 	col = col_help + strlen(token) + 1;
1674       } else {
1675 	fputs ( token, stdout );
1676 	putc ( ' ', stdout );
1677 	col += strlen(token) + 1;
1678       }
1679       token = strtok ( NULL, delimiters );
1680     }
1681   putc('\n', stdout);
1682 }
1683 
1684 
1685 /**
1686    Prints usage instructions
1687 **/
1688 static void
print_help()1689 print_help()
1690 {
1691   char *op[]=
1692     { "-b",
1693       "-e %",
1694       "-n",
1695       "-t",
1696       "-f P",
1697       "-c F,B",
1698       "-s",
1699       "-q",
1700       "-l L",
1701       "-w",
1702       "-k",
1703       "-i",
1704       "-h",
1705       "-v",
1706       "-S",
1707       "--banner-colors=F,B,P,V",
1708       "--scoring=wpm,cpm"};
1709   char *lop[]=
1710     { "--personal-best",
1711       "--max-error=%",
1712       "--notimer",
1713       "--term-cursor",
1714       "--curs-flash=P",
1715       "--colours=F,B",
1716       "--silent",
1717       "--quiet",
1718       "--start-label=L",
1719       "--word-processor",
1720       "--no-skip",
1721       "--show-errors",
1722       "--help",
1723       "--version",
1724       "--always-sure",
1725       "",
1726       ""};
1727   char *help[] =
1728     {
1729       _("track personal best typing speeds"),
1730       _("default maximum error percentage (default 3.0); valid values are "
1731 	"between 0.0 and 100.0"),
1732       _("turn off WPM timer in drills"),
1733       _("use the terminal's hardware cursor"),
1734       _("cursor flash period P*.1 sec (default 10); valid  values are between "
1735 	"0 and 512; this is ignored if -t is specified"),
1736       _("set initial display colours where available"),
1737       _("don't beep on errors"),
1738       _("same as -s, --silent"),
1739       _("start the lesson at label 'L'"),
1740       _("try to mimic word processors"),
1741       _("forbid the user to skip exercises"),
1742       _("highlight errors with reverse video"),
1743       _("print this message"),
1744       _("output version information and exit"),
1745       _("do not ask confirmation questions"),
1746       _("set top banner colours (background, foreground, package and version "
1747         "respectively)"),
1748       _("set scoring mode (words per minute or characters per minute)")};
1749 
1750   int loop;
1751 
1752   printf(_("`gtypist' is a typing tutor with several lessons for different "
1753 	   "keyboards and languages.  New lessons can be written by the user "
1754 	   "easily.\n\n"));
1755   printf("%s: %s [ %s... ] [ %s ]\n\n",
1756 	 _("Usage"),argv0,_("options"),_("script-file"));
1757   printf("%s:\n",_("Options"));
1758   /* print out each line of the help text array */
1759   for ( loop = 0; loop < sizeof(help)/sizeof(char *); loop++ )
1760     {
1761       print_usage_item( op[loop], lop[loop], help[loop], 1, 8, 25, 75 );
1762     }
1763 
1764   printf(_("\nIf not supplied, script-file defaults to '%s/%s'.\n")
1765 	 ,DATADIR,DEFAULT_SCRIPT);
1766   printf(_("The path $GTYPIST_PATH is searched for script files.\n\n"));
1767 
1768   printf("%s:\n",_("Examples"));
1769   printf("  %s:\n    %s\n\n",
1770 	 _("To run the default lesson in english `gtypist.typ'"),argv0);
1771   printf("  %s:\n    %s esp.typ\n\n",
1772 	 _("To run the lesson in spanish"),argv0);
1773   printf("  %s:\n    GTYPIST_PATH=\"/home/foo\" %s bar.typ\n\n",
1774 	 _("To instruct gtypist to look for lesson `bar.typ' in a non standard "
1775 	   "directory"),argv0);
1776   printf("  %s:\n    %s -t -q -l TEST1 /temp/test.typ\n\n",
1777 	 _("To run the lesson in the file `test.typ' of directory `temp', "
1778 	   "starting at label `TEST1', using the terminal's cursor, and run "
1779 	   "silently"),argv0);
1780   printf("%s\n",_("Report bugs to bug-gtypist@gnu.org"));
1781 }
1782 
1783 
1784 /*
1785   parse command line options for initial values
1786 */
1787 static void
parse_cmdline(int argc,char ** argv)1788 parse_cmdline( int argc, char **argv ) {
1789   int	c;				/* option character */
1790   int	option_index;			/* option index */
1791   char *str_option; /* to store string vals of cl opts */
1792   static struct option long_options[] = {	/* options table */
1793     { "personal-best",	no_argument, 0, 'b' },
1794     { "max-error",      required_argument, 0, 'e' },
1795     { "notimer",	no_argument, 0, 'n' },
1796     { "term-cursor",	no_argument, 0, 't' },
1797     { "curs-flash",	required_argument, 0, 'f' },
1798     { "colours",	required_argument, 0, 'c' },
1799     { "colors",		required_argument, 0, 'c' },
1800     { "banner-colours",	required_argument, 0, '\x01' },
1801     { "banner-colors",	required_argument, 0, '\x01' },
1802     { "silent",		no_argument, 0, 's' },
1803     { "quiet",		no_argument, 0, 'q' },
1804     { "start-label",	required_argument, 0, 'l' },
1805     { "word-processor",	no_argument, 0, 'w' },
1806     { "no-skip",        no_argument, 0, 'k' },
1807     { "show-errors",    no_argument, 0, 'i' },
1808     { "help",		no_argument, 0, 'h' },
1809     { "version",	no_argument, 0, 'v' },
1810     { "always-sure",	no_argument, 0, 'S' },
1811     { "scoring",	required_argument, 0, '\x02'},
1812     { 0, 0, 0, 0 }};
1813 
1814   /* process every option */
1815   while ( (c=getopt_long( argc, argv, "be:ntf:c:sql:wkihvS",
1816 			  long_options, &option_index )) != -1 )
1817     {
1818       switch (c)
1819 	{
1820 	case 'b':
1821 	  cl_personal_best = TRUE;
1822 	  break;
1823 	case 'e':
1824 	  cl_error_max_specified = TRUE;
1825 	  if ( sscanf( optarg, "%f", &cl_default_error_max ) != 1
1826 	       || cl_default_error_max < 0.0
1827 	       || cl_default_error_max > 100.0 )
1828 	    {
1829 	      fprintf( stderr, _("%s: invalid error-max value\n"),
1830 		       argv0 );
1831 	      exit( 1 );
1832 	    }
1833 	  break;
1834 	case 'n':
1835 	  cl_notimer = TRUE;
1836 	  break;
1837 	case 't':
1838 	  cl_term_cursor = TRUE;
1839 	  break;
1840 	case 'f':
1841 	  if ( sscanf( optarg, "%d", &cl_curs_flash ) != 1
1842 	       || cl_curs_flash < 0
1843 	       || cl_curs_flash > 512 )
1844 	    {
1845 	      fprintf( stderr, _("%s: invalid curs-flash value\n"),
1846 		       argv0 );
1847 	      exit( 1 );
1848 	    }
1849 	  break;
1850 	case 'c':
1851 	  if ( sscanf( optarg, "%d,%d",
1852 		       &cl_fgcolour, &cl_bgcolour ) != 2 ||
1853 	       cl_fgcolour < 0 || cl_fgcolour >= NUM_COLOURS ||
1854 	       cl_bgcolour < 0 || cl_bgcolour >= NUM_COLOURS )
1855 	    {
1856 	      fprintf( stderr, _("%s: invalid colours value\n"),argv0 );
1857 	      exit( 1 );
1858 	    }
1859 	  cl_colour = TRUE;
1860 	  break;
1861 	case '\x01':
1862 	  if (sscanf (optarg, "%d,%d,%d,%d",
1863 			&cl_banner_bg_colour, &cl_banner_fg_colour,
1864 			&cl_prog_name_colour, &cl_prog_version_colour) != 4)
1865 	  {
1866 	     fprintf (stderr, _("%s:  argument format is incorrect\n"), optarg);
1867 	     exit (1);
1868 	  }
1869 
1870 	  if (cl_banner_bg_colour < 0 || cl_banner_bg_colour >= NUM_COLOURS ||
1871 		cl_banner_fg_colour < 0 || cl_banner_bg_colour >= NUM_COLOURS ||
1872 		cl_prog_version_colour < 0 ||
1873 		cl_prog_version_colour >= NUM_COLOURS ||
1874 		cl_prog_name_colour < 0 || cl_prog_name_colour >= NUM_COLOURS)
1875 	  {
1876 	     fprintf (stderr, _("%s:  incorrect colour values\n"), optarg);
1877 	     exit (1);
1878 	  }
1879 
1880 	  break;
1881     case '\x02': /* Scoring */
1882       str_option = (char*)malloc( strlen(optarg) + 1 );
1883       if (sscanf (optarg, "%s", str_option) != 1)
1884         {
1885           fprintf (stderr, _("%s: invalid scoring mode"), optarg);
1886           exit (1);
1887         }
1888       if( strcmp(str_option, "cpm") == 0 )
1889         {
1890           cl_scoring_cpm = TRUE;
1891         }
1892       else if( strcmp(str_option, "wpm") == 0 )
1893         {
1894           cl_scoring_cpm = FALSE;
1895         }
1896       else
1897         {
1898           fprintf (stderr, _("%s: invalid scoring mode"), optarg);
1899           exit (1);
1900         }
1901       free( str_option );
1902 	  break;
1903 	case 's':
1904 	case 'q':
1905 	  cl_silent = TRUE;
1906 	  break;
1907 	case 'l':
1908 	  cl_start_label = (char*)malloc( strlen( optarg ) + 1 );
1909 	  if ( cl_start_label == NULL )
1910 	    {
1911 	      fprintf( stderr, _("%s: internal error: malloc\n"),argv0 );
1912 	      exit( 1 );
1913 	    }
1914 	  strcpy( cl_start_label, optarg );
1915 	  break;
1916 	case 'w':
1917 	  cl_wp_emu = TRUE;
1918 	  break;
1919 	case 'k':
1920 	  cl_no_skip = TRUE;
1921 	  break;
1922 	case 'i':
1923 	  cl_rev_video_errors = TRUE;
1924 	  break;
1925 	case 'h':
1926 	  print_help();
1927 	  exit( 0 );
1928 	  break;
1929 	case 'v':
1930 	  printf( "%s %s\n\n", PACKAGE,VERSION );
1931 	  printf( "%s\n\n", COPYRIGHT );
1932 	  printf( "%s\n", _("Written by Simon Baldwin"));
1933 	  exit( 0 );
1934 	  break;
1935 	case 'S':
1936 	  user_is_always_sure = TRUE;
1937 	  break;
1938 	case '?':
1939 	default:
1940 	  fprintf( stderr,
1941 		   _("Try '%s --help' for more information.\n"), argv0 );
1942 	  exit( 1 );
1943 	}
1944     }
1945   if ( argc - optind > 1 )
1946     {
1947       fprintf( stderr,
1948 	       _("Try '%s --help' for more information.\n"), argv0 );
1949       exit( 1 );
1950     }
1951 }
1952 
1953 
1954 /*
1955   signal handler to stop curses stuff on intr
1956 */
1957 static void
catcher(int signal)1958 catcher( int signal ) {
1959 
1960   /* unravel colours and curses, clean up and exit */
1961   if ( cl_colour && has_colors() )
1962     wbkgdset( stdscr, 0 );
1963   clear(); refresh(); endwin();
1964   printf("\n");
1965   exit( 1 );
1966 }
1967 
1968 /*
1969   open a script file
1970  */
open_script(const char * filename)1971 FILE *open_script( const char *filename )
1972 {
1973   FILE *script;
1974 
1975 #ifdef MINGW
1976   /* MinGW's ftell doesn't work properly for absolute file positions in
1977      text mode, so open in binary mode instead. */
1978   script = fopen( filename, "rb" );
1979 #else
1980   script = fopen( filename, "r" );
1981 #endif
1982 
1983   /* record script filename */
1984   if( script != NULL )
1985     global_script_filename = strdup( filename );
1986 
1987   return script;
1988 }
1989 
1990 /*
1991   main routine
1992 */
main(int argc,char ** argv)1993 int main( int argc, char **argv )
1994 {
1995   WINDOW	*scr;			/* curses window */
1996   FILE	*script;			/* script file handle */
1997   char	*p, filepath[FILENAME_MAX];	/* file paths */
1998   char	script_file[FILENAME_MAX];	/* more file paths */
1999 
2000   /* Internationalization */
2001 
2002 #if defined(ENABLE_NLS) && defined(LC_ALL)
2003   setlocale (LC_ALL, "");
2004   bindtextdomain (PACKAGE, LOCALEDIR);
2005   /* make gettext always return strings as UTF-8
2006      => this makes programming easier because now _all_ strings
2007      (from gettext and from script file) are encoded as UTF8!
2008    */
2009   bind_textdomain_codeset(PACKAGE, "utf-8");
2010   textdomain (PACKAGE);
2011 #endif
2012 
2013 #ifdef MINGW
2014   locale_encoding = "UTF-8";
2015 #else
2016   locale_encoding = nl_langinfo(CODESET);
2017 #endif
2018   isUTF8Locale = strcasecmp(locale_encoding, "UTF-8") == 0 ||
2019       strcasecmp(locale_encoding, "UTF8") == 0;
2020   /* printf("encoding is %s, UTF8=%d\n", locale_encoding, isUTF8Locale); */
2021 
2022   COPYRIGHT=
2023     _("Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Simon Baldwin.\n"
2024       "Copyright (C) 2003, 2004, 2008, 2011 GNU Typist Development Team.\n"
2025       "This program comes with ABSOLUTELY NO WARRANTY; for details\n"
2026       "please see the file 'COPYING' supplied with the source code.\n"
2027       "This is free software, and you are welcome to redistribute it\n"
2028       "under certain conditions; again, see 'COPYING' for details.\n"
2029       "This program is released under the GNU General Public License.");
2030   /* this string is displayed in the mode-line when in a tutorial */
2031   MODE_TUTORIAL=_(" Tutorial ");
2032   /* this string is displayed in the mode-line when in a query */
2033   MODE_QUERY=_("  Query   ");
2034   /* this string is displayed in the mode-line when running a drill */
2035   MODE_DRILL=_("  Drill   ");
2036   /* this string is displayed in the mode-line when running a speedtest */
2037   MODE_SPEEDTEST=_("Speed test");
2038   WAIT_MESSAGE=
2039 	  _(" Press RETURN or SPACE to continue, ESC to return to the menu ");
2040   /* this message is displayed when the user has failed in a [DS]: drill */
2041   ERROR_TOO_HIGH_MSG=
2042     _(" Your error-rate is too high. You have to achieve %.1f%%. ");
2043   /* this message is displayed when the user has failed in a [DS]: drill,
2044      and an F:<LABEL> ("on failure label") is in effect */
2045   SKIPBACK_VIA_F_MSG=
2046     _(" You failed this test, so you need to go back to %s. ");
2047   /* this is used for repeat-queries. you can translate the keys as well
2048      (if you translate msgid "R/N/E" accordingly) */
2049   REPEAT_NEXT_EXIT_MSG=
2050 	_(" Press R to repeat, N for next exercise or E to exit ");
2051   /* this is used for repeat-queries with --no-skip. you can translate
2052      the keys as well (if you translate msgid "R/N/E" accordingly) */
2053   REPEAT_EXIT_MSG=
2054 	_(" Press R to repeat or E to exit ");
2055   /* This is used make the user confirm (E)xit in REPEAT_NEXT_EXIT_MSG */
2056   CONFIRM_EXIT_LESSON_MSG=
2057     _(" Are you sure you want to exit this lesson? [Y/N] ");
2058   /* This message is displayed if the user tries to skip a lesson
2059      (ESC ESC) and --no-skip is specified */
2060   NO_SKIP_MSG=
2061     _(" Sorry, gtypist is configured to forbid skipping exercises. ");
2062   /* this must be adjusted to the right with one space at the end.
2063      Leading whitespace is important because it is displayed in reverse
2064      video because it must be aligned with the next messages (it's best
2065      to run gtypist to see this in practice) */
2066   SPEED_RAW_WPM=_(" Raw speed      = %6.2f wpm ");
2067   /* This is the CPM version of the above.  The same rules apply. */
2068   SPEED_RAW_CPM=_(" Raw speed      = %6.2f cpm ");
2069   /* this must be adjusted to the right with one space at the end.
2070      Leading whitespace is important because it is displayed in reverse
2071      video and because it must be aligned with the previous and next
2072      messages (it's best to run gtypist to see this in practice) */
2073   SPEED_ADJ_WPM=  _(" Adjusted speed = %6.2f wpm ");
2074   /* This is the CPM version of the above.  The same rules apply. */
2075   SPEED_ADJ_CPM=  _(" Adjusted speed = %6.2f cpm ");
2076   /* this must be adjusted to the right with one space at the end.
2077      Leading whitespace is important because it is displayed in reverse
2078      video and because it must be aligned with the previous and next
2079      messages (it's best to run gtypist to see this in practice) */
2080   SPEED_PCT_ERROR=_("            with %.1f%% errors ");
2081   /* this must be adjusted to the right with one space at the end.
2082      Leading whitespace is important because it is displayed in reverse
2083      video and because it must be aligned with the previous messages (it's
2084      best to run gtypist to see this in practice) */
2085   SPEED_BEST_WPM= _(" Personal best  = %6.2f wpm ");
2086   /* This is the CPM version of the above.  The same rules apply. */
2087   SPEED_BEST_CPM= _(" Personal best  = %6.2f cpm ");
2088   /* this must be adjusted to the right with one space at the end.
2089      Leading whitespace is important because it is displayed in reverse
2090      video and because it must be aligned with the previous messages
2091      (it's best to run gtypist to see this in practice) */
2092   SPEED_BEST_NEW_MSG= _("           new personal best ");
2093   /* this is used to translate the keys for Y/N-queries. Must be two
2094      uppercase letters separated by '/'. Y/N will still be accepted as
2095      well. Note that the messages (prompts) themselves cannot be
2096      translated because they are read from the script-file. */
2097   YN = convertFromUTF8(_("Y/N"));
2098   if (wcslen(YN) != 3 || YN[1] != '/' || !iswideupper(YN[0]) || !iswideupper(YN[2]))
2099     {
2100       fprintf( stderr,
2101 	       "%s: i18n problem: invalid value for msgid \"Y/N\" (3 uppercase UTF-8 chars?): %ls\n",
2102 	       argv0, YN );
2103       exit( 1 );
2104     }
2105   /* this is used to translate the keys for Repeat/Next/Exit
2106      queries. Must be three uppercase letters separated by slashes. */
2107   RNE = convertFromUTF8(_("R/N/E"));
2108   if (wcslen(RNE) != 5 ||
2109       !iswideupper(RNE[0]) || RNE[1] != '/' ||
2110       !iswideupper(RNE[2]) || RNE[3] != '/' ||
2111       !iswideupper(RNE[4]))
2112     {
2113       fprintf( stderr,
2114 	       "%s: i18n problem: invalid value for msgid \"R/N/E\" (5 uppercase UTF-8 chars?): %ls\n",
2115 	       argv0, RNE );
2116       exit( 1 );
2117     }
2118 
2119   /* get our name for error messages */
2120   argv0 = argv[0] + strlen( argv[0] );
2121   while ( argv0 > argv[0] && *argv0 != '/' )
2122     argv0--;
2123   if ( *argv0 == '/' ) argv0++;
2124 
2125   /* check usage */
2126   parse_cmdline( argc, argv );
2127 
2128   /* figure out what script file to use */
2129   if ( argc - optind == 1 )
2130     {
2131       /* try and open scipr file from command line */
2132       strcpy( script_file, argv[optind] );
2133       script = open_script( script_file );
2134 
2135       /* we failed, so check for script in GTYPIST_PATH */
2136       if( !script && getenv( "GTYPIST_PATH" ) )
2137         {
2138           for( p = strtok( getenv( "GTYPIST_PATH" ), ":" );
2139 	       p != NULL; p = strtok( NULL, ":" ) )
2140 	    {
2141 	      strcpy( filepath, p );
2142 	      strcat( filepath, "/" );
2143 	      strcat( filepath, script_file );
2144 	      script = open_script( filepath );
2145 	      if( script )
2146 		break;
2147 	    }
2148 	}
2149 
2150       /* we failed, so try to find script in DATADIR */
2151       if( !script )
2152         {
2153           strcpy( filepath, DATADIR );
2154           strcat( filepath, "/" );
2155           strcat( filepath, script_file );
2156           script = open_script( filepath );
2157 	}
2158     }
2159   else
2160     {
2161       /* open default script */
2162       sprintf( script_file, "%s/%s", DATADIR, DEFAULT_SCRIPT );
2163       script = open_script( script_file );
2164     }
2165 
2166   /* check to make sure we open a script */
2167   if( !script )
2168     {
2169       fprintf( stderr, "%s: %s %s\n",
2170 	       argv0, _("can't find or open file"), script_file );
2171       exit( 1 );
2172     }
2173 
2174   /* reset global_error_max */
2175   global_error_max = cl_default_error_max;
2176 
2177   /* check for user home directory */
2178 #ifdef MINGW
2179   global_home_env = "APPDATA";
2180 #else
2181   global_home_env = "HOME";
2182 #endif
2183   if( !getenv( global_home_env ) || !strlen( getenv( global_home_env ) ) )
2184     {
2185       fprintf( stderr, _("%s: %s environment variable not set\n"), \
2186 	  argv0, global_home_env );
2187       exit( 1 );
2188     }
2189 
2190   /* prepare for curses stuff, and set up a signal handler
2191      to undo curses if we get interrupted */
2192   scr = initscr();
2193   signal( SIGINT, catcher );
2194   signal( SIGTERM, catcher );
2195 #ifndef MINGW
2196   signal( SIGHUP, catcher );
2197   signal( SIGQUIT, catcher );
2198 #ifndef DJGPP
2199   signal( SIGCHLD, catcher );
2200 #endif
2201   signal( SIGPIPE, catcher );
2202 #endif
2203   clear(); refresh(); typeahead( -1 );
2204   keypad( scr, TRUE ); noecho(); curs_set( 0 ); raw();
2205 
2206   // Quick hack to get rid of the escape delays
2207 #ifdef __NCURSES_H
2208   ESCDELAY = 1;
2209 #endif
2210 
2211   /* set up colour pairs if possible */
2212   if (has_colors ())
2213   {
2214      start_color ();
2215 
2216      init_pair (C_NORMAL,
2217 		 colour_array [cl_fgcolour],
2218 		 colour_array [cl_bgcolour]);
2219      wbkgdset (stdscr, COLOR_PAIR (C_NORMAL));
2220 
2221      init_pair (C_BANNER,
2222 		     colour_array [cl_banner_fg_colour],
2223 		     colour_array [cl_banner_bg_colour]);
2224      init_pair (C_PROG_NAME,
2225 		     colour_array [cl_banner_fg_colour],
2226 		     colour_array [cl_prog_name_colour]);
2227      init_pair (C_PROG_VERSION,
2228 		     colour_array [cl_banner_fg_colour],
2229 		     colour_array [cl_prog_version_colour]);
2230      init_pair (C_MENU_TITLE,
2231 		     colour_array [cl_menu_title_colour],
2232 		     colour_array [cl_bgcolour]);
2233   }
2234 
2235   /* put up the top line banner */
2236   clear();
2237   banner (_("Loading the script..."));
2238 
2239   check_script_file_with_current_encoding(script);
2240 
2241   /* index all the labels in the file */
2242   build_label_index( script );
2243 
2244   /* run the input file */
2245   parse_file( script, cl_start_label );
2246   do_exit( script );
2247 
2248   /* for lint... */
2249   return( 0 );
2250 }
2251 
do_bell()2252 void do_bell() {
2253 #ifndef MINGW
2254   putchar( ASCII_BELL );
2255   fflush( stdout );
2256 #endif
2257 }
2258 
get_best_speed(const char * script_filename,const char * excersise_label,double * adjusted_cpm)2259 bool get_best_speed( const char *script_filename,
2260 		     const char *excersise_label, double *adjusted_cpm )
2261 {
2262   FILE *blfile;				/* bestlog file */
2263   char *filename;			/* bestlog filename */
2264   char *search;				/* string to match in bestlog */
2265   char line[FILENAME_MAX];		/* single line from bestlog */
2266   int search_len;			/* length of search string */
2267   bool found = FALSE;			/* did we find it? */
2268   int a;
2269   char *fixed_script_filename;		/* fixed-up script filename */
2270   char *p;
2271 
2272   /* calculate filename */
2273   filename = (char *)malloc( strlen( getenv( global_home_env ) ) +
2274                              strlen( BESTLOG_FILENAME ) + 2 );
2275   if( filename == NULL )
2276     {
2277        perror( "malloc" );
2278        fatal_error( _( "internal error: malloc" ), NULL );
2279     }
2280   sprintf( filename, "%s/%s", getenv( global_home_env ), BESTLOG_FILENAME );
2281 
2282   /* open best speeds file */
2283   blfile = fopen( filename, "r" );
2284   if( blfile == NULL )
2285     {
2286       free( filename );
2287       return FALSE;
2288     }
2289 
2290   /* fix-up script filename */
2291   fixed_script_filename = strdup( script_filename );
2292   if( fixed_script_filename == NULL )
2293     {
2294        perror( "malloc" );
2295        fatal_error( _( "internal error: malloc" ), NULL );
2296     }
2297   p = fixed_script_filename;
2298   while( *p != '\0' )
2299     {
2300       if( *p == ' ' )
2301 	*p = '+';
2302       p++;
2303     }
2304 
2305   /* construct search string */
2306   search_len = strlen( script_filename ) + strlen( excersise_label ) + 3;
2307   search = (char *)malloc( search_len + 1 );
2308   if( search == NULL )
2309     {
2310        perror( "malloc" );
2311        fatal_error( _( "internal error: malloc" ), NULL );
2312     }
2313   sprintf( search, " %s:%s ", fixed_script_filename, excersise_label );
2314 
2315   /* search for lines that match and use data from the last one */
2316   while( fgets( line, FILENAME_MAX, blfile ) )
2317     {
2318       /* check that there are at least 19 chars (yyyy-mm-dd hh:mm:ss) */
2319       for( a = 0; a < 19; a++ )
2320 	if( line[ a ] == '\0' )
2321 	  continue;
2322 
2323       /* look for search string and try to obtain a speed */
2324       if( !strncmp( search, line + 19, search_len ) &&
2325 	  sscanf( line + 19 + search_len, "%lg", adjusted_cpm ) == 1 )
2326 	{
2327 	  found = TRUE;
2328 	}
2329     }
2330 
2331   /* cleanup and return */
2332   free( search );
2333   free( filename );
2334   free( fixed_script_filename );
2335   fclose( blfile );
2336   return found;
2337 }
2338 
put_best_speed(const char * script_filename,const char * excersise_label,double adjusted_cpm)2339 void put_best_speed( const char *script_filename,
2340 		     const char *excersise_label, double adjusted_cpm )
2341 {
2342   FILE *blfile;				/* bestlog file */
2343   char *filename;			/* bestlog filename */
2344   char *fixed_script_filename;		/* fixed-up script filename */
2345   char *p;
2346 
2347   /* calculate filename */
2348   filename = (char *)malloc( strlen( getenv( global_home_env ) ) +
2349                              strlen( BESTLOG_FILENAME ) + 2 );
2350   if( filename == NULL )
2351     {
2352        perror( "malloc" );
2353        fatal_error( _( "internal error: malloc" ), NULL );
2354     }
2355   sprintf( filename, "%s/%s", getenv( global_home_env ), BESTLOG_FILENAME );
2356 
2357   /* open best speeds files */
2358   blfile = fopen( filename, "a" );
2359   if( blfile == NULL )
2360     {
2361        perror( "fopen" );
2362        fatal_error( _(" internal error: fopen" ), NULL );
2363     }
2364 
2365   /* fix-up script filename */
2366   fixed_script_filename = strdup( script_filename );
2367   if( fixed_script_filename == NULL )
2368     {
2369        perror( "malloc" );
2370        fatal_error( _( "internal error: malloc" ), NULL );
2371     }
2372   p = fixed_script_filename;
2373   while( *p != '\0' )
2374     {
2375       if( *p == ' ' )
2376 	*p = '+';
2377       p++;
2378     }
2379 
2380   /* get time */
2381   time_t nowts = time( NULL );
2382   struct tm *now = localtime( &nowts );
2383 
2384   /* append new score */
2385   fprintf( blfile, "%04d-%02d-%02d %02d:%02d:%02d %s:%s %g\n",
2386 	   now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour,
2387 	   now->tm_min, now->tm_sec, fixed_script_filename, excersise_label,
2388 	   adjusted_cpm );
2389 
2390   /* cleanup */
2391   free( filename );
2392   free( fixed_script_filename );
2393   fclose( blfile );
2394 }
2395 
2396 /*
2397   Local Variables:
2398   tab-width: 8
2399   End:
2400 */
2401