Port to qt4
[kdeaccessibility.git] / kttsd / libkttsd / talkercode.cpp
blob42eafa87a455a1b944ffd4a9f62782e82f6354a7
1 /***************************************************** vim:set ts=4 sw=4 sts=4:
2 Convenience object for manipulating Talker Codes.
3 For an explanation of what a Talker Code is, see kspeech.h.
4 -------------------
5 Copyright:
6 (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net>
7 -------------------
8 Original author: Gary Cramblitt <garycramblitt@comcast.net>
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
23 ******************************************************************************/
25 // KDE includes.
26 #include <kglobal.h>
27 #include <klocale.h>
28 #include <ktrader.h>
29 #include <kdebug.h>
31 // TalkerCode includes.
32 #include "talkercode.h"
33 //Added by qt3to4:
34 #include <Q3MemArray>
36 /**
37 * Constructor.
39 TalkerCode::TalkerCode(const QString &code/*=QString::null*/, bool normal /*=false*/)
41 if (!code.isEmpty())
42 parseTalkerCode(code);
43 if (normal) normalize();
46 /**
47 * Copy Constructor.
49 TalkerCode::TalkerCode(TalkerCode* talker, bool normal /*=false*/)
51 m_languageCode = talker->languageCode();
52 m_countryCode = talker->countryCode();
53 m_voice = talker->voice();
54 m_gender = talker->gender();
55 m_volume = talker->volume();
56 m_rate = talker->rate();
57 m_plugInName = talker->plugInName();
58 if (normal) normalize();
61 /**
62 * Destructor.
64 TalkerCode::~TalkerCode() { }
66 /**
67 * Properties.
69 QString TalkerCode::languageCode() const { return m_languageCode; }
70 QString TalkerCode::countryCode() const { return m_countryCode; }
71 QString TalkerCode::voice() const { return m_voice; }
72 QString TalkerCode::gender() const { return m_gender; }
73 QString TalkerCode::volume() const { return m_volume; }
74 QString TalkerCode::rate() const { return m_rate; }
75 QString TalkerCode::plugInName() const { return m_plugInName; }
77 void TalkerCode::setLanguageCode(const QString &languageCode) { m_languageCode = languageCode; }
78 void TalkerCode::setCountryCode(const QString &countryCode) { m_countryCode = countryCode; }
79 void TalkerCode::setVoice(const QString &voice) { m_voice = voice; }
80 void TalkerCode::setGender(const QString &gender) { m_gender = gender; }
81 void TalkerCode::setVolume(const QString &volume) { m_volume = volume; }
82 void TalkerCode::setRate(const QString &rate) { m_rate = rate; }
83 void TalkerCode::setPlugInName(const QString plugInName) { m_plugInName = plugInName; }
85 /**
86 * Sets the language code and country code (if given).
88 void TalkerCode::setFullLanguageCode(const QString &fullLanguageCode)
90 splitFullLanguageCode(fullLanguageCode, m_languageCode, m_countryCode);
93 /**
94 * Returns the language code plus country code (if any).
96 QString TalkerCode::fullLanguageCode() const
98 if (!m_countryCode.isEmpty())
99 return m_languageCode + "_" + m_countryCode;
100 else
101 return m_languageCode;
105 * The Talker Code returned in XML format.
107 QString TalkerCode::getTalkerCode() const
109 QString code;
110 QString languageCode = m_languageCode;
111 if (!m_countryCode.isEmpty()) languageCode += "_" + m_countryCode;
112 if (!languageCode.isEmpty()) code = "lang=\"" + languageCode + "\" ";
113 if (!m_voice.isEmpty()) code += "name=\"" + m_voice + "\" ";
114 if (!m_gender.isEmpty()) code += "gender=\"" + m_gender + "\" ";
115 if (!code.isEmpty()) code = "<voice " + code + "/>";
116 QString prosody;
117 if (!m_volume.isEmpty()) prosody = "volume=\"" + m_volume + "\" ";
118 if (!m_rate.isEmpty()) prosody += "rate=\"" + m_rate + "\" ";
119 if (!prosody.isEmpty()) code += "<prosody " + prosody + "/>";
120 if (!m_plugInName.isEmpty()) code += "<kttsd synthesizer=\"" + m_plugInName + "\" />";
121 return code;
125 * The Talker Code translated for display.
127 QString TalkerCode::getTranslatedDescription() const
129 QString code;
130 bool prefer;
131 QString fullLangCode = fullLanguageCode();
132 if (!fullLangCode.isEmpty()) code = languageCodeToLanguage( fullLangCode );
133 // TODO: The PlugInName is always English. Need a way to convert this to a translated
134 // name (possibly via DesktopEntryNameToName, but to do that, we need the desktopEntryName
135 // from the config file).
136 if (!m_plugInName.isEmpty()) code += " " + stripPrefer(m_plugInName, prefer);
137 if (!m_voice.isEmpty()) code += " " + stripPrefer(m_voice, prefer);
138 if (!m_gender.isEmpty()) code += " " + translatedGender(stripPrefer(m_gender, prefer));
139 if (!m_volume.isEmpty()) code += " " + translatedVolume(stripPrefer(m_volume, prefer));
140 if (!m_rate.isEmpty()) code += " " + translatedRate(stripPrefer(m_rate, prefer));
141 code = code.stripWhiteSpace();
142 if (code.isEmpty()) code = i18n("default");
143 return code;
147 * Normalizes the Talker Code by filling in defaults.
149 void TalkerCode::normalize()
151 if (m_voice.isEmpty()) m_voice = "fixed";
152 if (m_gender.isEmpty()) m_gender = "neutral";
153 if (m_volume.isEmpty()) m_volume = "medium";
154 if (m_rate.isEmpty()) m_rate = "medium";
158 * Given a talker code, normalizes it into a standard form and also returns
159 * the language code.
160 * @param talkerCode Unnormalized talker code.
161 * @return fullLanguageCode Language code from the talker code (including country code if any).
162 * @return Normalized talker code.
164 /*static*/ QString TalkerCode::normalizeTalkerCode(const QString &talkerCode, QString &fullLanguageCode)
166 TalkerCode tmpTalkerCode(talkerCode);
167 tmpTalkerCode.normalize();
168 fullLanguageCode = tmpTalkerCode.fullLanguageCode();
169 return tmpTalkerCode.getTalkerCode();
173 * Given a language code that might contain a country code, splits the code into
174 * the two letter language code and country code.
175 * @param fullLanguageCode Language code to be split.
176 * @return languageCode Just the language part of the code.
177 * @return countryCode The country code part (if any).
179 * If the input code begins with an asterisk, it is ignored and removed from the returned
180 * languageCode.
182 /*static*/ void TalkerCode::splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode)
184 QString language = lang;
185 if (language.left(1) == "*") language = language.mid(1);
186 QString charSet;
187 KGlobal::locale()->splitLocale(language, languageCode, countryCode, charSet);
191 * Given a full language code and plugin name, returns a normalized default talker code.
192 * @param fullLanguageCode Language code.
193 * @param plugInName Name of the Synthesizer plugin.
194 * @return Full normalized talker code.
196 * Example returned from defaultTalkerCode("en", "Festival")
197 * <voice lang="en" name="fixed" gender="neutral"/>
198 * <prosody volume="medium" rate="medium"/>
199 * <kttsd synthesizer="Festival" />
201 /*static*/ QString TalkerCode::defaultTalkerCode(const QString &fullLanguageCode, const QString &plugInName)
203 TalkerCode tmpTalkerCode;
204 tmpTalkerCode.setFullLanguageCode(fullLanguageCode);
205 tmpTalkerCode.setPlugInName(plugInName);
206 tmpTalkerCode.normalize();
207 return tmpTalkerCode.getTalkerCode();
211 * Converts a language code plus optional country code to language description.
213 /*static*/ QString TalkerCode::languageCodeToLanguage(const QString &languageCode)
215 QString twoAlpha;
216 QString countryCode;
217 QString language;
218 if (languageCode == "other")
219 language = i18n("Other");
220 else
222 splitFullLanguageCode(languageCode, twoAlpha, countryCode);
223 language = KGlobal::locale()->twoAlphaToLanguageName(twoAlpha);
225 if (!countryCode.isEmpty())
227 QString countryName = KGlobal::locale()->twoAlphaToCountryName(countryCode);
228 // Some abbreviations to save screen space.
229 if (countryName == i18n("full country name", "United States of America"))
230 countryName = i18n("abbreviated country name", "USA");
231 if (countryName == i18n("full country name", "United Kingdom"))
232 countryName = i18n("abbreviated country name", "UK");
233 language += " (" + countryName + ")";
235 return language;
239 * These functions return translated Talker Code attributes.
241 /*static*/ QString TalkerCode::translatedGender(const QString &gender)
243 if (gender == "male")
244 return i18n("male");
245 else if (gender == "female")
246 return i18n("female");
247 else if (gender == "neutral")
248 return i18n("neutral gender", "neutral");
249 else return gender;
251 /*static*/ QString TalkerCode::untranslatedGender(const QString &gender)
253 if (gender == i18n("male"))
254 return "male";
255 else if (gender == i18n("female"))
256 return "female";
257 else if (gender == i18n("neutral gender", "neutral"))
258 return "neutral";
259 else return gender;
261 /*static*/ QString TalkerCode::translatedVolume(const QString &volume)
263 if (volume == "medium")
264 return i18n("medium sound", "medium");
265 else if (volume == "loud")
266 return i18n("loud sound", "loud");
267 else if (volume == "soft")
268 return i18n("soft sound", "soft");
269 else return volume;
271 /*static*/ QString TalkerCode::untranslatedVolume(const QString &volume)
273 if (volume == i18n("medium sound", "medium"))
274 return "medium";
275 else if (volume == i18n("loud sound", "loud"))
276 return "loud";
277 else if (volume == i18n("soft sound", "soft"))
278 return "soft";
279 else return volume;
281 /*static*/ QString TalkerCode::translatedRate(const QString &rate)
283 if (rate == "medium")
284 return i18n("medium speed", "medium");
285 else if (rate == "fast")
286 return i18n("fast speed", "fast");
287 else if (rate == "slow")
288 return i18n("slow speed", "slow");
289 else return rate;
291 /*static*/ QString TalkerCode::untranslatedRate(const QString &rate)
293 if (rate == i18n("medium speed", "medium"))
294 return "medium";
295 else if (rate == i18n("fast speed", "fast"))
296 return "fast";
297 else if (rate == i18n("slow speed", "slow"))
298 return "slow";
299 else return rate;
303 * Given a talker code, parses out the attributes.
304 * @param talkerCode The talker code.
306 void TalkerCode::parseTalkerCode(const QString &talkerCode)
308 QString fullLanguageCode;
309 if (talkerCode.contains("\""))
311 fullLanguageCode = talkerCode.section("lang=", 1, 1);
312 fullLanguageCode = fullLanguageCode.section('"', 1, 1);
314 else
315 fullLanguageCode = talkerCode;
316 QString languageCode;
317 QString countryCode;
318 splitFullLanguageCode(fullLanguageCode, languageCode, countryCode);
319 m_languageCode = languageCode;
320 if (fullLanguageCode.left(1) == "*") countryCode = "*" + countryCode;
321 m_countryCode = countryCode;
322 m_voice = talkerCode.section("name=", 1, 1);
323 m_voice = m_voice.section('"', 1, 1);
324 m_gender = talkerCode.section("gender=", 1, 1);
325 m_gender = m_gender.section('"', 1, 1);
326 m_volume = talkerCode.section("volume=", 1, 1);
327 m_volume = m_volume.section('"', 1, 1);
328 m_rate = talkerCode.section("rate=", 1, 1);
329 m_rate = m_rate.section('"', 1, 1);
330 m_plugInName = talkerCode.section("synthesizer=", 1, 1);
331 m_plugInName = m_plugInName.section('"', 1, 1);
335 * Given a list of parsed talker codes and a desired talker code, finds the closest
336 * matching talker in the list.
337 * @param talkers The list of parsed talker codes.
338 * @param talker The desired talker code.
339 * @param assumeDefaultLang If true, and desired talker code lacks a language code,
340 * the default language is assumed.
341 * @return Index into talkers of the closest matching talker.
343 /*static*/ int TalkerCode::findClosestMatchingTalker(
344 const TalkerCodeList& talkers,
345 const QString& talker,
346 bool assumeDefaultLang)
348 // kdDebug() << "TalkerCode::findClosestMatchingTalker: matching on talker code " << talker << endl;
349 // If nothing to match on, winner is top in the list.
350 if (talker.isEmpty()) return 0;
351 // Parse the given talker.
352 TalkerCode parsedTalkerCode(talker);
353 // If no language code specified, use the language code of the default talker.
354 if (assumeDefaultLang)
356 if (parsedTalkerCode.languageCode().isEmpty()) parsedTalkerCode.setLanguageCode(
357 talkers[0].languageCode());
359 // The talker that matches on the most priority attributes wins.
360 int talkersCount = int(talkers.count());
361 Q3MemArray<int> priorityMatch(talkersCount);
362 for (int ndx = 0; ndx < talkersCount; ++ndx)
364 priorityMatch[ndx] = 0;
365 // kdDebug() << "Comparing language code " << parsedTalkerCode.languageCode() << " to " << m_loadedPlugIns[ndx].parsedTalkerCode.languageCode() << endl;
366 if (parsedTalkerCode.languageCode() == talkers[ndx].languageCode())
368 ++priorityMatch[ndx];
369 // kdDebug() << "TalkerCode::findClosestMatchingTalker: Match on language " << parsedTalkerCode.languageCode() << endl;
371 if (parsedTalkerCode.countryCode().left(1) == "*")
372 if (parsedTalkerCode.countryCode().mid(1) ==
373 talkers[ndx].countryCode())
374 ++priorityMatch[ndx];
375 if (parsedTalkerCode.voice().left(1) == "*")
376 if (parsedTalkerCode.voice().mid(1) == talkers[ndx].voice())
377 ++priorityMatch[ndx];
378 if (parsedTalkerCode.gender().left(1) == "*")
379 if (parsedTalkerCode.gender().mid(1) == talkers[ndx].gender())
380 ++priorityMatch[ndx];
381 if (parsedTalkerCode.volume().left(1) == "*")
382 if (parsedTalkerCode.volume().mid(1) == talkers[ndx].volume())
383 ++priorityMatch[ndx];
384 if (parsedTalkerCode.rate().left(1) == "*")
385 if (parsedTalkerCode.rate().mid(1) == talkers[ndx].rate())
386 ++priorityMatch[ndx];
387 if (parsedTalkerCode.plugInName().left(1) == "*")
388 if (parsedTalkerCode.plugInName().mid(1) ==
389 talkers[ndx].plugInName())
390 ++priorityMatch[ndx];
392 // Determine the maximum number of priority attributes that were matched.
393 int maxPriority = -1;
394 for (int ndx = 0; ndx < talkersCount; ++ndx)
396 if (priorityMatch[ndx] > maxPriority) maxPriority = priorityMatch[ndx];
398 // Find the talker(s) that matched on most priority attributes.
399 int winnerCount = 0;
400 int winner = -1;
401 for (int ndx = 0; ndx < talkersCount; ++ndx)
403 if (priorityMatch[ndx] == maxPriority)
405 ++winnerCount;
406 winner = ndx;
409 // kdDebug() << "Priority phase: winnerCount = " << winnerCount
410 // << " winner = " << winner
411 // << " maxPriority = " << maxPriority << endl;
412 // If a tie, the one that matches on the most priority and preferred attributes wins.
413 // If there is still a tie, the one nearest the top of the kttsmgr display
414 // (first configured) will be chosen.
415 if (winnerCount > 1)
417 Q3MemArray<int> preferredMatch(talkersCount);
418 for (int ndx = 0; ndx < talkersCount; ++ndx)
420 preferredMatch[ndx] = 0;
421 if (priorityMatch[ndx] == maxPriority)
423 if (parsedTalkerCode.countryCode().left(1) != "*")
424 if (!talkers[ndx].countryCode().isEmpty())
425 if (parsedTalkerCode.countryCode() == talkers[ndx].countryCode())
426 ++preferredMatch[ndx];
427 if (parsedTalkerCode.voice().left(1) != "*")
428 if (parsedTalkerCode.voice() == talkers[ndx].voice())
429 ++preferredMatch[ndx];
430 if (parsedTalkerCode.gender().left(1) != "*")
431 if (parsedTalkerCode.gender() == talkers[ndx].gender())
432 ++preferredMatch[ndx];
433 if (parsedTalkerCode.volume().left(1) != "*")
434 if (parsedTalkerCode.volume() == talkers[ndx].volume())
435 ++preferredMatch[ndx];
436 if (parsedTalkerCode.rate().left(1) != "*")
437 if (parsedTalkerCode.rate() == talkers[ndx].rate())
438 ++preferredMatch[ndx];
439 if (parsedTalkerCode.plugInName().left(1) != "*")
440 if (parsedTalkerCode.plugInName() ==
441 talkers[ndx].plugInName())
442 ++preferredMatch[ndx];
445 // Determine the maximum number of preferred attributes that were matched.
446 int maxPreferred = -1;
447 for (int ndx = 0; ndx < talkersCount; ++ndx)
449 if (preferredMatch[ndx] > maxPreferred) maxPreferred = preferredMatch[ndx];
451 winner = -1;
452 winnerCount = 0;
453 // Find the talker that matched on most priority and preferred attributes.
454 // Work bottom to top so topmost wins in a tie.
455 for (int ndx = talkersCount-1; ndx >= 0; --ndx)
457 if (priorityMatch[ndx] == maxPriority)
459 if (preferredMatch[ndx] == maxPreferred)
461 ++winnerCount;
462 winner = ndx;
466 // kdDebug() << "Preferred phase: winnerCount = " << winnerCount
467 // << " winner = " << winner
468 // << " maxPreferred = " << maxPreferred << endl;
470 // If no winner found, use the first talker.
471 if (winner < 0) winner = 0;
472 // kdDebug() << "TalkerCode::findClosestMatchingTalker: returning winner = " << winner << endl;
473 return winner;
476 /*static*/ QString TalkerCode::stripPrefer( const QString& code, bool& preferred)
478 if ( code.left(1) == "*" )
480 preferred = true;
481 return code.mid(1);
482 } else {
483 preferred = false;
484 return code;
489 * Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName.
490 * @param name The translated plugin name. From Name= line in .desktop file.
491 * @return DesktopEntryName. The name of the .desktop file (less .desktop).
492 * QString::null if not found.
494 /*static*/ QString TalkerCode::TalkerNameToDesktopEntryName(const QString& name)
496 if (name.isEmpty()) return QString::null;
497 KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin",
498 QString("Name == '%1'").arg(name));
500 if (offers.count() == 1)
501 return offers[0]->desktopEntryName();
502 else
503 return QString::null;
507 * Uses KTrader to convert a DesktopEntryName into a translated Synth Plugin Name.
508 * @param desktopEntryName The DesktopEntryName.
509 * @return The translated Name of the plugin, from Name= line in .desktop file.
511 /*static*/ QString TalkerCode::TalkerDesktopEntryNameToName(const QString& desktopEntryName)
513 if (desktopEntryName.isEmpty()) return QString::null;
514 KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin",
515 QString("DesktopEntryName == '%1'").arg(desktopEntryName));
517 if (offers.count() == 1)
518 return offers[0]->name();
519 else
520 return QString::null;