/*****************************************************************************/ /* Klavaro - a flexible touch typing tutor */ /* Copyright (C) 2005, 2006, 2007, 2008 Felipe Castro */ /* Copyright (C) 2009 Free Software Foundation */ /* */ /* This program is free software, licensed under the terms of the GNU */ /* General Public License as published by the Free Software Foundation, */ /* either version 3 of the License, or (at your option) any later version. */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see . */ /*****************************************************************************/ /* * Shared tutor window tasks */ #include #include #include #include #include #include #include #include #include #include "main.h" #include "auxiliar.h" #include "callbacks.h" #include "translation.h" #include "keyboard.h" #include "cursor.h" #include "basic.h" #include "adaptability.h" #include "velocity.h" #include "fluidness.h" #include "accuracy.h" #include "top10.h" #include "tutor.h" #define MAX_TOUCH_TICS 8000 struct { TutorType type; TutorQuery query; GTimer *tmr; gdouble elapsed_time; gdouble touch_time[MAX_TOUCH_TICS + 1]; guint ttidx; gint n_touchs; gint n_errors; gint retro_pos; gint correcting; } tutor; extern gchar *OTHER_DEFAULT; /******************************************************************************* * Interface functions */ TutorType tutor_get_type () { return (tutor.type); } gchar * tutor_get_type_name () { static gchar type_name[4][6] = { "basic", "adapt", "velo", "fluid" }; return (type_name[tutor.type]); } TutorQuery tutor_get_query () { return (tutor.query); } void tutor_set_query (TutorQuery query) { tutor.query = query; } gint tutor_get_correcting () { return (tutor.correcting); } void tutor_init_timers () { tutor.tmr = g_timer_new (); } gdouble tutor_goal_accuracy () { switch (tutor.type) { case TT_BASIC: return 95.0; case TT_ADAPT: return 98.0; case TT_VELO: return 95.0; case TT_FLUID: return 97.0; } return -1.0; } gdouble tutor_goal_speed () { switch (tutor.type) { case TT_BASIC: return 10.0; case TT_ADAPT: return 10.0; case TT_VELO: return 50.0; case TT_FLUID: return 50.0; } return -1.0; } gdouble tutor_goal_fluidity () { switch (tutor.type) { case TT_BASIC: case TT_ADAPT: case TT_VELO: return 0.0; case TT_FLUID: return 70.0; } return -1.0; } /********************************************************************** * Initialize the course */ void tutor_init (TutorType tt_type) { gchar *tmp_title = NULL; gchar *tmp_name = NULL; GtkWidget *wg; gtk_widget_hide (get_wg ("window_main")); gtk_widget_hide (get_wg ("window_keyboard")); gtk_widget_hide (get_wg ("dialog_info")); gtk_widget_hide (get_wg ("aboutdialog_klavaro")); gtk_widget_hide (get_wg ("kadro_tro_eraroj")); gtk_widget_show (get_wg ("window_tutor")); gtk_widget_grab_focus (get_wg ("entry_mesg")); tutor.type = tt_type; cursor_set_blink (FALSE); /****************************** * Set the layout for each exercise type */ if (!UNIX_OK) gtk_widget_hide (get_wg ("button_tutor_top10")); gtk_widget_hide (get_wg ("entry_custom_basic_lesson")); if (tutor.type == TT_BASIC || tutor.type == TT_FLUID) { gtk_widget_show (get_wg ("label_lesson")); gtk_widget_show (get_wg ("spinbutton_lesson")); gtk_widget_show (get_wg ("vseparator_tutor_2")); if (tutor.type == TT_BASIC) { gtk_widget_show (get_wg ("togglebutton_edit_basic_lesson")); gtk_widget_hide (get_wg ("button_tutor_top10")); gtk_widget_show (get_wg ("button_tutor_show_keyb")); gtk_label_set_text (GTK_LABEL (get_wg ("label_lesson")), _("Lesson:")); callbacks_shield_set (TRUE); gtk_spin_button_set_range (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")), 1, 50); callbacks_shield_set (FALSE); } else { gtk_widget_hide (get_wg ("togglebutton_edit_basic_lesson")); if (UNIX_OK) gtk_widget_show (get_wg ("button_tutor_top10")); gtk_widget_hide (get_wg ("button_tutor_show_keyb")); gtk_label_set_text (GTK_LABEL (get_wg ("label_lesson")), _("Paragraphs:")); callbacks_shield_set (TRUE); gtk_spin_button_set_range (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")), 1, 10); callbacks_shield_set (FALSE); } } else { gtk_widget_hide (get_wg ("label_lesson")); gtk_widget_hide (get_wg ("spinbutton_lesson")); gtk_widget_hide (get_wg ("vseparator_tutor_2")); gtk_widget_hide (get_wg ("togglebutton_edit_basic_lesson")); gtk_widget_hide (get_wg ("button_tutor_show_keyb")); gtk_widget_hide (get_wg ("button_tutor_top10")); } if (tutor.type == TT_BASIC || tutor.type == TT_ADAPT) { gtk_widget_hide (get_wg ("button_tutor_other")); gtk_widget_hide (get_wg ("vseparator_tutor_1")); } else { gtk_widget_show (get_wg ("button_tutor_other")); gtk_widget_show (get_wg ("vseparator_tutor_1")); } /****************************** * Set decoration texts and tips */ switch (tutor.type) { case TT_BASIC: tmp_title = g_strdup (_("Klavaro - Basic Course")); tmp_name = g_strdup (""); break; case TT_ADAPT: tmp_title = g_strdup (_("Klavaro - Adaptability")); tmp_name = g_strdup (_ ("Adaptability exercises: automating the fingers" " responses, typing over all the keyboard.")); break; case TT_VELO: tmp_title = g_strdup (_("Klavaro - Velocity")); tmp_name = g_strdup (_("Velocity exercises: accelerate typing real words.")); break; case TT_FLUID: tmp_title = g_strdup (_("Klavaro - Fluidness")); tmp_name = g_strdup (_("Fluidness exercises: accuracy typing good sense paragraphs.")); break; } gtk_window_set_title (get_win ("window_tutor"), tmp_title); wg = get_wg ("label_heading"); gtk_label_set_text (GTK_LABEL (wg), tmp_name); g_free (tmp_title); g_free (tmp_name); /****************************** * Set tooltips of tutor entry (drag and drop) */ if (tutor.type == TT_VELO || tutor.type == TT_FLUID) gtk_widget_set_tooltip_text (get_wg ("entry_mesg"), _("Drag and drop text here to practice with it.")); else gtk_widget_set_tooltip_text (get_wg ("entry_mesg"), ""); /****************************** * Set specific variables */ tutor.query = QUERY_INTRO; if (tutor.type == TT_BASIC) { basic_init (); if (basic_get_lesson () > 1) { tutor_process_touch ('\0'); return; } } else if (tutor.type == TT_VELO) { velo_init (); } else if (tutor.type == TT_FLUID) { fluid_init (); } tutor_update (); } /********************************************************************** * Update what is shown in the tutor window. */ void tutor_update () { switch (tutor.query) { case QUERY_INTRO: tutor_update_intro (); break; case QUERY_START: tutor_update_start (); break; case QUERY_PROCESS_TOUCHS: //gtk_widget_set_sensitive (get_wg ("button_tutor_restart"), TRUE); break; case QUERY_END: tutor_message (_("End of exercise. Press [Enter] to start another.")); //gtk_widget_set_sensitive (get_wg ("button_tutor_restart"), FALSE); break; } } void tutor_update_intro () { gchar *tmp_name; gchar *text; GdkColor color; GtkWidget *wg; GtkLabel *wg_label; GtkTextView *wg_text; GtkAdjustment *scroll; GtkTextIter start; GtkTextIter end; if (tutor.type == TT_BASIC) { callbacks_shield_set (TRUE); gtk_spin_button_set_value (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")), basic_get_lesson ()); callbacks_shield_set (FALSE); wg_label = GTK_LABEL (get_wg ("label_heading")); gtk_label_set_text (wg_label, _("Learning the key positions.")); } tutor_message (_("Press any key to start the exercise. ")); //gtk_widget_set_sensitive (get_wg ("button_tutor_restart"), FALSE); tmp_name = g_strconcat ("_", tutor_get_type_name (), "_intro.txt", NULL); text = trans_read_text (tmp_name); g_free (tmp_name); wg_text = GTK_TEXT_VIEW (get_wg ("text_tutor")); gtk_text_buffer_set_text (gtk_text_view_get_buffer (wg_text), text, -1); g_free (text); gtk_text_buffer_get_bounds (gtk_text_view_get_buffer (wg_text), &start, &end); gtk_text_buffer_apply_tag_by_name (gtk_text_view_get_buffer (wg_text), "lesson_font", &start, &end); gtk_text_buffer_apply_tag_by_name (gtk_text_view_get_buffer (wg_text), "text_intro", &start, &end); gdk_color_parse (TUTOR_WHITE, &color); gtk_widget_modify_base (get_wg ("text_tutor"), GTK_STATE_INSENSITIVE, &color); wg = get_wg ("scrolledwindow_tutor_main"); scroll = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (wg)); gtk_adjustment_set_value (scroll, 0); callbacks_shield_set (TRUE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_wg ("togglebutton_tutor_intro")), TRUE); callbacks_shield_set (FALSE); } void tutor_update_start () { gchar *tmp_name; gchar *text; GdkColor color; GtkWidget *wg; GtkTextBuffer *buf; GtkTextIter start; GtkTextIter end; GtkAdjustment *scroll; /* * Delete all the text on tutor window */ wg = get_wg ("text_tutor"); buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg)); gtk_text_buffer_set_text (buf, "", -1); if (tutor.type == TT_BASIC) { callbacks_shield_set (TRUE); gtk_spin_button_set_value (GTK_SPIN_BUTTON (get_wg ("spinbutton_lesson")), basic_get_lesson ()); callbacks_shield_set (FALSE); tmp_name = g_ucs4_to_utf8 (basic_get_char_set (), -1, NULL, NULL, NULL); text = g_strdup_printf ("%s %s", _("Keys:"), tmp_name); g_free (tmp_name); wg = get_wg ("label_heading"); gtk_label_set_text (GTK_LABEL (wg), text); g_free (text); basic_draw_lesson (); } switch (tutor.type) { case TT_BASIC: break; case TT_ADAPT: adapt_draw_random_pattern (); break; case TT_VELO: velo_draw_random_words (); break; case TT_FLUID: fluid_draw_random_paragraphs (); } //gtk_widget_set_sensitive (get_wg ("button_tutor_restart"), FALSE); /* * Apply tutor background color and font to the text */ gtk_text_buffer_get_bounds (buf, &start, &end); gtk_text_iter_backward_char (&end); gtk_text_buffer_apply_tag_by_name (buf, "char_untouched", &start, &end); gtk_text_buffer_apply_tag_by_name (buf, "lesson_font", &start, &end); gdk_color_parse (TUTOR_CREAM, &color); gtk_widget_modify_base (get_wg ("text_tutor"), GTK_STATE_INSENSITIVE, &color); tutor_message (_("Start typing when you are ready. ")); wg = get_wg ("scrolledwindow_tutor_main"); scroll = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (wg)); gtk_adjustment_set_value (scroll, 0); callbacks_shield_set (TRUE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_wg ("togglebutton_tutor_intro")), FALSE); callbacks_shield_set (FALSE); } /********************************************************************** * Respond to each touch of the user, according to the tutor.query mode */ void tutor_process_touch (gunichar user_chr) { gboolean go_on; gchar *u8ch; GtkTextView *wg_text; GtkTextBuffer *wg_buffer; GtkTextIter start; wg_text = GTK_TEXT_VIEW (get_wg ("text_tutor")); wg_buffer = gtk_text_view_get_buffer (wg_text); switch (tutor.query) { case QUERY_PROCESS_TOUCHS: break; case QUERY_INTRO: tutor.query = QUERY_START; tutor_update (); tutor.n_touchs = 0; tutor.n_errors = 0; tutor.retro_pos = 0; tutor.correcting = 0; tutor.ttidx = 0; gtk_text_buffer_get_start_iter (wg_buffer, &start); gtk_text_buffer_place_cursor (wg_buffer, &start); cursor_set_blink (TRUE); cursor_on (NULL); if (tutor.type == TT_BASIC) hints_update_from_char (cursor_get_char ()); return; case QUERY_START: tutor.query = QUERY_PROCESS_TOUCHS; tutor_update (); callbacks_shield_set (TRUE); u8ch = g_malloc0 (7); if (g_unichar_to_utf8 (user_chr, u8ch) > 0) tutor_message (u8ch); else tutor_message (""); g_free (u8ch); callbacks_shield_set (FALSE); if (tutor.type == TT_BASIC) hints_update_from_char (cursor_get_char ()); g_timer_start (tutor.tmr); if (tutor.type == TT_FLUID) tutor_eval_forward_backward (user_chr); else tutor_eval_forward (user_chr); return; case QUERY_END: if (user_chr == UPSYM) { basic_set_lesson_increased (FALSE); tutor.query = QUERY_INTRO; tutor_process_touch (L'\0'); } else { tutor_beep (); tutor_update (); } return; } /* * It is time to analise the correctness of typing. */ if (tutor.type == TT_FLUID) go_on = tutor_eval_forward_backward (user_chr); else go_on = tutor_eval_forward (user_chr); if (go_on == FALSE) { cursor_set_blink (FALSE); cursor_off (NULL); tutor.elapsed_time = g_timer_elapsed (tutor.tmr, NULL); tutor_calc_stats (); tutor.query = QUERY_END; tutor_update (); tutor_beep (); } else { cursor_on (NULL); if (tutor.type == TT_BASIC) hints_update_from_char (cursor_get_char ()); } } /********************************************************************** * Advances the cursor one position and test for correctness, * in the shared tutor window. * Updates the variables: * cursor_pos, n_touchs and n_errors. */ gboolean tutor_eval_forward (gunichar user_chr) { gunichar real_chr; if (user_chr == L'\b' || user_chr == L'\t') { tutor_beep (); return (TRUE); } tutor.n_touchs++; real_chr = cursor_get_char (); // Minimizing the line breaking bug: if (user_chr == UPSYM && real_chr == L' ') user_chr = L' '; /* * Compare the user char with the real char and set the color */ if (user_chr == real_chr) { cursor_paint_char ("char_correct"); if (tutor.type != TT_BASIC) accur_correct (real_chr); } else { tutor_beep (); cursor_paint_char ("char_wrong"); tutor.n_errors++; if (tutor.type != TT_BASIC) accur_wrong (real_chr); } /* * Go forward and test end of text */ if (cursor_advance (1) != 1) return (FALSE); /* * Test end of text */ if (cursor_get_char () == L'\n') if (cursor_advance (1) != 1) return (FALSE); return (TRUE); } /********************************************************************** * Like the previous, but allows to go back and forth. */ gboolean tutor_eval_forward_backward (gunichar user_chr) { gunichar real_chr; /* * Work on backspaces * L'\t' means a super + */ if (user_chr == L'\b' || user_chr == L'\t') { tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL); /* * Test for end of errors to be corrected */ if (tutor.retro_pos == 0) { tutor_beep (); return (TRUE); } /* * Go backwards and test for begin of the text */ if (cursor_advance (-1) != -1) { tutor_beep (); return (TRUE); } /* * Test begin of line */ if (cursor_get_char () == L'\n') if (cursor_advance (-1) != -1) { tutor_beep (); return (TRUE); } /* * Reinitialize the color */ cursor_paint_char ("char_untouched"); /* * Update state */ tutor.retro_pos--; tutor.correcting++; if (user_chr == L'\t') tutor_eval_forward_backward (L'\t'); return (TRUE); } real_chr = cursor_get_char (); // Minimizing the line breaking bug: if (user_chr == UPSYM && real_chr == L' ') user_chr = L' '; /* * Compare the user char with the real char and set the color */ if (user_chr == real_chr && tutor.retro_pos == 0) { tutor.n_touchs++; if (tutor.ttidx < MAX_TOUCH_TICS) { tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL) - tutor.touch_time[tutor.ttidx]; tutor.ttidx++; tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL); } if (tutor.correcting != 0) { cursor_paint_char ("char_retouched"); tutor.n_errors++; } else { cursor_paint_char ("char_correct"); accur_correct (real_chr); } } else { tutor.touch_time[tutor.ttidx] = g_timer_elapsed (tutor.tmr, NULL); cursor_paint_char ("char_wrong"); tutor_beep (); tutor.retro_pos++; if (tutor.retro_pos == 1) accur_wrong (real_chr); } if (tutor.correcting > 0) tutor.correcting--; /* * Go forward and test end of text */ if (cursor_advance (1) != 1) { if (tutor.retro_pos == 0) return (FALSE); tutor.retro_pos--; } /* * Test end of paragraph */ if (cursor_get_char () == L'\n') if (cursor_advance (1) != 1 && tutor.retro_pos == 0) return (FALSE); return (TRUE); } /********************************************************************** * Calculate the final results */ void tutor_calc_stats () { guint i = 0; gint minutes; gint seconds; gboolean may_log = TRUE; gdouble accuracy; gdouble touchs_per_second; gdouble velocity; gdouble fluidness; gdouble standard_deviation = 0; gdouble sum; gdouble average = 0; gdouble sample; gchar *contest_ps = NULL; gchar *tmp_locale; gchar *tmp_str = NULL; gchar *tmp_str2 = NULL; gchar *tmp_name; gchar *tmp; FILE *fh; time_t tmp_time; struct tm *ltime; GtkWidget *wg; GtkTextBuffer *buf; GtkTextIter start; GtkTextIter end; Statistics stat; /* * Calculate statistics */ minutes = ((gulong) tutor.elapsed_time) / 60; seconds = ((gulong) tutor.elapsed_time) % 60; accuracy = 100 * (1.0 - (gfloat) tutor.n_errors / tutor.n_touchs); touchs_per_second = (gdouble) (tutor.n_touchs - tutor.n_errors) / tutor.elapsed_time; velocity = 12 * touchs_per_second; // touched: new_WPM = 1.2 old_WPM if (tutor.type == TT_FLUID) { /* * "Magic" fluidness calculation */ sum = 0; for (i = 2; i < tutor.ttidx; i++) { if (tutor.touch_time[i] <= 0) tutor.touch_time[i] = 1.0e-8; sample = sqrt (1 / tutor.touch_time[i]); sum += sample; } if (i == 2) i++; average = sum / (i - 2); sum = 0; for (i = 2; i < tutor.ttidx; i++) { sample = sqrt (1 / tutor.touch_time[i]); sum += (sample - average) * (sample - average); } if (i < 4) i = 4; standard_deviation = sqrt (sum / (i - 3)); if (average <= 0) average = 1.0e-9; fluidness = 100 * (1 - standard_deviation / average); if (fluidness < 2) fluidness = 2; } else fluidness = 0; stat.score = 0; /* Verify if logging is allowed */ may_log = TRUE; if (tutor.type == TT_FLUID) if (tutor.n_touchs < MIN_CHARS_TO_LOG) { gdk_beep (); gdk_beep (); contest_ps = g_strdup_printf (_ ("ps.: logging not performed for this session: " "the number of typed characters (%i) must be greater than %i."), tutor.n_touchs, MIN_CHARS_TO_LOG); may_log = FALSE; } if (may_log) { /* * Changing to "C" locale: remember to copy the previous value! */ tmp_locale = g_strdup (setlocale (LC_NUMERIC, NULL)); if (tmp_locale != NULL) setlocale (LC_NUMERIC, "C"); /* * Logging */ tmp_name = g_strconcat (main_path_stats (), G_DIR_SEPARATOR_S "stat_", tutor_get_type_name (), ".txt", NULL); assert_user_dir (); if (!(fh = (FILE *) g_fopen (tmp_name, "r"))) { fh = (FILE *) g_fopen (tmp_name, "w"); fprintf (fh, "Accuracy\tVelocity\tFluidness\tDate\tHour\tLesson\tLanguage\n"); } else { fclose (fh); fh = (FILE *) g_fopen (tmp_name, "a"); } if (fh) { tmp_time = time (NULL); ltime = localtime (&tmp_time); fprintf (fh, "%.2f\t%.2f\t%.2f\t%i-%2.2i-%2.2i\t%2.2i:%2.2i\t", accuracy, velocity, fluidness, (ltime->tm_year) + 1900, (ltime->tm_mon) + 1, (ltime->tm_mday), (ltime->tm_hour), (ltime->tm_min)); switch (tutor.type) { case TT_BASIC: fprintf (fh, "%2.2i\t", basic_get_lesson ()); break; case TT_ADAPT: fprintf (fh, "%s\t", _("Default")); break; case TT_VELO: fprintf (fh, "%s\t", velo_get_dict_name ()); break; case TT_FLUID: fprintf (fh, "%s\t", fluid_get_paragraph_name ()); } fprintf (fh, "%s\n", trans_get_current_language ()); fclose (fh); } else g_message ("not able to log on this file:\n %s", tmp_name); g_free (tmp_name); if (tutor.type == TT_FLUID) { /* Log the fluidness results of the last session */ tmp_name = g_build_filename (main_path_stats (), "deviation_fluid.txt", NULL); if ((fh = (FILE *) g_fopen (tmp_name, "w"))) { g_message ("writing further fluidness results at:\n %s", tmp_name); fprintf (fh, "(i)\tdt(i)\tsqrt(1/dt(i))\tAverage:\t%g\tStd. dev.:\t%g\n", average, standard_deviation); for (i = 1; i < tutor.ttidx; i++) fprintf (fh, "%i\t%g\t%g\n", i, tutor.touch_time[i], sqrt (1 / (tutor.touch_time[i] > 0 ? tutor.touch_time[i] : 1.0e-9))); fclose (fh); } else g_message ("not able to log on this file:\n %s", tmp_name); g_free (tmp_name); /* Add results to Top 10 */ tmp_name = main_preferences_get_string ("interface", "language"); stat.lang[0] = ((tmp_name[0] == 'C') ? 'e' : tmp_name[0]); stat.lang[1] = ((tmp_name[0] == 'C') ? 'n' : tmp_name[1]); stat.genv = (UNIX_OK ? 'x' : 'w'); stat.when = time (NULL); stat.nchars = tutor.n_touchs; stat.accur = accuracy; stat.velo = velocity; stat.fluid = fluidness; stat.score = top10_calc_score (&stat); g_free (tmp_name); tmp = main_preferences_get_string ("tutor", "keyboard"); tmp_name = g_strdup_printf ("%s [%s]", g_get_real_name (), tmp); g_free (tmp); stat.name_len = strlen (tmp_name); if (stat.name_len > MAX_NAME_LEN) stat.name_len = MAX_NAME_LEN; strncpy (stat.name, tmp_name, stat.name_len + 1); g_free (tmp_name); top10_read_stats (LOCAL, -1); if (tutor_char_distribution_approved ()) { if (top10_compare_insert_stat (&stat, LOCAL)) { contest_ps = g_strdup (_ ("ps.: you have entered the Top 10 list, great!")); top10_write_stats (LOCAL, -1); if (main_preferences_get_boolean ("game", "autopublish") && UNIX_OK) { top10_show_stats (LOCAL); top10_show_stats (GLOBAL); top10_global_publish (NULL); } } } else contest_ps = g_strdup (_("ps.: the text you just typed doesn't seem to be similar" " to ordinary texts in the language currently selected:" " we can't account for it in the 'Top 10' contest.")); /* Anyway, log also the scoring */ tmp_name = g_build_filename (main_path_stats (), "scores_fluid.txt", NULL); assert_user_dir (); if (!g_file_test (tmp_name, G_FILE_TEST_IS_REGULAR)) { fh = (FILE *) g_fopen (tmp_name, "w"); fprintf (fh, "Score\tDate\tTime\tNumber of chars\tLanguage\n"); } else fh = (FILE *) g_fopen (tmp_name, "a"); if (fh) { ltime = localtime (&stat.when); fprintf (fh, "%3.4f\t%i-%2.2i-%2.2i\t%2.2i:%2.2i\t%i\t%s\n", stat.score, (ltime->tm_year) + 1900, (ltime->tm_mon) + 1, (ltime->tm_mday), (ltime->tm_hour), (ltime->tm_min), stat.nchars, trans_get_current_language ()); fclose (fh); } else g_message ("not able to log on this file:\n %s", tmp_name); g_free (tmp_name); } /* * Coming back to the right locale */ if (tmp_locale != NULL) setlocale (LC_NUMERIC, tmp_locale); g_free (tmp_locale); } /* * Print statistics */ wg = get_wg ("text_tutor"); buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg)); // Begin the accuracy tmp_str = g_strconcat ("\n", _("STATISTICS"), "\n", _("Elapsed time:"), " %i ", dngettext (PACKAGE, "minute and", "minutes and", minutes), " %i ", dngettext (PACKAGE, "second", "seconds", seconds), "\n", _("Error ratio:"), " %i/%i\n", _("Accuracy:"), " ", NULL); tmp_str2 = g_strdup_printf (tmp_str, minutes, seconds, tutor.n_errors, tutor.n_touchs); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); // Paint the accuracy g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%.1f%%", accuracy); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); gtk_text_buffer_get_end_iter (buf, &start); gtk_text_buffer_get_end_iter (buf, &end); gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2)); if (accuracy > tutor_goal_accuracy ()) gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end); else gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end); // Finish the accuracy g_free (tmp_str2); tmp_str2 = g_strdup_printf ("\t\t%s %.0f%%\n", _("Goal:"), tutor_goal_accuracy ()); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); if (tutor.type == TT_VELO || tutor.type == TT_FLUID) { // Begin the CPS g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%s ", _("Characters per second:")); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); // Paint the CPS g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%.2f", velocity / 12); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); gtk_text_buffer_get_end_iter (buf, &start); gtk_text_buffer_get_end_iter (buf, &end); gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2)); if (velocity > tutor_goal_speed ()) gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end); else gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end); // Finish the CPS g_free (tmp_str2); tmp_str2 = g_strdup_printf ("\t\t%s %.1f %s\n", _("Goal:"), tutor_goal_speed () / 12, _("(CPS)")); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); // Begin the WPM g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%s ", _("Words per minute:")); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); // Paint the WPM g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%.1f", velocity); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); gtk_text_buffer_get_end_iter (buf, &start); gtk_text_buffer_get_end_iter (buf, &end); gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2)); if (velocity > tutor_goal_speed ()) gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end); else gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end); // Finish the WPM g_free (tmp_str2); tmp_str2 = g_strdup_printf ("\t\t%s %.0f %s\n", _("Goal:"), tutor_goal_speed (), _("(WPM)")); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); if (tutor.type == TT_FLUID) { // Begin the fluidity g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%s ", _("Fluidness:")); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); // Paint the fluidity g_free (tmp_str2); tmp_str2 = g_strdup_printf ("%.1f%%", fluidness); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); gtk_text_buffer_get_end_iter (buf, &start); gtk_text_buffer_get_end_iter (buf, &end); gtk_text_iter_backward_cursor_positions (&start, strlen (tmp_str2)); if (fluidness > tutor_goal_fluidity ()) gtk_text_buffer_apply_tag_by_name (buf, "char_correct", &start, &end); else gtk_text_buffer_apply_tag_by_name (buf, "char_wrong", &start, &end); // Finish the fluidity and scores g_free (tmp_str2); tmp_str2 = g_strdup_printf ("\t\t%s %.0f%%\n%s: %f\n", _("Goal:"), tutor_goal_fluidity (), _("Score"), stat.score); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); } } // Begin the comments g_free (tmp_str2); tmp_str2 = g_strdup_printf ("\n%s\n", _("Comments:")); gtk_text_buffer_insert_at_cursor (buf, tmp_str2, strlen (tmp_str2)); switch (tutor.type) { case TT_BASIC: basic_comment (accuracy); break; case TT_ADAPT: adapt_comment (accuracy); break; case TT_VELO: velo_comment (accuracy, velocity); break; case TT_FLUID: fluid_comment (accuracy, velocity, fluidness); if (contest_ps != NULL) { if (UNIX_OK) { gtk_text_buffer_insert_at_cursor (buf, "\n", 1); gtk_text_buffer_insert_at_cursor (buf, contest_ps, strlen (contest_ps)); } g_free (contest_ps); } break; } g_free (tmp_str); g_free (tmp_str2); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (wg), gtk_text_buffer_get_insert (buf)); } /********************************************************************** * Ensure the user is not trying to type with weird texts in the fluidness contest */ #define DECEIVENESS_LIMIT 0.195 // 0.135 gboolean tutor_char_distribution_approved () { guint i, j; gfloat deceiveness; gchar *tmp_code; gchar *tmp_name; struct MODEL { gchar *text; Char_Distribution dist; } model; struct EXAM { gchar *text; Char_Distribution dist; } exam; GtkWidget *wg; GtkTextBuffer *buf; GtkTextIter start; GtkTextIter end; /* Get model text */ tmp_code = main_preferences_get_string ("interface", "language"); tmp_name = g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, tmp_code, ".paragraphs", NULL); g_free (tmp_code); if (!g_file_get_contents (tmp_name, &model.text, NULL, NULL)) { g_free (tmp_name); tmp_name = trans_lang_get_similar_file_name (".paragraphs"); if (!g_file_get_contents (tmp_name, &model.text, NULL, NULL)) { g_message ("Can't read file:\n %s\n So, not logging your score.", tmp_name); g_free (tmp_name); return FALSE; } } /* Get text under examination */ wg = get_wg ("text_tutor"); buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg)); gtk_text_buffer_get_bounds (buf, &start, &end); exam.text = gtk_text_buffer_get_text (buf, &start, &end, FALSE); /* Get char distributions */ tutor_char_distribution_count (model.text, &model.dist); tutor_char_distribution_count (exam.text, &exam.dist); /* Compare both distributions */ deceiveness = 0; for (i = 0; i < 9 && deceiveness < 1.0e+6; i++) { for (j = 0; j < exam.dist.size; j++) if (model.dist.ch[i].letter == exam.dist.ch[j].letter) { deceiveness += powf ((exam.dist.ch[j].freq - model.dist.ch[i].freq), 2); break; } if (j == exam.dist.size) { deceiveness += 1.0e+7; break; } } deceiveness = sqrtf (deceiveness / 9); g_print ("Corpus file: %s\n", tmp_name); if (deceiveness < DECEIVENESS_LIMIT) g_print ("\tDeviation: %.3f. OK, it is less than %.3f.\n", deceiveness, DECEIVENESS_LIMIT); else g_print ("\tDeviation: %.3f! It should be less than %.3f.\n", deceiveness, DECEIVENESS_LIMIT); g_free (tmp_name); g_free (model.text); g_free (exam.text); return (deceiveness < DECEIVENESS_LIMIT); } /********************************************************************** * Count relative frequency of letters in text */ void tutor_char_distribution_count (gchar * text, Char_Distribution * dist) { gchar *pt; gunichar ch; gsize i, j; pt = text; dist->size = 0; dist->total = 0; while ((ch = g_utf8_get_char (pt)) != L'\0') { /* Only count letters */ if (!g_unichar_isalpha (ch)) { pt = g_utf8_next_char (pt); continue; } ch = g_unichar_tolower (ch); /* Verify if ch was already counted */ for (i = 0; i < dist->size; i++) { if (ch == dist->ch[i].letter) { dist->ch[i].count++; dist->total++; break; } } /* If ch was not counted yet, start to do it */ if (i == dist->size && i < MAX_ALPHABET_LEN) { dist->ch[dist->size].letter = ch; dist->ch[dist->size].count = 1; dist->total++; dist->size++; } pt = g_utf8_next_char (pt); } /* Sort the list */ for (i = 1; i < dist->size; i++) { gunichar aletter; guint acount; if (dist->ch[i].count > dist->ch[i - 1].count) for (j = i; j > 0; j--) { if (dist->ch[j].count <= dist->ch[j - 1].count) break; aletter = dist->ch[j - 1].letter; dist->ch[j - 1].letter = dist->ch[j].letter; dist->ch[j].letter = aletter; acount = dist->ch[j - 1].count; dist->ch[j - 1].count = dist->ch[j].count; dist->ch[j].count = acount; } } /* Write the relative frequency */ for (i = 0; i < dist->size; i++) dist->ch[i].freq = ((gfloat) dist->ch[i].count) / ((gfloat) dist->ch[0].count); /* for (i = 0; i < dist->size; i++) g_message ("Char: %x, count: %u, freq:%g", dist->ch[i].letter, dist->ch[i].count, dist->ch[i].freq); g_message ("Total: %u / Size: %u ------------------------------", dist->total, dist->size); */ } /********************************************************************** * Formats and draws one paragraph at the tutor window */ void tutor_draw_paragraph (gchar * utf8_text) { static gchar *tmp1; static gchar *tmp2 = NULL; gchar *ptr; GtkWidget *wg; GtkTextBuffer *buf; g_free (tmp1); g_free (tmp2); if (g_utf8_strrchr (utf8_text, -1, L'\n') == NULL) { g_message ("paragraph not terminated by carriage return: adding one."); tmp1 = g_strconcat (utf8_text, "\n", NULL); } else tmp1 = g_strdup (utf8_text); ptr = g_utf8_strrchr (tmp1, -1, L'\n'); if (ptr) *ptr = '\0'; else g_error ("draw_paragraph () ==> string error"); wg = get_wg ("text_tutor"); buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg)); tmp2 = g_strconcat (tmp1, keyb_get_utf8_paragraph_symbol (), "\n", NULL); gtk_text_buffer_insert_at_cursor (buf, tmp2, -1); } /********************************************************************** * Put 'mesg' in the message entry line of the shared tutor window */ void tutor_message (gchar * mesg) { gint pos = 0; GtkWidget *wg; if (mesg == NULL) { g_message ("tutor_message() --> not showing NULL message!"); return; } wg = get_wg ("entry_mesg"); callbacks_shield_set (TRUE); gtk_editable_delete_text (GTK_EDITABLE (wg), 0, -1); gtk_editable_insert_text (GTK_EDITABLE (wg), g_strdup (mesg), strlen (mesg), &pos); gtk_editable_set_position (GTK_EDITABLE (wg), -1); callbacks_shield_set (FALSE); } /********************************************************************** * Beeps (or not) at the user, in the tutor window */ void tutor_beep () { GtkWidget *wg; wg = get_wg ("togglebutton_tutor_beep"); if ((GTK_TOGGLE_BUTTON (wg))->active) gdk_beep (); } /********************************************************************** * Load the list of files to include in the set of "other exercises" */ void tutor_load_list_other (gchar * file_name_end, GtkListStore * list) { gchar *tmp_str; gchar *dentry; GDir *dir; GtkTreeIter iter; static gchar *defstr = NULL; if (defstr == NULL) defstr = g_strdup (OTHER_DEFAULT); gtk_list_store_clear (list); gtk_list_store_append (list, &iter); gtk_list_store_set (list, &iter, 0, defstr, -1); assert_user_dir (); dir = g_dir_open (main_path_user (), 0, NULL); while ((dentry = g_strdup (g_dir_read_name (dir))) != NULL) { if (strlen (dentry) < 5) { g_free (dentry); continue; } if (!(tmp_str = strrchr (dentry, '.'))) { g_free (dentry); continue; } if (! g_str_equal (file_name_end, tmp_str)) { g_free (dentry); continue; } *(strrchr (dentry, '.')) = '\0'; gtk_list_store_append (list, &iter); gtk_list_store_set (list, &iter, 0, dentry, -1); g_free (dentry); } g_dir_close (dir); gtk_widget_set_sensitive (get_wg ("button_other_remove"), FALSE); gtk_widget_set_sensitive (get_wg ("label_other_rename"), FALSE); gtk_widget_set_sensitive (get_wg ("entry_other_rename"), FALSE); gtk_widget_set_sensitive (get_wg ("button_other_apply"), FALSE); } void tutor_other_rename (const gchar *new_tx, const gchar *old_tx) { if (! g_str_equal (new_tx, old_tx) && ! g_str_equal (new_tx, OTHER_DEFAULT) && ! g_str_equal (new_tx, "") && g_strrstr (old_tx, "*") == NULL ) { gchar *old_name; gchar *new_name; gchar *old_file; gchar *new_file; if (tutor.type == TT_VELO) { old_name = g_strconcat (old_tx, ".words", NULL); new_name = g_strconcat (new_tx, ".words", NULL); } else { old_name = g_strconcat (old_tx, ".paragraphs", NULL); new_name = g_strconcat (new_tx, ".paragraphs", NULL); } old_file = g_build_filename (main_path_user (), old_name, NULL); new_file = g_build_filename (main_path_user (), new_name, NULL); if (g_file_test (new_file, G_FILE_TEST_IS_REGULAR)) { g_message ("File already exists, not renaming.\n\t%s\n", new_file); gdk_beep (); } else { g_printf ("Renaming from:\n\t%s\nTo:\n\t%s\n", old_file, new_file); if (g_rename (old_file, new_file)) { g_printf ("Fail: %s\n", strerror (errno)); } else g_printf ("Success!\n"); } g_free (old_name); g_free (new_name); g_free (old_file); g_free (new_file); } }