1 /**************************************************************************/
2 /* Klavaro - a flexible touch typing tutor */
3 /* Copyright (C) from 2005 until 2008 Felipe Castro */
4 /* Copyright (C) from 2009 until 2019 The Free Software Foundation */
5 /* */
6 /* This program is free software, licensed under the terms of the GNU */
7 /* General Public License as published by the Free Software Foundation, */
8 /* either version 3 of the License, or (at your option) any later */
9 /* version. You should have received a copy of the GNU General Public */
10 /* License along with this program. If not, */
11 /* see <https://www.gnu.org/licenses/>. */
12 /**************************************************************************/
13
14 /*
15 * Shared tutor window tasks
16 */
17 #include <math.h>
18 #include <string.h>
19 #include <time.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <locale.h>
23 #include <errno.h>
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <gtk/gtk.h>
27
28 #include "main.h"
29 #include "auxiliar.h"
30 #include "callbacks.h"
31 #include "translation.h"
32 #include "keyboard.h"
33 #include "cursor.h"
34 #include "basic.h"
35 #include "adaptability.h"
36 #include "velocity.h"
37 #include "fluidness.h"
38 #include "accuracy.h"
39 #include "top10.h"
40 #include "tutor.h"
41
42 extern GtkCssProvider *keyb_css;
43
44 #define MAX_TOUCH_TICS 4000
45 struct
46 {
47 TutorType type;
48 TutorQuery query;
49 GTimer *tmr;
50 gdouble elapsed_time;
51 gdouble touch_time[MAX_TOUCH_TICS + 1];
52 guint ttidx;
53 gint n_touchs;
54 gint n_errors;
55 gint retro_pos;
56 gint correcting;
57 } tutor;
58
59 struct
60 {
61 struct
62 {
63 double accuracy;
64 double speed;
65 } basic;
66 struct
67 {
68 double accuracy;
69 double speed;
70 double accuracy_learning;
71 double accuracy_improving;
72 double accuracy_reaching;
73 } adapt;
74 struct
75 {
76 double accuracy;
77 double speed;
78 double speed_crawling;
79 double speed_stepping;
80 double speed_walking;
81 double speed_jogging;
82 double speed_running;
83 double speed_professional;
84 double speed_racer;
85 double speed_flying;
86 } velo;
87 struct
88 {
89 double accuracy;
90 double speed;
91 double fluidity;
92 double fluidity_stumbling;
93 double speed_flying;
94 } fluid;
95 } goal;
96
97 extern gchar *OTHER_DEFAULT;
98
99 /*******************************************************************************
100 * Interface functions
101 */
102 TutorType
tutor_get_type()103 tutor_get_type ()
104 {
105 return (tutor.type);
106 }
107
108 gchar *
tutor_get_type_name()109 tutor_get_type_name ()
110 {
111 static gchar type_name[4][6] = { "basic", "adapt", "velo", "fluid" };
112
113 return (type_name[tutor.type]);
114 }
115
116 gboolean
tutor_is_tibetan()117 tutor_is_tibetan ()
118 {
119 gchar *code;
120 gboolean is_tibt;
121
122 if (tutor.type == TT_BASIC || tutor.type == TT_ADAPT)
123 {
124 code = keyb_get_country_code (keyb_get_name ());
125 is_tibt = g_str_equal (code, "bo");
126 g_free (code);
127 return (is_tibt);
128 }
129 else
130 {
131 return (g_str_has_prefix (main_preferences_get_string ("interface", "language"), "bo"));
132 }
133 }
134
135 TutorQuery
tutor_get_query()136 tutor_get_query ()
137 {
138 return (tutor.query);
139 }
140
141 void
tutor_set_query(TutorQuery query)142 tutor_set_query (TutorQuery query)
143 {
144 tutor.query = query;
145 }
146
147 gint
tutor_get_correcting()148 tutor_get_correcting ()
149 {
150 if (tutor.type != TT_FLUID)
151 return (FALSE);
152 return (tutor.correcting);
153 }
154
155 void
tutor_init_timers()156 tutor_init_timers ()
157 {
158 tutor.tmr = g_timer_new ();
159 }
160
161 #define GOAL_GSET(MODULE, GOAL, DEFAULT_VAL) \
162 if (main_preferences_exist ("goals", #MODULE "_" #GOAL))\
163 goal.MODULE.GOAL = (gdouble) main_preferences_get_int ("goals", #MODULE "_" #GOAL);\
164 else\
165 {\
166 goal.MODULE.GOAL = DEFAULT_VAL;\
167 main_preferences_set_int ("goals", #MODULE "_" #GOAL, DEFAULT_VAL);\
168 }
169 #define LEVEL_GSET(MODULE, GOAL, DEFAULT_VAL) \
170 if (main_preferences_exist ("levels", #MODULE "_" #GOAL))\
171 goal.MODULE.GOAL = (gdouble) main_preferences_get_int ("levels", #MODULE "_" #GOAL);\
172 else\
173 {\
174 goal.MODULE.GOAL = DEFAULT_VAL;\
175 main_preferences_set_int ("levels", #MODULE "_" #GOAL, DEFAULT_VAL);\
176 }
177 void
tutor_init_goals()178 tutor_init_goals ()
179 {
180 GOAL_GSET (basic, accuracy, 95);
181 GOAL_GSET (basic, speed, 10);
182 GOAL_GSET (adapt, accuracy, 98);
183 GOAL_GSET (adapt, speed, 10);
184 GOAL_GSET (velo, accuracy, 95);
185 GOAL_GSET (velo, speed, 50);
186 GOAL_GSET (fluid, accuracy, 97);
187 GOAL_GSET (fluid, speed, 50);
188 GOAL_GSET (fluid, fluidity, 70);
189
190 LEVEL_GSET (adapt, accuracy_learning, 50);
191 LEVEL_GSET (adapt, accuracy_improving, 90);
192 LEVEL_GSET (adapt, accuracy_reaching, 95);
193
194 LEVEL_GSET (velo, speed_crawling, 10);
195 LEVEL_GSET (velo, speed_stepping, 20);
196 LEVEL_GSET (velo, speed_walking, 30);
197 LEVEL_GSET (velo, speed_jogging, 40);
198 LEVEL_GSET (velo, speed_running, 60);
199 LEVEL_GSET (velo, speed_professional, 70);
200 LEVEL_GSET (velo, speed_racer, 80);
201 LEVEL_GSET (velo, speed_flying, 90);
202
203 LEVEL_GSET (fluid, fluidity_stumbling, 60);
204 LEVEL_GSET (fluid, speed_flying, 90);
205
206 /*
207 g_printf ("basic accur: %.0f\n", goal.basic.accuracy);
208 g_printf ("basic speed: %.0f\n", goal.basic.speed);
209 g_printf ("adapt accur: %.0f\n", goal.adapt.accuracy);
210 g_printf ("adapt speed: %.0f\n", goal.adapt.speed);
211 g_printf ("velo accur: %.0f\n", goal.velo.accuracy);
212 g_printf ("velo speed: %.0f\n", goal.velo.speed);
213 g_printf ("fluid accur: %.0f\n", goal.fluid.accuracy);
214 g_printf ("fluid speed: %.0f\n", goal.fluid.speed);
215 g_printf ("fluid fluidity: %.0f\n", goal.fluid.fluidity);
216
217 g_printf ("adapt learning: %.0f\n", goal.adapt.accuracy_learning);
218 g_printf ("adapt improving: %.0f\n", goal.adapt.accuracy_improving);
219 g_printf ("adapt reaching: %.0f\n", goal.adapt.accuracy_reaching);
220
221 g_printf ("velo crawling: %.0f\n", goal.velo.speed_crawling);
222 g_printf ("velo stepping: %.0f\n", goal.velo.speed_stepping);
223 g_printf ("velo walking: %.0f\n", goal.velo.speed_walking);
224 g_printf ("velo jogging: %.0f\n", goal.velo.speed_jogging);
225 g_printf ("velo running: %.0f\n", goal.velo.speed_running);
226 g_printf ("velo professional: %.0f\n", goal.velo.speed_professional);
227 g_printf ("velo racer: %.0f\n", goal.velo.speed_racer);
228 g_printf ("velo flying: %.0f\n", goal.velo.speed_flying);
229
230 g_printf ("fluid stumbling: %.0f\n", goal.fluid.fluidity_stumbling);
231 g_printf ("fluid flying: %.0f\n", goal.fluid.speed_flying);
232 */
233 }
234
235 gdouble
tutor_goal_accuracy()236 tutor_goal_accuracy ()
237 {
238 switch (tutor.type)
239 {
240 case TT_BASIC: return goal.basic.accuracy;
241 case TT_ADAPT: return goal.adapt.accuracy;
242 case TT_VELO: return goal.velo.accuracy;
243 case TT_FLUID: return goal.fluid.accuracy;
244 }
245 return -1.0;
246 }
247
248 gdouble
tutor_goal_speed()249 tutor_goal_speed ()
250 {
251 switch (tutor.type)
252 {
253 case TT_BASIC: return goal.basic.speed;
254 case TT_ADAPT: return goal.adapt.speed;
255 case TT_VELO: return goal.velo.speed;
256 case TT_FLUID: return goal.fluid.speed;
257 }
258 return -1.0;
259 }
260
261 gdouble
tutor_goal_fluidity()262 tutor_goal_fluidity ()
263 {
264 switch (tutor.type)
265 {
266 case TT_BASIC:
267 case TT_ADAPT:
268 case TT_VELO: return 0.0;
269 case TT_FLUID: return goal.fluid.fluidity;
270 }
271 return -1.0;
272 }
273
274 gdouble
tutor_goal_level(guint n)275 tutor_goal_level (guint n)
276 {
277 switch (tutor.type)
278 {
279 case TT_ADAPT: if (n > 2) return -1.0; return (&goal.adapt.accuracy_learning) [n];
280 case TT_VELO: if (n > 7) return -2.0; return (&goal.velo.speed_crawling) [n];
281 case TT_FLUID: if (n > 1) return -3.0; return (&goal.fluid.fluidity_stumbling) [n];
282 }
283 return -4.0;
284 }
285
286 /**********************************************************************
287 * Initialize the course
288 */
289 void
tutor_init(TutorType tt_type)290 tutor_init (TutorType tt_type)
291 {
292 gchar *tmp_title = NULL;
293 gchar *tmp_name = NULL;
294 GtkWidget *wg;
295
296 gtk_widget_hide (get_wg ("window_main"));
297 gtk_widget_hide (get_wg ("window_keyboard"));
298 gtk_widget_hide (get_wg ("dialog_info"));
299 gtk_widget_hide (get_wg ("aboutdialog_klavaro"));
300 gtk_widget_hide (get_wg ("togglebutton_toomuch_errors"));
301 gtk_widget_show (get_wg ("window_tutor"));
302 gtk_widget_grab_focus (get_wg ("entry_mesg"));
303
304 tutor.type = tt_type;
305 cursor_set_blink (FALSE);
306
307 /******************************
308 * Set the layout for each exercise type
309 */
310 gtk_widget_hide (get_wg ("button_tutor_top10"));
311 gtk_widget_hide (get_wg ("entry_custom_basic_lesson"));
312 if (tutor.type == TT_BASIC || tutor.type == TT_FLUID)
313 {
314 gtk_widget_show (get_wg ("label_lesson"));
315 gtk_widget_show (get_wg ("spinbutton_lesson"));
316 gtk_widget_show (get_wg ("vseparator_tutor_2"));
317 if (tutor.type == TT_BASIC)
318 {
319 gtk_widget_show (get_wg ("togglebutton_edit_basic_lesson"));
320 gtk_widget_hide (get_wg ("button_tutor_top10"));
321 gtk_widget_show (get_wg ("button_tutor_show_keyb"));
322 gtk_label_set_text (GTK_LABEL (get_wg ("label_lesson")), _("Lesson:"));
323 callbacks_shield_set (TRUE);
324 gtk_spin_button_set_range (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")), 1, MAX_BASIC_LESSONS);
325 callbacks_shield_set (FALSE);
326 }
327 else
328 {
329 gtk_widget_hide (get_wg ("togglebutton_edit_basic_lesson"));
330 gtk_widget_show (get_wg ("button_tutor_top10"));
331 gtk_widget_hide (get_wg ("button_tutor_show_keyb"));
332 gtk_label_set_text (GTK_LABEL (get_wg ("label_lesson")), _("Paragraphs:"));
333 callbacks_shield_set (TRUE);
334 gtk_spin_button_set_range (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")), 0, 10);
335 callbacks_shield_set (FALSE);
336 }
337 }
338 else
339 {
340 gtk_widget_hide (get_wg ("label_lesson"));
341 gtk_widget_hide (get_wg ("spinbutton_lesson"));
342 gtk_widget_hide (get_wg ("vseparator_tutor_2"));
343 gtk_widget_hide (get_wg ("togglebutton_edit_basic_lesson"));
344 gtk_widget_hide (get_wg ("button_tutor_show_keyb"));
345 gtk_widget_hide (get_wg ("button_tutor_top10"));
346 }
347
348 if (tutor.type == TT_BASIC || tutor.type == TT_ADAPT)
349 {
350 gtk_widget_hide (get_wg ("button_tutor_other"));
351 gtk_widget_hide (get_wg ("vseparator_tutor_1"));
352 }
353 else
354 {
355 gtk_widget_show (get_wg ("button_tutor_other"));
356 gtk_widget_show (get_wg ("vseparator_tutor_1"));
357 }
358
359 /******************************
360 * Set decoration texts and tips
361 */
362 switch (tutor.type)
363 {
364 case TT_BASIC:
365 tmp_title = g_strdup (_("Klavaro - Basic Course"));
366 tmp_name = g_strdup ("");
367 break;
368
369 case TT_ADAPT:
370 tmp_title = g_strdup (_("Klavaro - Adaptability"));
371 tmp_name =
372 g_strdup (_
373 ("Adaptability exercises: automating the fingers"
374 " responses, typing over all the keyboard."));
375 break;
376
377 case TT_VELO:
378 tmp_title = g_strdup (_("Klavaro - Velocity"));
379 tmp_name = g_strdup (_("Velocity exercises: accelerate typing real words."));
380 break;
381
382 case TT_FLUID:
383 tmp_title = g_strdup (_("Klavaro - Fluidness"));
384 tmp_name =
385 g_strdup (_("Fluidness exercises: accuracy typing good sense paragraphs."));
386 break;
387 }
388 gtk_window_set_title (get_win ("window_tutor"), tmp_title);
389 wg = get_wg ("label_heading");
390 gtk_label_set_text (GTK_LABEL (wg), tmp_name);
391 g_free (tmp_title);
392 g_free (tmp_name);
393
394 /******************************
395 * Set tooltips of tutor entry (drag and drop)
396 */
397 if (tutor.type == TT_VELO || tutor.type == TT_FLUID)
398 gtk_widget_set_tooltip_text (get_wg ("entry_mesg"), _("Drag and drop text here to practice with it."));
399 else
400 gtk_widget_set_tooltip_text (get_wg ("entry_mesg"), "");
401
402 /******************************
403 * Set specific variables
404 */
405 tutor.query = QUERY_INTRO;
406 if (tutor.type == TT_BASIC)
407 {
408 basic_init ();
409 if (basic_get_lesson () > 1)
410 {
411 tutor_process_touch ('\0');
412 return;
413 }
414 }
415 else if (tutor.type == TT_VELO)
416 {
417 velo_init ();
418 }
419 else if (tutor.type == TT_FLUID)
420 {
421 fluid_init ();
422 }
423 tutor_update ();
424 }
425
426
427 /**********************************************************************
428 * Update what is shown in the tutor window.
429 */
430 void
tutor_update()431 tutor_update ()
432 {
433 switch (tutor.query)
434 {
435 case QUERY_INTRO:
436 tutor_update_intro ();
437 break;
438
439 case QUERY_START:
440 tutor_update_start ();
441 break;
442
443 case QUERY_PROCESS_TOUCHS:
444 break;
445
446 case QUERY_END:
447 tutor_message (_("End of exercise. Press [Enter] to start another."));
448 break;
449 }
450 }
451
452 void
tutor_update_intro()453 tutor_update_intro ()
454 {
455 gchar *tmp_name;
456 gchar *text;
457 gchar *color_bg;
458 GdkRGBA color;
459 GtkWidget *wg;
460 GtkLabel *wg_label;
461 GtkTextView *wg_text;
462 GtkAdjustment *scroll;
463 GtkTextIter start;
464 GtkTextIter end;
465 GtkStyleContext *sc;
466
467 if (tutor.type == TT_BASIC)
468 {
469 callbacks_shield_set (TRUE);
470 gtk_spin_button_set_value (GTK_SPIN_BUTTON
471 (get_wg ("spinbutton_lesson")),
472 basic_get_lesson ());
473 callbacks_shield_set (FALSE);
474
475 wg_label = GTK_LABEL (get_wg ("label_heading"));
476 gtk_label_set_text (wg_label, _("Learning the key positions."));
477 }
478
479 tutor_message (_("Press any key to start the exercise. "));
480
481 tmp_name = g_strconcat ("_", tutor_get_type_name (), "_intro.txt", NULL);
482 text = trans_read_text (tmp_name);
483 g_free (tmp_name);
484
485 wg_text = GTK_TEXT_VIEW (get_wg ("text_tutor"));
486 gtk_text_buffer_set_text (gtk_text_view_get_buffer (wg_text), text, -1);
487 g_free (text);
488
489 gtk_text_buffer_get_bounds (gtk_text_view_get_buffer (wg_text), &start, &end);
490 gtk_text_buffer_apply_tag_by_name (gtk_text_view_get_buffer (wg_text), "lesson_font", &start, &end);
491 gtk_text_buffer_apply_tag_by_name (gtk_text_view_get_buffer (wg_text), "text_intro", &start, &end);
492
493 /*
494 * Apply tutor background color and font to the intro
495 */
496 if (main_preferences_exist ("colors", "text_intro_bg"))
497 color_bg = main_preferences_get_string ("colors", "text_intro_bg");
498 else
499 color_bg = g_strdup (TUTOR_WHITE);
500 /* Check if altcolor applies */
501 if (main_altcolor_get_boolean ("colors", "altcolor") == TRUE)
502 {
503 g_free (color_bg);
504 color_bg = main_altcolor_get_string ("colors", "text_intro_bg");
505 }
506 gdk_rgba_parse (&color, color_bg);
507 /* FIXME: update deprecated function for background color */
508 gtk_widget_override_background_color (get_wg ("text_tutor"), GTK_STATE_FLAG_INSENSITIVE, &color);
509 g_free (color_bg);
510
511 wg = get_wg ("scrolledwindow_tutor_main");
512 scroll = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (wg));
513 gtk_adjustment_set_value (scroll, 0);
514
515 callbacks_shield_set (TRUE);
516 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_wg ("togglebutton_tutor_intro")), TRUE);
517 callbacks_shield_set (FALSE);
518 }
519
520 void
tutor_update_start()521 tutor_update_start ()
522 {
523 gchar *tmp_name;
524 gchar *text;
525 gchar *color_bg;
526 GdkRGBA color;
527 GtkWidget *wg;
528 GtkTextBuffer *buf;
529 GtkTextIter start;
530 GtkTextIter end;
531 GtkAdjustment *scroll;
532
533 /*
534 * Delete all the text on tutor window
535 */
536 wg = get_wg ("text_tutor");
537 buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg));
538 gtk_text_buffer_set_text (buf, "", -1);
539
540 if (tutor.type == TT_BASIC)
541 {
542 callbacks_shield_set (TRUE);
543 gtk_spin_button_set_value (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")),
544 basic_get_lesson ());
545 callbacks_shield_set (FALSE);
546
547 tmp_name = g_ucs4_to_utf8 (basic_get_char_set (), -1, NULL, NULL, NULL);
548 text = g_strdup_printf ("%s %s", _("Keys:"), tmp_name);
549 g_free (tmp_name);
550 wg = get_wg ("label_heading");
551 gtk_label_set_text (GTK_LABEL (wg), text);
552 g_free (text);
553
554 basic_draw_lesson ();
555 }
556
557 switch (tutor.type)
558 {
559 case TT_BASIC:
560 break;
561 case TT_ADAPT:
562 adapt_draw_random_pattern ();
563 break;
564 case TT_VELO:
565 if (main_velo_txt ())
566 fluid_draw_random_paragraphs ();
567 else
568 velo_draw_random_words ();
569 break;
570 case TT_FLUID:
571 fluid_draw_random_paragraphs ();
572 }
573
574 /*
575 * Apply tutor background color and font to the text
576 */
577 if (main_preferences_exist ("colors", "char_untouched_bg"))
578 color_bg = main_preferences_get_string ("colors", "char_untouched_bg");
579 else
580 color_bg = g_strdup (TUTOR_CREAM);
581 /* Check if altcolor applies */
582 if (main_altcolor_get_boolean ("colors", "altcolor") == TRUE)
583 {
584 g_free (color_bg);
585 color_bg = main_altcolor_get_string ("colors", "char_untouched_bg");
586 }
587 /* FIXME: update deprecated function for background color */
588 gdk_rgba_parse (&color, color_bg);
589 gtk_widget_override_background_color (get_wg ("text_tutor"), GTK_STATE_FLAG_INSENSITIVE, &color);
590 g_free (color_bg);
591
592 gtk_text_buffer_get_bounds (buf, &start, &end);
593 gtk_text_buffer_apply_tag_by_name (buf, "lesson_font", &start, &end);
594 gtk_text_buffer_apply_tag_by_name (buf, "char_untouched", &start, &end);
595
596 /* Trying to minimize wrapping toggle because of cursor blinking:
597 */
598 end = start;
599 while (gtk_text_iter_forward_word_end (&end))
600 {
601 gtk_text_buffer_apply_tag_by_name (buf, "char_keep_wrap", &start, &end);
602 start = end;
603 if (! gtk_text_iter_forward_char (&end))
604 break;
605 /* This second one seems to not be needed. FIXME ?
606 gtk_text_buffer_apply_tag_by_name (buf, "char_keep_wrap2", &start, &end);
607 */
608 start = end;
609 }
610
611 if (tutor.type == TT_FLUID)
612 tmp_name = g_strconcat (_("Start typing when you are ready. "), " ",
613 _("Use backspace to correct errors."), " ", NULL);
614 else
615 tmp_name = g_strdup (_("Start typing when you are ready. "));
616
617 tutor_message (tmp_name);
618 g_free (tmp_name);
619
620 wg = get_wg ("scrolledwindow_tutor_main");
621 scroll = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (wg));
622 gtk_adjustment_set_value (scroll, 0);
623
624 callbacks_shield_set (TRUE);
625 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_wg ("togglebutton_tutor_intro")), FALSE);
626 callbacks_shield_set (FALSE);
627 }
628
629
630 /**********************************************************************
631 * Respond to each touch of the user, according to the tutor.query mode
632 */
633 void
tutor_process_touch(gunichar user_chr)634 tutor_process_touch (gunichar user_chr)
635 {
636 gboolean go_on;
637 gchar *u8ch;
638 GtkTextView *wg_text;
639 GtkTextBuffer *wg_buffer;
640 GtkTextIter start;
641
642 wg_text = GTK_TEXT_VIEW (get_wg ("text_tutor"));
643 wg_buffer = gtk_text_view_get_buffer (wg_text);
644
645 switch (tutor.query)
646 {
647 case QUERY_PROCESS_TOUCHS:
648 break;
649
650 case QUERY_INTRO:
651 tutor.query = QUERY_START;
652 tutor_update ();
653 tutor.n_touchs = 0;
654 tutor.n_errors = 0;
655 tutor.retro_pos = 0;
656 tutor.correcting = 0;
657 tutor.ttidx = 0;
658 gtk_text_buffer_get_start_iter (wg_buffer, &start);
659 gtk_text_buffer_place_cursor (wg_buffer, &start);
660 cursor_set_blink (TRUE);
661 cursor_on (NULL);
662 accur_sort ();
663
664 switch (tutor.type)
665 {
666 case TT_BASIC:
667 hints_update_from_char (cursor_get_char ());
668 tutor_speak_char ();
669 return;
670 case TT_ADAPT:
671 tutor_speak_char ();
672 return;
673 case TT_VELO:
674 tutor_speak_word ();
675 return;
676 case TT_FLUID:
677 return;
678 }
679 return;
680
681 case QUERY_START:
682 tutor.query = QUERY_PROCESS_TOUCHS;
683 callbacks_shield_set (TRUE);
684 u8ch = g_malloc0 (7);
685 if (g_unichar_to_utf8 (user_chr, u8ch) > 0)
686 tutor_message (u8ch);
687 else
688 tutor_message ("");
689 g_free (u8ch);
690 callbacks_shield_set (FALSE);
691
692 g_timer_start (tutor.tmr);
693 if (tutor.type == TT_FLUID)
694 tutor_eval_forward_backward (user_chr);
695 else
696 tutor_eval_forward (user_chr);
697
698 switch (tutor.type)
699 {
700 case TT_BASIC:
701 hints_update_from_char (cursor_get_char ());
702 tutor_speak_char ();
703 return;
704 case TT_ADAPT:
705 tutor_speak_char ();
706 return;
707 case TT_VELO:
708 tutor_speak_word ();
709 return;
710 case TT_FLUID:
711 return;
712 }
713 return;
714
715 case QUERY_END:
716 if (user_chr == UPSYM)
717 {
718 basic_set_lesson_increased (FALSE);
719 tutor.query = QUERY_INTRO;
720 tutor_process_touch (L'\0');
721 }
722 else if (user_chr == (gunichar) 8 && tutor.type == TT_BASIC)
723 {
724 basic_set_lesson (basic_get_lesson () - 1);
725 basic_init_char_set ();
726 basic_set_lesson_increased (FALSE);
727 tutor.query = QUERY_INTRO;
728 tutor_process_touch (L'\0');
729 }
730 else
731 {
732 tutor_beep ();
733 tutor_update ();
734 }
735 return;
736 }
737
738 /* It is time to analise the correctness of typing.
739 */
740 if (tutor.type == TT_FLUID)
741 go_on = tutor_eval_forward_backward (user_chr);
742 else
743 go_on = tutor_eval_forward (user_chr);
744
745 if (go_on == FALSE)
746 {
747 cursor_set_blink (FALSE);
748 cursor_off (NULL);
749 tutor.elapsed_time = g_timer_elapsed (tutor.tmr, NULL);
750
751 tutor_calc_stats ();
752 tutor.query = QUERY_END;
753 tutor_update ();
754 tutor_beep ();
755 }
756 else
757 {
758 switch (tutor.type)
759 {
760 case TT_BASIC:
761 cursor_on (NULL);
762 hints_update_from_char (cursor_get_char ());
763 tutor_speak_char ();
764 return;
765 case TT_ADAPT:
766 cursor_on (NULL);
767 tutor_speak_char ();
768 return;
769 case TT_VELO:
770 cursor_on (NULL);
771 tutor_speak_word ();
772 return;
773 case TT_FLUID:
774 cursor_off (NULL);
775 return;
776 }
777 }
778 }
779
780 /**********************************************************************
781 * Advances the cursor one position and test for correctness,
782 * in the shared tutor window.
783 * Updates the variables:
784 * cursor_pos, n_touchs and n_errors.
785 */
786 gboolean
tutor_eval_forward(gunichar user_chr)787 tutor_eval_forward (gunichar user_chr)
788 {
789 gunichar real_chr;
790
791 if (user_chr == L'\b' || user_chr == L'\t')
792 {
793 tutor_beep ();
794 return (TRUE);
795 }
796
797 gsize n_touchs = g_unichar_fully_decompose (user_chr, FALSE, NULL, 0);
798 tutor.n_touchs += (int) n_touchs;
799
800 real_chr = cursor_get_char ();
801
802 // Minimizing the line breaking bug:
803 if (user_chr == UPSYM && real_chr == L' ')
804 user_chr = L' ';
805
806 /*
807 * Compare the user char with the real char and set the color
808 */
809 if (user_chr == real_chr)
810 {
811 if (tutor.ttidx < MAX_TOUCH_TICS)
812 {
813 tutor.touch_time[tutor.ttidx] =
814 g_timer_elapsed (tutor.tmr, NULL) - tutor.touch_time[tutor.ttidx];
815 tutor.ttidx++;
816 tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL);
817 if (tutor.type != TT_BASIC)
818 accur_correct (real_chr, tutor.touch_time[tutor.ttidx-1]);
819 }
820 cursor_paint_char ("char_correct");
821 }
822 else
823 {
824 tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL);
825 if (tutor.type != TT_BASIC)
826 accur_wrong (real_chr);
827 cursor_paint_char ("char_wrong");
828 tutor.n_errors += (int) n_touchs;
829 tutor_beep ();
830 }
831
832
833 /*
834 * Go forward and test end of text
835 */
836 if (cursor_advance (1) != 1)
837 return (FALSE);
838
839 /*
840 * Test end of text
841 */
842 if (cursor_get_char () == L'\n')
843 if (cursor_advance (1) != 1)
844 return (FALSE);
845
846 return (TRUE);
847 }
848
849 /**********************************************************************
850 * Like the previous, but allows to go back and forth.
851 */
852 gboolean
tutor_eval_forward_backward(gunichar user_chr)853 tutor_eval_forward_backward (gunichar user_chr)
854 {
855 gunichar real_chr;
856
857 /*
858 * Work on backspaces
859 * L'\t' means an hyper <Ctrl> + <Backspace>
860 */
861 if (user_chr == L'\b' || user_chr == L'\t')
862 {
863 tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL);
864
865 /*
866 * Test for end of errors to be corrected
867 */
868 if (tutor.retro_pos == 0)
869 {
870 tutor_beep ();
871 return (TRUE);
872 }
873
874 /*
875 * Go backwards and test for begin of the text
876 */
877 if (cursor_advance (-1) != -1)
878 {
879 tutor_beep ();
880 return (TRUE);
881 }
882
883 /*
884 * Test for start of line
885 */
886 if (cursor_get_char () == L'\n')
887 if (cursor_advance (-1) != -1)
888 {
889 tutor_beep ();
890 return (TRUE);
891 }
892
893 /*
894 * Reinitialize the color (no visible effect at all...)
895 */
896 cursor_paint_char ("char_untouched");
897
898 /*
899 * Update state
900 */
901 tutor.retro_pos--;
902 tutor.correcting++;
903
904 if (user_chr == L'\t')
905 tutor_eval_forward_backward (L'\t');
906 return (TRUE);
907 }
908
909 real_chr = cursor_get_char ();
910
911 // Minimizing the line-breaking bug:
912 if (user_chr == UPSYM && real_chr == L' ')
913 user_chr = L' ';
914
915 /*
916 * Compare the user char with the real char and set the color
917 */
918 if (user_chr == real_chr && tutor.retro_pos == 0)
919 {
920 gsize n_touchs = g_unichar_fully_decompose (user_chr, FALSE, NULL, 0);
921 tutor.n_touchs += (int) n_touchs;
922 if (tutor.ttidx < MAX_TOUCH_TICS)
923 {
924 tutor.touch_time[tutor.ttidx] =
925 g_timer_elapsed (tutor.tmr, NULL) - tutor.touch_time[tutor.ttidx];
926 tutor.ttidx++;
927 tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL);
928 }
929 if (tutor.correcting != 0)
930 {
931 cursor_paint_char ("char_retouched");
932 tutor.n_errors += (int) n_touchs;
933 }
934 else
935 {
936 cursor_paint_char ("char_correct");
937 accur_correct (real_chr, tutor.touch_time[tutor.ttidx-1]);
938 }
939 }
940 else
941 {
942 tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL);
943 cursor_paint_char ("char_wrong");
944 tutor.retro_pos++;
945 if (tutor.retro_pos == 1)
946 accur_wrong (real_chr);
947 tutor_beep ();
948 }
949
950 if (tutor.correcting > 0)
951 tutor.correcting--;
952
953 /*
954 * Go forward and test end of text
955 */
956 if (cursor_advance (1) != 1)
957 {
958 if (tutor.retro_pos == 0)
959 return (FALSE);
960 tutor.retro_pos--;
961 }
962
963 /*
964 * Test end of paragraph
965 */
966
967 if (cursor_get_char () == L'\n')
968 if (cursor_advance (1) != 1 && tutor.retro_pos == 0)
969 return (FALSE);
970
971 return (TRUE);
972 }
973
974
975 /**********************************************************************
976 * Calculate the final results
977 */
978 void
tutor_calc_stats()979 tutor_calc_stats ()
980 {
981 guint i = 0;
982 gint minutes;
983 gint seconds;
984 gboolean may_log = TRUE;
985 gdouble accuracy;
986 gdouble touchs_per_second;
987 gdouble velocity;
988 gdouble fluidness;
989 gdouble standard_deviation = 0;
990 gdouble sum;
991 gdouble average = 0;
992 gdouble sample;
993 gchar *contest_ps = NULL;
994 gchar *tmp_locale;
995 gchar *tmp_str = NULL;
996 gchar *tmp_str2 = NULL;
997 gchar *tmp_name;
998 gchar *tmp;
999 FILE *fh;
1000 time_t tmp_time;
1001 struct tm *ltime;
1002 GtkWidget *wg;
1003 GtkTextBuffer *buf;
1004 GtkTextIter start;
1005 GtkTextIter end;
1006 Statistics stat;
1007
1008 /*
1009 * Calculate statistics
1010 */
1011 minutes = ((gulong) tutor.elapsed_time) / 60;
1012 seconds = ((gulong) tutor.elapsed_time) % 60;
1013
1014 accuracy = 100 * (1.0 - (gfloat) tutor.n_errors / tutor.n_touchs);
1015 touchs_per_second = (gdouble) (tutor.n_touchs - tutor.n_errors) / tutor.elapsed_time;
1016 velocity = 12 * touchs_per_second;
1017
1018 /* Average for touch timing
1019 */
1020 sum = 0;
1021 for (i = 2; i < tutor.ttidx; i++)
1022 {
1023 if (tutor.touch_time[i] <= 0)
1024 tutor.touch_time[i] = 1.0e-8;
1025 sample = sqrt (1 / tutor.touch_time[i]);
1026 sum += sample;
1027 }
1028 if (i == 2)
1029 i++;
1030 average = sum / (i - 2);
1031 if (average <= 0)
1032 average = 1.0e-9;
1033
1034 /* Standard deviation for touch timing
1035 */
1036 sum = 0;
1037 for (i = 2; i < tutor.ttidx; i++)
1038 {
1039 sample = sqrt (1 / tutor.touch_time[i]);
1040 sum += (sample - average) * (sample - average);
1041 }
1042 if (i < 4)
1043 i = 4;
1044 standard_deviation = sqrt (sum / (i - 3));
1045
1046 /* "Magic" fluidness calculation
1047 */
1048 fluidness = 100 * (1 - standard_deviation / average);
1049 if (fluidness < 2)
1050 fluidness = 2;
1051 stat.score = 0;
1052
1053 /* Verify if logging is allowed
1054 */
1055 may_log = TRUE;
1056 if (tutor.type == TT_FLUID)
1057 if (tutor.n_touchs < MIN_CHARS_TO_LOG)
1058 {
1059 gdk_display_beep (gdk_display_get_default ());
1060 contest_ps = g_strdup_printf (_("ps.: logging not performed for this session: "
1061 "the number of typed characters (%i) must be greater than %i."),
1062 tutor.n_touchs, MIN_CHARS_TO_LOG);
1063 may_log = FALSE;
1064 }
1065 if (may_log)
1066 {
1067 /*
1068 * Changing to "C" locale: remember to copy the previous value!
1069 */
1070 tmp_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
1071 if (tmp_locale != NULL)
1072 setlocale (LC_NUMERIC, "C");
1073
1074 /* Logging
1075 */
1076 tmp_name =
1077 g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S "stat_", tutor_get_type_name (), ".txt",
1078 NULL);
1079 assert_user_dir ();
1080 if (!(fh = (FILE *) g_fopen (tmp_name, "r")))
1081 {
1082 fh = (FILE *) g_fopen (tmp_name, "w");
1083 if (tutor.type == TT_BASIC)
1084 fprintf (fh, "Accuracy\tVelocity\tFluidness\tDate\tHour\tLesson\tKeyboard\n");
1085 else if (tutor.type == TT_ADAPT)
1086 fprintf (fh, "Accuracy\tVelocity\tFluidness\tDate\tHour\tKeyboard\tLanguage\n");
1087 else
1088 fprintf (fh, "Accuracy\tVelocity\tFluidness\tDate\tHour\tLesson\tLanguage\n");
1089 }
1090 else
1091 {
1092 fclose (fh);
1093 fh = (FILE *) g_fopen (tmp_name, "a");
1094 }
1095 if (fh)
1096 {
1097 tmp_time = time (NULL);
1098 ltime = localtime (&tmp_time);
1099 fprintf (fh,
1100 "%.2f\t%.2f\t%.2f\t%i-%2.2i-%2.2i\t%2.2i:%2.2i\t",
1101 accuracy, velocity, fluidness,
1102 (ltime->tm_year) + 1900, (ltime->tm_mon) + 1,
1103 (ltime->tm_mday), (ltime->tm_hour), (ltime->tm_min));
1104 switch (tutor.type)
1105 {
1106 case TT_BASIC:
1107 fprintf (fh, "%2.2i\t", basic_get_lesson ());
1108 break;
1109 case TT_ADAPT:
1110 tmp = g_strdup (keyb_get_name ());
1111 for (i=0; tmp[i]; i++)
1112 tmp[i] = (tmp[i] == ' ') ? '_' : tmp[i];
1113 fprintf (fh, "%s\t", tmp);
1114 g_free (tmp);
1115 break;
1116 case TT_VELO:
1117 tmp = g_strdup (velo_get_dict_name ());
1118 for (i=0; tmp[i]; i++)
1119 tmp[i] = (tmp[i] == ' ') ? '_' : tmp[i];
1120 fprintf (fh, "%s\t", tmp);
1121 g_free (tmp);
1122 break;
1123 case TT_FLUID:
1124 tmp = g_strdup (fluid_get_paragraph_name ());
1125 for (i=0; tmp[i]; i++)
1126 tmp[i] = (tmp[i] == ' ') ? '_' : tmp[i];
1127 fprintf (fh, "%s\t", tmp);
1128 g_free (tmp);
1129 break;
1130 }
1131
1132 if (tutor.type == TT_BASIC)
1133 {
1134 tmp = g_strdup (keyb_get_name ());
1135 for (i=0; tmp[i]; i++)
1136 tmp[i] = (tmp[i] == ' ') ? '_' : tmp[i];
1137 fprintf (fh, "%s\n", tmp );
1138 g_free (tmp);
1139 }
1140 else
1141 fprintf (fh, "%s\n", trans_get_current_language ());
1142
1143 fclose (fh);
1144 }
1145 else
1146 g_message ("not able to log on this file:\n %s", tmp_name);
1147 g_free (tmp_name);
1148
1149 /* Log the touch times deviation results of the last session
1150 */
1151 tmp_name = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S, "deviation_",
1152 tutor_get_type_name (), ".txt", NULL);
1153 if ((fh = (FILE *) g_fopen (tmp_name, "w")))
1154 {
1155 g_message ("writing touch timing deviation results at:\n %s", tmp_name);
1156 fprintf (fh, "i\tdt(i)\t1/sqrt(dt(i))\tAver: %g\tStd: %g\tFluidity: %g\n", average, standard_deviation, fluidness);
1157 for (i = 1; i < tutor.ttidx; i++)
1158 fprintf (fh, "%i\t%g\t%g\n", i, tutor.touch_time[i],
1159 1.0 / sqrt(tutor.touch_time[i] > 0 ? tutor.touch_time[i] : 1.0e-9));
1160 fclose (fh);
1161 }
1162 else
1163 g_message ("not able to log on this file:\n %s", tmp_name);
1164 g_free (tmp_name);
1165
1166 /* Fluidity specific results
1167 */
1168 if (tutor.type == TT_FLUID)
1169 {
1170 /* Add results to Top 10
1171 */
1172 tmp_name = main_preferences_get_string ("interface", "language");
1173 stat.lang[0] = ((tmp_name[0] == 'C') ? 'e' : tmp_name[0]);
1174 stat.lang[1] = ((tmp_name[0] == 'C') ? 'n' : tmp_name[1]);
1175 stat.genv = (UNIX_OK ? 'x' : 'w');
1176 stat.when = time (NULL);
1177 stat.nchars = tutor.n_touchs;
1178 stat.accur = accuracy;
1179 stat.velo = velocity;
1180 stat.fluid = fluidness;
1181 stat.score = top10_calc_score (&stat);
1182
1183 g_free (tmp_name);
1184 tmp = main_preferences_get_string ("tutor", "keyboard");
1185 tmp_name = g_strdup_printf ("%s [%s]", g_get_real_name (), tmp);
1186 g_free (tmp);
1187 stat.name_len = strlen (tmp_name);
1188 if (stat.name_len > MAX_NAME_LEN)
1189 stat.name_len = MAX_NAME_LEN;
1190 strncpy (stat.name, tmp_name, stat.name_len + 1);
1191 g_free (tmp_name);
1192
1193 top10_read_stats (LOCAL, -1);
1194 if (tutor_char_distribution_approved ())
1195 {
1196 if (top10_validate_stat (&stat))
1197 {
1198 if (top10_compare_insert_stat (&stat, LOCAL))
1199 {
1200 contest_ps = g_strdup (_("ps.: you have entered the Top 10 list, great!"));
1201 top10_write_stats (LOCAL, -1);
1202 if (main_preferences_get_boolean ("game", "autopublish"))
1203 {
1204 top10_show_stats (LOCAL);
1205 top10_show_stats (GLOBAL);
1206 top10_global_publish (NULL);
1207 }
1208 }
1209 }
1210 else
1211 contest_ps = g_strdup (":-P");
1212 }
1213 else
1214 contest_ps = g_strdup (_("ps.: the text you just typed doesn't seem to be similar"
1215 " to ordinary texts in the language currently selected:"
1216 " we can't account for it in the 'Top 10' contest."));
1217
1218 /* Anyway, log also the scoring
1219 */
1220 tmp_name = g_build_filename (main_path_stats (), "scores_fluid.txt", NULL);
1221 assert_user_dir ();
1222 if (!g_file_test (tmp_name, G_FILE_TEST_IS_REGULAR))
1223 {
1224 fh = (FILE *) g_fopen (tmp_name, "w");
1225 fprintf (fh, "Score\tDate\tTime\t# chars\tLang\n");
1226 }
1227 else
1228 fh = (FILE *) g_fopen (tmp_name, "a");
1229 if (fh)
1230 {
1231 ltime = localtime (&stat.when);
1232 fprintf (fh,
1233 "%3.3f\t%i-%2.2i-%2.2i\t%2.2i:%2.2i\t%i\t%s\n",
1234 stat.score, (ltime->tm_year) + 1900, (ltime->tm_mon) + 1,
1235 (ltime->tm_mday), (ltime->tm_hour), (ltime->tm_min),
1236 stat.nchars, trans_get_current_language ());
1237 fclose (fh);
1238 }
1239 else
1240 g_message ("not able to log on this file:\n %s", tmp_name);
1241 g_free (tmp_name);
1242 }
1243
1244 /* Coming back to the right locale
1245 */
1246 if (tmp_locale != NULL)
1247 {
1248 setlocale (LC_NUMERIC, tmp_locale);
1249 g_free (tmp_locale);
1250 }
1251 }
1252
1253 /* Print statistics
1254 */
1255 wg = get_wg ("text_tutor");
1256 buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg));
1257
1258 /* Begin the accuracy */
1259 tmp_str = g_strconcat ("\n", _("STATISTICS"), "\n",
1260 _("Elapsed time:"), " %i ",
1261 dngettext (PACKAGE, "minute and", "minutes and", minutes),
1262 " %i ", dngettext (PACKAGE, "second", "seconds", seconds),
1263 "\n", _("Error ratio:"), " %i/%i\n", _("Accuracy:"), " ", NULL);
1264
1265 tmp_str2 = g_strdup_printf (tmp_str, minutes, seconds, tutor.n_errors, tutor.n_touchs);
1266 g_free (tmp_str);
1267 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1268
1269 /* Paint the accuracy */
1270 g_free (tmp_str2);
1271 tmp_str2 = g_strdup_printf ("%.1f%%", accuracy);
1272 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1273
1274 gtk_text_buffer_get_end_iter (buf, &start);
1275 gtk_text_buffer_get_end_iter (buf, &end);
1276 gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2));
1277 if (accuracy > tutor_goal_accuracy ())
1278 gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end);
1279 else
1280 gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end);
1281
1282 // Finish the accuracy
1283 g_free (tmp_str2);
1284 tmp_str2 = g_strdup_printf ("\t\t%s %.0f%%\n", _("Goal:"), tutor_goal_accuracy ());
1285 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1286
1287 if (tutor.type == TT_VELO || tutor.type == TT_FLUID)
1288 {
1289 // Begin the CPS
1290 g_free (tmp_str2);
1291 tmp_str2 = g_strdup_printf ("%s ", _("Characters per second:"));
1292 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1293
1294 // Paint the CPS
1295 g_free (tmp_str2);
1296 tmp_str2 = g_strdup_printf ("%.2f", velocity / 12);
1297 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1298
1299 gtk_text_buffer_get_end_iter (buf, &start);
1300 gtk_text_buffer_get_end_iter (buf, &end);
1301 gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2));
1302 if (velocity > tutor_goal_speed ())
1303 gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end);
1304 else
1305 gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end);
1306
1307 // Finish the CPS
1308 g_free (tmp_str2);
1309 tmp_str2 = g_strdup_printf ("\t\t%s %.1f %s\n", _("Goal:"), tutor_goal_speed () / 12, _("(CPS)"));
1310 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1311
1312 // Begin the WPM
1313 g_free (tmp_str2);
1314 tmp_str2 = g_strdup_printf ("%s ", _("Words per minute:"));
1315 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1316
1317 // Paint the WPM
1318 g_free (tmp_str2);
1319 tmp_str2 = g_strdup_printf ("%.1f", velocity);
1320 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1321
1322 gtk_text_buffer_get_end_iter (buf, &start);
1323 gtk_text_buffer_get_end_iter (buf, &end);
1324 gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2));
1325 if (velocity > tutor_goal_speed ())
1326 gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end);
1327 else
1328 gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end);
1329
1330 // Finish the WPM
1331 g_free (tmp_str2);
1332 tmp_str2 = g_strdup_printf ("\t\t%s %.0f %s\n", _("Goal:"), tutor_goal_speed (), _("(WPM)"));
1333 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1334
1335 if (tutor.type == TT_FLUID)
1336 {
1337 // Begin the fluidity
1338 g_free (tmp_str2);
1339 tmp_str2 = g_strdup_printf ("%s ", _("Fluidness:"));
1340 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1341
1342 // Paint the fluidity
1343 g_free (tmp_str2);
1344 tmp_str2 = g_strdup_printf ("%.1f%%", fluidness);
1345 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1346
1347 gtk_text_buffer_get_end_iter (buf, &start);
1348 gtk_text_buffer_get_end_iter (buf, &end);
1349 gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2));
1350 if (fluidness > tutor_goal_fluidity ())
1351 gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end);
1352 else
1353 gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end);
1354
1355 // Finish the fluidity and scores
1356 g_free (tmp_str2);
1357 tmp_str2 = g_strdup_printf ("\t\t%s %.0f%%\n%s: %f\n",
1358 _("Goal:"), tutor_goal_fluidity (),
1359 _("Score"), stat.score);
1360 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1361 }
1362 }
1363
1364 // Begin the comments
1365 g_free (tmp_str2);
1366 tmp_str2 = g_strdup_printf ("\n%s\n", _("Comments:"));
1367 gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2));
1368
1369 switch (tutor.type)
1370 {
1371 case TT_BASIC:
1372 basic_comment (accuracy);
1373 break;
1374 case TT_ADAPT:
1375 adapt_comment (accuracy);
1376 break;
1377 case TT_VELO:
1378 velo_comment (accuracy, velocity);
1379 break;
1380 case TT_FLUID:
1381 fluid_comment (accuracy, velocity, fluidness);
1382 if (contest_ps != NULL)
1383 {
1384 gtk_text_buffer_insert_at_cursor (buf, "\n", 1);
1385 gtk_text_buffer_insert_at_cursor (buf, contest_ps, strlen (contest_ps));
1386 g_free (contest_ps);
1387 }
1388 break;
1389 }
1390 g_free (tmp_str2);
1391
1392 /* This is needed to repaint the final comments with normal black foreground. */
1393 gtk_text_buffer_get_bounds (buf, &start, &end);
1394 gtk_text_buffer_apply_tag_by_name (buf, "char_keep_wrap", &start, &end);
1395
1396 gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (wg), gtk_text_buffer_get_insert (buf));
1397 }
1398
1399 /**********************************************************************
1400 * Ensure the user is not trying to type with weird texts in the fluidness contest
1401 */
1402 #define DECEIVENESS_LIMIT 0.205
1403 gboolean
tutor_char_distribution_approved()1404 tutor_char_distribution_approved ()
1405 {
1406 guint i, j;
1407 gfloat deceiveness;
1408 gchar *tmp_code;
1409 gchar *tmp_name;
1410
1411 struct MODEL
1412 {
1413 gchar *text;
1414 Char_Distribution dist;
1415 } model;
1416
1417 struct EXAM
1418 {
1419 gchar *text;
1420 Char_Distribution dist;
1421 } exam;
1422
1423 GtkWidget *wg;
1424 GtkTextBuffer *buf;
1425 GtkTextIter start;
1426 GtkTextIter end;
1427
1428 /* Get model text
1429 */
1430 tmp_code = main_preferences_get_string ("interface", "language");
1431 tmp_name = g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, tmp_code, ".paragraphs", NULL);
1432 g_free (tmp_code);
1433 if (!g_file_get_contents (tmp_name, &model.text, NULL, NULL))
1434 {
1435 g_free (tmp_name);
1436 tmp_name = trans_lang_get_similar_file_name (".paragraphs");
1437 if (!g_file_get_contents (tmp_name, &model.text, NULL, NULL))
1438 {
1439 g_message ("Can't read file:\n %s\n So, not logging your score.", tmp_name);
1440 g_free (tmp_name);
1441 return FALSE;
1442 }
1443 }
1444
1445 /* Get text under examination
1446 */
1447 wg = get_wg ("text_tutor");
1448 buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg));
1449 gtk_text_buffer_get_bounds (buf, &start, &end);
1450 exam.text = gtk_text_buffer_get_text (buf, &start, &end, FALSE);
1451
1452 /* Get char distributions
1453 */
1454 tutor_char_distribution_count (model.text, &model.dist);
1455 tutor_char_distribution_count (exam.text, &exam.dist);
1456
1457 /* Compare both distributions
1458 */
1459 deceiveness = 0;
1460 for (i = 0; i < 9 && deceiveness < 1.0e+6; i++)
1461 {
1462 for (j = 0; j < exam.dist.size; j++)
1463 if (model.dist.ch[i].letter == exam.dist.ch[j].letter)
1464 {
1465 deceiveness +=
1466 powf ((exam.dist.ch[j].freq - model.dist.ch[i].freq), 2);
1467 break;
1468 }
1469 if (j == exam.dist.size)
1470 {
1471 deceiveness += 1.0e+7;
1472 break;
1473 }
1474 }
1475 deceiveness = sqrtf (deceiveness / 9);
1476
1477 g_print ("Corpus file: %s\n", tmp_name);
1478 if (deceiveness < DECEIVENESS_LIMIT)
1479 g_print ("\tDeviation: %.3f. OK, it is less than %.3f.\n", deceiveness, DECEIVENESS_LIMIT);
1480 else
1481 g_print ("\tDeviation: %.3f! It should be less than %.3f.\n", deceiveness, DECEIVENESS_LIMIT);
1482
1483 g_free (tmp_name);
1484 g_free (model.text);
1485 g_free (exam.text);
1486 return (deceiveness < DECEIVENESS_LIMIT);
1487 }
1488
1489 /**********************************************************************
1490 * Count relative frequency of letters in text
1491 */
1492 void
tutor_char_distribution_count(gchar * text,Char_Distribution * dist)1493 tutor_char_distribution_count (gchar * text, Char_Distribution * dist)
1494 {
1495 gchar *pt;
1496 gunichar ch;
1497 gsize i, j;
1498
1499 pt = text;
1500
1501 dist->size = 0;
1502 dist->total = 0;
1503 while ((ch = g_utf8_get_char (pt)) != L'\0')
1504 {
1505 /* Only count letters
1506 */
1507 if (!g_unichar_isalpha (ch))
1508 {
1509 pt = g_utf8_next_char (pt);
1510 continue;
1511 }
1512 ch = g_unichar_tolower (ch);
1513
1514 /* Verify if ch was already counted
1515 */
1516 for (i = 0; i < dist->size; i++)
1517 {
1518 if (ch == dist->ch[i].letter)
1519 {
1520 dist->ch[i].count++;
1521 dist->total++;
1522 break;
1523 }
1524 }
1525
1526 /* If ch was not counted yet, start to do it
1527 */
1528 if (i == dist->size && i < MAX_ALPHABET_LEN)
1529 {
1530 dist->ch[dist->size].letter = ch;
1531 dist->ch[dist->size].count = 1;
1532 dist->total++;
1533 dist->size++;
1534 }
1535
1536 pt = g_utf8_next_char (pt);
1537 }
1538
1539 /* Sort the list
1540 */
1541 for (i = 1; i < dist->size; i++)
1542 {
1543 gunichar aletter;
1544 guint acount;
1545
1546 if (dist->ch[i].count > dist->ch[i - 1].count)
1547 for (j = i; j > 0; j--)
1548 {
1549 if (dist->ch[j].count <= dist->ch[j - 1].count)
1550 break;
1551
1552 aletter = dist->ch[j - 1].letter;
1553 dist->ch[j - 1].letter = dist->ch[j].letter;
1554 dist->ch[j].letter = aletter;
1555
1556 acount = dist->ch[j - 1].count;
1557 dist->ch[j - 1].count = dist->ch[j].count;
1558 dist->ch[j].count = acount;
1559 }
1560 }
1561
1562 /* Write the relative frequency
1563 */
1564 for (i = 0; i < dist->size; i++)
1565 dist->ch[i].freq = ((gfloat) dist->ch[i].count) / ((gfloat) dist->ch[0].count);
1566
1567 /*
1568 for (i = 0; i < dist->size; i++)
1569 g_message ("Char: %x, count: %u, freq:%g", dist->ch[i].letter, dist->ch[i].count, dist->ch[i].freq);
1570 g_message ("Total: %u / Size: %u ------------------------------", dist->total, dist->size);
1571 */
1572 }
1573
1574 /**********************************************************************
1575 * Formats and draws one paragraph at the tutor window
1576 */
1577 void
tutor_draw_paragraph(gchar * utf8_text)1578 tutor_draw_paragraph (gchar * utf8_text)
1579 {
1580 static gchar *tmp1 = NULL;
1581 static gchar *tmp2 = NULL;
1582 gchar *ptr;
1583 GtkTextBuffer *buf;
1584
1585 //g_free (tmp1);
1586 //g_free (tmp2);
1587
1588 if (g_utf8_strrchr (utf8_text, -1, L'\n') == NULL)
1589 {
1590 g_message ("paragraph not terminated by carriage return: adding one.");
1591 tmp1 = g_strconcat (utf8_text, "\n", NULL);
1592 }
1593 else
1594 tmp1 = g_strdup (utf8_text);
1595
1596 ptr = g_utf8_strrchr (tmp1, -1, L'\n');
1597 if (ptr)
1598 *ptr = '\0';
1599 else
1600 g_error ("draw_paragraph () ==> string error");
1601
1602 tmp2 = g_strconcat (tmp1, keyb_get_utf8_paragraph_symbol (), "\n", NULL);
1603 g_free (tmp1);
1604
1605 buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (get_wg ("text_tutor")));
1606 gtk_text_buffer_insert_at_cursor (buf, tmp2, -1);
1607 g_free (tmp2);
1608 }
1609
1610 /**********************************************************************
1611 * Load the list of files to include in the set of "other exercises"
1612 */
1613 void
tutor_load_list_other(gchar * file_name_end,GtkListStore * list)1614 tutor_load_list_other (gchar * file_name_end, GtkListStore * list)
1615 {
1616 gchar *tmp_str;
1617 gchar *dentry;
1618 GDir *dir;
1619 GtkTreeIter iter;
1620 static gchar *defstr = NULL;
1621
1622 if (defstr == NULL)
1623 defstr = g_strdup (OTHER_DEFAULT);
1624
1625 gtk_list_store_clear (list);
1626 gtk_list_store_append (list, &iter);
1627 gtk_list_store_set (list, &iter, 0, defstr, -1);
1628
1629 assert_user_dir ();
1630 dir = g_dir_open (main_path_user (), 0, NULL);
1631 while ((dentry = g_strdup (g_dir_read_name (dir))) != NULL)
1632 {
1633 if (strlen (dentry) < 5)
1634 {
1635 g_free (dentry);
1636 continue;
1637 }
1638 if (!(tmp_str = strrchr (dentry, '.')))
1639 {
1640 g_free (dentry);
1641 continue;
1642 }
1643 if (! g_str_equal (file_name_end, tmp_str))
1644 {
1645 g_free (dentry);
1646 continue;
1647 }
1648
1649 *(strrchr (dentry, '.')) = '\0';
1650 gtk_list_store_append (list, &iter);
1651 gtk_list_store_set (list, &iter, 0, dentry, -1);
1652 g_free (dentry);
1653 }
1654 g_dir_close (dir);
1655
1656 gtk_widget_set_sensitive (get_wg ("button_other_remove"), FALSE);
1657 gtk_widget_set_sensitive (get_wg ("label_other_rename"), FALSE);
1658 gtk_widget_set_sensitive (get_wg ("entry_other_rename"), FALSE);
1659 gtk_widget_set_sensitive (get_wg ("button_other_apply"), FALSE);
1660 }
1661
1662 void
tutor_other_rename(const gchar * new_tx,const gchar * old_tx)1663 tutor_other_rename (const gchar *new_tx, const gchar *old_tx)
1664 {
1665 if (! g_str_equal (new_tx, old_tx) &&
1666 ! g_str_equal (new_tx, OTHER_DEFAULT) &&
1667 ! g_str_equal (new_tx, "") &&
1668 g_strrstr (old_tx, "*") == NULL )
1669 {
1670 gchar *old_name;
1671 gchar *new_name;
1672 gchar *old_file;
1673 gchar *new_file;
1674
1675 if (tutor.type == TT_VELO)
1676 {
1677 old_name = g_strconcat (old_tx, ".words", NULL);
1678 new_name = g_strconcat (new_tx, ".words", NULL);
1679 }
1680 else
1681 {
1682 old_name = g_strconcat (old_tx, ".paragraphs", NULL);
1683 new_name = g_strconcat (new_tx, ".paragraphs", NULL);
1684 }
1685 old_file = g_build_filename (main_path_user (), old_name, NULL);
1686 new_file = g_build_filename (main_path_user (), new_name, NULL);
1687
1688 if (g_file_test (new_file, G_FILE_TEST_IS_REGULAR))
1689 {
1690 g_message ("File already exists, not renaming.\n\t%s\n", new_file);
1691 gdk_display_beep (gdk_display_get_default ());
1692 }
1693 else
1694 {
1695 g_printf ("Renaming from:\n\t%s\nTo:\n\t%s\n", old_file, new_file);
1696 if (g_rename (old_file, new_file))
1697 {
1698 g_printf ("Fail: %s\n", strerror (errno));
1699 }
1700 else
1701 g_printf ("Success!\n");
1702 }
1703 g_free (old_name);
1704 g_free (new_name);
1705 g_free (old_file);
1706 g_free (new_file);
1707 }
1708 }
1709
1710 /**********************************************************************
1711 * Put 'mesg' in the message entry line of the shared tutor window
1712 */
1713 void
tutor_message(gchar * mesg)1714 tutor_message (gchar * mesg)
1715 {
1716 gint pos = 0;
1717 GtkWidget *wg;
1718
1719 if (mesg == NULL)
1720 {
1721 g_message ("tutor_message() --> not showing NULL message!");
1722 return;
1723 }
1724
1725 wg = get_wg ("entry_mesg");
1726 callbacks_shield_set (TRUE);
1727 gtk_editable_delete_text (GTK_EDITABLE (wg), 0, -1);
1728 gtk_editable_insert_text (GTK_EDITABLE (wg), g_strdup (mesg), strlen (mesg), &pos);
1729 gtk_editable_set_position (GTK_EDITABLE (wg), -1);
1730 callbacks_shield_set (FALSE);
1731 }
1732
1733 /**********************************************************************
1734 * Beeps (or not) at the user, in the tutor window
1735 */
1736 void
tutor_beep()1737 tutor_beep ()
1738 {
1739 if (main_preferences_get_boolean ("tutor", "tutor_beep"))
1740 gdk_display_beep (gdk_display_get_default ());
1741 }
1742
1743 /**********************************************************************
1744 * Speak some phrase
1745 */
1746 void
tutor_speak_string(gchar * string,gboolean wait)1747 tutor_speak_string (gchar *string, gboolean wait)
1748 {
1749 gchar *tmp_code;
1750 gchar *command;
1751 static gboolean espeak_OK = TRUE;
1752 static GtkWidget *wg = NULL;
1753
1754 if (wg == NULL)
1755 wg = get_wg ("checkbutton_speech");
1756
1757 if (espeak_OK == FALSE)
1758 return;
1759 if (!gtk_widget_get_visible (wg) )
1760 return;
1761 if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wg)) )
1762 return;
1763
1764 /* Translators: your language code (first 2 letters of your po-file)*/
1765 tmp_code = g_strdup (_("en"));
1766 tmp_code[2] = '\0';
1767 if (wait)
1768 {
1769 command = g_strdup_printf ("espeak -v%s -k1 \"%s\"", tmp_code, string);
1770 #ifdef G_OS_UNIX
1771 espeak_OK = g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL);
1772 #else
1773 espeak_OK = ! system (command);
1774 #endif
1775 }
1776 else
1777 {
1778 #ifdef G_OS_UNIX
1779 if (g_utf8_strlen (string, -1) == 1 || tutor.type == TT_VELO)
1780 command = g_strdup_printf ("espeak -v%s -k1 --punct '%s'", tmp_code, string);
1781 else
1782 command = g_strdup_printf ("espeak -v%s -k1 \"%s\"", tmp_code, string);
1783 espeak_OK = g_spawn_command_line_async (command, NULL);
1784 #else
1785 if (g_utf8_strlen (string, -1) == 1 || tutor.type == TT_VELO)
1786 command = g_strdup_printf ("espeak -v%s -k1 --punct \"%s\"", tmp_code, string);
1787 else
1788 command = g_strdup_printf ("espeak -v%s -k1 \"%s\"", tmp_code, string);
1789 espeak_OK = ! system (command);
1790 #endif
1791 }
1792 if (espeak_OK == FALSE)
1793 g_message ("Espeak not installed, so we'll say nothing:\n %s", command);
1794 g_free (tmp_code);
1795 g_free (command);
1796 }
1797
1798 /**********************************************************************
1799 * Control delayed tips for the finger to be used
1800 */
1801 gboolean
tutor_delayed_finger_tip(gpointer unich)1802 tutor_delayed_finger_tip (gpointer unich)
1803 {
1804 gchar *finger;
1805 gunichar *uch = (gunichar*) unich;
1806 static gint counter = 0;
1807
1808 if (unich == NULL)
1809 {
1810 counter++;
1811 return FALSE;
1812 }
1813 counter--;
1814
1815 if (counter > 0)
1816 return FALSE;
1817
1818 finger = hints_finger_name_from_char (*uch);
1819 tutor_speak_string (finger, TRUE);
1820 g_free (finger);
1821
1822 return FALSE;
1823 }
1824
1825 /**********************************************************************
1826 * Speak the current character to be typed
1827 */
1828 void
tutor_speak_char()1829 tutor_speak_char ()
1830 {
1831 gchar ut8[100];
1832 static gunichar uch;
1833
1834 if (tutor.type == TT_BASIC)
1835 {
1836 g_timeout_add (3000, (GSourceFunc) tutor_delayed_finger_tip, (gpointer) &uch);
1837 tutor_delayed_finger_tip (NULL);
1838 }
1839
1840 uch = cursor_get_char ();
1841 switch (uch)
1842 {
1843 case L' ':
1844 strcpy (ut8, _("space"));
1845 break;
1846 case L'y':
1847 case L'Y':
1848 /* Translators: the name of letter Y */
1849 strcpy (ut8, _("wye"));
1850 break;
1851 case UPSYM:
1852 /* Translators: the name of the Return key */
1853 strcpy (ut8, _("enter"));
1854 break;
1855 case L'%':
1856 strcpy (ut8, "%");
1857 break;
1858 case L'\'':
1859 strcpy (ut8, _("apostrophe"));
1860 break;
1861 case L'\"':
1862 /* Translators: double quote symbol: " */
1863 strcpy (ut8, _("quote"));
1864 break;
1865 case L'&':
1866 /* Translators: ampersand symbol: & */
1867 strcpy (ut8, _("ampersand"));
1868 break;
1869 default:
1870 ut8[g_unichar_to_utf8 (uch, ut8)] = '\0';
1871 }
1872
1873 tutor_speak_string (ut8, FALSE);
1874
1875 }
1876
1877 /**********************************************************************
1878 * Speak the next word to be typed
1879 */
1880 void
tutor_speak_word()1881 tutor_speak_word ()
1882 {
1883 gunichar uch[100];
1884 gchar *ut8;
1885 gint i;
1886
1887 if (cursor_advance (-1) != -1)
1888 uch[0] = L' ';
1889 else
1890 {
1891 uch[0] = cursor_get_char ();
1892 cursor_advance (1);
1893 }
1894 if (uch[0] == L' ' || uch[0] == UPSYM || uch[0] == L'\n' || uch[0] == L'\r')
1895 {
1896 for (i = 0; i < 100; i++)
1897 {
1898 uch[i] = cursor_get_char ();
1899 if (uch[i] == L' ' || uch[i] == UPSYM || uch[i] == L'\n' || uch[i] == L'\r')
1900 break;
1901 cursor_advance (1);
1902 }
1903 cursor_advance (-i);
1904 }
1905 else
1906 return;
1907
1908 ut8 = g_ucs4_to_utf8 (uch, i, NULL, NULL, NULL);
1909 if (ut8)
1910 tutor_speak_string (ut8, FALSE);
1911 g_free (ut8);
1912 }
1913