/*****************************************************************************/
/* Klavaro - a flexible touch typing tutor */
/* Copyright (C) 2005, 2006, 2007, 2008 Felipe Castro */
/* Copyright (C) 2009, 2010, 2011 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
#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 ()
{
if (tutor.type != TT_FLUID)
return (FALSE);
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 ("togglebutton_toomuch_errors"));
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, MAX_BASIC_LESSONS);
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 ();
}
/*
* Apply tutor background color and font to the text
*/
gdk_color_parse (TUTOR_CREAM, &color);
gtk_widget_modify_base (get_wg ("text_tutor"), GTK_STATE_INSENSITIVE, &color);
gtk_text_buffer_get_bounds (buf, &start, &end);
gtk_text_iter_backward_char (&end);
gtk_text_buffer_apply_tag_by_name (buf, "lesson_font", &start, &end);
end = start;
while (gtk_text_iter_forward_word_end (&end))
{
gtk_text_buffer_apply_tag_by_name (buf, "char_keep_wrap", &start, &end);
start = end;
if (! gtk_text_iter_forward_char (&end))
break;
gtk_text_buffer_apply_tag_by_name (buf, "char_keep_wrap2", &start, &end);
start = end;
}
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);
switch (tutor.type)
{
case TT_BASIC:
hints_update_from_char (cursor_get_char ());
tutor_speak_char ();
return;
case TT_ADAPT:
tutor_speak_char ();
return;
case TT_VELO:
tutor_speak_word ();
return;
case TT_FLUID:
return;
}
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);
g_timer_start (tutor.tmr);
if (tutor.type == TT_FLUID)
tutor_eval_forward_backward (user_chr);
else
tutor_eval_forward (user_chr);
switch (tutor.type)
{
case TT_BASIC:
hints_update_from_char (cursor_get_char ());
tutor_speak_char ();
return;
case TT_ADAPT:
tutor_speak_char ();
return;
case TT_VELO:
tutor_speak_word ();
return;
case TT_FLUID:
return;
}
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);
switch (tutor.type)
{
case TT_BASIC:
hints_update_from_char (cursor_get_char ());
tutor_speak_char ();
return;
case TT_ADAPT:
tutor_speak_char ();
return;
case TT_VELO:
tutor_speak_word ();
return;
case TT_FLUID:
return;
}
}
}
/**********************************************************************
* 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)
if (main_preferences_get_boolean ("game", "autopublish"))
{
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);
g_free (tmp_str);
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_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 = NULL;
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);
}
/**********************************************************************
* 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);
}
}
/**********************************************************************
* 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 ();
}
/**********************************************************************
* Speak some phrase
*/
void
tutor_speak_string (gchar *string, gboolean wait)
{
gchar *tmp_code;
gchar *command;
static gboolean espeak_OK = TRUE;
static GtkWidget *wg = NULL;
if (wg == NULL)
wg = get_wg ("checkbutton_speech");
if (espeak_OK == FALSE)
return;
if (!GTK_WIDGET_VISIBLE (wg) )
return;
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wg)) )
return;
/* Translators: your language code (first 2 letters of your po-file)*/
tmp_code = g_strdup (_("en"));
tmp_code[2] = '\0';
if (wait)
{
command = g_strdup_printf ("espeak -v%s -k1 \"%s\"", tmp_code, string);
#ifdef G_OS_UNIX
espeak_OK = g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL);
#else
espeak_OK = ! system (command);
#endif
}
else
{
#ifdef G_OS_UNIX
if (g_utf8_strlen (string, -1) == 1 || tutor.type == TT_VELO)
command = g_strdup_printf ("espeak -v%s -k1 --punct '%s'", tmp_code, string);
else
command = g_strdup_printf ("espeak -v%s -k1 \"%s\"", tmp_code, string);
espeak_OK = g_spawn_command_line_async (command, NULL);
#else
if (g_utf8_strlen (string, -1) == 1 || tutor.type == TT_VELO)
command = g_strdup_printf ("espeak -v%s -k1 --punct \"%s\"", tmp_code, string);
else
command = g_strdup_printf ("espeak -v%s -k1 \"%s\"", tmp_code, string);
espeak_OK = ! system (command);
#endif
}
if (espeak_OK == FALSE)
g_message ("Espeak not installed, so we'll say nothing:\n %s", command);
g_free (tmp_code);
g_free (command);
}
/**********************************************************************
* Control delayed tips for the finger to be used
*/
gboolean
tutor_delayed_finger_tip (gpointer unich)
{
gchar *finger;
gunichar *uch = (gunichar*) unich;
static gint counter = 0;
if (unich == NULL)
{
counter++;
return FALSE;
}
counter--;
if (counter > 0)
return FALSE;
finger = hints_finger_name_from_char (*uch);
tutor_speak_string (finger, TRUE);
g_free (finger);
return FALSE;
}
/**********************************************************************
* Speak the current character to be typed
*/
void
tutor_speak_char ()
{
gchar ut8[100];
static gunichar uch;
if (tutor.type == TT_BASIC)
{
g_timeout_add (3000, (GSourceFunc) tutor_delayed_finger_tip, (gpointer) &uch);
tutor_delayed_finger_tip (NULL);
}
uch = cursor_get_char ();
switch (uch)
{
case L' ':
strcpy (ut8, _("space"));
break;
case L'y':
case L'Y':
/* Translators: the name of letter Y */
strcpy (ut8, _("wye"));
break;
case UPSYM:
/* Translators: the name of the Return key */
strcpy (ut8, _("enter"));
break;
case L'%':
strcpy (ut8, "%");
break;
case L'\'':
strcpy (ut8, _("apostrophe"));
break;
case L'\"':
/* Translators: double quote symbol: " */
strcpy (ut8, _("quote"));
break;
case L'&':
/* Translators: ampersand symbol: & */
strcpy (ut8, _("ampersand"));
break;
default:
ut8[g_unichar_to_utf8 (uch, ut8)] = '\0';
}
tutor_speak_string (ut8, FALSE);
}
/**********************************************************************
* Speak the next word to be typed
*/
void
tutor_speak_word ()
{
gunichar uch[100];
gchar *ut8;
gint i;
if (cursor_advance (-1) != -1)
uch[0] = L' ';
else
{
uch[0] = cursor_get_char ();
cursor_advance (1);
}
if (uch[0] == L' ' || uch[0] == UPSYM || uch[0] == L'\n' || uch[0] == L'\r')
{
for (i = 0; i < 100; i++)
{
uch[i] = cursor_get_char ();
if (uch[i] == L' ' || uch[i] == UPSYM || uch[i] == L'\n' || uch[i] == L'\r')
break;
cursor_advance (1);
}
cursor_advance (-i);
}
else
return;
ut8 = g_ucs4_to_utf8 (uch, i, NULL, NULL, NULL);
if (ut8)
tutor_speak_string (ut8, FALSE);
g_free (ut8);
}