1 /*  Copyright 2006-2011 Enrico Tröger <enrico(at)xfce(dot)org>
2  *
3  *  This program is free software; you can redistribute it and/or modify
4  *  it under the terms of the GNU General Public License as published by
5  *  the Free Software Foundation; either version 2 of the License, or
6  *  (at your option) any later version.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU Library General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License
14  *  along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21 
22 #include <gtk/gtk.h>
23 #include <libxfce4ui/libxfce4ui.h>
24 
25 #include "common.h"
26 #include "speedreader.h"
27 
28 
29 typedef struct _XfdSpeedReaderPrivate			XfdSpeedReaderPrivate;
30 
31 struct _XfdSpeedReaderPrivate
32 {
33 	GtkWidget *first_page;
34 	GtkWidget *second_page;
35 
36 	GtkWidget *button_start;
37 	GtkWidget *button_stop;
38 	GtkWidget *button_pause;
39 
40 	GtkWidget *spin_wpm;
41 	GtkWidget *spin_grouping;
42 	GtkWidget *button_font;
43 	GtkWidget *check_mark_paragraphs;
44 	GtkWidget *display_label;
45 	GtkTextBuffer *buffer;
46 
47 	guint timer_id;
48 	guint word_idx;
49 	gsize words_len;
50 	gchar **words;
51 
52 	GString *group;
53 	gsize group_size;
54 
55 	gboolean paused;
56 
57 	DictData *dd;
58 };
59 
60 enum
61 {
62 	RESPONSE_START,
63 	RESPONSE_STOP,
64 	RESPONSE_PAUSE
65 };
66 
67 enum
68 {
69 	XSR_STATE_INITIAL,
70 	XSR_STATE_RUNNING,
71 	XSR_STATE_FINISHED
72 };
73 
74 #define XFD_TITLE_PAUSE _("P_ause")
75 #define XFD_TITLE_RESUME _("_Resume")
76 
77 
78 G_DEFINE_TYPE_WITH_PRIVATE(XfdSpeedReader, xfd_speed_reader, GTK_TYPE_DIALOG);
79 
80 static void sr_stop(XfdSpeedReader *dialog);
81 static void sr_stop_timer(XfdSpeedReader *dialog);
82 static void sr_pause(XfdSpeedReader *dialog, gboolean paused);
83 
84 
xfd_speed_reader_finalize(GObject * object)85 static void xfd_speed_reader_finalize(GObject *object)
86 {
87 	g_return_if_fail(object != NULL);
88 	g_return_if_fail(IS_XFD_SPEED_READER(object));
89 
90 	sr_stop_timer(XFD_SPEED_READER(object));
91 
92 	G_OBJECT_CLASS(xfd_speed_reader_parent_class)->finalize(object);
93 }
94 
95 
xfd_speed_reader_class_init(XfdSpeedReaderClass * klass)96 static void xfd_speed_reader_class_init(XfdSpeedReaderClass *klass)
97 {
98 	GObjectClass *g_object_class;
99 
100 	g_object_class = G_OBJECT_CLASS(klass);
101 	g_object_class->finalize = xfd_speed_reader_finalize;
102 }
103 
104 
105 /* Based on GLib's g_strsplit_set() but slightly modified to split exactly what we need for
106  * speed reading (e.g. splitting but not removing dashes). */
sr_strsplit_set(const gchar * string,const gchar * delimiters)107 static gchar **sr_strsplit_set(const gchar *string, const gchar *delimiters)
108 {
109 	gboolean delim_table[256];
110 	GSList *tokens, *list;
111 	gint n_tokens;
112 	guint x;
113 	const gchar *s;
114 	const gchar *current;
115 	gchar *token;
116 	gchar **result;
117 
118 	g_return_val_if_fail(string != NULL, NULL);
119 	g_return_val_if_fail(delimiters != NULL, NULL);
120 
121 	if (*string == '\0')
122 	{
123 		result = g_new(char *, 1);
124 		result[0] = NULL;
125 		return result;
126 	}
127 
128 	memset(delim_table, FALSE, sizeof (delim_table));
129 	for (s = delimiters; *s != '\0'; ++s)
130 		delim_table[*(guchar *)s] = TRUE;
131 
132 	tokens = NULL;
133 	n_tokens = 0;
134 
135 	s = current = string;
136 	while (*s != '\0')
137 	{
138 		if (delim_table[*(guchar *)s])
139 		{
140 			x = (*s == '-') ? 1 : 0;
141 			token = g_strndup(current, s - current + x);
142 			tokens = g_slist_prepend(tokens, token);
143 			++n_tokens;
144 
145 			current = s + 1;
146 		}
147 		++s;
148 	}
149 
150 	token = g_strndup(current, s - current);
151 	tokens = g_slist_prepend(tokens, token);
152 	++n_tokens;
153 
154 	result = g_new(gchar *, n_tokens + 1);
155 
156 	result[n_tokens] = NULL;
157 	for (list = tokens; list != NULL; list = list->next)
158 		result[--n_tokens] = list->data;
159 
160 	g_slist_free(tokens);
161 
162 	return result;
163 }
164 
165 
sr_replace_unicode_characters(const gchar * text,gboolean mark_paragraphs)166 static gchar *sr_replace_unicode_characters(const gchar *text, gboolean mark_paragraphs)
167 {
168 	GString *str;
169 	gchar *result;
170 	gunichar c = 0, last_c, x;
171 	gboolean last_line_was_empty = FALSE;
172 
173 	g_return_val_if_fail(text != NULL, NULL);
174 
175 	if (! g_utf8_validate(text, -1, NULL))
176 		return g_strdup(text);
177 
178 	str = g_string_new(NULL);
179 
180 	while (*text)
181 	{
182 		last_c = c;
183 		c = g_utf8_get_char(text);
184 
185 		switch (g_unichar_type(c))
186 		{
187 			case G_UNICODE_DASH_PUNCTUATION:
188 				g_string_append_c(str, '-');
189 				break;
190 			case G_UNICODE_SPACE_SEPARATOR:
191 				g_string_append_c(str, ' ');
192 				break;
193 			case G_UNICODE_PARAGRAPH_SEPARATOR:
194 				if (mark_paragraphs)
195 					g_string_append_unichar(str, 182); /* 182 = ¶ */
196 				/* intended fall-through */
197 			case G_UNICODE_LINE_SEPARATOR:
198 				g_string_append_c(str, '\n');
199 				break;
200 			case G_UNICODE_CONTROL:
201 			{
202 				/* if not \n or \r, add it literally */
203 				if (! mark_paragraphs || strchr("\n\r", c) == NULL)
204 				{
205 					g_string_append_unichar(str, c);
206 				}
207 				else /* add pilcrows for paragraphs */
208 				{
209 					if ((last_c == '\r' && c == '\n') || /* CRLF */
210 						(last_c == '\r' && c != '\n') || /* CR */
211 						(last_c != '\r' && c == '\n'))   /* LF */
212 					{
213 						if (c == '\n')
214 							/* skip to the next character */
215 							x = g_utf8_get_char(g_utf8_next_char(text));
216 						else
217 							x = c;
218 
219 						if (strchr("\r\n", x))
220 						{
221 							last_line_was_empty = TRUE;
222 						}
223 						else if (last_line_was_empty)
224 						{
225 							last_line_was_empty = FALSE;
226 							g_string_append(str, "¶\n");
227 						}
228 					}
229 					g_string_append_unichar(str, c);
230 				}
231 				break;
232 			}
233 			default:
234 				g_string_append_unichar(str, c);
235 		}
236 		text = g_utf8_next_char(text);
237 	}
238 
239 	result = g_string_free(str, (str->len == 0));
240 
241 	return (result) ? result : g_strdup(text);
242 }
243 
244 
xfd_speed_reader_set_window_title(XfdSpeedReader * dialog,gint state)245 static void xfd_speed_reader_set_window_title(XfdSpeedReader *dialog, gint state)
246 {
247 	gchar *title, *state_str, *name;
248 	const gchar *button_label = _("S_top");
249 	const gchar *button_image = "media-playback-stop";
250 	gboolean pausable = TRUE;
251 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
252 
253 	switch (state)
254 	{
255 		case XSR_STATE_RUNNING:
256 			state_str = _("Running");
257 			break;
258 		case XSR_STATE_FINISHED:
259 			state_str = _("Finished");
260 			button_label = _("_Back");
261 			button_image = "go-previous";
262 			pausable = FALSE;
263 			break;
264 		default:
265 			state_str = "";
266 	}
267 
268 	name = _("Speed Reader");
269 	title = g_strdup_printf("%s%s%s", name, (NZV(state_str)) ? " - " : "", state_str);
270 
271 	gtk_window_set_title(GTK_WINDOW(dialog), title);
272 	gtk_button_set_label(GTK_BUTTON(priv->button_stop), button_label);
273 	gtk_button_set_image(GTK_BUTTON(priv->button_stop),
274 		gtk_image_new_from_icon_name(button_image, GTK_ICON_SIZE_MENU));
275 	gtk_widget_set_sensitive(priv->button_pause, pausable);
276 
277 	g_free(title);
278 }
279 
280 
sr_set_label_text(XfdSpeedReader * dialog)281 static void sr_set_label_text(XfdSpeedReader *dialog)
282 {
283 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
284 
285 	if (NZV(priv->group->str))
286 		gtk_label_set_text(GTK_LABEL(priv->display_label), priv->group->str);
287 	g_string_erase(priv->group, 0, -1);
288 }
289 
290 
sr_timer(gpointer data)291 static gboolean sr_timer(gpointer data)
292 {
293 	gsize i;
294 	XfdSpeedReader *dialog = XFD_SPEED_READER(data);
295 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
296 
297 	if (priv->paused)
298 		return TRUE;
299 
300 	if (priv->word_idx >= priv->words_len)
301 	{
302 		sr_stop(dialog);
303 		xfd_speed_reader_set_window_title(dialog, XSR_STATE_FINISHED);
304 		return FALSE;
305 	}
306 
307 	for (i = 0; (i < priv->group_size) && (priv->word_idx < priv->words_len); i++)
308 	{
309 		/* skip empty elements */
310 		while (priv->word_idx < priv->words_len && ! NZV(priv->words[priv->word_idx]))
311 			priv->word_idx++;
312 
313 		if (priv->word_idx < priv->words_len)
314 		{
315 			if (g_utf8_get_char(priv->words[priv->word_idx]) == 182)
316 			{	/* paragraph sign inside the group */
317 				g_string_append_unichar(priv->group, 182);
318 				sr_set_label_text(data);
319 				priv->word_idx++;
320 				return TRUE;
321 			}
322 			if ((priv->word_idx + 1) < priv->words_len &&
323 				g_utf8_get_char(priv->words[priv->word_idx + 1]) == 182)
324 			{	/* paragraph sign in the next group, so move it to this group */
325 				g_string_append(priv->group, priv->words[priv->word_idx]);
326 				g_string_append_unichar(priv->group, 182);
327 				sr_set_label_text(data);
328 				priv->word_idx += 2;
329 				return TRUE;
330 			}
331 			else
332 			{
333 				g_string_append(priv->group, priv->words[priv->word_idx]);
334 				if (i < (priv->group_size - 1))
335 					g_string_append_c(priv->group, ' ');
336 			}
337 		}
338 		priv->word_idx++;
339 	}
340 	sr_set_label_text(data);
341 
342 	return TRUE;
343 }
344 
345 
sr_start(XfdSpeedReader * dialog)346 static void sr_start(XfdSpeedReader *dialog)
347 {
348 	gint wpm, grouping;
349 	gint interval;
350 	gchar *fontname;
351 	gchar *text, *cleaned_text;
352 	GtkTextIter start, end;
353 	gchar *css;
354 	GtkCssProvider *provider;
355 	PangoFontDescription *font;
356 
357 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
358 
359 	/* clear the label text */
360 	gtk_label_set_text(GTK_LABEL(priv->display_label), NULL);
361 
362 	/* get the text */
363 	gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(priv->buffer), &start);
364 	gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(priv->buffer), &end);
365 	text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(priv->buffer), &start, &end, FALSE);
366 	if (! NZV(text))
367 	{
368 		gtk_dialog_response(GTK_DIALOG(dialog), RESPONSE_STOP);
369 		dict_show_msgbox(priv->dd, GTK_MESSAGE_ERROR, _("You must enter a text."));
370 		return;
371 	}
372 
373 	xfd_speed_reader_set_window_title(dialog, XSR_STATE_RUNNING);
374 
375 	/* mark paragraphs? */
376 	priv->dd->speedreader_mark_paragraphs = gtk_toggle_button_get_active(
377 		GTK_TOGGLE_BUTTON(priv->check_mark_paragraphs));
378 
379 	/* set the font */
380 	fontname = gtk_font_chooser_get_font(GTK_FONT_CHOOSER(priv->button_font));
381 	font = pango_font_description_from_string(fontname);
382 
383 	if (G_LIKELY (font))
384 	{
385 		css = g_strdup_printf("label { font-family: %s; font-size: %dpt; font-style: %s; font-weight: %s }",
386 													pango_font_description_get_family (font),
387 													pango_font_description_get_size (font) / PANGO_SCALE,
388 													(pango_font_description_get_style(font) == PANGO_STYLE_ITALIC ||
389 													pango_font_description_get_style(font) == PANGO_STYLE_OBLIQUE) ? "italic" : "normal",
390 													(pango_font_description_get_weight(font) >= PANGO_WEIGHT_BOLD) ? "bold" : "normal");
391 		pango_font_description_free (font);
392 	}
393 	else
394 		css = g_strdup_printf("* { font: %s; }", fontname);
395 
396 	provider = gtk_css_provider_new ();
397 	gtk_css_provider_load_from_data (provider, css, -1, NULL);
398 	gtk_style_context_add_provider (
399 			GTK_STYLE_CONTEXT (gtk_widget_get_style_context (GTK_WIDGET (priv->display_label))),
400 			GTK_STYLE_PROVIDER(provider),
401 			GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
402 	g_free(css);
403 
404 	/* word grouping */
405 	grouping = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(priv->spin_grouping));
406 	if (grouping >= 1 && grouping < 100) /* paranoia */
407 		priv->group_size = grouping;
408 
409 	/* calculate the rate */
410 	wpm = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(priv->spin_wpm));
411 	if (wpm < 1)
412 		wpm = 400;
413 	interval = 60000 / wpm;
414 
415 	/* save the settings */
416 	priv->dd->speedreader_wpm = wpm;
417 	priv->dd->speedreader_grouping = grouping;
418 	g_free(priv->dd->speedreader_font);
419 	priv->dd->speedreader_font = g_strdup(fontname);
420 
421 	/* prepare word list and start the timer */
422 	priv->word_idx = 0;
423 	priv->group = g_string_new(NULL);
424 	/* replace Unicode dashes and spaces and mark paragraphs */
425 	cleaned_text = sr_replace_unicode_characters(text, priv->dd->speedreader_mark_paragraphs);
426 	priv->words = sr_strsplit_set(cleaned_text, " -_=\t\n\r");
427 	priv->words_len = g_strv_length(priv->words);
428 
429 	priv->timer_id = g_timeout_add(interval, sr_timer, dialog);
430 	sr_pause(dialog, FALSE);
431 
432 	g_free(text);
433 	g_free(cleaned_text);
434 	g_free(fontname);
435 }
436 
437 
sr_stop_timer(XfdSpeedReader * dialog)438 static void sr_stop_timer(XfdSpeedReader *dialog)
439 {
440 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
441 
442 	if (priv->timer_id > 0)
443 	{
444 		g_source_remove(priv->timer_id);
445 		priv->timer_id = 0;
446 
447 		g_string_free(priv->group, TRUE);
448 		priv->group = NULL;
449 		g_strfreev(priv->words);
450 		priv->words = NULL;
451 	}
452 }
453 
454 
sr_stop(XfdSpeedReader * dialog)455 static void sr_stop(XfdSpeedReader *dialog)
456 {
457 	sr_stop_timer(dialog);
458 	sr_pause(dialog, FALSE);
459 	xfd_speed_reader_set_window_title(dialog, XSR_STATE_INITIAL);
460 }
461 
462 
sr_pause(XfdSpeedReader * dialog,gboolean paused)463 static void sr_pause(XfdSpeedReader *dialog, gboolean paused)
464 {
465 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
466 
467 	if (paused)
468 	{
469 		gtk_button_set_image(GTK_BUTTON(priv->button_pause),
470 			gtk_image_new_from_icon_name("media-playback-start", GTK_ICON_SIZE_MENU));
471 		gtk_button_set_label(GTK_BUTTON(priv->button_pause), XFD_TITLE_RESUME);
472 	}
473 	else
474 	{
475 		gtk_button_set_image(GTK_BUTTON(priv->button_pause),
476 			gtk_image_new_from_icon_name("media-playback-pause", GTK_ICON_SIZE_MENU));
477 		gtk_button_set_label(GTK_BUTTON(priv->button_pause), XFD_TITLE_PAUSE);
478 	}
479 	/* set the new value */
480 	priv->paused = paused;
481 }
482 
483 
xfd_speed_reader_response_cb(XfdSpeedReader * dialog,gint response,gpointer data)484 static void xfd_speed_reader_response_cb(XfdSpeedReader *dialog, gint response, gpointer data)
485 {
486 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
487 
488 	if (response == GTK_RESPONSE_CLOSE || response == GTK_RESPONSE_DELETE_EVENT)
489 	{
490 		gtk_widget_destroy(GTK_WIDGET(dialog));
491 	}
492 	else if (response == RESPONSE_START)
493 	{
494 		gtk_widget_hide(priv->button_start);
495 		gtk_widget_show(priv->button_stop);
496 		gtk_widget_show(priv->button_pause);
497 
498 		gtk_widget_hide(priv->first_page);
499 		gtk_widget_show(priv->second_page);
500 
501 		sr_start(dialog);
502 	}
503 	else if (response == RESPONSE_STOP)
504 	{
505 		gtk_widget_hide(priv->button_stop);
506 		gtk_widget_hide(priv->button_pause);
507 		gtk_widget_show(priv->button_start);
508 
509 		gtk_widget_hide(priv->second_page);
510 		gtk_widget_show(priv->first_page);
511 
512 		sr_stop(dialog);
513 	}
514 	else if (response == RESPONSE_PAUSE)
515 	{
516 		/* update the GUI */
517 		sr_pause(dialog, ! priv->paused);
518 	}
519 }
520 
521 
sr_open_clicked_cb(GtkButton * button,XfdSpeedReader * window)522 static void sr_open_clicked_cb(GtkButton *button, XfdSpeedReader *window)
523 {
524 	GtkWidget *dialog;
525 
526 	dialog = gtk_file_chooser_dialog_new(_("Choose a file to load"),
527 		GTK_WINDOW(window),
528 		GTK_FILE_CHOOSER_ACTION_OPEN,
529 		_("_Cancel"), GTK_RESPONSE_CANCEL,
530 		_("_Open"), GTK_RESPONSE_ACCEPT,
531 		NULL);
532 
533 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
534 	gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
535 	gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), TRUE);
536 	gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
537 	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
538 	gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
539 
540 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
541 	{
542 		gchar *filename;
543 		gchar *text;
544 		gsize len;
545 		XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(window);
546 
547 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
548 		if (g_file_get_contents(filename, &text, &len, NULL))
549 		{
550 			gtk_text_buffer_set_text(GTK_TEXT_BUFFER(priv->buffer), text, len);
551 			g_free(text);
552 		}
553 		else
554 			dict_show_msgbox(priv->dd, GTK_MESSAGE_ERROR,
555 				_("The file '%s' could not be loaded."), filename);
556 
557 		g_free(filename);
558 	}
559 	gtk_widget_destroy(dialog);
560 }
561 
562 
sr_clear_clicked_cb(GtkButton * button,GtkTextBuffer * buffer)563 static void sr_clear_clicked_cb(GtkButton *button, GtkTextBuffer *buffer)
564 {
565 	gtk_text_buffer_set_text(buffer, "", 0);
566 }
567 
568 
sr_paste_clicked_cb(GtkButton * button,GtkTextBuffer * buffer)569 static void sr_paste_clicked_cb(GtkButton *button, GtkTextBuffer *buffer)
570 {
571 	GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
572  	gtk_text_buffer_set_text(buffer, "", 0);
573 	gtk_text_buffer_paste_clipboard(buffer, clipboard, NULL, TRUE);
574 }
575 
576 
sr_spin_grouping_changed_cb(GtkSpinButton * button,GtkLabel * label)577 static void sr_spin_grouping_changed_cb(GtkSpinButton *button, GtkLabel *label)
578 {
579 	gint count = gtk_spin_button_get_value_as_int(button);
580 	gchar *text = g_strdup_printf(ngettext(
581 		"(display %d word at a time)",
582 		"(display %d words at a time)", count),
583 		count);
584 
585 	gtk_label_set_text(label, text);
586 	g_free(text);
587 }
588 
589 
xfd_speed_reader_init(XfdSpeedReader * dialog)590 static void xfd_speed_reader_init(XfdSpeedReader *dialog)
591 {
592 	GtkWidget *label_intro, *label_words, *label_font, *label_grouping, *label_grouping_desc;
593 	GtkWidget *vbox, *hbox_words, *hbox_font, *hbox_grouping, *swin, *textview;
594 	GtkWidget *vbox_text_buttons, *hbox_text, *button_clear, *button_paste, *button_open, *button_close;
595 	GtkSizeGroup *sizegroup;
596 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(dialog);
597 
598 	gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
599 	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 330);
600 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
601 	gtk_widget_set_name(GTK_WIDGET(dialog), "Xfce4Dict");
602 
603 	/* First page */
604 	label_intro = gtk_label_new(
605 		_("This is an easy speed reading utility to help train you to read faster. "
606 		  "It does this by flashing words at a rapid rate on the screen."));
607 	gtk_label_set_line_wrap (GTK_LABEL (label_intro), TRUE);
608 	gtk_label_set_line_wrap ( GTK_LABEL (label_intro), TRUE);
609 
610 	label_words = gtk_label_new_with_mnemonic(_("_Words per Minute:"));
611 	gtk_widget_set_halign(label_words, GTK_ALIGN_END);
612 	gtk_widget_set_valign(label_words, GTK_ALIGN_CENTER);
613 
614 	priv->spin_wpm = gtk_spin_button_new_with_range(5.0, 10000.0, 5);
615 	gtk_label_set_mnemonic_widget(GTK_LABEL(label_words), priv->spin_wpm);
616 
617 	priv->check_mark_paragraphs = gtk_check_button_new_with_mnemonic(_("_Mark Paragraphs"));
618 
619 	hbox_words = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
620 	gtk_box_pack_start(GTK_BOX(hbox_words), label_words, FALSE, FALSE, 6);
621 	gtk_box_pack_start(GTK_BOX(hbox_words), priv->spin_wpm, FALSE, FALSE, 6);
622 	gtk_box_pack_start(GTK_BOX(hbox_words), priv->check_mark_paragraphs, FALSE, FALSE, 12);
623 
624 	label_grouping = gtk_label_new_with_mnemonic(_("Word _Grouping:"));
625 	gtk_widget_set_halign(label_grouping, GTK_ALIGN_END);
626 	gtk_widget_set_valign(label_grouping, GTK_ALIGN_CENTER);
627 
628 	label_grouping_desc = gtk_label_new(NULL);
629 
630 	priv->spin_grouping = gtk_spin_button_new_with_range(1.0, 100.0, 1);
631 	gtk_label_set_mnemonic_widget(GTK_LABEL(label_grouping), priv->spin_grouping);
632 	g_signal_connect(priv->spin_grouping, "value-changed",
633 		G_CALLBACK(sr_spin_grouping_changed_cb), label_grouping_desc);
634 	sr_spin_grouping_changed_cb(GTK_SPIN_BUTTON(priv->spin_grouping), GTK_LABEL(label_grouping_desc));
635 
636 	hbox_grouping = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
637 	gtk_box_pack_start(GTK_BOX(hbox_grouping), label_grouping, FALSE, FALSE, 6);
638 	gtk_box_pack_start(GTK_BOX(hbox_grouping), priv->spin_grouping, FALSE, FALSE, 6);
639 	gtk_box_pack_start(GTK_BOX(hbox_grouping), label_grouping_desc, FALSE, FALSE, 6);
640 
641 	label_font = gtk_label_new_with_mnemonic(_("_Font Size:"));
642 	gtk_widget_set_halign(label_font, GTK_ALIGN_END);
643 	gtk_widget_set_valign(label_font, GTK_ALIGN_CENTER);
644 
645 	priv->button_font = gtk_font_button_new();
646 	gtk_label_set_mnemonic_widget(GTK_LABEL(label_font), priv->button_font);
647 
648 	hbox_font = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
649 	gtk_box_pack_start(GTK_BOX(hbox_font), label_font, FALSE, FALSE, 6);
650 	gtk_box_pack_start(GTK_BOX(hbox_font), priv->button_font, FALSE, FALSE, 6);
651 
652 	sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
653 	gtk_size_group_add_widget(sizegroup, label_words);
654 	gtk_size_group_add_widget(sizegroup, label_grouping);
655 	gtk_size_group_add_widget(sizegroup, label_font);
656 	g_object_unref(G_OBJECT(sizegroup));
657 
658 	textview = gtk_text_view_new();
659 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD);
660 	priv->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
661 	gtk_text_buffer_set_text(priv->buffer,
662 		_("Enter some text here you would like to read.\n\n"
663 		  "Be relaxed and make yourself comfortable, then "
664 		  "press Start to begin speed reading."),
665 		-1);
666 
667 	swin = gtk_scrolled_window_new(NULL, NULL);
668 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin), GTK_SHADOW_IN);
669 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
670 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
671 	gtk_container_add(GTK_CONTAINER(swin), textview);
672 
673 	button_open = gtk_button_new_from_icon_name("document-open", GTK_ICON_SIZE_MENU);
674 	g_signal_connect(button_open, "clicked", G_CALLBACK(sr_open_clicked_cb), dialog);
675 	gtk_widget_set_tooltip_text(button_open, _("Load the contents of a file"));
676 
677 	button_paste = gtk_button_new_from_icon_name("edit-paste", GTK_ICON_SIZE_MENU);
678 	g_signal_connect(button_paste, "clicked", G_CALLBACK(sr_paste_clicked_cb), priv->buffer);
679 	gtk_widget_set_tooltip_text(button_paste,
680 		_("Clear the contents of the text field and paste the contents of the clipboard"));
681 
682 	button_clear = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU);
683 	g_signal_connect(button_clear, "clicked", G_CALLBACK(sr_clear_clicked_cb), priv->buffer);
684 	gtk_widget_set_tooltip_text(button_clear, _("Clear the contents of the text field"));
685 
686 	vbox_text_buttons = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
687 	gtk_box_pack_start(GTK_BOX(vbox_text_buttons), button_open, FALSE, FALSE, 0);
688 	gtk_box_pack_start(GTK_BOX(vbox_text_buttons), button_paste, FALSE, FALSE, 0);
689 	gtk_box_pack_start(GTK_BOX(vbox_text_buttons), button_clear, FALSE, FALSE, 0);
690 
691 	hbox_text = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
692 	gtk_box_pack_start(GTK_BOX(hbox_text), swin, TRUE, TRUE, 0);
693 	gtk_box_pack_start(GTK_BOX(hbox_text), vbox_text_buttons, FALSE, FALSE, 3);
694 
695 	priv->button_pause = gtk_dialog_add_button(GTK_DIALOG(dialog), _("P_ause"), RESPONSE_PAUSE);
696 	priv->button_start = gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Start"), RESPONSE_START);
697 	priv->button_stop = gtk_dialog_add_button(GTK_DIALOG(dialog), _("S_top"), RESPONSE_STOP);
698 	button_close = gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Close"), GTK_RESPONSE_CLOSE);
699 
700 	gtk_widget_hide(priv->button_pause);
701 	gtk_widget_hide(priv->button_stop);
702 
703 	gtk_button_set_image(GTK_BUTTON(priv->button_pause),
704 		gtk_image_new_from_icon_name("media-playback-pause", GTK_ICON_SIZE_MENU));
705 	gtk_button_set_image(GTK_BUTTON(priv->button_start),
706 		gtk_image_new_from_icon_name("media-playback-start", GTK_ICON_SIZE_MENU));
707 	gtk_button_set_image(GTK_BUTTON(priv->button_stop),
708 		gtk_image_new_from_icon_name("media-playback-stop", GTK_ICON_SIZE_MENU));
709 	gtk_button_set_image(GTK_BUTTON(button_close),
710 		gtk_image_new_from_icon_name("window-close", GTK_ICON_SIZE_MENU));
711 
712 	g_signal_connect(dialog, "response", G_CALLBACK(xfd_speed_reader_response_cb), NULL);
713 
714 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
715 	gtk_box_pack_start(GTK_BOX(vbox), label_intro, FALSE, FALSE, 5);
716 	gtk_box_pack_start(GTK_BOX(vbox), hbox_words, FALSE, FALSE, 3);
717 	gtk_box_pack_start(GTK_BOX(vbox), hbox_grouping, FALSE, FALSE, 3);
718 	gtk_box_pack_start(GTK_BOX(vbox), hbox_font, FALSE, FALSE, 3);
719 	gtk_box_pack_start(GTK_BOX(vbox), hbox_text, TRUE, TRUE, 0);
720 
721 	priv->first_page = vbox;
722 
723 	/* Second page */
724 	priv->display_label = gtk_label_new(NULL);
725 	gtk_widget_show(priv->display_label);
726 
727 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
728 	gtk_box_pack_start(GTK_BOX(vbox), priv->display_label, TRUE, TRUE, 6);
729 
730 	priv->second_page = vbox;
731 
732 	gtk_widget_show_all(priv->first_page);
733 
734 	gtk_widget_grab_focus(textview);
735 
736 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), priv->first_page, TRUE, TRUE, 6);
737 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), priv->second_page, TRUE, TRUE, 6);
738 
739 	xfd_speed_reader_set_window_title(dialog, XSR_STATE_INITIAL);
740 }
741 
742 
xfd_speed_reader_new(GtkWindow * parent,DictData * dd)743 GtkWidget *xfd_speed_reader_new(GtkWindow *parent, DictData *dd)
744 {
745 	GtkWidget *dialog = g_object_new(XFD_SPEED_READER_TYPE, "transient-for", parent, NULL);
746 	XfdSpeedReaderPrivate *priv = xfd_speed_reader_get_instance_private(XFD_SPEED_READER (dialog));
747 
748 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->spin_wpm), dd->speedreader_wpm);
749 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->spin_grouping), dd->speedreader_grouping);
750 	gtk_font_chooser_set_font(GTK_FONT_CHOOSER(priv->button_font), dd->speedreader_font);
751 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->check_mark_paragraphs),
752 		dd->speedreader_mark_paragraphs);
753 
754 	priv->dd = dd;
755 
756 	return dialog;
757 }
758