Added toggle buttons for switching speak engine, update PO files
[mstardict] / src / tts.cpp
1 /*
2  *  MStarDict - International dictionary for Maemo.
3  *  Copyright (C) 2010 Roman Moravcik
4  *
5  *  base on code of stardict:
6  *  Copyright (C) 2003-2007 Hu Zheng <huzheng_001@163.com>
7  *
8  *  based on code of sdcv:
9  *  Copyright (C) 2005-2006 Evgeniy <dushistov@mail.ru>
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include <string>
31 #include <vector>
32 #include <memory>
33 #include <list>
34
35 #include <glib.h>
36 #include <glib/gi18n.h>
37
38 #include <gtk/gtk.h>
39 #include <hildon/hildon.h>
40
41 #include "conf.hpp"
42 #include "tts.hpp"
43 #include "mstardict.hpp"
44
45 #include <espeak/speak_lib.h>
46
47 enum {
48     DISPNAME_COLUMN,
49     NAME_COLUMN,
50     N_COLUMNS
51 };
52
53 enum {
54     ENGINE_NONE = 0,
55     ENGINE_ESPEAK,
56     ENGINE_REALPEOPLE
57 };
58
59 enum {
60     GENDER_NONE = 0,
61     GENDER_MALE,
62     GENDER_FEMALE
63 };
64
65 static TtsVoice voices[] = {
66     {"afrikaans",               N_("Afrikaans")},
67     {"bosnian",                 N_("Bosnian")},
68     {"catalan",                 N_("Catalan")},
69     {"czech",                   N_("Czech")},
70     {"welsh-test",              N_("Welsh")},
71     {"danish",                  N_("Danish")},
72     {"german",                  N_("German")},
73     {"greek",                   N_("Greek")},
74     {"en-scottish",             N_("English (Scottish)")},
75     {"english",                 N_("English")},
76     {"lancashire",              N_("English (Lancashire)")},
77     {"english_rp",              N_("English (Received Pronunciation)")},
78     {"english_wmids",           N_("English (West Midlands)")},
79     {"english-us",              N_("English (American)")},
80     {"en-westindies",           N_("English (Westindies)")},
81     {"esperanto",               N_("Esperanto")},
82     {"spanish",                 N_("Spanish")},
83     {"spanish-latin-american",  N_("Spanish (Latin American)")},
84     {"finnish",                 N_("Finnish")},
85     {"french",                  N_("French")},
86     {"french (Belgium)",        N_("French (Belgium)")},
87     {"greek-ancient",           N_("Greek (Ancient)")},
88     {"hindi",                   N_("Hindi")},
89     {"croatian",                N_("Croatian")},
90     {"hungarian",               N_("Hungarian")},
91     {"armenian",                N_("Armenian")},
92     {"armenian-west",           N_("Armenian (West)")},
93     {"indonesian-test",         N_("Indonesian")},
94     {"icelandic-test",          N_("Icelandic")},
95     {"italian",                 N_("Italian")},
96     {"lojban",                  N_("Lojban")},
97     {"kurdish",                 N_("Kurdish")},
98     {"latin",                   N_("Latin")},
99     {"latvian",                 N_("Latvian")},
100     {"macedonian-test",         N_("Macedonian")},
101     {"dutch-test",              N_("Dutch")},
102     {"norwegian",               N_("Norwegian")},
103     {"papiamento-test",         N_("Papiamento")},
104     {"polish",                  N_("Polish")},
105     {"brazil",                  N_("Brazil")},
106     {"portugal",                N_("Portugal")},
107     {"romanian",                N_("Romanian")},
108     {"russian_test",            N_("Russian")},
109     {"slovak",                  N_("Slovak")},
110     {"albanian",                N_("Albanian")},
111     {"serbian",                 N_("Serbian")},
112     {"swedish",                 N_("Swedish")},
113     {"swahihi-test",            N_("Swahihi")},
114     {"tamil",                   N_("Tamil")},
115     {"turkish",                 N_("Turkish")},
116     {"vietnam",                 N_("Vietnamese")},
117     {"Mandarin",                N_("Chinese (Mandarin)")},
118     {"cantonese",               N_("Chinese (Cantonese)")},
119     {"akan-test",               N_("Akam")},
120     {"amharic-test",            N_("Amharic")},
121     {"azerbaijani-test",        N_("Azerbaijani")},
122     {"bulgarian-test",          N_("Bulgarian")},
123     {"dari-test",               N_("Dari")},
124     {"divehi-test",             N_("Divehi")},
125     {"georgian-test",           N_("Georgian")},
126     {"haitian",                 N_("Haitian")},
127     {"kannada",                 N_("Kannada")},
128     {"kinyarwanda-test",        N_("Kinyarwanda")},
129     {"malayalam",               N_("Mlayalam")},
130     {"nahuatl - clasical",      N_("Nahuatl (Clasical)")},
131     {"nepali-test",             N_("Nepali")},
132     {"northern-sotho",          N_("Northern Sotho")},
133     {"punjabi-test",            N_("Punjabi")},
134     {"setswana-test",           N_("Setswana")},
135     {"sinhala",                 N_("Sinhala")},
136     {"slovenian-test",          N_("Slovenian")},
137     {"swahili-test",            N_("Swahili")},
138     {"telugu",                  N_("Telugu")},
139     {"urdu-test",               N_("Urdu")},
140     {"wolof-test",              N_("Wolof")},
141     {"default",                 N_("Default")},
142     {NULL,                      NULL}
143 };
144
145
146 Tts::Tts(MStarDict *mStarDict)
147 {
148     int rate = 0;
149     bool tts_enabled = false;
150     int engine = ENGINE_NONE;
151     gchar *language = NULL;
152     int gender = GENDER_NONE;
153
154     oStarDict = mStarDict;
155     Enabled = false;
156
157     /* initialize espeak */
158     rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 512, NULL, 0);
159
160     /* check if tts is enabled */
161     if (!oStarDict->oConf->GetBool("/apps/maemo/mstardict/tts_enabled", &tts_enabled)) {
162         tts_enabled = true;
163     }
164     Enable(tts_enabled);
165
166     /* read configured engine */
167     if (!oStarDict->oConf->GetInt("/apps/maemo/mstardict/tts_engine", &engine)) {
168         engine = ENGINE_ESPEAK;
169     }
170
171     /* read configured language */
172     if (!oStarDict->oConf->GetString("/apps/maemo/mstardict/tts_language", &language)) {
173         language = g_strdup("english");
174     }
175
176     /* read configured gender */
177     if (!oStarDict->oConf->GetInt("/apps/maemo/mstardict/tts_gender", &gender)) {
178         gender = GENDER_MALE;
179     }
180     SetVoice(engine, language, gender);
181
182     if (language) {
183         g_free(language);
184     }
185 }
186
187 Tts::~Tts()
188 {
189     /* deinitialize espeak */
190     espeak_Terminate();
191 }
192
193 void
194 Tts::Enable(bool bEnable)
195 {
196     Enabled = bEnable;
197 }
198
199 bool
200 Tts::IsEnabled()
201 {
202     return Enabled;
203 }
204
205 void
206 Tts::SetVoice(int engine, const gchar *language, int gender)
207 {
208     Engine = engine;
209
210     espeak_VOICE voice;
211
212     memset(&voice, 0, sizeof(espeak_VOICE));
213     voice.name = language;
214     voice.gender = gender;
215
216     espeak_SetVoiceByProperties(&voice);
217 }
218
219 void
220 Tts::SayText(const gchar *sText)
221 {
222     if (Engine == ENGINE_ESPEAK) {
223         espeak_Synth(sText, strlen(sText) + 1, 0, POS_CHARACTER, 0, espeakCHARS_UTF8, NULL, NULL);
224         return;
225     } else if (Engine == ENGINE_REALPEOPLE) {
226         gchar *lower = g_utf8_strdown(sText, -1);
227         if (!lower)
228             return;
229         
230         gchar *cmd = g_strdup_printf("paplay /home/user/MyDocs/mstardict/WyabdcRealPeopleTTS/%c/%s.wav", lower[0], lower);
231         if (!cmd)
232             return;
233         
234         system(cmd);
235         
236         g_free(lower);
237         g_free(cmd);
238     } else {
239         /* unknown engine */
240     }
241 }
242
243 GtkListStore *
244 Tts::GetVoicesList()
245 {
246     const espeak_VOICE **espeak_voices;
247     GtkListStore *list_store;
248     GtkTreeIter iter;
249     size_t i = 0;
250
251     list_store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
252
253     espeak_voices = espeak_ListVoices(NULL);
254     while (espeak_voices[i]) {
255         gchar *disp_name = NULL;
256
257         for (int j = 0; voices[j].name != NULL; j++) {
258             if (!strcmp(espeak_voices[i]->name, voices[j].name)) {
259                 disp_name = g_strdup(_(voices[j].disp_name));
260                 break;
261             }
262         }
263
264         if (disp_name == NULL)
265             disp_name = g_strdup(espeak_voices[i]->name);
266
267         gtk_list_store_append(list_store, &iter);
268         gtk_list_store_set(list_store,
269                            &iter,
270                            DISPNAME_COLUMN, disp_name,
271                            NAME_COLUMN, espeak_voices[i]->name,
272                            -1);
273
274         if (disp_name)
275             g_free(disp_name);
276
277         i++;
278     }
279     return list_store;
280 }
281
282 gboolean
283 Tts::onTtsEnableButtonClicked(GtkButton *button,
284                               Tts *oTts)
285 {
286     if (hildon_check_button_get_active(HILDON_CHECK_BUTTON(button))) {
287         gtk_widget_set_sensitive(GTK_WIDGET(oTts->engine_espeak_button), TRUE);
288         gtk_widget_set_sensitive(GTK_WIDGET(oTts->engine_realpeople_button), TRUE);
289         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_male_button), TRUE);
290         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_female_button), TRUE);
291         gtk_widget_set_sensitive(GTK_WIDGET(oTts->language_button), TRUE);
292     } else {
293         gtk_widget_set_sensitive(GTK_WIDGET(oTts->engine_espeak_button), FALSE);
294         gtk_widget_set_sensitive(GTK_WIDGET(oTts->engine_realpeople_button), FALSE);
295         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_male_button), FALSE);
296         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_female_button), FALSE);
297         gtk_widget_set_sensitive(GTK_WIDGET(oTts->language_button), FALSE);
298     }
299     return true;
300 }
301
302 gboolean
303 Tts::onTtsEngineButtonClicked(GtkWidget *button,
304                               Tts *oTts)
305 {
306     GtkWidget *button2;
307
308     if (button != oTts->engine_realpeople_button)
309         button2 = oTts->engine_realpeople_button;
310     else
311         button2 = oTts->engine_espeak_button;
312
313     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
314         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button2), FALSE);
315     else
316         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button2), TRUE);
317     
318     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oTts->engine_espeak_button))) {
319         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oTts->engine_realpeople_button), FALSE);
320         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_male_button), TRUE);
321         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_female_button), TRUE);
322         gtk_widget_set_sensitive(GTK_WIDGET(oTts->language_button), TRUE);
323     } else {
324         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oTts->engine_realpeople_button), TRUE);
325         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_male_button), FALSE);
326         gtk_widget_set_sensitive(GTK_WIDGET(oTts->gender_female_button), FALSE);
327         gtk_widget_set_sensitive(GTK_WIDGET(oTts->language_button), FALSE);
328     }
329
330     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oTts->engine_realpeople_button))) {
331         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oTts->engine_espeak_button), FALSE);
332     } else {
333         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oTts->engine_espeak_button), TRUE);
334     }
335     
336     return true;
337 }
338
339 gboolean
340 Tts::onTtsGenderButtonClicked(GtkToggleButton *button1,
341                               GtkToggleButton *button2)
342 {
343     if (gtk_toggle_button_get_active(button1))
344         gtk_toggle_button_set_active(button2, FALSE);
345     else
346         gtk_toggle_button_set_active(button2, TRUE);
347     return true;
348 }
349
350 GtkWidget *
351 Tts::CreateTtsConfWidget()
352 {
353     GtkWidget *frame, *vbox, *engine_hbox, *gender_hbox;
354     GtkWidget *selector;
355     HildonTouchSelectorColumn *column;
356     GtkListStore *voices_list;
357
358     frame = gtk_frame_new(_("Text-To-Speech"));
359
360     vbox = gtk_vbox_new(FALSE, 0);
361     gtk_container_add(GTK_CONTAINER(frame), vbox);
362
363     /* enable tts button */
364     enable_button = hildon_check_button_new(HILDON_SIZE_FINGER_HEIGHT);
365     gtk_button_set_label(GTK_BUTTON(enable_button), _("Enable"));
366     gtk_box_pack_start(GTK_BOX(vbox), enable_button, TRUE, TRUE, 0);
367
368     /* engine selection */
369     engine_hbox = gtk_hbox_new(TRUE, 0);
370     gtk_box_pack_start(GTK_BOX(vbox), engine_hbox, TRUE, TRUE, 0);
371
372     engine_espeak_button = hildon_gtk_toggle_button_new(HILDON_SIZE_FINGER_HEIGHT);
373     gtk_button_set_label(GTK_BUTTON(engine_espeak_button), _("eSpeak"));
374     gtk_box_pack_start(GTK_BOX(engine_hbox), engine_espeak_button, TRUE, TRUE, 0);
375
376     engine_realpeople_button = hildon_gtk_toggle_button_new(HILDON_SIZE_FINGER_HEIGHT);
377     gtk_button_set_label(GTK_BUTTON(engine_realpeople_button), _("Real People"));
378     gtk_box_pack_start(GTK_BOX(engine_hbox), engine_realpeople_button, TRUE, TRUE, 0);
379
380     /* gender selection */
381     gender_hbox = gtk_hbox_new(TRUE, 0);
382     gtk_box_pack_start(GTK_BOX(vbox), gender_hbox, TRUE, TRUE, 0);
383
384     gender_male_button = hildon_gtk_toggle_button_new(HILDON_SIZE_FINGER_HEIGHT);
385     gtk_button_set_label(GTK_BUTTON(gender_male_button), _("Male"));
386     gtk_box_pack_start(GTK_BOX(gender_hbox), gender_male_button, TRUE, TRUE, 0);
387
388     gender_female_button = hildon_gtk_toggle_button_new(HILDON_SIZE_FINGER_HEIGHT);
389     gtk_button_set_label(GTK_BUTTON(gender_female_button), _("Female"));
390     gtk_box_pack_start(GTK_BOX(gender_hbox), gender_female_button, TRUE, TRUE, 0);
391
392     /* language picker button */
393     language_button = hildon_picker_button_new(HILDON_SIZE_FINGER_HEIGHT,
394                                                HILDON_BUTTON_ARRANGEMENT_VERTICAL);
395     hildon_button_set_title(HILDON_BUTTON(language_button), _("Language"));
396     hildon_button_set_alignment(HILDON_BUTTON(language_button), 0.0, 0.5, 1.0, 0.0);
397     hildon_button_set_title_alignment(HILDON_BUTTON(language_button), 0.0, 0.5);
398     gtk_box_pack_start(GTK_BOX(vbox), language_button, TRUE, TRUE, 0);
399
400     /* get list of voices */
401     voices_list = GetVoicesList();
402
403     /* sort list of voices */
404     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(voices_list),
405                                          DISPNAME_COLUMN, GTK_SORT_ASCENDING);
406
407     /* voices selector */
408     selector = hildon_touch_selector_new();
409     column = hildon_touch_selector_append_text_column(HILDON_TOUCH_SELECTOR(selector),
410                                                       GTK_TREE_MODEL(voices_list), TRUE);
411     g_object_set(G_OBJECT(column), "text-column", DISPNAME_COLUMN, NULL);
412
413     hildon_picker_button_set_selector(HILDON_PICKER_BUTTON(language_button),
414                                       HILDON_TOUCH_SELECTOR(selector));
415
416     g_signal_connect(enable_button, "clicked", G_CALLBACK(onTtsEnableButtonClicked), this);
417     g_signal_connect(engine_espeak_button, "clicked", G_CALLBACK(onTtsEngineButtonClicked), this);
418     g_signal_connect(engine_realpeople_button, "clicked", G_CALLBACK(onTtsEngineButtonClicked), this);
419     g_signal_connect(gender_male_button, "clicked", G_CALLBACK(onTtsGenderButtonClicked), gender_female_button);
420     g_signal_connect(gender_female_button, "clicked", G_CALLBACK(onTtsGenderButtonClicked), gender_male_button);
421
422     return frame;
423 }
424
425 void
426 Tts::TtsConfWidgetLoadConf()
427 {
428     HildonTouchSelector *selector;
429     GtkTreeIter iter;
430     int engine = ENGINE_NONE;
431     int gender = GENDER_NONE;
432     gchar *language = NULL;
433     GtkTreeModel *model;
434     gboolean iter_valid = TRUE;
435
436     if (IsEnabled()) {
437         hildon_check_button_set_active(HILDON_CHECK_BUTTON(enable_button), TRUE);
438         gtk_widget_set_sensitive(language_button, TRUE);
439     } else {
440         hildon_check_button_set_active(HILDON_CHECK_BUTTON(enable_button), FALSE);
441         gtk_widget_set_sensitive(language_button, FALSE);
442     }
443
444     if (oStarDict->oConf->GetInt("/apps/maemo/mstardict/tts_engine", &engine)) {
445         if (engine == ENGINE_ESPEAK)
446             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(engine_espeak_button), TRUE);
447         else
448             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(engine_realpeople_button), TRUE);
449     } else {
450             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(engine_espeak_button), TRUE);
451     }
452
453     if (oStarDict->oConf->GetInt("/apps/maemo/mstardict/tts_gender", &gender)) {
454         if (gender == GENDER_MALE)
455             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gender_male_button), TRUE);
456         else
457             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gender_female_button), TRUE);
458     } else {
459             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gender_male_button), TRUE);
460     }
461
462     if (oStarDict->oConf->GetString("/apps/maemo/mstardict/tts_language", &language)) {
463         if (language) {
464             selector = hildon_picker_button_get_selector(HILDON_PICKER_BUTTON(language_button));
465             model = hildon_touch_selector_get_model(HILDON_TOUCH_SELECTOR(selector), DISPNAME_COLUMN);
466             for (iter_valid = gtk_tree_model_get_iter_first(model, &iter); iter_valid; iter_valid = gtk_tree_model_iter_next(model, &iter)) {
467                 const gchar *tmp;
468                 gtk_tree_model_get(model, &iter, NAME_COLUMN, &tmp, -1);
469                 if (strcmp(tmp, language) == 0) {
470                     hildon_touch_selector_select_iter(HILDON_TOUCH_SELECTOR(selector), DISPNAME_COLUMN, &iter, FALSE);
471                     break;
472                 }
473             }
474             g_free(language);
475         }
476     } else {
477         hildon_picker_button_set_active (HILDON_PICKER_BUTTON (language_button), -1);
478     }
479 }
480
481 void
482 Tts::TtsConfWidgetSaveConf()
483 {
484     bool enabled = false;
485     HildonTouchSelector *selector;
486     GtkTreeIter iter;
487     int engine = ENGINE_NONE;
488     int gender = GENDER_NONE;
489     const gchar *language = NULL;
490
491     enabled = hildon_check_button_get_active(HILDON_CHECK_BUTTON(enable_button));
492     if (oStarDict->oConf->SetBool("/apps/maemo/mstardict/tts_enabled", enabled))
493         Enable(enabled);
494
495     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(engine_espeak_button)))
496         engine = ENGINE_ESPEAK;
497     else
498         engine = ENGINE_REALPEOPLE;
499     oStarDict->oConf->SetInt("/apps/maemo/mstardict/tts_engine", engine);
500
501     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gender_male_button)))
502         gender = GENDER_MALE;
503     else
504         gender = GENDER_FEMALE;
505     oStarDict->oConf->SetInt("/apps/maemo/mstardict/tts_gender", gender);
506
507     if (engine)
508         Engine = engine;
509
510     if (hildon_picker_button_get_active(HILDON_PICKER_BUTTON(language_button)) > -1) {
511         selector = hildon_picker_button_get_selector(HILDON_PICKER_BUTTON(language_button));
512         if (hildon_touch_selector_get_selected(selector, DISPNAME_COLUMN, &iter)) {
513             gtk_tree_model_get(hildon_touch_selector_get_model(selector, DISPNAME_COLUMN), &iter, NAME_COLUMN, &language, -1);
514
515             /* fixme convert back disp_name */
516             if (oStarDict->oConf->SetString("/apps/maemo/mstardict/tts_language", language))
517                 SetVoice(engine, language, gender);
518         }
519     }
520 }