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.
6 (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net>
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 ******************************************************************************/
31 // TalkerCode includes.
32 #include "talkercode.h"
38 TalkerCode::TalkerCode(const QString
&code
/*=QString::null*/, bool normal
/*=false*/)
41 parseTalkerCode(code
);
42 if (normal
) normalize();
48 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 m_desktopEntryName
= talker
->desktopEntryName();
59 if (normal
) normalize();
65 TalkerCode::~TalkerCode() { }
70 QString
TalkerCode::id() const { return m_id
; }
71 QString
TalkerCode::languageCode() const { return m_languageCode
; }
72 QString
TalkerCode::countryCode() const { return m_countryCode
; }
73 QString
TalkerCode::voice() const { return m_voice
; }
74 QString
TalkerCode::gender() const { return m_gender
; }
75 QString
TalkerCode::volume() const { return m_volume
; }
76 QString
TalkerCode::rate() const { return m_rate
; }
77 QString
TalkerCode::plugInName() const { return m_plugInName
; }
78 QString
TalkerCode::desktopEntryName() const { return m_desktopEntryName
; }
80 void TalkerCode::setId(const QString
&id
) { m_id
= id
; }
81 void TalkerCode::setLanguageCode(const QString
&languageCode
) { m_languageCode
= languageCode
; }
82 void TalkerCode::setCountryCode(const QString
&countryCode
) { m_countryCode
= countryCode
; }
83 void TalkerCode::setVoice(const QString
&voice
) { m_voice
= voice
; }
84 void TalkerCode::setGender(const QString
&gender
) { m_gender
= gender
; }
85 void TalkerCode::setVolume(const QString
&volume
) { m_volume
= volume
; }
86 void TalkerCode::setRate(const QString
&rate
) { m_rate
= rate
; }
87 void TalkerCode::setPlugInName(const QString plugInName
) { m_plugInName
= plugInName
; }
88 void TalkerCode::setDesktopEntryName(const QString
&desktopEntryName
) { m_desktopEntryName
= desktopEntryName
; }
91 * Sets the language code and country code (if given).
93 void TalkerCode::setFullLanguageCode(const QString
&fullLanguageCode
)
95 splitFullLanguageCode(fullLanguageCode
, m_languageCode
, m_countryCode
);
99 * Returns the language code plus country code (if any).
101 QString
TalkerCode::fullLanguageCode() const
103 if (!m_countryCode
.isEmpty())
104 return m_languageCode
+ "_" + m_countryCode
;
106 return m_languageCode
;
110 * The Talker Code returned in XML format.
112 void TalkerCode::setTalkerCode(const QString
& code
)
114 parseTalkerCode(code
);
117 QString
TalkerCode::getTalkerCode() const
120 QString languageCode
= m_languageCode
;
121 if (!m_countryCode
.isEmpty()) languageCode
+= "_" + m_countryCode
;
122 if (!languageCode
.isEmpty()) code
= "lang=\"" + languageCode
+ "\" ";
123 if (!m_voice
.isEmpty()) code
+= "name=\"" + m_voice
+ "\" ";
124 if (!m_gender
.isEmpty()) code
+= "gender=\"" + m_gender
+ "\" ";
125 if (!code
.isEmpty()) code
= "<voice " + code
+ "/>";
127 if (!m_volume
.isEmpty()) prosody
= "volume=\"" + m_volume
+ "\" ";
128 if (!m_rate
.isEmpty()) prosody
+= "rate=\"" + m_rate
+ "\" ";
129 if (!prosody
.isEmpty()) code
+= "<prosody " + prosody
+ "/>";
130 if (!m_plugInName
.isEmpty()) code
+= "<kttsd synthesizer=\"" + m_plugInName
+ "\" />";
135 * The Talker Code translated for display.
137 QString
TalkerCode::getTranslatedDescription() const
141 QString fullLangCode
= fullLanguageCode();
142 if (!fullLangCode
.isEmpty()) code
= languageCodeToLanguage( fullLangCode
);
143 // TODO: The PlugInName is always English. Need a way to convert this to a translated
144 // name (possibly via DesktopEntryNameToName, but to do that, we need the desktopEntryName
145 // from the config file).
146 if (!m_plugInName
.isEmpty()) code
+= " " + stripPrefer(m_plugInName
, prefer
);
147 if (!m_voice
.isEmpty()) code
+= " " + stripPrefer(m_voice
, prefer
);
148 if (!m_gender
.isEmpty()) code
+= " " + translatedGender(stripPrefer(m_gender
, prefer
));
149 if (!m_volume
.isEmpty()) code
+= " " + translatedVolume(stripPrefer(m_volume
, prefer
));
150 if (!m_rate
.isEmpty()) code
+= " " + translatedRate(stripPrefer(m_rate
, prefer
));
151 code
= code
.trimmed();
152 if (code
.isEmpty()) code
= i18n("default");
157 * Normalizes the Talker Code by filling in defaults.
159 void TalkerCode::normalize()
161 if (m_voice
.isEmpty()) m_voice
= "fixed";
162 if (m_gender
.isEmpty()) m_gender
= "neutral";
163 if (m_volume
.isEmpty()) m_volume
= "medium";
164 if (m_rate
.isEmpty()) m_rate
= "medium";
168 * Given a talker code, normalizes it into a standard form and also returns
170 * @param talkerCode Unnormalized talker code.
171 * @return fullLanguageCode Language code from the talker code (including country code if any).
172 * @return Normalized talker code.
174 /*static*/ QString
TalkerCode::normalizeTalkerCode(const QString
&talkerCode
, QString
&fullLanguageCode
)
176 TalkerCode
tmpTalkerCode(talkerCode
);
177 tmpTalkerCode
.normalize();
178 fullLanguageCode
= tmpTalkerCode
.fullLanguageCode();
179 return tmpTalkerCode
.getTalkerCode();
183 * Given a language code that might contain a country code, splits the code into
184 * the two letter language code and country code.
185 * @param fullLanguageCode Language code to be split.
186 * @return languageCode Just the language part of the code.
187 * @return countryCode The country code part (if any).
189 * If the input code begins with an asterisk, it is ignored and removed from the returned
192 /*static*/ void TalkerCode::splitFullLanguageCode(const QString
&lang
, QString
&languageCode
, QString
&countryCode
)
194 QString language
= lang
;
195 if (language
.left(1) == "*") language
= language
.mid(1);
197 KGlobal::locale()->splitLocale(language
, languageCode
, countryCode
, charSet
);
201 * Given a full language code and plugin name, returns a normalized default talker code.
202 * @param fullLanguageCode Language code.
203 * @param plugInName Name of the Synthesizer plugin.
204 * @return Full normalized talker code.
206 * Example returned from defaultTalkerCode("en", "Festival")
207 * <voice lang="en" name="fixed" gender="neutral"/>
208 * <prosody volume="medium" rate="medium"/>
209 * <kttsd synthesizer="Festival" />
211 /*static*/ QString
TalkerCode::defaultTalkerCode(const QString
&fullLanguageCode
, const QString
&plugInName
)
213 TalkerCode tmpTalkerCode
;
214 tmpTalkerCode
.setFullLanguageCode(fullLanguageCode
);
215 tmpTalkerCode
.setPlugInName(plugInName
);
216 tmpTalkerCode
.normalize();
217 return tmpTalkerCode
.getTalkerCode();
221 * Converts a language code plus optional country code to language description.
223 /*static*/ QString
TalkerCode::languageCodeToLanguage(const QString
&languageCode
)
228 if (languageCode
== "other")
229 language
= i18n("Other");
232 splitFullLanguageCode(languageCode
, twoAlpha
, countryCode
);
233 language
= KGlobal::locale()->twoAlphaToLanguageName(twoAlpha
);
235 if (!countryCode
.isEmpty())
237 QString countryName
= KGlobal::locale()->twoAlphaToCountryName(countryCode
);
238 // Some abbreviations to save screen space.
239 if (countryName
== i18n("full country name", "United States of America"))
240 countryName
= i18n("abbreviated country name", "USA");
241 if (countryName
== i18n("full country name", "United Kingdom"))
242 countryName
= i18n("abbreviated country name", "UK");
243 language
+= " (" + countryName
+ ")";
249 * These functions return translated Talker Code attributes.
251 /*static*/ QString
TalkerCode::translatedGender(const QString
&gender
)
253 if (gender
== "male")
255 else if (gender
== "female")
256 return i18n("female");
257 else if (gender
== "neutral")
258 return i18n("neutral gender", "neutral");
261 /*static*/ QString
TalkerCode::untranslatedGender(const QString
&gender
)
263 if (gender
== i18n("male"))
265 else if (gender
== i18n("female"))
267 else if (gender
== i18n("neutral gender", "neutral"))
271 /*static*/ QString
TalkerCode::translatedVolume(const QString
&volume
)
273 if (volume
== "medium")
274 return i18n("medium sound", "medium");
275 else if (volume
== "loud")
276 return i18n("loud sound", "loud");
277 else if (volume
== "soft")
278 return i18n("soft sound", "soft");
281 /*static*/ QString
TalkerCode::untranslatedVolume(const QString
&volume
)
283 if (volume
== i18n("medium sound", "medium"))
285 else if (volume
== i18n("loud sound", "loud"))
287 else if (volume
== i18n("soft sound", "soft"))
291 /*static*/ QString
TalkerCode::translatedRate(const QString
&rate
)
293 if (rate
== "medium")
294 return i18n("medium speed", "medium");
295 else if (rate
== "fast")
296 return i18n("fast speed", "fast");
297 else if (rate
== "slow")
298 return i18n("slow speed", "slow");
301 /*static*/ QString
TalkerCode::untranslatedRate(const QString
&rate
)
303 if (rate
== i18n("medium speed", "medium"))
305 else if (rate
== i18n("fast speed", "fast"))
307 else if (rate
== i18n("slow speed", "slow"))
313 * Given a talker code, parses out the attributes.
314 * @param talkerCode The talker code.
316 void TalkerCode::parseTalkerCode(const QString
&talkerCode
)
318 QString fullLanguageCode
;
319 if (talkerCode
.contains("\""))
321 fullLanguageCode
= talkerCode
.section("lang=", 1, 1);
322 fullLanguageCode
= fullLanguageCode
.section('"', 1, 1);
325 fullLanguageCode
= talkerCode
;
326 QString languageCode
;
328 splitFullLanguageCode(fullLanguageCode
, languageCode
, countryCode
);
329 m_languageCode
= languageCode
;
330 if (fullLanguageCode
.left(1) == "*") countryCode
= "*" + countryCode
;
331 m_countryCode
= countryCode
;
332 m_voice
= talkerCode
.section("name=", 1, 1);
333 m_voice
= m_voice
.section('"', 1, 1);
334 m_gender
= talkerCode
.section("gender=", 1, 1);
335 m_gender
= m_gender
.section('"', 1, 1);
336 m_volume
= talkerCode
.section("volume=", 1, 1);
337 m_volume
= m_volume
.section('"', 1, 1);
338 m_rate
= talkerCode
.section("rate=", 1, 1);
339 m_rate
= m_rate
.section('"', 1, 1);
340 m_plugInName
= talkerCode
.section("synthesizer=", 1, 1);
341 m_plugInName
= m_plugInName
.section('"', 1, 1);
345 * Given a list of parsed talker codes and a desired talker code, finds the closest
346 * matching talker in the list.
347 * @param talkers The list of parsed talker codes.
348 * @param talker The desired talker code.
349 * @param assumeDefaultLang If true, and desired talker code lacks a language code,
350 * the default language is assumed.
351 * @return Index into talkers of the closest matching talker.
353 /*static*/ int TalkerCode::findClosestMatchingTalker(
354 const TalkerCodeList
& talkers
,
355 const QString
& talker
,
356 bool assumeDefaultLang
)
358 // kdDebug() << "TalkerCode::findClosestMatchingTalker: matching on talker code " << talker << endl;
359 // If nothing to match on, winner is top in the list.
360 if (talker
.isEmpty()) return 0;
361 // Parse the given talker.
362 TalkerCode
parsedTalkerCode(talker
);
363 // If no language code specified, use the language code of the default talker.
364 if (assumeDefaultLang
)
366 if (parsedTalkerCode
.languageCode().isEmpty()) parsedTalkerCode
.setLanguageCode(
367 talkers
[0].languageCode());
369 // The talker that matches on the most priority attributes wins.
370 int talkersCount
= int(talkers
.count());
371 QVector
<int> priorityMatch(talkersCount
);
372 for (int ndx
= 0; ndx
< talkersCount
; ++ndx
)
374 priorityMatch
[ndx
] = 0;
375 // kdDebug() << "Comparing language code " << parsedTalkerCode.languageCode() << " to " << m_loadedPlugIns[ndx].parsedTalkerCode.languageCode() << endl;
376 if (parsedTalkerCode
.languageCode() == talkers
[ndx
].languageCode())
378 ++priorityMatch
[ndx
];
379 // kdDebug() << "TalkerCode::findClosestMatchingTalker: Match on language " << parsedTalkerCode.languageCode() << endl;
381 if (parsedTalkerCode
.countryCode().left(1) == "*")
382 if (parsedTalkerCode
.countryCode().mid(1) ==
383 talkers
[ndx
].countryCode())
384 ++priorityMatch
[ndx
];
385 if (parsedTalkerCode
.voice().left(1) == "*")
386 if (parsedTalkerCode
.voice().mid(1) == talkers
[ndx
].voice())
387 ++priorityMatch
[ndx
];
388 if (parsedTalkerCode
.gender().left(1) == "*")
389 if (parsedTalkerCode
.gender().mid(1) == talkers
[ndx
].gender())
390 ++priorityMatch
[ndx
];
391 if (parsedTalkerCode
.volume().left(1) == "*")
392 if (parsedTalkerCode
.volume().mid(1) == talkers
[ndx
].volume())
393 ++priorityMatch
[ndx
];
394 if (parsedTalkerCode
.rate().left(1) == "*")
395 if (parsedTalkerCode
.rate().mid(1) == talkers
[ndx
].rate())
396 ++priorityMatch
[ndx
];
397 if (parsedTalkerCode
.plugInName().left(1) == "*")
398 if (parsedTalkerCode
.plugInName().mid(1) ==
399 talkers
[ndx
].plugInName())
400 ++priorityMatch
[ndx
];
402 // Determine the maximum number of priority attributes that were matched.
403 int maxPriority
= -1;
404 for (int ndx
= 0; ndx
< talkersCount
; ++ndx
)
406 if (priorityMatch
[ndx
] > maxPriority
) maxPriority
= priorityMatch
[ndx
];
408 // Find the talker(s) that matched on most priority attributes.
411 for (int ndx
= 0; ndx
< talkersCount
; ++ndx
)
413 if (priorityMatch
[ndx
] == maxPriority
)
419 // kdDebug() << "Priority phase: winnerCount = " << winnerCount
420 // << " winner = " << winner
421 // << " maxPriority = " << maxPriority << endl;
422 // If a tie, the one that matches on the most priority and preferred attributes wins.
423 // If there is still a tie, the one nearest the top of the kttsmgr display
424 // (first configured) will be chosen.
427 QVector
<int> preferredMatch(talkersCount
);
428 for (int ndx
= 0; ndx
< talkersCount
; ++ndx
)
430 preferredMatch
[ndx
] = 0;
431 if (priorityMatch
[ndx
] == maxPriority
)
433 if (parsedTalkerCode
.countryCode().left(1) != "*")
434 if (!talkers
[ndx
].countryCode().isEmpty())
435 if (parsedTalkerCode
.countryCode() == talkers
[ndx
].countryCode())
436 ++preferredMatch
[ndx
];
437 if (parsedTalkerCode
.voice().left(1) != "*")
438 if (parsedTalkerCode
.voice() == talkers
[ndx
].voice())
439 ++preferredMatch
[ndx
];
440 if (parsedTalkerCode
.gender().left(1) != "*")
441 if (parsedTalkerCode
.gender() == talkers
[ndx
].gender())
442 ++preferredMatch
[ndx
];
443 if (parsedTalkerCode
.volume().left(1) != "*")
444 if (parsedTalkerCode
.volume() == talkers
[ndx
].volume())
445 ++preferredMatch
[ndx
];
446 if (parsedTalkerCode
.rate().left(1) != "*")
447 if (parsedTalkerCode
.rate() == talkers
[ndx
].rate())
448 ++preferredMatch
[ndx
];
449 if (parsedTalkerCode
.plugInName().left(1) != "*")
450 if (parsedTalkerCode
.plugInName() ==
451 talkers
[ndx
].plugInName())
452 ++preferredMatch
[ndx
];
455 // Determine the maximum number of preferred attributes that were matched.
456 int maxPreferred
= -1;
457 for (int ndx
= 0; ndx
< talkersCount
; ++ndx
)
459 if (preferredMatch
[ndx
] > maxPreferred
) maxPreferred
= preferredMatch
[ndx
];
463 // Find the talker that matched on most priority and preferred attributes.
464 // Work bottom to top so topmost wins in a tie.
465 for (int ndx
= talkersCount
-1; ndx
>= 0; --ndx
)
467 if (priorityMatch
[ndx
] == maxPriority
)
469 if (preferredMatch
[ndx
] == maxPreferred
)
476 // kdDebug() << "Preferred phase: winnerCount = " << winnerCount
477 // << " winner = " << winner
478 // << " maxPreferred = " << maxPreferred << endl;
480 // If no winner found, use the first talker.
481 if (winner
< 0) winner
= 0;
482 // kdDebug() << "TalkerCode::findClosestMatchingTalker: returning winner = " << winner << endl;
486 /*static*/ QString
TalkerCode::stripPrefer( const QString
& code
, bool& preferred
)
488 if ( code
.left(1) == "*" )
499 * Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName.
500 * @param name The translated plugin name. From Name= line in .desktop file.
501 * @return DesktopEntryName. The name of the .desktop file (less .desktop).
502 * QString::null if not found.
504 /*static*/ QString
TalkerCode::TalkerNameToDesktopEntryName(const QString
& name
)
506 if (name
.isEmpty()) return QString::null
;
507 KTrader::OfferList offers
= KTrader::self()->query("KTTSD/SynthPlugin",
508 QString("Name == '%1'").arg(name
));
510 if (offers
.count() == 1)
511 return offers
[0]->desktopEntryName();
513 return QString::null
;
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::null
;
524 KTrader::OfferList offers
= KTrader::self()->query("KTTSD/SynthPlugin",
525 QString("DesktopEntryName == '%1'").arg(desktopEntryName
));
527 if (offers
.count() == 1)
528 return offers
[0]->name();
530 return QString::null
;