1 /**************************************************************************/
2 /* Klavaro - a flexible touch typing tutor */
3 /* Copyright (C) from 2005 until 2008 Felipe Castro */
4 /* Copyright (C) from 2009 until 2019 The Free Software Foundation */
5 /* */
6 /* This program is free software, licensed under the terms of the GNU */
7 /* General Public License as published by the Free Software Foundation, */
8 /* either version 3 of the License, or (at your option) any later */
9 /* version. You should have received a copy of the GNU General Public */
10 /* License along with this program. If not, */
11 /* see <https://www.gnu.org/licenses/>. */
12 /**************************************************************************/
13
14 /*
15 * Fluidness exercise
16 */
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <glib.h>
21 #include <glib/gstdio.h>
22 #include <gtk/gtk.h>
23
24 #include "auxiliar.h"
25 #include "main.h"
26 #include "callbacks.h"
27 #include "translation.h"
28 #include "keyboard.h"
29 #include "tutor.h"
30 #include "velocity.h"
31 #include "fluidness.h"
32
33 typedef struct
34 {
35 gchar *buffer;
36 gint len;
37 gchar name[21];
38 } Paragraph;
39
40 Paragraph par = { NULL, 0, "" };
41
42 extern gchar *OTHER_DEFAULT;
43
44 /*******************************************************************************
45 * Interface functions
46 */
47 gchar *
fluid_get_paragraph_name()48 fluid_get_paragraph_name ()
49 {
50 return par.name;
51 }
52
53 void
fluid_reset_paragraph()54 fluid_reset_paragraph ()
55 {
56 g_free (par.buffer);
57 par.buffer = NULL;
58 par.len = 0;
59 par.name[0] = '\0';
60 }
61
62 /*
63 * Get from the structure 'par' the paragraph defined by 'index'
64 *
65 */
66 gchar *
get_par(gint index)67 get_par (gint index)
68 {
69 gint i;
70 gint size;
71 gint stops;
72 gchar *par_1;
73 gchar *par_2;
74 gchar *par_i;
75
76 par_1 = par.buffer;
77 par_2 = strchr (par_1, '\n') + 1;
78 if (par_2 == NULL)
79 par_2 = par_1;
80 for (i = 0; i < index && i < par.len; i++)
81 {
82 par_1 = par_2;
83 par_2 = strchr (par_1, '\n') + 1;
84 if (par_2 == NULL)
85 par_2 = par_1;
86 }
87 size = par_2 - par_1;
88 if (size < 1)
89 {
90 g_message ("internal error while picking the paragraph %i.", index);
91 par_i = g_strdup_printf ("#%i\n", index);
92 }
93 else
94 {
95 stops = 0;
96 for (i = 1; i < size; i++)
97 if (par_1[i] == ' ')
98 if (par_1[i - 1] == '.' || par_1[i - 1] == '!' || par_1[i - 1] == '?')
99 stops++;
100 par_i = g_malloc (size + stops + 10);
101 strncpy (par_i, par_1, size);
102 par_i[size] = '\0';
103 //g_message ("Paragraph %i: %i stops", index, stops);
104
105 if (main_preferences_get_boolean ("tutor", "double_spaces"))
106 {
107 for (i = 0; i < size - 1; i++)
108 {
109 if (par_i[i + 1] == ' ' && par_i[i + 2] != ' ')
110 if (par_i[i] == '.' || par_i[i] == '!' || par_i[i] == '?')
111 {
112 memmove (par_i + i + 3, par_i + i + 2, size - i - 1);
113 par_i[i+2] = ' ';
114 i += 2;
115 size++;
116 }
117 }
118 }
119 }
120
121 size = strlen (par_i);
122 if (size > 0)
123 par_i[size - 1] = '\n';
124 return (par_i);
125 }
126
127 /**********************************************************************
128 * Initialize the fluid exercise window.
129 */
130 void
fluid_init()131 fluid_init ()
132 {
133 gchar *tmp_name;
134 gchar *tmp_str;
135 FILE *fh;
136 GtkWidget *wg;
137
138 if (!main_preferences_exist ("tutor", "fluid_paragraphs"))
139 main_preferences_set_int ("tutor", "fluid_paragraphs", 3);
140
141 callbacks_shield_set (TRUE);
142 wg = get_wg ("spinbutton_lesson");
143 gtk_spin_button_set_value (GTK_SPIN_BUTTON (wg),
144 main_preferences_get_int ("tutor", "fluid_paragraphs"));
145 callbacks_shield_set (FALSE);
146
147 if (par.len == 0)
148 {
149 if (main_preferences_exist ("tutor", "paragraph_list"))
150 {
151 tmp_str = main_preferences_get_string ("tutor", "paragraph_list");
152 tmp_name = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, tmp_str, ".paragraphs", NULL);
153 if ((fh = (FILE *) g_fopen (tmp_name, "r")))
154 {
155 fluid_init_paragraph_list (tmp_str);
156 fclose (fh);
157 }
158 g_free (tmp_str);
159 g_free (tmp_name);
160 }
161 }
162 if (par.len == 0)
163 fluid_init_paragraph_list (NULL);
164 }
165
166 /**********************************************************************
167 * Reads paragraphs from the text file.
168 */
169 void
fluid_init_paragraph_list(gchar * list_name)170 fluid_init_paragraph_list (gchar * list_name)
171 {
172 guint len;
173 gchar *memory_ok;
174 gchar *tmp_name;
175 gchar *tmp_code;
176 gchar str_9000[9001];
177 FILE *fh;
178
179 if (list_name && !g_str_equal (list_name, OTHER_DEFAULT))
180 {
181 main_preferences_set_string ("tutor", "paragraph_list", list_name);
182 tmp_name = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, list_name, ".paragraphs", NULL);
183 g_message ("loading text file: %s.paragraphs", list_name);
184 strncpy (par.name, list_name, 20);
185 par.name[20] = '\0';
186 }
187 else
188 {
189 main_preferences_remove ("tutor", "paragraph_list");
190 tmp_code = main_preferences_get_string ("interface", "language");
191 tmp_name = g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, tmp_code, ".paragraphs", NULL);
192 g_message ("loading text file: %s.paragraphs", tmp_code);
193 strcpy (par.name, "Default");
194 g_free (tmp_code);
195 }
196
197 fh = (FILE *) g_fopen (tmp_name, "r");
198 if (fh == NULL && g_str_equal (par.name, "Default"))
199 fh = trans_lang_get_similar_file (".paragraphs");
200
201 if (fh)
202 {
203 g_free (par.buffer);
204 par.buffer = g_strdup ("");
205 par.len = 0;
206 g_print ("Paragraphs:\n0");
207 while (fgets (str_9000, 9001, fh))
208 {
209 len = strlen (str_9000);
210 if (len < 2)
211 continue;
212 memory_ok = g_try_renew (gchar, par.buffer, strlen (par.buffer) + len + 2);
213 if (memory_ok)
214 par.buffer = memory_ok;
215 else
216 {
217 g_print ("\nThere was truncation: memory restrictions...");
218 break;
219 }
220 strcat (par.buffer, str_9000);
221 if (len == 9000)
222 strcat (par.buffer, "\n");
223 par.len++;
224 g_print (" - %i", par.len);
225 }
226 fclose (fh);
227 g_print ("\nText file loaded!\n\n");
228 }
229 else
230 {
231 g_message ("could not open the file: %s", tmp_name);
232 g_free (tmp_name);
233 tmp_code = main_preferences_get_string ("interface", "language");
234 if (g_str_equal (tmp_code, "C"))
235 g_error ("so, we must quit!");
236 main_preferences_set_string ("interface", "language", "C");
237 fluid_init_paragraph_list (list_name);
238 main_preferences_set_string ("interface", "language", tmp_code);
239 g_free (tmp_code);
240 return;
241 }
242 g_free (tmp_name);
243 }
244
245 /**********************************************************************
246 * Draw random sentences selected from a '.paragraphs' file
247 */
248 void
fluid_draw_random_paragraphs()249 fluid_draw_random_paragraphs ()
250 {
251 gint i, j;
252 gint par_num;
253 gint rand_i[10];
254 # define FLUID_PARBUF 50
255 static gchar *text[FLUID_PARBUF] = {
256 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
257 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
258 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
259 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
260 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
261 };
262
263 par_num = main_preferences_get_int ("tutor", "fluid_paragraphs");
264
265 /* Use all the text, without mangling it
266 */
267 if (par_num == 0 || (main_velo_txt () && tutor_get_type () == TT_VELO))
268 {
269 par_num = par.len > FLUID_PARBUF ? FLUID_PARBUF : par.len;
270
271 for (i = 0; i < par_num; i++)
272 g_free (text[i]);
273
274 for (i = 0; i < par_num; i++)
275 {
276 text[i] = get_par (i);
277 tutor_draw_paragraph (text[i]);
278 }
279 return;
280 }
281
282 /* Use some paragraphs, pseudo-randomly
283 */
284 for (i = 0; (i < par_num) && (i < par.len); i++)
285 g_free (text[i]);
286
287 for (i = 0; (i < par_num) && (i < par.len); i++)
288 {
289 do
290 {
291 rand_i[i] = rand () % par.len;
292 for (j = 0; j < i; j++)
293 {
294 if (rand_i[i] == rand_i[j])
295 rand_i[i] = par.len;
296 }
297 }
298 while (rand_i[i] == par.len);
299
300 text[i] = get_par (rand_i[i]);
301 tutor_draw_paragraph (text[i]);
302 }
303 }
304
305 /**********************************************************************
306 * Takes text and validate it as UTF-8
307 */
308 gchar *
fluid_filter_utf8(gchar * text)309 fluid_filter_utf8 (gchar * text)
310 {
311 gulong i;
312 gunichar uch = 0;
313 gboolean is_symbol;
314 struct INPUT_TEXT
315 {
316 gchar *pt;
317 gulong len;
318 guint npar;
319 } raw;
320 struct KEYBOARD_SYMBOLS
321 {
322 gunichar set[200];
323 guint n;
324 } sym;
325 struct FILTERED_TEXT
326 {
327 gchar *txt;
328 gulong i;
329 gulong len;
330 } flt;
331
332 raw.len = strlen (text);
333
334 /* Verify empty input string
335 */
336 if (raw.len == 0)
337 {
338 flt.txt = g_strdup_printf ("%i\n", rand () % 9999);
339 return (flt.txt);
340 }
341
342 /* Allocate memory space for the result
343 */
344 flt.i = 0;
345 flt.len = raw.len + 100;
346 flt.txt = g_malloc (flt.len);
347
348 /* By-pass BOM
349 */
350 raw.pt = text;
351 if (g_utf8_get_char_validated (raw.pt, 16) == 0xEFBBBF)
352 raw.pt = g_utf8_find_next_char (raw.pt, raw.pt + 16);
353
354 /* Replace Win/MAC-returns
355 */
356 for (i = 0; i < raw.len; i++)
357 if (text[i] == '\r')
358 text[i] = '\n';
359
360 /* Filter
361 */
362 sym.n = keyb_get_symbols (sym.set);
363 raw.npar = 0;
364 while (raw.pt && raw.npar < MAX_PARAGRAPHS)
365 {
366 if (*raw.pt == '\0')
367 break;
368 /* Test valid utf8 char
369 */
370 if ((uch = g_utf8_get_char_validated (raw.pt, 16)) == (gunichar) -1
371 || uch == (gunichar) -2)
372 uch = L' ';
373
374 /* Increase the pointer for the input text
375 */
376 raw.pt = g_utf8_find_next_char (raw.pt, raw.pt + 16);
377
378 /* Test reazonable char as valid for fluidness exercise
379 */
380 if (!(uch == L' ' || uch == L'\n' || g_unichar_isalnum (uch)))
381 {
382 is_symbol = FALSE;
383 for (i = 0; i < sym.n; i++)
384 if (uch == sym.set[i])
385 {
386 is_symbol = TRUE;
387 break;
388 }
389 if (!is_symbol)
390 uch = L' ';
391 }
392
393 /* Verify memory space of output buffer
394 */
395 if (flt.i < flt.len - 7)
396 {
397 flt.len += 100;
398 flt.txt = g_realloc (flt.txt, flt.len);
399 }
400
401 /* Verify new line and form the next UTF-8 char to be appended
402 */
403 if (uch == L'\n')
404 {
405 raw.npar++;
406 flt.txt[flt.i++] = '\n';
407 flt.txt[flt.i++] = '\n';
408 for (; *raw.pt == '\n' || *raw.pt == ' '; raw.pt++);
409 }
410 else
411 flt.i += g_unichar_to_utf8 (uch, &flt.txt[flt.i]);
412 }
413 if (uch != L'\n')
414 {
415 raw.npar++;
416 flt.txt[flt.i++] = '\n';
417 flt.txt[flt.i++] = '\n';
418 }
419 flt.txt[flt.i++] = '\0';
420
421 return (flt.txt);
422 }
423
424 /**********************************************************************
425 * Paste clipboard or dropped text in a file, so that it can be used as
426 * a customized exercise.
427 */
428 void
fluid_text_write_to_file(gchar * text_raw)429 fluid_text_write_to_file (gchar * text_raw)
430 {
431 gchar *pars_path;
432 gchar *pars_name;
433 gchar *text_filtered;
434 FILE *fh_destiny;
435
436 pars_name = g_strdup_printf ("(%s)", _("Pasted_or_dropped"));
437 pars_path = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, pars_name, ".paragraphs", NULL);
438 assert_user_dir ();
439 if (!(fh_destiny = (FILE *) g_fopen (pars_path, "w")))
440 {
441 gdk_display_beep (gdk_display_get_default ());
442 g_warning ("couldn't create the file:\n %s", pars_path);
443 g_free (pars_path);
444 g_free (pars_name);
445 return;
446 }
447 g_free (pars_path);
448
449 /* Filter the text
450 */
451 text_filtered = fluid_filter_utf8 (text_raw);
452 fwrite (text_filtered, sizeof (gchar), strlen (text_filtered), fh_destiny);
453 fclose (fh_destiny);
454
455 g_free (text_filtered);
456
457 fluid_init_paragraph_list (pars_name);
458 g_free (pars_name);
459 tutor_set_query (QUERY_INTRO);
460 tutor_process_touch ('\0');
461
462 velo_text_write_to_file (text_raw, FALSE);
463 }
464
465 /**********************************************************************
466 * Copy the file 'file_name' so that it can be used as a customized
467 * exercise.
468 */
469 void
fluid_copy_text_file(gchar * file_name)470 fluid_copy_text_file (gchar * file_name)
471 {
472 gchar *pars_path;
473 gchar *pars_name;
474 gchar *text_raw;
475 gchar *text_filtered;
476 FILE *fh_destiny;
477
478 if (!file_name)
479 {
480 gdk_display_beep (gdk_display_get_default ());
481 g_warning ("fluid_copy_text_file(): null file name as argument.");
482 return;
483 }
484
485 if (!g_file_get_contents (file_name, &text_raw, NULL, NULL))
486 {
487 gdk_display_beep (gdk_display_get_default ());
488 g_warning ("couldn't read the file:\n %s\n", file_name);
489 return;
490 }
491
492 pars_name = g_strdup (strrchr (file_name, G_DIR_SEPARATOR) + 1);
493 pars_path = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, pars_name, ".paragraphs", NULL);
494 assert_user_dir ();
495 if (!(fh_destiny = (FILE *) g_fopen (pars_path, "w")))
496 {
497 gdk_display_beep (gdk_display_get_default ());
498 g_warning ("couldn't create the file:\n %s", pars_path);
499 g_free (pars_path);
500 g_free (pars_name);
501 return;
502 }
503 g_free (pars_path);
504
505 /* Filter the text
506 */
507 text_filtered = fluid_filter_utf8 (text_raw);
508 fwrite (text_filtered, sizeof (gchar), strlen (text_filtered), fh_destiny);
509 fclose (fh_destiny);
510
511 g_free (text_raw);
512 g_free (text_filtered);
513
514 fluid_init_paragraph_list (pars_name);
515 g_free (pars_name);
516 tutor_set_query (QUERY_INTRO);
517 tutor_process_touch ('\0');
518
519 velo_create_dict (file_name, FALSE);
520 }
521
522 /**********************************************************************
523 * Put on the screen the final comments
524 */
525 #define FLUID_1 60
526 void
fluid_comment(gdouble accuracy,gdouble velocity,gdouble fluidness)527 fluid_comment (gdouble accuracy, gdouble velocity, gdouble fluidness)
528 {
529 gchar *tmp_str;
530 GtkWidget *wg;
531 GtkTextBuffer *buf;
532
533 /*
534 * Comments
535 */
536 if (accuracy < tutor_goal_accuracy ())
537 tmp_str = g_strdup (":-(\n");
538 else if (velocity < tutor_goal_speed ())
539 tmp_str = g_strdup_printf (_(" You type accurately but not so fast.\n"
540 " Can you reach %.0f WPM?\n"), tutor_goal_speed ());
541 else if (fluidness < tutor_goal_level (0))
542 tmp_str = g_strdup_printf (_(" Your rhythm is not so constant. Calm down.\n"
543 " For now, try to make the fluidness greater than %i%%.\n"), (gint) tutor_goal_level(0));
544 else if (fluidness < tutor_goal_fluidity ())
545 tmp_str = g_strdup_printf (_(" You are almost getting there. Type more fluently.\n"
546 " I want a fluidness greater than %.0f%%.\n"), tutor_goal_fluidity ());
547 else if (velocity < tutor_goal_level (1))
548 tmp_str = g_strdup (_(" Congratulations!\n"
549 " It seems to me that you are a professional.\n"
550 " You don't need this program (me) anymore.\n"
551 " Hope you have enjoyed. Thanks and be happy!\n"));
552 else
553 tmp_str = g_strdup (_(" How can you type so fast?\n"
554 " You have exceeded all my expectations.\n"
555 " Are you a machine? Could you teach me?\n"
556 " I can not help you anymore. Go to an expert!\n"));
557
558 wg = get_wg ("text_tutor");
559 buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (wg));
560 gtk_text_buffer_insert_at_cursor (buf, tmp_str, strlen (tmp_str));
561 g_free (tmp_str);
562 }
563