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