"Fossies" - the Fresh Open Source Software Archive

Member "klavaro-3.13/src/tutor.c" (18 Apr 2021, 48655 Bytes) of package /linux/privat/klavaro-3.13.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "tutor.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.12_vs_3.13.

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