Remove this line
[kdeaccessibility.git] / kttsd / libkttsd / talkercode.cpp
bloba84c982a310d75a7f3e1ac140bb02960eb8844f5
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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 ******************************************************************************/
25 // TalkerCode includes.
26 #include "talkercode.h"
28 // Qt includes.
29 #include <QtCore/QVector>
31 // KDE includes.
32 #include <kglobal.h>
33 #include <klocale.h>
34 #include <kdebug.h>
35 #include <kservicetypetrader.h>
37 /**
38 * Constructor.
40 TalkerCode::TalkerCode(const QString &code/*=QString()*/, bool normal /*=false*/)
42 if (!code.isEmpty())
43 parseTalkerCode(code);
44 if (normal) normalize();
47 /**
48 * Copy Constructor.
50 TalkerCode::TalkerCode(TalkerCode* talker, bool normal /*=false*/)
52 m_id = talker->id();
53 m_languageCode = talker->languageCode();
54 m_countryCode = talker->countryCode();
55 m_voice = talker->voice();
56 m_gender = talker->gender();
57 m_volume = talker->volume();
58 m_rate = talker->rate();
59 m_plugInName = talker->plugInName();
60 m_desktopEntryName = talker->desktopEntryName();
61 if (normal) normalize();
64 /**
65 * Destructor.
67 TalkerCode::~TalkerCode() { }
69 /**
70 * Properties.
72 QString TalkerCode::id() const { return m_id; }
73 QString TalkerCode::languageCode() const { return m_languageCode; }
74 QString TalkerCode::countryCode() const { return m_countryCode; }
75 QString TalkerCode::voice() const { return m_voice; }
76 QString TalkerCode::gender() const { return m_gender; }
77 QString TalkerCode::volume() const { return m_volume; }
78 QString TalkerCode::rate() const { return m_rate; }
79 QString TalkerCode::plugInName() const { return m_plugInName; }
80 QString TalkerCode::desktopEntryName() const { return m_desktopEntryName; }
82 void TalkerCode::setId(const QString &id) { m_id = id; }
83 void TalkerCode::setLanguageCode(const QString &languageCode) { m_languageCode = languageCode; }
84 void TalkerCode::setCountryCode(const QString &countryCode) { m_countryCode = countryCode; }
85 void TalkerCode::setVoice(const QString &voice) { m_voice = voice; }
86 void TalkerCode::setGender(const QString &gender) { m_gender = gender; }
87 void TalkerCode::setVolume(const QString &volume) { m_volume = volume; }
88 void TalkerCode::setRate(const QString &rate) { m_rate = rate; }
89 void TalkerCode::setPlugInName(const QString plugInName) { m_plugInName = plugInName; }
90 void TalkerCode::setDesktopEntryName(const QString &desktopEntryName) { m_desktopEntryName = desktopEntryName; }
92 /**
93 * Sets the language code and country code (if given).
95 void TalkerCode::setFullLanguageCode(const QString &fullLanguageCode)
97 splitFullLanguageCode(fullLanguageCode, m_languageCode, m_countryCode);
101 * Returns the language code plus country code (if any).
103 QString TalkerCode::fullLanguageCode() const
105 if (!m_countryCode.isEmpty())
106 return m_languageCode + '_' + m_countryCode;
107 else
108 return m_languageCode;
112 * The Talker Code returned in XML format.
114 void TalkerCode::setTalkerCode(const QString& code)
116 parseTalkerCode(code);
119 QString TalkerCode::getTalkerCode() const
121 QString code;
122 QString languageCode = m_languageCode;
123 if (!m_countryCode.isEmpty()) languageCode += '_' + m_countryCode;
124 if (!languageCode.isEmpty()) code = "lang=\"" + languageCode + "\" ";
125 if (!m_voice.isEmpty()) code += "name=\"" + m_voice + "\" ";
126 if (!m_gender.isEmpty()) code += "gender=\"" + m_gender + "\" ";
127 if (!code.isEmpty()) code = "<voice " + code + "/>";
128 QString prosody;
129 if (!m_volume.isEmpty()) prosody = "volume=\"" + m_volume + "\" ";
130 if (!m_rate.isEmpty()) prosody += "rate=\"" + m_rate + "\" ";
131 if (!prosody.isEmpty()) code += "<prosody " + prosody + "/>";
132 if (!m_plugInName.isEmpty()) code += "<kttsd synthesizer=\"" + m_plugInName + "\" />";
133 return code;
137 * The Talker Code translated for display.
139 QString TalkerCode::getTranslatedDescription() const
141 QString code;
142 bool prefer;
143 QString fullLangCode = fullLanguageCode();
144 if (!fullLangCode.isEmpty()) code = languageCodeToLanguage( fullLangCode );
145 // TODO: The PlugInName is always English. Need a way to convert this to a translated
146 // name (possibly via DesktopEntryNameToName, but to do that, we need the desktopEntryName
147 // from the config file).
148 if (!m_plugInName.isEmpty()) code += ' ' + stripPrefer(m_plugInName, prefer);
149 if (!m_voice.isEmpty()) code += ' ' + stripPrefer(m_voice, prefer);
150 if (!m_gender.isEmpty()) code += ' ' + translatedGender(stripPrefer(m_gender, prefer));
151 if (!m_volume.isEmpty()) code += ' ' + translatedVolume(stripPrefer(m_volume, prefer));
152 if (!m_rate.isEmpty()) code += ' ' + translatedRate(stripPrefer(m_rate, prefer));
153 code = code.trimmed();
154 if (code.isEmpty()) code = i18nc("Default language code", "default");
155 return code;
159 * Normalizes the Talker Code by filling in defaults.
161 void TalkerCode::normalize()
163 if (m_voice.isEmpty()) m_voice = "fixed";
164 if (m_gender.isEmpty()) m_gender = "neutral";
165 if (m_volume.isEmpty()) m_volume = "medium";
166 if (m_rate.isEmpty()) m_rate = "medium";
170 * Given a talker code, normalizes it into a standard form and also returns
171 * the language code.
172 * @param talkerCode Unnormalized talker code.
173 * @return fullLanguageCode Language code from the talker code (including country code if any).
174 * @return Normalized talker code.
176 /*static*/ QString TalkerCode::normalizeTalkerCode(const QString &talkerCode, QString &fullLanguageCode)
178 TalkerCode tmpTalkerCode(talkerCode);
179 tmpTalkerCode.normalize();
180 fullLanguageCode = tmpTalkerCode.fullLanguageCode();
181 return tmpTalkerCode.getTalkerCode();
185 * Given a language code that might contain a country code, splits the code into
186 * the two letter language code and country code.
187 * @param fullLanguageCode Language code to be split.
188 * @return languageCode Just the language part of the code.
189 * @return countryCode The country code part (if any).
191 * If the input code begins with an asterisk, it is ignored and removed from the returned
192 * languageCode.
194 /*static*/ void TalkerCode::splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode)
196 QString language = lang;
197 if (language.left(1) == "*") language = language.mid(1);
198 QString modifier;
199 QString charSet;
200 KGlobal::locale()->splitLocale(language, languageCode, countryCode, modifier, charSet);
204 * Given a full language code and plugin name, returns a normalized default talker code.
205 * @param fullLanguageCode Language code.
206 * @param plugInName Name of the Synthesizer plugin.
207 * @return Full normalized talker code.
209 * Example returned from defaultTalkerCode("en", "Festival")
210 * <voice lang="en" name="fixed" gender="neutral"/>
211 * <prosody volume="medium" rate="medium"/>
212 * <kttsd synthesizer="Festival" />
214 /*static*/ QString TalkerCode::defaultTalkerCode(const QString &fullLanguageCode, const QString &plugInName)
216 TalkerCode tmpTalkerCode;
217 tmpTalkerCode.setFullLanguageCode(fullLanguageCode);
218 tmpTalkerCode.setPlugInName(plugInName);
219 tmpTalkerCode.normalize();
220 return tmpTalkerCode.getTalkerCode();
224 * Converts a language code plus optional country code to language description.
226 /*static*/ QString TalkerCode::languageCodeToLanguage(const QString &languageCode)
228 QString langAlpha;
229 QString countryCode;
230 QString language;
231 if (languageCode == "other")
232 language = i18nc("Other language", "Other");
233 else
235 splitFullLanguageCode(languageCode, langAlpha, countryCode);
236 language = KGlobal::locale()->languageCodeToName(langAlpha);
238 if (!countryCode.isEmpty())
240 QString countryName = KGlobal::locale()->countryCodeToName(countryCode);
241 // Some abbreviations to save screen space.
242 if (countryName == i18nc("full country name", "United States of America"))
243 countryName = i18nc("abbreviated country name", "USA");
244 if (countryName == i18nc("full country name", "United Kingdom"))
245 countryName = i18nc("abbreviated country name", "UK");
246 language += " (" + countryName + ')';
248 return language;
252 * These functions return translated Talker Code attributes.
254 /*static*/ QString TalkerCode::translatedGender(const QString &gender)
256 if (gender == "male")
257 return i18nc("Male gender", "male");
258 else if (gender == "female")
259 return i18nc("Female gender", "female");
260 else if (gender == "neutral")
261 return i18nc("neutral gender", "neutral");
262 else return gender;
264 /*static*/ QString TalkerCode::untranslatedGender(const QString &gender)
266 if (gender == i18nc("Male gender", "male"))
267 return "male";
268 else if (gender == i18nc("Female gender", "female"))
269 return "female";
270 else if (gender == i18nc("neutral gender", "neutral"))
271 return "neutral";
272 else return gender;
274 /*static*/ QString TalkerCode::translatedVolume(const QString &volume)
276 if (volume == "medium")
277 return i18nc("medium sound", "medium");
278 else if (volume == "loud")
279 return i18nc("loud sound", "loud");
280 else if (volume == "soft")
281 return i18nc("soft sound", "soft");
282 else return volume;
284 /*static*/ QString TalkerCode::untranslatedVolume(const QString &volume)
286 if (volume == i18nc("medium sound", "medium"))
287 return "medium";
288 else if (volume == i18nc("loud sound", "loud"))
289 return "loud";
290 else if (volume == i18nc("soft sound", "soft"))
291 return "soft";
292 else return volume;
294 /*static*/ QString TalkerCode::translatedRate(const QString &rate)
296 if (rate == "medium")
297 return i18nc("medium speed", "medium");
298 else if (rate == "fast")
299 return i18nc("fast speed", "fast");
300 else if (rate == "slow")
301 return i18nc("slow speed", "slow");
302 else return rate;
304 /*static*/ QString TalkerCode::untranslatedRate(const QString &rate)
306 if (rate == i18nc("medium speed", "medium"))
307 return "medium";
308 else if (rate == i18nc("fast speed", "fast"))
309 return "fast";
310 else if (rate == i18nc("slow speed", "slow"))
311 return "slow";
312 else return rate;
316 * Given a talker code, parses out the attributes.
317 * @param talkerCode The talker code.
319 void TalkerCode::parseTalkerCode(const QString &talkerCode)
321 QString fullLanguageCode;
322 if (talkerCode.contains("\""))
324 fullLanguageCode = talkerCode.section("lang=", 1, 1);
325 fullLanguageCode = fullLanguageCode.section('"', 1, 1);
327 else
328 fullLanguageCode = talkerCode;
329 QString languageCode;
330 QString countryCode;
331 splitFullLanguageCode(fullLanguageCode, languageCode, countryCode);
332 m_languageCode = languageCode;
333 if (fullLanguageCode.left(1) == "*") countryCode = '*' + countryCode;
334 m_countryCode = countryCode;
335 m_voice = talkerCode.section("name=", 1, 1);
336 m_voice = m_voice.section('"', 1, 1);
337 m_gender = talkerCode.section("gender=", 1, 1);
338 m_gender = m_gender.section('"', 1, 1);
339 m_volume = talkerCode.section("volume=", 1, 1);
340 m_volume = m_volume.section('"', 1, 1);
341 m_rate = talkerCode.section("rate=", 1, 1);
342 m_rate = m_rate.section('"', 1, 1);
343 m_plugInName = talkerCode.section("synthesizer=", 1, 1);
344 m_plugInName = m_plugInName.section('"', 1, 1);
348 * Given a list of parsed talker codes and a desired talker code, finds the closest
349 * matching talker in the list.
350 * @param talkers The list of parsed talker codes.
351 * @param talker The desired talker code.
352 * @param assumeDefaultLang If true, and desired talker code lacks a language code,
353 * the default language is assumed.
354 * @return Index into talkers of the closest matching talker.
356 /*static*/ int TalkerCode::findClosestMatchingTalker(
357 const TalkerCodeList& talkers,
358 const QString& talker,
359 bool assumeDefaultLang)
361 // kDebug() << "TalkerCode::findClosestMatchingTalker: matching on talker code " << talker;
362 // If nothing to match on, winner is top in the list.
363 if (talker.isEmpty()) return 0;
364 // Parse the given talker.
365 TalkerCode parsedTalkerCode(talker);
366 // If no language code specified, use the language code of the default talker.
367 if (assumeDefaultLang)
369 if (parsedTalkerCode.languageCode().isEmpty()) parsedTalkerCode.setLanguageCode(
370 talkers[0].languageCode());
372 // The talker that matches on the most priority attributes wins.
373 int talkersCount = int(talkers.count());
374 QVector<int> priorityMatch(talkersCount);
375 for (int ndx = 0; ndx < talkersCount; ++ndx)
377 priorityMatch[ndx] = 0;
378 // kDebug() << "Comparing language code " << parsedTalkerCode.languageCode() << " to " << m_loadedPlugIns[ndx].parsedTalkerCode.languageCode();
379 if (parsedTalkerCode.languageCode() == talkers[ndx].languageCode())
381 ++priorityMatch[ndx];
382 // kDebug() << "TalkerCode::findClosestMatchingTalker: Match on language " << parsedTalkerCode.languageCode();
384 if (parsedTalkerCode.countryCode().left(1) == "*")
385 if (parsedTalkerCode.countryCode().mid(1) ==
386 talkers[ndx].countryCode())
387 ++priorityMatch[ndx];
388 if (parsedTalkerCode.voice().left(1) == "*")
389 if (parsedTalkerCode.voice().mid(1) == talkers[ndx].voice())
390 ++priorityMatch[ndx];
391 if (parsedTalkerCode.gender().left(1) == "*")
392 if (parsedTalkerCode.gender().mid(1) == talkers[ndx].gender())
393 ++priorityMatch[ndx];
394 if (parsedTalkerCode.volume().left(1) == "*")
395 if (parsedTalkerCode.volume().mid(1) == talkers[ndx].volume())
396 ++priorityMatch[ndx];
397 if (parsedTalkerCode.rate().left(1) == "*")
398 if (parsedTalkerCode.rate().mid(1) == talkers[ndx].rate())
399 ++priorityMatch[ndx];
400 if (parsedTalkerCode.plugInName().left(1) == "*")
401 if (parsedTalkerCode.plugInName().mid(1) ==
402 talkers[ndx].plugInName())
403 ++priorityMatch[ndx];
405 // Determine the maximum number of priority attributes that were matched.
406 int maxPriority = -1;
407 for (int ndx = 0; ndx < talkersCount; ++ndx)
409 if (priorityMatch[ndx] > maxPriority) maxPriority = priorityMatch[ndx];
411 // Find the talker(s) that matched on most priority attributes.
412 int winnerCount = 0;
413 int winner = -1;
414 for (int ndx = 0; ndx < talkersCount; ++ndx)
416 if (priorityMatch[ndx] == maxPriority)
418 ++winnerCount;
419 winner = ndx;
422 // kDebug() << "Priority phase: winnerCount = " << winnerCount
423 // << " winner = " << winner
424 // << " maxPriority = " << maxPriority << endl;
425 // If a tie, the one that matches on the most priority and preferred attributes wins.
426 // If there is still a tie, the one nearest the top of the kttsmgr display
427 // (first configured) will be chosen.
428 if (winnerCount > 1)
430 QVector<int> preferredMatch(talkersCount);
431 for (int ndx = 0; ndx < talkersCount; ++ndx)
433 preferredMatch[ndx] = 0;
434 if (priorityMatch[ndx] == maxPriority)
436 if (parsedTalkerCode.countryCode().left(1) != "*")
437 if (!talkers[ndx].countryCode().isEmpty())
438 if (parsedTalkerCode.countryCode() == talkers[ndx].countryCode())
439 ++preferredMatch[ndx];
440 if (parsedTalkerCode.voice().left(1) != "*")
441 if (parsedTalkerCode.voice() == talkers[ndx].voice())
442 ++preferredMatch[ndx];
443 if (parsedTalkerCode.gender().left(1) != "*")
444 if (parsedTalkerCode.gender() == talkers[ndx].gender())
445 ++preferredMatch[ndx];
446 if (parsedTalkerCode.volume().left(1) != "*")
447 if (parsedTalkerCode.volume() == talkers[ndx].volume())
448 ++preferredMatch[ndx];
449 if (parsedTalkerCode.rate().left(1) != "*")
450 if (parsedTalkerCode.rate() == talkers[ndx].rate())
451 ++preferredMatch[ndx];
452 if (parsedTalkerCode.plugInName().left(1) != "*")
453 if (parsedTalkerCode.plugInName() ==
454 talkers[ndx].plugInName())
455 ++preferredMatch[ndx];
458 // Determine the maximum number of preferred attributes that were matched.
459 int maxPreferred = -1;
460 for (int ndx = 0; ndx < talkersCount; ++ndx)
462 if (preferredMatch[ndx] > maxPreferred) maxPreferred = preferredMatch[ndx];
464 winner = -1;
465 winnerCount = 0;
466 // Find the talker that matched on most priority and preferred attributes.
467 // Work bottom to top so topmost wins in a tie.
468 for (int ndx = talkersCount-1; ndx >= 0; --ndx)
470 if (priorityMatch[ndx] == maxPriority)
472 if (preferredMatch[ndx] == maxPreferred)
474 ++winnerCount;
475 winner = ndx;
479 // kDebug() << "Preferred phase: winnerCount = " << winnerCount
480 // << " winner = " << winner
481 // << " maxPreferred = " << maxPreferred << endl;
483 // If no winner found, use the first talker.
484 if (winner < 0) winner = 0;
485 // kDebug() << "TalkerCode::findClosestMatchingTalker: returning winner = " << winner;
486 return winner;
489 /*static*/ QString TalkerCode::stripPrefer( const QString& code, bool& preferred)
491 if ( code.left(1) == "*" )
493 preferred = true;
494 return code.mid(1);
495 } else {
496 preferred = false;
497 return code;
502 * Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName.
503 * @param name The translated plugin name. From Name= line in .desktop file.
504 * @return DesktopEntryName. The name of the .desktop file (less .desktop).
505 * QString() if not found.
507 /*static*/ QString TalkerCode::TalkerNameToDesktopEntryName(const QString& name)
509 if (name.isEmpty()) return QString();
510 KService::List offers = KServiceTypeTrader::self()->query("KTTSD/SynthPlugin");
511 for (int ndx = 0; ndx < offers.count(); ++ndx)
512 if (offers[ndx]->name() == name) return offers[ndx]->desktopEntryName();
513 return QString();
517 * Uses KTrader to convert a DesktopEntryName into a translated Synth Plugin Name.
518 * @param desktopEntryName The DesktopEntryName.
519 * @return The translated Name of the plugin, from Name= line in .desktop file.
521 /*static*/ QString TalkerCode::TalkerDesktopEntryNameToName(const QString& desktopEntryName)
523 if (desktopEntryName.isEmpty()) return QString();
524 KService::List offers = KServiceTypeTrader::self()->query("KTTSD/SynthPlugin",
525 QString("DesktopEntryName == '%1'").arg(desktopEntryName));
527 if (offers.count() == 1)
528 return offers[0]->name();
529 else
530 return QString();