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