From 70d0414d27b06b5c437141f7b89d0c0c756d4cc5 Mon Sep 17 00:00:00 2001 From: cramblitt Date: Fri, 7 Jul 2006 02:11:08 +0000 Subject: [PATCH] API change and refactoring. Major breakage. Best to stay out of kdeaccessibility module until I get it all squared away again. git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/KDE/kdeaccessibility@559327 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kttsd/TODO | 47 + kttsd/filters/sbd/sbdproc.cpp | 14 +- kttsd/filters/sbd/sbdproc.h | 5 +- .../filters/stringreplacer/stringreplacerproc.cpp | 2 +- kttsd/filters/stringreplacer/stringreplacerproc.h | 3 +- kttsd/filters/talkerchooser/talkerchooserproc.cpp | 2 +- kttsd/filters/talkerchooser/talkerchooserproc.h | 3 +- .../filters/xmltransformer/xmltransformerproc.cpp | 4 +- kttsd/filters/xmltransformer/xmltransformerproc.h | 5 +- kttsd/kcmkttsmgr/kcmkttsmgr.cpp | 28 +- kttsd/kcmkttsmgr/kcmkttsmgr.h | 2 +- kttsd/kcmkttsmgr/org.kde.KSpeech.xml | 297 +- kttsd/kttsd/CMakeLists.txt | 13 +- kttsd/kttsd/README | 5 + kttsd/kttsd/appdata.cpp | 97 + kttsd/kttsd/appdata.h | 213 ++ kttsd/kttsd/configdata.cpp | 165 ++ kttsd/kttsd/configdata.h | 211 ++ kttsd/kttsd/filtermgr.cpp | 49 +- kttsd/kttsd/filtermgr.h | 4 +- kttsd/kttsd/kspeech.cpp | 725 +++++ kttsd/kttsd/kspeech.h | 1336 +++++++++ kttsd/kttsd/kspeechadaptor_p.cpp | 952 ++++--- kttsd/kttsd/kspeechadaptor_p.h | 571 ++-- kttsd/kttsd/kttsd.cpp | 1114 -------- kttsd/kttsd/kttsd.h | 655 ----- kttsd/kttsd/main.cpp | 38 +- kttsd/kttsd/speaker.cpp | 2866 ++++++++------------ kttsd/kttsd/speaker.h | 900 ++---- kttsd/kttsd/speechdata.cpp | 2083 ++++++-------- kttsd/kttsd/speechdata.h | 1156 +++----- kttsd/kttsd/speechjob.cpp | 145 + kttsd/kttsd/speechjob.h | 144 + kttsd/kttsd/ssmlconvert.cpp | 4 +- kttsd/kttsd/talkermgr.cpp | 1 + kttsd/kttsd/utt.cpp | 247 ++ kttsd/kttsd/utt.h | 214 ++ kttsd/kttsjobmgr/jobinfolistmodel.cpp | 52 +- kttsd/kttsjobmgr/jobinfolistmodel.h | 16 +- kttsd/kttsjobmgr/kttsjobmgr.cpp | 524 ++-- kttsd/kttsjobmgr/kttsjobmgr.h | 110 +- kttsd/kttsjobmgr/org.kde.KSpeech.xml | 297 +- kttsd/kttsmgr/kttsmgr.cpp | 69 +- kttsd/kttsmgr/kttsmgr.h | 3 +- kttsd/kttsmgr/org.kde.KSpeech.xml | 297 +- kttsd/plugins/command/commandconf.cpp | 2 +- kttsd/plugins/command/commandproc.cpp | 2 +- kttsd/plugins/epos/eposconf.cpp | 2 +- kttsd/plugins/festivalint/festivalintconf.cpp | 2 +- kttsd/plugins/flite/fliteconf.cpp | 2 +- kttsd/plugins/freetts/freettsconf.cpp | 2 +- kttsd/plugins/hadifix/hadifixconf.cpp | 4 +- 52 files changed, 7824 insertions(+), 7880 deletions(-) create mode 100644 kttsd/kttsd/README create mode 100644 kttsd/kttsd/appdata.cpp create mode 100644 kttsd/kttsd/appdata.h create mode 100644 kttsd/kttsd/configdata.cpp create mode 100644 kttsd/kttsd/configdata.h create mode 100644 kttsd/kttsd/kspeech.cpp create mode 100644 kttsd/kttsd/kspeech.h rewrite kttsd/kttsd/kspeechadaptor_p.cpp (81%) rewrite kttsd/kttsd/kspeechadaptor_p.h (67%) delete mode 100644 kttsd/kttsd/kttsd.cpp delete mode 100644 kttsd/kttsd/kttsd.h rewrite kttsd/kttsd/speaker.cpp (71%) rewrite kttsd/kttsd/speaker.h (96%) rewrite kttsd/kttsd/speechdata.cpp (81%) rewrite kttsd/kttsd/speechdata.h (92%) create mode 100644 kttsd/kttsd/speechjob.cpp create mode 100644 kttsd/kttsd/speechjob.h create mode 100644 kttsd/kttsd/utt.cpp create mode 100644 kttsd/kttsd/utt.h diff --git a/kttsd/TODO b/kttsd/TODO index 59f61007..57dda6eb 100644 --- a/kttsd/TODO +++ b/kttsd/TODO @@ -1,3 +1,50 @@ +_ A GPLed French language synth is available at + http://www.cam.org/~nico/cicero + +_ Possible bug. "<" characters not being properly stored in the .xml file? + +_ DBUS Interface + _ Refactor so that each job is an object and emits signals from + object path /org/kde/KSpeechJob_NN. Apps can receive these signals + using + + QDBus::sessionBus().connect("org.kde.kttsd", QString(), + "org.kde.KSpeechJob", "signalname", this, SLOT(slotName())); + + Thiago tells me that the QString() for object path name will + filter on all objects with interface org.kde.KSpeechJob. + This is because QtDBus receives all signals. + + right now, I gave it only two rules: "all signals" + and "everything whose destination is me" + + and then filters on what the connect specifies. + Once in place, the only signals emitted by KSpeech will be + kttsdStarted and kttsdExiting. Everything else will come + from the jobs. In theory, this means that apps that aren't + interesting in tracking jobs don't need to receive those events, + but OTOH, if an app is receiving any events, then it + chances are it is interested in most/all of the events. + The only real advantage to this change is that apps that are + interesting in tracking only a single job can receive those + signals and don't have to check the appId (assuming they + created the job in the first place). Is this advantage + worth the pain of refactoring kttsd? Thoughts: + _ I want to make everything (text, messages, warnings, and SR) + jobs so that KttsJobMgr can track all speech. + _ There's already an mlTextJob structure, so converting that + to an object won't be that hard. + + _ Add setFriendlyName(QString &name) to interface and + QString getFriendlyName(QString &appId) so that friendly names + can be displayed in KttsJobMgr. + + hint: connect to the nameOwnerChange signal from + QDBus::sessionBus().busService() to find out when the application + :1.120 goes away, so that you can release the resource + +KDE4 above this line +------------------------------------------------------------------------- _ Filters: _ XHTMLtoSSML stylesheet needs work to provide better mappings for most web pages. _ Add KNewStuff capability for folks to upload/download filter configs. diff --git a/kttsd/filters/sbd/sbdproc.cpp b/kttsd/filters/sbd/sbdproc.cpp index f608e1bd..f74dc6d3 100644 --- a/kttsd/filters/sbd/sbdproc.cpp +++ b/kttsd/filters/sbd/sbdproc.cpp @@ -485,6 +485,7 @@ QString SbdThread::parsePlainText( const QString& inputText, const QString& re ) temp.replace(QRegExp(" +\\t"), "\t"); // Remove blank lines. temp.replace(QRegExp("\t\t+"),"\t"); + // kDebug() << "SbdThread::parsePlainText: returning " << temp << endl; return temp; } @@ -517,6 +518,8 @@ QString SbdThread::parsePlainText( const QString& inputText, const QString& re ) // Replace spaces, tabs, and formfeeds with a single space. m_text.replace(QRegExp("[ \\t\\f]+"), " "); + + // kDebug() << "SbdThread::run: textType = " << textType << endl; // Perform the filtering based on type of text. switch ( textType ) @@ -598,7 +601,7 @@ SbdProc::~SbdProc() * separate configuration files of their own. */ bool SbdProc::init(KConfig* config, const QString& configGroup){ - // kDebug() << "PlugInProc::init: Running" << endl; + // kDebug() << "SbdProc::init: Running" << endl; config->setGroup( configGroup ); // m_configuredRe = config->readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])\\s|(\\n *\\n)" ); m_configuredRe = config->readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))" ); @@ -640,7 +643,7 @@ bool SbdProc::init(KConfig* config, const QString& configGroup){ * Also useful for hints about how to do the filtering. */ /*virtual*/ QString SbdProc::convert(const QString& inputText, TalkerCode* talkerCode, - const QByteArray& appId) + const QString& appId) { if ( asyncConvert( inputText, talkerCode, appId) ) { @@ -665,15 +668,16 @@ bool SbdProc::init(KConfig* config, const QString& configGroup){ * program must call @ref ackFinished to acknowledge the conversion. */ /*virtual*/ bool SbdProc::asyncConvert(const QString& inputText, TalkerCode* talkerCode, - const QByteArray& appId) + const QString& appId) { + // kDebug() << "SbdProc::asyncConvert: running" << endl; m_sbdThread->setWasModified( false ); // If language doesn't match, return input unmolested. if ( !m_languageCodeList.isEmpty() ) { QString languageCode = talkerCode->languageCode(); // kDebug() << "StringReplacerProc::convert: converting " << inputText << - // " if language code " << languageCode << " matches " << m_languageCodeList << endl; + // " if language code " << languageCode << " matches " << m_languageCodeList << endl; if ( !m_languageCodeList.contains( languageCode ) ) { if ( !talkerCode->countryCode().isEmpty() ) @@ -689,7 +693,7 @@ bool SbdProc::init(KConfig* config, const QString& configGroup){ if ( !m_appIdList.isEmpty() ) { // kDebug() << "SbdProc::convert: converting " << inputText << " if appId " - // << appId << " matches " << m_appIdList << endl; + // << appId << " matches " << m_appIdList << endl; bool found = false; QString appIdStr = appId; for ( int ndx=0; ndx < m_appIdList.count(); ++ndx ) diff --git a/kttsd/filters/sbd/sbdproc.h b/kttsd/filters/sbd/sbdproc.h index f0408d18..965029bf 100644 --- a/kttsd/filters/sbd/sbdproc.h +++ b/kttsd/filters/sbd/sbdproc.h @@ -50,7 +50,6 @@ #include #include #include -#include #include // KTTS includes. @@ -292,7 +291,7 @@ class SbdProc : virtual public KttsFilterProc * @param appId The DCOP appId of the application that queued the text. * Also useful for hints about how to do the filtering. */ - virtual QString convert( const QString& inputText, TalkerCode* talkerCode, const QByteArray& appId ); + virtual QString convert( const QString& inputText, TalkerCode* talkerCode, const QString& appId ); /** * Convert input. Runs asynchronously. @@ -308,7 +307,7 @@ class SbdProc : virtual public KttsFilterProc * program may then call @ref getOutput to retrieve converted text. Calling * program must call @ref ackFinished to acknowledge the conversion. */ - virtual bool asyncConvert( const QString& inputText, TalkerCode* talkerCode, const QByteArray& appId ); + virtual bool asyncConvert( const QString& inputText, TalkerCode* talkerCode, const QString& appId ); /** * Waits for a previous call to asyncConvert to finish. diff --git a/kttsd/filters/stringreplacer/stringreplacerproc.cpp b/kttsd/filters/stringreplacer/stringreplacerproc.cpp index e34307cc..04f106fa 100644 --- a/kttsd/filters/stringreplacer/stringreplacerproc.cpp +++ b/kttsd/filters/stringreplacer/stringreplacerproc.cpp @@ -169,7 +169,7 @@ bool StringReplacerProc::init(KConfig* config, const QString& configGroup){ * Also useful for hints about how to do the filtering. */ /*virtual*/ QString StringReplacerProc::convert(const QString& inputText, TalkerCode* talkerCode, - const QByteArray& appId) + const QString& appId) { m_wasModified = false; // If language doesn't match, return input unmolested. diff --git a/kttsd/filters/stringreplacer/stringreplacerproc.h b/kttsd/filters/stringreplacer/stringreplacerproc.h index 88779c10..354ecd79 100644 --- a/kttsd/filters/stringreplacer/stringreplacerproc.h +++ b/kttsd/filters/stringreplacer/stringreplacerproc.h @@ -31,7 +31,6 @@ #include #include #include -#include // KTTS includes. #include "filterproc.h" @@ -71,7 +70,7 @@ public: * @param appId The DCOP appId of the application that queued the text. * Also useful for hints about how to do the filtering. */ - virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QByteArray& appId); + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QString& appId); /** * Did this filter do anything? If the filter returns the input as output diff --git a/kttsd/filters/talkerchooser/talkerchooserproc.cpp b/kttsd/filters/talkerchooser/talkerchooserproc.cpp index f5301ac4..a37171ee 100644 --- a/kttsd/filters/talkerchooser/talkerchooserproc.cpp +++ b/kttsd/filters/talkerchooser/talkerchooserproc.cpp @@ -104,7 +104,7 @@ bool TalkerChooserProc::init(KConfig* config, const QString& configGroup){ * Also useful for hints about how to do the filtering. */ /*virtual*/ QString TalkerChooserProc::convert(const QString& inputText, TalkerCode* talkerCode, - const QByteArray& appId) + const QString& appId) { if ( !m_re.isEmpty() ) { diff --git a/kttsd/filters/talkerchooser/talkerchooserproc.h b/kttsd/filters/talkerchooser/talkerchooserproc.h index 8f202d5f..f2b89cf4 100644 --- a/kttsd/filters/talkerchooser/talkerchooserproc.h +++ b/kttsd/filters/talkerchooser/talkerchooserproc.h @@ -26,7 +26,6 @@ // Qt includes. #include -#include // KTTS includes. #include "filterproc.h" @@ -79,7 +78,7 @@ public: * @param appId The DCOP appId of the application that queued the text. * Also useful for hints about how to do the filtering. */ - virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QByteArray& appId); + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QString& appId); private: diff --git a/kttsd/filters/xmltransformer/xmltransformerproc.cpp b/kttsd/filters/xmltransformer/xmltransformerproc.cpp index 90a7a358..4065ff4d 100644 --- a/kttsd/filters/xmltransformer/xmltransformerproc.cpp +++ b/kttsd/filters/xmltransformer/xmltransformerproc.cpp @@ -107,7 +107,7 @@ bool XmlTransformerProc::init(KConfig* config, const QString& configGroup) * Also useful for hints about how to do the filtering. */ /*virtual*/ QString XmlTransformerProc::convert(const QString& inputText, TalkerCode* talkerCode, - const QByteArray& appId) + const QString& appId) { // kDebug() << "XmlTransformerProc::convert: Running." << endl; // If not properly configured, do nothing. @@ -141,7 +141,7 @@ bool XmlTransformerProc::init(KConfig* config, const QString& configGroup) * program must call @ref ackFinished to acknowledge the conversion. */ /*virtual*/ bool XmlTransformerProc::asyncConvert(const QString& inputText, TalkerCode* /*talkerCode*/, - const QByteArray& appId) + const QString& appId) { m_wasModified = false; diff --git a/kttsd/filters/xmltransformer/xmltransformerproc.h b/kttsd/filters/xmltransformer/xmltransformerproc.h index d70ee99a..6917f85f 100644 --- a/kttsd/filters/xmltransformer/xmltransformerproc.h +++ b/kttsd/filters/xmltransformer/xmltransformerproc.h @@ -27,7 +27,6 @@ // Qt includes. #include #include -#include // KTTS includes. #include "filterproc.h" @@ -81,7 +80,7 @@ public: * @param appId The DCOP appId of the application that queued the text. * Also useful for hints about how to do the filtering. */ - virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QByteArray& appId); + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QString& appId); /** * Convert input. Runs asynchronously. @@ -97,7 +96,7 @@ public: * program may then call @ref getOutput to retrieve converted text. Calling * program must call @ref ackFinished to acknowledge the conversion. */ - virtual bool asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QByteArray& appId); + virtual bool asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QString& appId); /** * Waits for a previous call to asyncConvert to finish. diff --git a/kttsd/kcmkttsmgr/kcmkttsmgr.cpp b/kttsd/kcmkttsmgr/kcmkttsmgr.cpp index f0939d31..417a179e 100644 --- a/kttsd/kcmkttsmgr/kcmkttsmgr.cpp +++ b/kttsd/kcmkttsmgr/kcmkttsmgr.cpp @@ -53,6 +53,7 @@ #include #include #include +#include // KTTS includes. #include "talkercode.h" @@ -402,7 +403,8 @@ KCMKttsMgr::KCMKttsMgr(QWidget *parent, const QStringList &) : // Set up Keep Audio Path KURLRequestor. keepAudioPath->setMode(KFile::Directory); - keepAudioPath->setUrl(KUrl::fromPath(locateLocal("data", "kttsd/audio/"))); + keepAudioPath->setUrl(KUrl::fromPath( + KStandardDirs::locateLocal("data", "kttsd/audio/"))); // Object for the KTTSD configuration. m_config = new KConfig("kttsdrc"); @@ -525,7 +527,7 @@ KCMKttsMgr::KCMKttsMgr(QWidget *parent, const QStringList &) : this, SLOT(slotTabChanged())); // See if KTTSD is already running, and if so, create jobs tab. - if (QDBus::sessionBus().busService()->nameHasOwner("org.kde.kttsd")) + if (QDBus::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")) kttsdStarted(); else // Start KTTSD if check box is checked. @@ -607,7 +609,7 @@ void KCMKttsMgr::load() m_config->readEntry("ExcludeEventsWithSound", notifyExcludeEventsWithSoundCheckBox->isChecked())); slotNotifyClearButton_clicked(); - loadNotifyEventsFromFile( locateLocal("config", "kttsd_notifyevents.xml"), true ); + loadNotifyEventsFromFile(KStandardDirs::locateLocal("config", "kttsd_notifyevents.xml"), true ); slotNotifyEnableCheckBox_toggled( notifyEnableCheckBox->isChecked() ); // Auto-expand and position on the Default item. QTreeWidgetItem* item = findTreeWidgetItem( notifyListView, "default", nlvcEventSrc ); @@ -872,7 +874,7 @@ void KCMKttsMgr::save() m_config->writeEntry("Notify", notifyEnableCheckBox->isChecked()); m_config->writeEntry("ExcludeEventsWithSound", notifyExcludeEventsWithSoundCheckBox->isChecked()); - saveNotifyEventsToFile( locateLocal("config", "kttsd_notifyevents.xml") ); + saveNotifyEventsToFile(KStandardDirs::locateLocal("config", "kttsd_notifyevents.xml") ); // Audio Output. int audioOutputMethod = 0; @@ -1080,10 +1082,11 @@ void KCMKttsMgr::defaults() { changed = true; keepAudioCheckBox->setChecked(keepAudioCheckBoxValue); } - if (keepAudioPath->url().path() != locateLocal("data", "kttsd/audio/")) + if (keepAudioPath->url().path() != KStandardDirs::locateLocal("data", "kttsd/audio/")) { changed = true; - keepAudioPath->setUrl(KUrl::fromPath(locateLocal("data", "kttsd/audio/"))); + keepAudioPath->setUrl(KUrl::fromPath( + KStandardDirs::locateLocal("data", "kttsd/audio/"))); } keepAudioPath->setEnabled(keepAudioCheckBox->isEnabled()); } @@ -1725,7 +1728,8 @@ void KCMKttsMgr::slotEnableKttsd_toggled(bool) if (reenter) return; reenter = true; // See if KTTSD is running. - bool kttsdRunning = (QDBus::sessionBus().busService()->nameHasOwner("org.kde.kttsd")); + bool kttsdRunning = (QDBus::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")); + // kDebug() << "KCMKttsMgr::slotEnableKttsd_toggled: kttsdRunning = " << kttsdRunning << endl; // If Enable KTTSD check box is checked and it is not running, then start KTTSD. if (enableKttsdCheckBox->isChecked()) @@ -1844,8 +1848,11 @@ void KCMKttsMgr::kttsdStarted() enableKttsdCheckBox->setChecked(true); // Enable/disable notify Test button. slotNotifyListView_currentItemChanged(); - m_kspeech = (org::kde::KSpeech*)(QDBus::sessionBus().findInterface("org.kde.kttsd", "/org/kde/KSpeech")); + m_kspeech = new OrgKdeKSpeechInterface("org.kde.kttsd", "/KSpeech", QDBus::sessionBus()); m_kspeech->setParent(this); + m_kspeech->setApplicationName("kcmkttsmgr"); + m_kspeech->setDefaultPriority(KSpeech::jpMessage); + m_kspeech->setIsSystemManager(true); // Connect KTTSD DBUS signals to our slots. connect(m_kspeech, SIGNAL(kttsdStarted()), this, SLOT(kttsdStarted())); @@ -2466,7 +2473,10 @@ void KCMKttsMgr::slotNotifyTestButton_clicked() msg.replace("%m", i18n("sample notification message")); break; } - if (!msg.isEmpty()) m_kspeech->sayMessage(msg, item->text(nlvcTalker)); + if (!msg.isEmpty()) { + m_kspeech->setDefaultTalker(item->text(nlvcTalker)); + m_kspeech->say(msg, 0); + } } } diff --git a/kttsd/kcmkttsmgr/kcmkttsmgr.h b/kttsd/kcmkttsmgr/kcmkttsmgr.h index 2030908f..2aeca40d 100644 --- a/kttsd/kcmkttsmgr/kcmkttsmgr.h +++ b/kttsd/kcmkttsmgr/kcmkttsmgr.h @@ -185,7 +185,7 @@ class KCMKttsMgr : } }; - protected: + protected slots: /** DCOP Methods connected to DCOP Signals emitted by KTTSD. */ /** Most of these are not used */ diff --git a/kttsd/kcmkttsmgr/org.kde.KSpeech.xml b/kttsd/kcmkttsmgr/org.kde.KSpeech.xml index 6b4dc47f..eb99b787 100644 --- a/kttsd/kcmkttsmgr/org.kde.KSpeech.xml +++ b/kttsd/kcmkttsmgr/org.kde.KSpeech.xml @@ -4,169 +4,197 @@ + + - - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + - - - - - + + + + + + + - - - + + + + + + + + + - - - + + + + + + - - - - + + - - - - - + + + - + - + - - + + - - + - - + - + - - + + - - + + - - - + + + + - - - + + + + + + + - - - + + - - - + + + - - - - + + + - - + + + - - - + + + - - - + + + + - - - + + - - - - + + + + - - - + + + - - + + + + + + + + - + + - - - - - - - + + - - - - - - - + + - - - - - - + - + @@ -177,72 +205,23 @@ - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - + - - - - - - + + + - - - - - - - - - - - - - - - diff --git a/kttsd/kttsd/CMakeLists.txt b/kttsd/kttsd/CMakeLists.txt index 8d658a52..9cf1ea5f 100644 --- a/kttsd/kttsd/CMakeLists.txt +++ b/kttsd/kttsd/CMakeLists.txt @@ -6,22 +6,23 @@ include_directories( ${CMAKE_SOURCE_DIR}/kttsd/libkttsd ) set(kttsd_SRCS main.cpp - kttsd.cpp + kspeech.cpp + utt.cpp speaker.cpp + appdata.cpp + configdata.cpp speechdata.cpp threadedplugin.cpp ssmlconvert.cpp filtermgr.cpp - talkermgr.cpp + talkermgr.cpp + speechjob.cpp kspeechadaptor_p.cpp ) kde4_automoc(${kttsd_SRCS}) -# kde4_add_dcop_skels(kttsd_SRCS ${CMAKE_INSTALL_PREFIX}/include/kspeech.h kttsd.h ) - -# kde4_add_dcop_stubs(kttsd_SRCS ${CMAKE_INSTALL_PREFIX}/include/kspeechsink.h ) - kde4_add_executable(kttsd_bin ${kttsd_SRCS}) + set_target_properties(kttsd_bin PROPERTIES OUTPUT_NAME kttsd) target_link_libraries(kttsd_bin diff --git a/kttsd/kttsd/README b/kttsd/kttsd/README new file mode 100644 index 00000000..5fc01e4c --- /dev/null +++ b/kttsd/kttsd/README @@ -0,0 +1,5 @@ +The kspeech.h file in this directory is a copy from +kdelibs/interfaces/kspeech/ + +If anyone has can offer a way to eliminate it and build +using the copy in kdelibs, I'd be grateful..grc. diff --git a/kttsd/kttsd/appdata.cpp b/kttsd/kttsd/appdata.cpp new file mode 100644 index 00000000..87fe2b11 --- /dev/null +++ b/kttsd/kttsd/appdata.cpp @@ -0,0 +1,97 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This class holds the data for a single application. + It contains the application's default settings. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include "appdata.h" + +/* -------------------------------------------------------------------------- */ + +class AppDataPrivate +{ +public: + AppDataPrivate(const QString& newAppId) : + appId(newAppId), + applicationName(appId), + defaultTalker(""), + defaultPriority(KSpeech::jpText), + sentenceDelimiter("([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))"), + filteringOn(true), + isApplicationPaused(false), + htmlFilterXsltFile(""), + ssmlFilterXsltFile(""), + autoConfigureTalkersOn(false), + isSystemManager(false), + jobList() {} + + friend class AppData; + +protected: + QString appId; + QString applicationName; + QString defaultTalker; + KSpeech::JobPriority defaultPriority; + QString sentenceDelimiter; + bool filteringOn; + bool isApplicationPaused; + QString htmlFilterXsltFile; + QString ssmlFilterXsltFile; + bool autoConfigureTalkersOn; + bool isSystemManager; + TJobList jobList; +}; + +/* -------------------------------------------------------------------------- */ + +AppData::AppData(const QString& appId) { d = new AppDataPrivate(appId); } +AppData::~AppData() { delete d; } + +QString AppData::appId() const { return d->appId; } +void AppData::setAppId(const QString& appId) { d->appId = appId; } +QString AppData::applicationName() const { return d->applicationName; } +void AppData::setApplicationName(const QString& applicationName) { d->applicationName = applicationName; } +QString AppData::defaultTalker() const { return d->defaultTalker; } +void AppData::setDefaultTalker(const QString& defaultTalker) { d->defaultTalker = defaultTalker; } +KSpeech::JobPriority AppData::defaultPriority() const { return d->defaultPriority; } +void AppData::setDefaultPriority(KSpeech::JobPriority defaultPriority) { d->defaultPriority = defaultPriority; } +QString AppData::sentenceDelimiter() const { return d->sentenceDelimiter; } +void AppData::setSentenceDelimiter(const QString& sentenceDelimiter) { d->sentenceDelimiter = sentenceDelimiter; } +bool AppData::filteringOn() const { return d->filteringOn; } +void AppData::setFilteringOn(bool filteringOn) { d->filteringOn = filteringOn; } +bool AppData::isApplicationPaused() const { return d->isApplicationPaused; } +void AppData::setIsApplicationPaused(bool isApplicationPaused) { d->isApplicationPaused = isApplicationPaused; } +QString AppData::htmlFilterXsltFile() const { return d->htmlFilterXsltFile; } +void AppData::setHtmlFilterXsltFile(const QString& filename) { d->htmlFilterXsltFile = filename; } +QString AppData::ssmlFilterXsltFile() const { return d->ssmlFilterXsltFile; } +void AppData::setSsmlFilterXsltFile(const QString& filename) { d->ssmlFilterXsltFile = filename; } +bool AppData::autoConfigureTalkersOn() const { return d->autoConfigureTalkersOn; } +void AppData::setAutoConfigureTalkersOn(bool autoConfigureTalkersOn) { d->autoConfigureTalkersOn = autoConfigureTalkersOn; } +bool AppData::isSystemManager() const { return d->isSystemManager; } +void AppData::setIsSystemManager(bool isSystemManager) { d->isSystemManager = isSystemManager; } +int AppData::lastJobNum() const +{ + if (d->jobList.isEmpty()) + return 0; + else + return d->jobList.last(); +} +TJobListPtr AppData::jobList() const { return &(d->jobList); } diff --git a/kttsd/kttsd/appdata.h b/kttsd/kttsd/appdata.h new file mode 100644 index 00000000..8d1f1cf8 --- /dev/null +++ b/kttsd/kttsd/appdata.h @@ -0,0 +1,213 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This class holds the data for a single application. + It contains the application's default settings. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _APPDATA_H_ +#define _APPDATA_H_ + +// Qt includes. +#include +#include + +// KDE includes. +#include + +typedef QList TJobList; +typedef TJobList* TJobListPtr; + +class AppDataPrivate; +class AppData +{ +public: + /** + * Constructs a new AppData object for the given DBUS AppId. + * @param appId DBUS sender id. + */ + AppData(const QString& appId); + + /** + * Destructor. + */ + ~AppData(); + + /** + * Returns the appId for the application. + */ + QString appId() const; + + /** + * Sets the appId for the application. + */ + void setAppId(const QString& appId); + + /** + * Returns the friendly display name for the application. + * These generally are not translated. + */ + QString applicationName() const; + + /** + * Sets the friendly display name for the application. + * @param applicationName Friendly application name. + * + * If not set, the AppId is used as the applicationName. + */ + void setApplicationName(const QString& applicationName); + + /** + * Returns the default talker code for the application. + * Defaults to "", i.e., use the default talker configured in the system. + */ + QString defaultTalker() const; + + /** + * Sets the default talker code for the application. + * @param defaultTalker Talker code. + */ + void setDefaultTalker(const QString& defaultTalker); + + /** + * Returns the default priority (job type) for the application. + */ + KSpeech::JobPriority defaultPriority() const; + + /** + * Set the default priority (job type) for the application. + * @param defaultPriority Job Priority. + */ + void setDefaultPriority(KSpeech::JobPriority defaultPriority); + + /** + * Returns the GREP pattern that will be used as the sentence delimiter. + * + * The default delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])\\s + @endverbatim + * + * @see setSentenceDelimiter + */ + QString sentenceDelimiter() const; + + /** + * Sets the GREP pattern that will be used as the sentence delimiter. + * @param sentenceDelimiter A valid GREP pattern. + * + * Note that backward slashes must be escaped. + * + * @see sentenceDelimiter, sentenceparsing + */ + void setSentenceDelimiter(const QString& sentenceDelimiter); + + /** + * Returns the applications's current filtering enabled flag. + */ + bool filteringOn() const; + + /** + * Sets the applications's current filtering enabled flag. + * @param filteringOn True or False. + */ + void setFilteringOn(bool filteringOn); + + /** + * Returns whether the jobs of the application are currently paused. + */ + bool isApplicationPaused() const; + + /** + * Sets whether the jobs of the application are currently paused. + * @param isApplicationPaused True of False. + */ + void setIsApplicationPaused(bool isApplicationPaused); + + /** + * Returns the full path name of the XSLT file that performs + * HTML filtering on jobs for the application. + */ + QString htmlFilterXsltFile() const; + + /** + * Sets the full path name of the XSLT file that performs + * HTML filtering on jobs for the application. + * @param filename Name of the XSLT file. Full path name. + */ + void setHtmlFilterXsltFile(const QString& filename); + + /** + * Returns the full path name of the XSLT file that performs + * SSML filtering on jobs for the application. + */ + QString ssmlFilterXsltFile() const; + + /** + * Sets the full path name of the XSLT file that performs + * SSML filtering on jobs for the application. + * @param filename Name of the XSLT file. Full path name. + */ + void setSsmlFilterXsltFile(const QString& filename); + + /** + * Returns if KTTSD should attempt to automatically configure + * talkers to meet requested talker attributes. + */ + bool autoConfigureTalkersOn() const; + + /** + * Sets whether KTTSD should attempt to automatically configure + * talkers to meet requested talker attributes. + * @param autoConfigureTalkersOn True if autoconfigure should be on. + */ + void setAutoConfigureTalkersOn(bool autoConfigureTalkersOn); + + /** + * Returns whether this application is a KTTS System Manager. + * When an application is a System Manager, commands affect all jobs. + * For example, a pause() command will pause all jobs of all applications. + * Defaults to False. + */ + bool isSystemManager() const; + + /** + * Sets whether this application is a KTTS System Manager. + * @param isSystemManager True or False. + */ + void setIsSystemManager(bool isSystemManager); + + /** + * Return the JobNum of the last job queued by the application. + * 0 if none. + */ + int lastJobNum() const; + + /** + * List of jobs for this app. Caller may add or delete jobs, + * but must not delete the list. + */ + TJobListPtr jobList() const; + +private: + AppDataPrivate* d; +}; + +#endif diff --git a/kttsd/kttsd/configdata.cpp b/kttsd/kttsd/configdata.cpp new file mode 100644 index 00000000..d0b7bfe8 --- /dev/null +++ b/kttsd/kttsd/configdata.cpp @@ -0,0 +1,165 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This class holds KTTS data from config file. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include +#include + +// KDE includes. +#include +#include + +// KTTS includes. +#include "talkercode.h" +#include "notify.h" + +// ConfigData includes. +#include "configdata.h" + +ConfigData::ConfigData(KConfig* config) : m_config(config) +{ + readConfig(); +} + +ConfigData::~ConfigData() { delete m_config; } + +KConfig* ConfigData::config() { return m_config; } + +bool ConfigData::readConfig() +{ + // Load configuration + + // Set the group general for the configuration of KTTSD itself (no plug ins) + m_config->setGroup("General"); + + // Load the configuration of the text interruption messages and sound + textPreMsgEnabled = m_config->readEntry("TextPreMsgEnabled", QVariant(false)).toBool(); + textPreMsg = m_config->readEntry("TextPreMsg"); + + textPreSndEnabled = m_config->readEntry("TextPreSndEnabled", QVariant(false)).toBool(); + textPreSnd = m_config->readEntry("TextPreSnd"); + + textPostMsgEnabled = m_config->readEntry("TextPostMsgEnabled", QVariant(false)).toBool(); + textPostMsg = m_config->readEntry("TextPostMsg"); + + textPostSndEnabled = m_config->readEntry("TextPostSndEnabled", QVariant(false)).toBool(); + textPostSnd = m_config->readEntry("TextPostSnd"); + keepAudio = m_config->readEntry("KeepAudio", QVariant(false)).toBool(); + keepAudioPath = m_config->readEntry("KeepAudioPath", + KStandardDirs::locateLocal("data", "kttsd/audio/")); + + // Notification (KNotify). + notify = m_config->readEntry("Notify", QVariant(false)).toBool(); + notifyExcludeEventsWithSound = m_config->readEntry("ExcludeEventsWithSound", QVariant(true)).toBool(); + loadNotifyEventsFromFile(KStandardDirs::locateLocal("config", "kttsd_notifyevents.xml"), true ); + + // KTTSMgr auto start and auto exit. + autoStartManager = m_config->readEntry("AutoStartManager", QVariant(false)).toBool(); + autoExitManager = m_config->readEntry("AutoExitManager", QVariant(false)).toBool(); + + // Default to Phonon (0). + playerOption = m_config->readEntry("AudioOutputMethod", 0); + + // Map 50% to 100% onto 2.0 to 0.5. + audioStretchFactor = 1.0/(float(m_config->readEntry("AudioStretchFactor", 100))/100.0); + switch (playerOption) + { + case 0: + case 1: break; + case 2: + m_config->setGroup("ALSAPlayer"); + sinkName = m_config->readEntry("PcmName", "default"); + if ("custom" == sinkName) + sinkName = m_config->readEntry("CustomPcmName", "default"); + periodSize = m_config->readEntry("PeriodSize", 128); + periods = m_config->readEntry("Periods", 8); + playerDebugLevel = m_config->readEntry("DebugLevel", 1); + break; + } + + return true; +} + +void ConfigData::loadNotifyEventsFromFile( const QString& filename, bool clear) +{ + // Open existing event list. + QFile file( filename ); + if ( !file.open( QIODevice::ReadOnly ) ) { + kDebug() << "SpeechData::loadNotifyEventsFromFile: Unable to open file " << filename << endl; + return; + } + // QDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" ); + QDomDocument doc( "" ); + if ( !doc.setContent( &file ) ) { + file.close(); + kDebug() << "SpeechData::loadNotifyEventsFromFile: File not in proper XML format. " << filename << endl; + } + // kDebug() << "StringReplacerConf::load: document successfully parsed." << endl; + file.close(); + + if ( clear ) { + notifyDefaultPresent = NotifyPresent::Passive; + notifyDefaultOptions.action = NotifyAction::SpeakMsg; + notifyDefaultOptions.talker.clear(); + notifyDefaultOptions.customMsg.clear(); + notifyAppMap.clear(); + } + + // Event list. + QDomNodeList eventList = doc.elementsByTagName("notifyEvent"); + const int eventListCount = eventList.count(); + for (int eventIndex = 0; eventIndex < eventListCount; ++eventIndex) + { + QDomNode eventNode = eventList.item(eventIndex); + QDomNodeList propList = eventNode.childNodes(); + QString eventSrc; + QString event; + QString actionName; + QString message; + TalkerCode talkerCode; + const int propListCount = propList.count(); + for (int propIndex = 0; propIndex < propListCount; ++propIndex) { + QDomNode propNode = propList.item(propIndex); + QDomElement prop = propNode.toElement(); + if (prop.tagName() == "eventSrc") eventSrc = prop.text(); + if (prop.tagName() == "event") event = prop.text(); + if (prop.tagName() == "action") actionName = prop.text(); + if (prop.tagName() == "message") message = prop.text(); + if (prop.tagName() == "talker") talkerCode = TalkerCode(prop.text(), false); + } + NotifyOptions notifyOptions; + notifyOptions.action = NotifyAction::action( actionName ); + notifyOptions.talker = talkerCode.getTalkerCode(); + notifyOptions.customMsg = message; + if ( eventSrc != "default" ){ + notifyOptions.eventName = NotifyEvent::getEventName( eventSrc, event ); + NotifyEventMap notifyEventMap = notifyAppMap[ eventSrc ]; + notifyEventMap[ event ] = notifyOptions; + notifyAppMap[ eventSrc ] = notifyEventMap; + } else { + notifyOptions.eventName.clear(); + notifyDefaultPresent = NotifyPresent::present( event ); + notifyDefaultOptions = notifyOptions; + } + } +} diff --git a/kttsd/kttsd/configdata.h b/kttsd/kttsd/configdata.h new file mode 100644 index 00000000..b9ce3cb0 --- /dev/null +++ b/kttsd/kttsd/configdata.h @@ -0,0 +1,211 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This class holds KTTS data from config file. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _CONFIGDATA_H_ +#define _CONFIGDATA_H_ + +// Qt includes. +#include +#include + +class KConfig; + +/** + * Struct used to keep notification options. + */ +struct NotifyOptions { + QString eventName; + int action; + QString talker; + QString customMsg; +}; + +/** + * A list of notification options for a single app, indexed by event. + */ +typedef QMap NotifyEventMap; + +/** + * A list of notification event maps for all apps, indexed by app. + */ +typedef QMap NotifyAppMap; + +class ConfigData +{ +public: + ConfigData(KConfig* config); + ~ConfigData(); + + KConfig* config(); + + /** + * Text pre message + */ + QString textPreMsg; + + /** + * Text pre message enabled ? + */ + bool textPreMsgEnabled; + + /** + * Text pre sound + */ + QString textPreSnd; + + /** + * Text pre sound enabled ? + */ + bool textPreSndEnabled; + + /** + * Text post message + */ + QString textPostMsg; + + /** + * Text post message enabled ? + */ + bool textPostMsgEnabled; + + /** + * Text post sound + */ + QString textPostSnd; + + /** + * Text post sound enabled ? + */ + bool textPostSndEnabled; + + /** + * Paragraph pre message + */ + QString parPreMsg; + + /** + * Paragraph pre message enabled ? + */ + bool parPreMsgEnabled; + + /** + * Paragraph pre sound + */ + QString parPreSnd; + + /** + * Paragraph pre sound enabled ? + */ + bool parPreSndEnabled; + + /** + * Paragraph post message + */ + QString parPostMsg; + + /** + * Paragraph post message enabled ? + */ + bool parPostMsgEnabled; + + /** + * Paragraph post sound + */ + QString parPostSnd; + + /** + * Paragraph post sound enabled ? + */ + bool parPostSndEnabled; + + /** + * Keep audio files. Do not delete generated tmp wav files. + */ + bool keepAudio; + QString keepAudioPath; + + /** + * Notification settings. + */ + bool notify; + bool notifyExcludeEventsWithSound; + NotifyAppMap notifyAppMap; + int notifyDefaultPresent; + NotifyOptions notifyDefaultOptions; + + /** + * Automatically start KTTSMgr whenever speaking. + */ + bool autoStartManager; + + /** + * Automatically exit auto-started KTTSMgr when speaking finishes. + */ + bool autoExitManager; + + /** + * Which audio player to use. + * 0 = aRts + * 1 = gstreamer + * 2 = ALSA + */ + int playerOption; + + /** + * Audio stretch factor (Speed). + */ + float audioStretchFactor; + + /** + * GStreamer sink name to use, or ALSA PCM device name. + */ + QString sinkName; + + /** + * Some parameters used by ALSA plugin. + * Size of buffer interrupt period (in frames) + * Number of periods in buffer. + */ + uint periodSize; + uint periods; + + /** + * Debug level in players. + */ + uint playerDebugLevel; + +private: + /** + * Read the configuration + */ + bool readConfig(); + + /** + * Read application notification options from file. + */ + void loadNotifyEventsFromFile( const QString& filename, bool clear); + + KConfig* m_config; +}; + +#endif diff --git a/kttsd/kttsd/filtermgr.cpp b/kttsd/kttsd/filtermgr.cpp index 38b1784c..98cc6c08 100644 --- a/kttsd/kttsd/filtermgr.cpp +++ b/kttsd/kttsd/filtermgr.cpp @@ -67,26 +67,27 @@ FilterMgr::~FilterMgr() * @param config Settings object. * @return False if FilterMgr is not ready to filter. */ -bool FilterMgr::init(KConfig *config, const QString& /*configGroup*/) +bool FilterMgr::init() { // Load each of the filters and initialize. - config->setGroup("General"); - QStringList filterIDsList = config->readEntry("FilterIDs", QStringList(), ','); + KConfig config("kttsdrc"); + config.setGroup("General"); + QStringList filterIDsList = config.readEntry("FilterIDs", QStringList(), ','); // kDebug() << "FilterMgr::init: FilterIDs = " << filterIDsList << endl; // If no filters have been configured, automatically configure the standard SBD. if (filterIDsList.isEmpty()) { - config->setGroup("Filter_1"); - config->writeEntry("DesktopEntryName", "kttsd_sbdplugin"); - config->writeEntry("Enabled", true); - config->writeEntry("IsSBD", true); - config->writeEntry("MultiInstance", true); - config->writeEntry("SentenceBoundary", "\\1\\t"); - config->writeEntry("SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))"); - config->writeEntry("UserFilterName", i18n("Standard Sentence Boundary Detector")); - config->setGroup("General"); - config->writeEntry("FilterIDs", "1"); - filterIDsList = config->readEntry("FilterIDs", QStringList(), ','); + config.setGroup("Filter_1"); + config.writeEntry("DesktopEntryName", "kttsd_sbdplugin"); + config.writeEntry("Enabled", true); + config.writeEntry("IsSBD", true); + config.writeEntry("MultiInstance", true); + config.writeEntry("SentenceBoundary", "\\1\\t"); + config.writeEntry("SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))"); + config.writeEntry("UserFilterName", i18n("Standard Sentence Boundary Detector")); + config.setGroup("General"); + config.writeEntry("FilterIDs", "1"); + filterIDsList = config.readEntry("FilterIDs", QStringList(), ','); } if ( !filterIDsList.isEmpty() ) { @@ -95,8 +96,8 @@ bool FilterMgr::init(KConfig *config, const QString& /*configGroup*/) { QString filterID = *it; QString groupName = "Filter_" + filterID; - config->setGroup( groupName ); - QString desktopEntryName = config->readEntry( "DesktopEntryName" ); + config.setGroup( groupName ); + QString desktopEntryName = config.readEntry( "DesktopEntryName" ); // If a DesktopEntryName is not in the config file, it was configured before // we started using them, when we stored translated plugin names instead. // Try to convert the translated plugin name to a DesktopEntryName. @@ -104,23 +105,23 @@ bool FilterMgr::init(KConfig *config, const QString& /*configGroup*/) // and DesktopEntryName won't change. if (desktopEntryName.isEmpty()) { - QString filterPlugInName = config->readEntry("PlugInName", QString()); + QString filterPlugInName = config.readEntry("PlugInName", QString()); // See if the translated name will untranslate. If not, well, sorry. desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); // Record the DesktopEntryName from now on. - if (!desktopEntryName.isEmpty()) config->writeEntry("DesktopEntryName", desktopEntryName); + if (!desktopEntryName.isEmpty()) config.writeEntry("DesktopEntryName", desktopEntryName); } - if (config->readEntry("Enabled",QVariant(false)).toBool() || config->readEntry("IsSBD",QVariant(false)).toBool()) + if (config.readEntry("Enabled",QVariant(false)).toBool() || config.readEntry("IsSBD",QVariant(false)).toBool()) { // kDebug() << "FilterMgr::init: filterID = " << filterID << endl; KttsFilterProc* filterProc = loadFilterPlugin( desktopEntryName ); if ( filterProc ) { - filterProc->init( config, groupName ); + filterProc->init( &config, groupName ); m_filterList.append( filterProc ); } - if (config->readEntry("DocType").contains("html") || - config->readEntry("RootElement").contains("html")) + if (config.readEntry("DocType").contains("html") || + config.readEntry("RootElement").contains("html")) m_supportsHTML = true; } } @@ -230,6 +231,8 @@ void FilterMgr::nextFilter() m_filterProc = m_filterList.at(m_filterIndex); if ( m_noSBD && m_filterProc->isSBD() ) { + // kDebug() << "FilterMgr::nextFilter: skipping plugin " << m_filterIndex + // << " because it is an SBD filter and noSBD is true." << endl; m_state = fsFinished; // Post an event which will be later emitted as a signal. QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 301)); @@ -250,6 +253,7 @@ void FilterMgr::nextFilter() nextFilter(); } } else { + // kDebug() << "FilterMgr::nextFilter: calling convert on filter " << m_filterIndex << endl; m_text = m_filterProc->convert( m_text, m_talkerCode, m_appId ); nextFilter(); } @@ -372,6 +376,7 @@ KttsFilterProc* FilterMgr::loadFilterPlugin(const QString& desktopEntryName) &errorNo); if(plugIn){ // If everything went ok, return the plug in pointer. + // kDebug() << "FilterMgr::loadFilterPlugin: plugin " << offers[0]->library().toLatin1() << " loaded successfully." << endl; return plugIn; } else { // Something went wrong, returning null. diff --git a/kttsd/kttsd/filtermgr.h b/kttsd/kttsd/filtermgr.h index f3944f59..3a6a3607 100644 --- a/kttsd/kttsd/filtermgr.h +++ b/kttsd/kttsd/filtermgr.h @@ -55,14 +55,12 @@ class FilterMgr : public KttsFilterProc /** * Initialize the filters. - * @param config Settings object. - * @param configGroup Settings Group. * @return False if filter is not ready to filter. * * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain * separate configuration files of their own. */ - virtual bool init(KConfig *config, const QString &configGroup); + virtual bool init(); /** * Returns True if this filter is a Sentence Boundary Detector. diff --git a/kttsd/kttsd/kspeech.cpp b/kttsd/kttsd/kspeech.cpp new file mode 100644 index 00000000..47227471 --- /dev/null +++ b/kttsd/kttsd/kspeech.cpp @@ -0,0 +1,725 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KSpeech + + The KDE Text-to-Speech object. + ------------------------------ + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include +#include +#include +#include + +// KDE includes. +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KTTS includes. +#include "notify.h" + +// KTTSD includes. +#include "configdata.h" +#include "speechdata.h" +#include "talkermgr.h" +#include "speaker.h" + +// KSpeech includes. +#include "kspeechadaptor_p.h" + +/* KSpeechPrivate Class ================================================== */ + +class KSpeechPrivate +{ + KSpeechPrivate() : + configData(NULL), + talkerMgr(NULL), + speechData(NULL), + speaker(NULL) + { + } + + ~KSpeechPrivate() + { + delete configData; + delete talkerMgr; + delete speechData; + delete speaker; + } + + friend class KSpeech; + +protected: + /* + * The DBUS sender ID of last application to call KTTSD. + */ + QString callingAppId; + + /** + * Configuration data. + */ + ConfigData* configData; + + /* + * TalkerMgr keeps a list of all the Talkers (synth plugins). + */ + TalkerMgr* talkerMgr; + + /* + * SpeechData containing all the data and the manipulating methods for all KTTSD + */ + SpeechData* speechData; + + /* + * Speaker that will be run as another thread, actually saying the messages, warnings, and texts + */ + Speaker* speaker; +}; + +/* KSpeech Class ========================================================= */ + +/* ---- Public Methods --------------------------------------------------- */ + +KSpeech::KSpeech(QObject *parent) : + QObject(parent), d(new KSpeechPrivate()) +{ + kDebug() << "KSpeech::KSpeech Running" << endl; + new KSpeechAdaptor(this); + QDBus::sessionBus().registerObject("/KSpeech", this, QDBusConnection::ExportAdaptors); + ready(); +} + +KSpeech::~KSpeech(){ + kDebug() << "KSpeech::~KSpeech:: Stopping KTTSD service" << endl; + if (d->speaker) d->speaker->requestExit(); + delete d; + announceEvent("~KSpeech", "kttsdExiting"); + kttsdExiting(); +} + +/* ---- DBUS exported functions ------------------------------------------ */ + +bool KSpeech::isSpeaking() const +{ + return d->speaker->isSpeaking(); +} + +QString KSpeech::version() const +{ + return kapp->aboutData()->version(); +} + +QString KSpeech::applicationName() +{ + return d->speechData->getAppData(callingAppId())->applicationName(); +} + +void KSpeech::setApplicationName(const QString &applicationName) +{ + d->speechData->getAppData(callingAppId())->setApplicationName(applicationName); +} + +QString KSpeech::defaultTalker() +{ + return d->speechData->getAppData(callingAppId())->defaultTalker(); +} + +void KSpeech::setDefaultTalker(const QString &defaultTalker) +{ + d->speechData->getAppData(callingAppId())->setDefaultTalker(defaultTalker); +} + +int KSpeech::defaultPriority() +{ + return d->speechData->getAppData(callingAppId())->defaultPriority(); +} + +void KSpeech::setDefaultPriority(int defaultPriority) +{ + d->speechData->getAppData(callingAppId())->setDefaultPriority((JobPriority)defaultPriority); +} + +QString KSpeech::sentenceDelimiter() +{ + return d->speechData->getAppData(callingAppId())->sentenceDelimiter(); +} + +void KSpeech::setSentenceDelimiter(const QString &sentenceDelimiter) +{ + d->speechData->getAppData(callingAppId())->setSentenceDelimiter(sentenceDelimiter); +} + +bool KSpeech::filteringOn() +{ + return d->speechData->getAppData(callingAppId())->filteringOn(); +} + +void KSpeech::setFilteringOn(bool filteringOn) +{ + d->speechData->getAppData(callingAppId())->setFilteringOn(filteringOn); +} + +bool KSpeech::autoConfigureTalkersOn() +{ + return d->speechData->getAppData(callingAppId())->autoConfigureTalkersOn(); +} + +void KSpeech::setAutoConfigureTalkersOn(bool autoConfigureTalkersOn) +{ + d->speechData->getAppData(callingAppId())->setAutoConfigureTalkersOn(autoConfigureTalkersOn); +} + +bool KSpeech::isApplicationPaused() +{ + return d->speechData->isApplicationPaused(callingAppId()); +} + +QString KSpeech::htmlFilterXsltFile() +{ + return d->speechData->getAppData(callingAppId())->htmlFilterXsltFile(); +} + +void KSpeech::setHtmlFilterXsltFile(const QString &htmlFilterXsltFile) +{ + d->speechData->getAppData(callingAppId())->setHtmlFilterXsltFile(htmlFilterXsltFile); +} + +QString KSpeech::ssmlFilterXsltFile() +{ + return d->speechData->getAppData(callingAppId())->ssmlFilterXsltFile(); +} + +void KSpeech::setSsmlFilterXsltFile(const QString &ssmlFilterXsltFile) +{ + d->speechData->getAppData(callingAppId())->setSsmlFilterXsltFile(ssmlFilterXsltFile); +} + +bool KSpeech::isSystemManager() +{ + return d->speechData->getAppData(callingAppId())->isSystemManager(); +} + +void KSpeech::setIsSystemManager(bool isSystemManager) +{ + d->speechData->getAppData(callingAppId())->setIsSystemManager(isSystemManager); +} + +int KSpeech::say(const QString &text, int options) { + if (!d->speaker) return 0; + kDebug() << "KSpeech::say: Adding '" << text << "' to queue." << endl; + int jobNum = d->speechData->say(callingAppId(), text, options); + d->speaker->doUtterances(); + return jobNum; +} + +int KSpeech::sayFile(const QString &filename, const QString &encoding) +{ + // kDebug() << "KSpeech::setFile: Running" << endl; + if (!d->speaker) return 0; + QFile file(filename); + int jobNum = 0; + if ( file.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&file); + if (!encoding.isEmpty()) + { + QTextCodec* codec = QTextCodec::codecForName(encoding.toLatin1()); + if (codec) stream.setCodec(codec); + } + jobNum = d->speechData->say(callingAppId(), stream.readAll(), 0); + file.close(); + } + return jobNum; +} + +int KSpeech::sayClipboard() +{ + // Get the clipboard object. + QClipboard *cb = kapp->clipboard(); + + // Copy text from the clipboard. + QString text = cb->text(); + + // Speak it. + if (!text.isNull()) + return d->speechData->say(callingAppId(), text, 0); + else + return 0; +} + +void KSpeech::pause() +{ + if (!d->speaker) return; + d->speechData->pause(callingAppId()); + d->speaker->pause(callingAppId()); +} + +void KSpeech::resume() +{ + if (!d->speaker) return; + d->speechData->resume(callingAppId()); + d->speaker->doUtterances(); +} + +void KSpeech::removeJob(int jobNum) +{ + jobNum = applyDefaultJobNum(jobNum); + d->speechData->removeJob(jobNum); + d->speaker->removeJob(jobNum); +} + +void KSpeech::removeAllJobs() +{ + d->speechData->removeAllJobs(callingAppId()); + d->speaker->removeAllJobs(callingAppId()); +} + +int KSpeech::getSentenceCount(int jobNum) +{ + jobNum = applyDefaultJobNum(jobNum); + return d->speechData->sentenceCount(jobNum); +} + +int KSpeech::getCurrentJob() +{ + return d->speaker->getCurrentJobNum(); +} + +int KSpeech::getJobCount(int priority) +{ + return d->speechData->jobCount(callingAppId(), (JobPriority)priority); +} + +QStringList KSpeech::getJobNumbers(int priority) +{ + return d->speechData->jobNumbers(callingAppId(), (JobPriority)priority); +} + +int KSpeech::getJobState(int jobNum) +{ + jobNum = applyDefaultJobNum(jobNum); + return d->speechData->jobState(jobNum); +} + +QByteArray KSpeech::getJobInfo(int jobNum) +{ + jobNum = applyDefaultJobNum(jobNum); + return d->speechData->jobInfo(jobNum); +} + +QString KSpeech::getJobSentence(int jobNum, int seq) +{ + jobNum = applyDefaultJobNum(jobNum); + return d->speechData->jobSentence(jobNum, seq); +} + +QStringList KSpeech::getTalkerCodes() +{ + if (!d->talkerMgr) return QStringList(); + return d->talkerMgr->getTalkers(); +} + +QString KSpeech::talkerToTalkerId(const QString &talker) +{ + return d->talkerMgr->talkerCodeToTalkerId(talker); +} + +int KSpeech::getTalkerCapabilities1(const QString &talker) +{ + // TODO: + Q_UNUSED(talker); + return 0; +} + +int KSpeech::getTalkerCapabilities2(const QString &talker) +{ + // TODO: + Q_UNUSED(talker); + return 0; +} + +QStringList KSpeech::getTalkerVoices(const QString &talker) +{ + // TODO: + Q_UNUSED(talker); + return QStringList(); +} + +void KSpeech::changeJobTalker(int jobNum, const QString &talker) +{ + jobNum = applyDefaultJobNum(jobNum); + d->speechData->setTalker(jobNum, talker); +} + +void KSpeech::moveJobLater(int jobNum) +{ + jobNum = applyDefaultJobNum(jobNum); + d->speaker->moveJobLater(jobNum); +} + +int KSpeech::moveRelSentence(int jobNum, int n) +{ + jobNum = applyDefaultJobNum(jobNum); + int seq = d->speaker->moveRelSentence(jobNum, n); + return seq; +} + +void KSpeech::showManagerDialog() +{ + QString cmd = "kcmshell kcmkttsd --caption "; + cmd += "'" + i18n("KDE Text-to-Speech") + "'"; + KRun::runCommand(cmd); +} + +void KSpeech::kttsdExit() +{ + d->speechData->removeAllJobs("kttsd"); + d->speaker->removeAllJobs("kttsd"); + announceEvent("kttsdExit", "kttsdExiting"); + kttsdExiting(); + kapp->quit(); +} + +void KSpeech::reinit() +{ + // Restart ourself. + kDebug() << "KSpeech::reinit: Running" << endl; + if (d->speaker) + { + kDebug() << "KSpeech::reinit: Stopping KTTSD service" << endl; + if (d->speaker->isSpeaking()) + d->speaker->pause("kttsd"); + d->speaker->requestExit(); + } + delete d->speaker; + d->speaker = 0; + delete d->talkerMgr; + d->talkerMgr = 0; + ready(); +} + +void KSpeech::setCallingAppId(const QString& appId) +{ + d->callingAppId = appId; +} + +/* ---- Private Methods ------------------------------------------ */ + +bool KSpeech::initializeConfigData() +{ + if (d->configData) delete d->configData; + d->configData = new ConfigData(KGlobal::config()); + return true; +} + +bool KSpeech::ready() +{ + if (d->speaker) return true; + kDebug() << "KSpeech::ready: Starting KTTSD service" << endl; + if (!initializeSpeechData()) return false; + if (!initializeTalkerMgr()) return false; + if (!initializeSpeaker()) return false; + d->speaker->doUtterances(); + announceEvent("ready", "kttsdStarted"); + kttsdStarted(); + return true; +} + +bool KSpeech::initializeSpeechData() +{ + // Create speechData object. + if (!d->speechData) + { + d->speechData = new SpeechData(); + connect (d->speechData, SIGNAL(jobStateChanged(const QString&, int, KSpeech::JobState)), + this, SLOT(slotJobStateChanged(const QString&, int, KSpeech::JobState))); + connect (d->speechData, SIGNAL(filteringFinished()), + this, SLOT(slotFilteringFinished())); + + // TODO: Hook KNotify signal. + // if (!connectDCOPSignal(0, 0, + // "notifySignal(QString,QString,QString,QString,QString,int,int,int,int)", + // "notificationSignal(QString,QString,QString,QString,QString,int,int,int,int)", + // false)) kDebug() << "KTTSD:initializeSpeechData: connectDCOPSignal for knotify failed" << endl; + } + if (!d->configData) initializeConfigData(); + d->speechData->setConfigData(d->configData); + + // Establish ourself as a System Manager application. + d->speechData->getAppData("kttsd")->setIsSystemManager(true); + + return true; +} + +bool KSpeech::initializeTalkerMgr() +{ + if (!d->talkerMgr) + { + if (!d->speechData) initializeSpeechData(); + + d->talkerMgr = new TalkerMgr(this, "kttsdtalkermgr"); + int load = d->talkerMgr->loadPlugIns(d->configData->config()); + // If no Talkers configured, try to autoconfigure one, first in the user's + // desktop language, but if that fails, fallback to English. + if (load < 0) + { + QString languageCode = KGlobal::locale()->language(); + if (d->talkerMgr->autoconfigureTalker(languageCode, d->configData->config())) + load = d->talkerMgr->loadPlugIns(d->configData->config()); + else + { + if (d->talkerMgr->autoconfigureTalker("en", d->configData->config())) + load = d->talkerMgr->loadPlugIns(d->configData->config()); + } + } + if (load < 0) + { + // TODO: Would really like to eliminate ALL GUI stuff from kttsd. Find + // a better way to do this. + delete d->speaker; + d->speaker = 0; + delete d->talkerMgr; + d->talkerMgr = 0; + delete d->speechData; + d->speechData = 0; + kDebug() << "KSpeech::initializeTalkerMgr: no Talkers have been configured." << endl; + // Ask if user would like to run configuration dialog, but don't bug user unnecessarily. + QString dontAskConfigureKTTS = "DontAskConfigureKTTS"; + KMessageBox::ButtonCode msgResult; + if (KMessageBox::shouldBeShownYesNo(dontAskConfigureKTTS, msgResult)) + { + if (KMessageBox::questionYesNo( + 0, + i18n("KTTS has not yet been configured. At least one Talker must be configured. " + "Would you like to configure it now?"), + i18n("KTTS Not Configured"), + i18n("Configure"), + i18n("Do Not Configure"), + dontAskConfigureKTTS) == KMessageBox::Yes) msgResult = KMessageBox::Yes; + } + if (msgResult == KMessageBox::Yes) showManagerDialog(); + return false; + } + } + d->speechData->setTalkerMgr(d->talkerMgr); + return true; +} + +bool KSpeech::initializeSpeaker() +{ + // kDebug() << "KSpeech::initializeSpeaker: Instantiating Speaker" << endl; + + if (!d->talkerMgr) initializeTalkerMgr(); + + // Create speaker object and load plug ins; + d->speaker = new Speaker(d->speechData, d->talkerMgr, this); + + connect (d->speaker, SIGNAL(marker(const QString&, int, KSpeech::MarkerType, const QString&)), + this, SLOT(slotMarker(const QString&, int, KSpeech::MarkerType, const QString&))); + + d->speaker->setConfigData(d->configData); + + return true; +} + +/** +* This signal is emitted by KNotify when a notification event occurs. +* ds << event << fromApp << text << sound << file << present << level +* << winId << eventId; +* default_presentation contains these ORed events: None=0, Sound=1, Messagebox=2, Logfile=4, Stderr=8, +* PassivePopup=16, Execute=32, Taskbar=64 +*/ +void KSpeech::notificationSignal( const QString& event, const QString& fromApp, + const QString &text, const QString& sound, const QString& /*file*/, + const int present, const int /*level*/, const int /*windId*/, const int /*eventId*/) +{ + if (!d->speaker) return; + // kDebug() << "KTTSD:notificationSignal: event: " << event << " fromApp: " << fromApp << + // " text: " << text << " sound: " << sound << " file: " << file << " present: " << present << + // " level: " << level << " windId: " << windId << " eventId: " << eventId << endl; + if ( d->configData->notify ) + if ( !d->configData->notifyExcludeEventsWithSound || sound.isEmpty() ) + { + bool found = false; + NotifyOptions notifyOptions; + QString msg; + QString talker; + // Check for app-specific action. + if ( d->configData->notifyAppMap.contains( fromApp ) ) + { + NotifyEventMap notifyEventMap = d->configData->notifyAppMap[ fromApp ]; + if ( notifyEventMap.contains( event ) ) + { + found = true; + notifyOptions = notifyEventMap[ event ]; + } else { + // Check for app-specific default. + if ( notifyEventMap.contains( "default" ) ) + { + found = true; + notifyOptions = notifyEventMap[ "default" ]; + notifyOptions.eventName.clear(); + } + } + } + // If no app-specific action, try default. + if ( !found ) + { + switch ( d->configData->notifyDefaultPresent ) + { + case NotifyPresent::None: + found = false; + break; + case NotifyPresent::Dialog: + found = ( + (present & KNotifyClient::Messagebox) + && + !(present & KNotifyClient::PassivePopup) + ); + break; + case NotifyPresent::Passive: + found = ( + !(present & KNotifyClient::Messagebox) + && + (present & KNotifyClient::PassivePopup) + ); + break; + case NotifyPresent::DialogAndPassive: + found = ( + (present & KNotifyClient::Messagebox) + && + (present & KNotifyClient::PassivePopup) + ); + break; + case NotifyPresent::All: + found = true; + break; + } + if ( found ) + notifyOptions = d->configData->notifyDefaultOptions; + } + if ( found ) + { + int action = notifyOptions.action; + talker = notifyOptions.talker; + switch ( action ) + { + case NotifyAction::DoNotSpeak: + break; + case NotifyAction::SpeakEventName: + if (notifyOptions.eventName.isEmpty()) + msg = NotifyEvent::getEventName( fromApp, event ); + else + msg = notifyOptions.eventName; + break; + case NotifyAction::SpeakMsg: + msg = text; + break; + case NotifyAction::SpeakCustom: + msg = notifyOptions.customMsg; + msg.replace( "%a", fromApp ); + msg.replace( "%m", text ); + if ( msg.contains( "%e" ) ) + { + if ( notifyOptions.eventName.isEmpty() ) + msg.replace( "%e", NotifyEvent::getEventName( fromApp, event ) ); + else + msg.replace( "%e", notifyOptions.eventName ); + } + break; + } + } + // Queue msg if we should speak something. + if ( !msg.isEmpty() ) + { + QString fromApps = fromApp + ",knotify"; + d->speechData->getAppData(fromApp)->setDefaultTalker(talker); + d->speechData->say(fromApp, msg, 0); + d->speaker->doUtterances(); + } + } +} + +void KSpeech::slotJobStateChanged(const QString& appId, int jobNum, KSpeech::JobState state) +{ + announceEvent("slotJobStateChanged", "jobStateChanged", appId, jobNum, state); + emit jobStateChanged(appId, jobNum, state); +} + +void KSpeech::slotMarker(const QString& appId, int jobNum, KSpeech::MarkerType markerType, const QString& markerData) +{ + announceEvent("slotMarker", "marker", appId, jobNum, markerType, markerData); + emit marker(appId, jobNum, markerType, markerData); +} + +void KSpeech::slotFilteringFinished() +{ + d->speaker->doUtterances(); +} + +QString KSpeech::callingAppId() +{ + // TODO: What would be nice is if there were a way to get the + // last DBUS sender() without having to add DBusMessage to every + // slot. Then it would not be necessary to hand-edit the adaptor. + return d->callingAppId; +} + +int KSpeech::applyDefaultJobNum(int jobNum) +{ + int jNum = jobNum; + if (!jNum) + { + jNum = d->speechData->findJobNumByAppId(callingAppId()); + if (!jNum) jNum = getCurrentJob(); + if (!jNum) jNum = d->speechData->findJobNumByAppId(0); + } + return jNum; +} + +void KSpeech::announceEvent(const QString& slotName, const QString& eventName) +{ + kDebug() << "KSpeech::" << slotName << ": emitting DBUS signal " << eventName << endl; +} + +void KSpeech::announceEvent(const QString& slotName, const QString& eventName, const QString& appId, + int jobNum, MarkerType markerType, const QString& markerData) +{ + kDebug() << "KSpeech::" << slotName << ": emitting DBUS signal " << eventName + << " with appId " << appId << " job number " << jobNum << " marker type " << markerType << " and data " << markerData << endl; +} + +void KSpeech::announceEvent(const QString& slotName, const QString& eventName, const QString& appId, + int jobNum, JobState state) +{ + kDebug() << "KSpeech::" << slotName << ": emitting DBUS signal " << eventName << + " with appId " << appId << " job number " << jobNum << " and state " << SpeechJob::jobStateToStr(state) << endl; +} + +#include "kspeech.moc" + diff --git a/kttsd/kttsd/kspeech.h b/kttsd/kttsd/kspeech.h new file mode 100644 index 00000000..7e3384a9 --- /dev/null +++ b/kttsd/kttsd/kspeech.h @@ -0,0 +1,1336 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KSpeech + + The KDE Text-to-Speech object. + ------------------------------ + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _KSPEECH_H_ +#define _KSPEECH_H_ + +/** + * @interface KSpeech + * + * kspeech - the KDE Text-to-Speech API. + * + * @version 2.0 Draft 1 + * + * @since KDE 4.0 + * + * This class defines the DBUS interface for applications desiring to speak text. + * Applications may speak text by sending DBUS messages to application + * "org.kde.kttsd", object path "/KSpeech", interface "org.kde.KSpeech". + * + * %KTTSD -- the KDE Text-to-Speech Daemon -- is the program that supplies the services + * in the KDE Text-to-Speech API. + * + * @warning The KSpeech interface is still being developed and is likely to change in the future. + * + * @section Features + * + * - Priority system for Screen Readers, warnings and messages, while still playing + * regular texts. + * - Long text is parsed into sentences. User may backup by sentence, + * replay, pause, and stop playing. + * - Handles multiple speaking applications. Speech requests are treated like print jobs. + * Jobs may be created, stopped, paused, resumed, and deleted. + * - Speak contents of clipboard. + * - Speak contents of a file. + * - Speak KDE notifications. + * - Plugin-based text job filtering permits substitution for misspoken words, + * abbreviations, etc., transformation of XML or XHTML to SSML, and automatic + * choice of appropriate synthesis engine. + * + * @section Requirements + * + * You may build any KDE application to use KSpeech, since the interface is in kdelibs, but + * the kdeaccessibility package must be installed for KTTS to function. + * + * You will need a speech synthesis engine, such as Festival. See the KTTS Handbook + * for the latest information on installing and configuring speech engines and voices + * with KTTS. + * + * @section goals Design Goals + * + * The KDE Text-to-Speech API is designed with the following goals: + * + * - Support the features enumerated above. + * - Plugin-based architecture for support of a wide variety of speech synthesis + * engines and drivers. + * - Permit generation of speech from the command line (or via shell scripts) + * using the KDE DBUS utilities. + * - Provide a lightweight and easily usable interface for applications to + * generate speech output. + * - Applications need not be concerned about contention over the speech device. + * - Provide limited support for speech markup languages, such as Sable, + * Java %Speech Markup Language (JSML), and %Speech Markup Meta-language (SMML). + * - Provide limited support for embedded speech markers. + * - Asynchronous to prevent system blocking. + * - Plugin-based audio architecture. Currently supports ALSA or Phonon. + * + * Architecturally, applications interface with %KTTSD, which performs queuing, + * speech job management, plugin management and sentence parsing. %KTTSD interfaces with a + * %KTTSD speech plugin(s), which then interfaces with the speech engine(s) or driver(s). + * + @verbatim + application + ^ + | via DBUS (the KDE Text-to-Speech API) + v + kttsd + ^ + | KTTSD plugin API + v + kttsd plugin + ^ + | + v + speech engine + @endverbatim + * + * The %KTTSD Plugin API is documented in PluginConf in the kdeaccessibility module. + * + * There is a separate GUI application, called kttsmgr, for providing %KTTSD + * configuration and job management. + * + * When a request for speech is made, usally via the say method, + * a speech job is queued. The order by which jobs are spoken is determined + * by their priority: + * - Screen Reader Output + * - Warnings + * - Messages + * - Text Jobs + * + * Screen Reader output.pre-empts any other speech in progress, + * including other Screen Reader outputs, i.e., it is not a queue. + * This is reserved for use by Screen Readers. + * + * Warnings take priority over Messages, which take priority + * over text jobs. Warnings and Messages are spoken when the currently-speaking + * sentence of a text job is finished. + * + * Text Jobs are the lowest priority. + * + * The priority of jobs is determined by the setDefaultPriority method. + * After setting the priority, all subsequent say commands are queued + * at that priority. + * + * Within a job, the application (and user + * via the kttsmgr GUI), may back up or advance by sentence, or rewind + * to the beginning. + * @See moveRelTextSentence. + + * All jobs may be paused, resumed or deleted (stopped) from the queue. + * See pause, resume, removeJob, and removeAllJobs. + * + * @section cmdline DBUS Command-line Interface + * + * Examples of using the KSpeech interface via command-line DBUS follow. + * + * To create a text job to be spoken + * + @verbatim + dbus org.kde.kttsd "/KSpeech" say + @endverbatim + * + * where \ is the text to be spoken, and \ is one of the + * options defined in the SayOptions enum. Normally, this can be entered + * as zero. + * + * Example. + * + @verbatim + dbus org.kde.kttsd "/KSpeech" say "This is a test" 0 + @endverbatim + * + * To stop speaking and delete the last queued job. + * + @verbatim + dbus org.kde.kttsd "/KSpeech" removeJob 0 + @endverbatim + * + * Depending upon the speech plugin used, speaking may not immediately stop. + * + * To pause a job. + * + @verbatim + dbus org.kde.kttsd "/KSpeech" pause + @endverbatim + * + * To resume + * + @verbatim + dbus org.kde.kttsd "/KSpeech" resume + @endverbatim + * + * Depending upon the speech plugin used, speaking may not immediately stop. + * + * To submit a German-speaking job. + * + @verbatim + dbus org.kde.kttsd "/KSpeech" setDefaultTalker "de" + dbus org.kde.kttsd "/KSpeech" say "Guten Tag." 0 + @endverbatim + * + * Note: For more information about talker codes, see talkers below. + * + * @section programming Calling KTTSD from a Program + * + * There are two methods of making DBUS calls from your application to %KTTSD. + * + * - Manually code them using dcopClient object. See kdebase/konqueror/kttsplugin/khtmlkttsd.cpp + * for an example. This method is recommended if you want to make a few simple calls to KTTSD. + * - Use kspeech_stub as described below. This method generates the marshalling code for you + * and is recommended for a more complex speech-enabled applications. kcmkttsmgr in the + * kdeaccessibility module is an example that uses this method. + * + * To make DCOP calls from your program using kspeech_stub, follow these steps: + * + * 1. Include kspeech_stub.h in your code. Derive an object from the KSpeech_stub interface. + * For example, suppose you are developing a KPart and want to call %KTTSD. + * Your class declaration might look like this: + * + @verbatim + #include + class MyPart: public KParts::ReadOnlyPart, public KSpeech_stub { + @endverbatim + * + * 2. In your class constructor, initialize DCOPStub, giving it the sender + * "kttsd", object "KSpeech". + * + @verbatim + MyPart::MyPart(QWidget *parent) : + KParts::ReadOnlyPart(parent), + DCOPStub("kttsd", "KSpeech") { + @endverbatim + * + * 3. See if KTTSD is running, and if not, start it. + * + @verbatim + DCOPClient *client = dcopClient(); + client->attach(); + if (!client->isApplicationRegistered("kttsd")) { + QString error; + if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error)) + cout << "Starting KTTSD failed with message " << error << endl; + } + @endverbatim + * + * If you want to detect if KTTSD is installed without starting it, use this code. + * + @verbatim + KTrader::OfferList offers = KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'"); + if (offers.count() > 0) + { + // KTTSD is installed. + } + @endverbatim + * + * Typically, you would do this to hide a menu item or button if KTTSD is not installed. + * + * 4. Make calls to KTTSD in your code. + * + @verbatim + uint jobNum = setText("Hello World", "en"); + startText(jobNum); + @endverbatim + * + * 4. Add kspeech_DIR and kspeech.stub to your Makefile.am. Example: + * + @verbatim + kspeech_DIR = $(kde_includes) + libmypart_la_SOURCES = kspeech.stub + @endverbatim + * + * @section signals Signals Emitted by KTTSD + * + * %KTTSD emits a number of DCOP signals, which provide information about sentences spoken, + * text jobs started, stopped, paused, resumed, finished, or deleted and markers seen. + * In general, these signals are broadcast to any application that connects to them. + * Applications should check the appId argument to determine whether the signal belongs to + * them or not. + * + * To receive %KTTSD DCOP signals, follow these steps: + * + * 1. Include kspeechsink.h in your code. Derive an object from the KSpeechSink interface + * and declare a method for each signal you'd like to receive. For example, + * if you were coding a KPart and wanted to receive the KTTSD signal sentenceStarted: + * + @verbatim + #include + class MyPart: + public KParts::ReadOnlyPart, + virtual public KSpeechSink + { + protected: + ASYNC sentenceStarted(const QByteArray& appId, const uint jobNum, const uint seq); + @endverbatim + * + * You can combine sending and receiving in one object. + * + @verbatim + #include + class MyPart: + public KParts::ReadOnlyPart, + public KSpeech_stub, + virtual public KSpeechSink + { + protected: + ASYNC sentenceStarted(const QByteArray& appId, const uint jobNum, const uint seq); + @endverbatim + * + * See below for the signals you can declare. + * + * 2. In your class constructor, initialize DCOPObject with the name of your DCOP + * receiving object. + * + @verbatim + MyPart::MyPart(QWidget *parent) : + KParts::ReadOnlyPart(parent), + DCOPObject("mypart_kspeechsink") { + @endverbatim + * + * Use any name you like. + * + * 3. Where appropriate (usually in your constructor), make sure your DCOPClient + * is registered and connect the %KTTSD DCOP signals to your declared receiving + * methods. + * + @verbatim + // Register DCOP client. + DCOPClient *client = kapp->dcopClient(); + if (!client->isRegistered()) + { + client->attach(); + client->registerAs(kapp->name()); + } + // Connect KTTSD DCOP signals to our slots. + connectDCOPSignal("kttsd", "KSpeech", + "sentenceStarted(QByteArray,uint,uint)", + "sentenceStarted(QByteArray,uint,uint)", + false); + @endverbatim + * + * Notice that the argument signatures differ slightly from the actual declarations. For + * example + * + @verbatim + ASYNC sentenceStarted(const QByteArray& appId, const uint jobNum, const uint seq); + @endverbatim + * + * becomes + * + @verbatim + "sentenceStarted(QByteArray,uint,uint)", + @endverbatim + * + * in the connectDCOPSignal call. + * + * 4. Write the definition for the received signal. Be sure to check whether the signal + * is intended for your application. + * + @verbatim + ASYNC MyPart::sentenceStarted(const QByteArray& appId, const uint jobNum, const uint seq) + { + // Check appId to determine if this is our signal. + if (appId != dcopClient()->appId()) return; + // Do something here. + } + @endverbatim + * + * 5. Add kspeechsink_DIR and kspeechsink.skel to your Makefile.am. Example for an app + * both sending and receiving. + * + @verbatim + kspeech_DIR = $(kde_includes) + kspeechsink_DIR = $(kde_includes) + libmypart_la_SOURCES = kspeech.stub kspeechsink.skel + @endverbatim + * + * @section talkers Talkers, Talker Codes, and Plugins + * + * Many of the methods permit you to specify a desired "talker". This + * may be a simple language code, such as "en" for English, "es" for Spanish, etc. + * Code as "" to use the default configured talker. + * + * Within KTTSMGR, the user has the ability to configure more than one talker for each language, + * with different voices, genders, volumes, and talking speeds. + * + * Talker codes serve two functions: + * - They identify configured plugins, and + * - They provide a way for applications to specify the desired speaking attributes + * that influence the choice of plugin to speak text. + * + * A Talker Code consists of a series of XML tags and attributes. + * An example of a full Talker Code with all attributes specified is + * + * + * + * + * + * (The @e voice and @e prosody tags are adapted from the W3C Speech Synthesis + * Markup Language (SSML) and Java Speech Markup Language (JSML). + * The @e kttsd tag is an extension to the SMML and JSML languages to support + * named synthesizers and text encodings.) + * %KTTS doesn't really care about the @e voice, @e prosody, and @e kttsd tags. In fact, + * they may be omitted and just the attributes specified. The example above then + * becomes + * + * lang="en" name="kal" gender="male" volume="soft" rate="fast" + * synthesizer="Festival" + * + * The attributes may be specified in any order. + * + * For clarity, the rest of the discussion + * will omit the @e voice, @e prosody, and @e kttsd tags. + * + * The attributes that make up a talker code are: + * + * - @e lang. Language code and optional country code. + * Examples: en, es, en_US, en_GB. Codes + * are case in-sensitive and hyphen (-) or underscore (_) may be + * used to separate the country code from the language code. + * - @e synthesizer. The name of the synthesizer (plugin) used to produce the speech. + * - @e gender. May be either "male", "female", or "neutral". + * - @e name. The name of the voice code. + * The choice of voice codes is synthesizer-specific. + * - @e volume. May be "loud", "medium", or "quiet". A synonym for "quiet" is + * "soft". + * - @e rate. May be "fast", "medium", or "slow". + * + * Each plugin, once it has been configured by a user in kttsmgr, returns a + * fully-specified talker code to identify itself. If the plugin supports it, + * the user may configure another instance of the plugin with a different set + * of attributes. This is the difference between a "plugin" and a "talker". + * A talker is a configured instance of a plugin. Each plugin (if it supports it) + * may be configured as multiple talkers. + * + * When the user configures %KTTSD, she configures one or more talkers and then + * places them in preferred order, top to bottom in kttsmgr. In effect, + * she specifies her preferences for each of the talkers. + * + * When applications specify a talker code, they need not (and typically do not) + * give a full specification. An example of a talker code with only some of the + * attributes specified might be + * + * lang="en" gender="female" + * + * If the talker code is not in XML attribute format, it assumed to be a @e lang + * attribute. So the talker code + * + * en + * + * is interpreted as + * + * lang="en" + * + * When a program requests a talker code in calls to setText, appendText, + * sayMessage, sayWarning, and sayScreenReaderOutput, + * %KTTSD tries to match the requested talker code to the closest matching + * configured talker. + * + * The @e lang attribute has highest priority (attempting to speak English with + * a Spanish synthesizer would likely be unintelligible). So the language + * attribute is said to have "priority". + * If an application does not specify a language attribute, a default one will be assumed. + * The rest of the attributes are said to be "preferred". If %KTTSD cannot find + * a talker with the exact preferred attributes requested, the closest matching + * talker will likely still be understandable. + * + * An application may specify that one or more of the attributes it gives in a talker + * code have priority by preceding each priority attribute with an asterisk. + * For example, the following talker code + * + * lang="en" gender="*female" volume="soft" + * + * means that the application wants to use a talker that supports American English language + * and Female gender. If there is more than one such talker, one that supports + * Soft volume would be preferred. Notice that a talker configured as English, Male, + * and Soft volume would not be picked as long as an English Female talker is + * available. + * + * The algorithm used by %KTTSD to find a matching talker is as follows: + * + * - If language code is not specified by the application, assume default configured + * by user. The primary language code automatically has priority. + * - (Note: This is not yet implemented.) + * If there are no talkers configured in the language, %KTTSD will attempt + * to automatically configure one (see automatic configuraton discussion below) + * - The talker that matches on the most priority attributes wins. + * - If a tie, the one that matches on the most preferred attributes wins. + * - If there is still a tie, the one nearest the top of the kttsmgr display + * (first configured) will be chosen. + * + * Language codes actually consist of two parts, a language code and an optional + * country code. For example, en_GB is English (United Kingdom). The language code is + * treated as a priority attribute, but the country code (if specified) is treated + * as preferred. So for example, if an application requests the following + * talker code + * + * lang="en_GB" gender="male" volume="medium" + * + * then a talker configured as lang="en" gender="male" volume="medium" would be + * picked over one configured as lang="en_GB" gender="female" volume="soft", + * since the former matches on two preferred attributes and the latter only on the + * preferred attribute GB. An application can override this and make the country + * code priority with an asterisk. For example, + * + * lang="*en_GB" gender="male" volume="medium" + * + * To specify that American English is priority, put an asterisk in front of + * en_US, like this. + * + * lang="*en_US" gender="male" volume="medium" + * + * Here the application is indicating that a talker that speaks American English + * has priorty over one that speaks a different form of English. + * + * (Note: Not yet implemented). + * If a language code is specified, and no plugin is currently configured + * with a matching language code, %KTTSD will attempt to automatically + * load and configure a plugin to support the requested language. If + * there is no such plugin, or there is a plugin but it cannot automatically + * configure itself, %KTTSD will pick one of the configured plugins using the + * algorithm given above. + * + * Notice that %KTTSD will always pick a talker, even if it is a terrible match. + * (The principle is that something heard is better than nothing at all. If + * it sounds terrible, user will change his configuration.) + * If an attribute is absolutely mandatory -- in other words the application + * must speak with the attribute or not at all -- the application can determine if + * there are any talkers configured with the attribute by calling getTalkers, + * and if there are none, display an error message to the user. + * + * Applications can implement their own talker-matching algorithm by + * calling getTalkers, then finding the desired talker from the returned + * list. When the full talker code is passed in, %KKTSD will find an exact + * match and use the specified talker. + * + * If an application requires a configuration that user has not created, + * it should display a message to user instructing them to run kttsmgr and + * configure the desired talker. (This must be done interactively because + * plugins often need user assistance locating voice files, etc.) + * + * The above scheme is designed to balance the needs + * of applications against user preferences. Applications are given the control + * they @e might need, without unnecessarily burdening the application author. + * If you are an application author, the above discussion might seem overly + * complicated. It isn't really all that complicated. Here are rules of thumb: + * + * - It is legitimate to give a NULL (0) talker code, in which case, the user's default + * talker will be used. + * - If you know the language code, give that in the talker code, otherwise + * leave it out. + * - If there is an attribute your application @e requires for proper functioning, + * specify that with an asterisk in front of it. For example, your app might + * speak in two different voices, Male and Female. (Since your + * app requires both genders, call getTalkers to determine if both genders + * are available, and if not, advise user to configure them. Better yet, + * give the user a choice of available distinquishing attributes + * (loud/soft, fast/slow, etc.) + * - If there are other attributes you would prefer, specify those without an + * asterisk, but leave them out if it doesn't really make any difference + * to proper functioning of your application. Let the user decide them + * when they configure %KTTS. + * + * One final note about talkers. %KTTSD does talker matching for each sentence + * spoken, just before the sentence is sent to a plugin for synthesis. Therefore, + * the user can change the effective talker in mid processing of a text job by + * changing his preferences, or even deleting or adding new talkers to the configuration. + * + * @section markup Speech Markup + * + * Note: %Speech Markup is not yet fully implemented in %KTTSD. + * + * Each of the five methods for queuing text to be spoken -- sayScreenReaderOutput, + * setText, appendText, sayMessage, and sayWarning -- may contain speech markup, + * provided that the plugin the user has configured supports that markup. The markup + * languages and plugins currently supported are: + * + * - %Speech Synthesis Markup language (SSML): Festival and Hadifix. + * + * This may change in the future as synthesizers improve. + * + * Before including markup in the text sent to kttsd, the application should + * query whether the currently-configured plugin + * supports the markup language by calling supportsMarkup. + * + * It it does not support the markup, it will be stripped out of the text. + * + * @section markers Support for Markers + * + * Note: Markers are not yet implemented in %KTTSD. + * + * When using a speech markup language, such as Sable, JSML, or SSML, the application may embed + * named markers into the text. If the user's chosen speech plugin supports markers, %KTTSD + * will emit DCOP signal markerSeen when the speech engine encounters the marker. + * Depending upon the speech engine and plugin, this may occur either when the speech engine + * encounters the marker during synthesis from text to speech, or when the speech is actually + * spoken on the audio device. The calling application can call the supportsMarkers + * method to determine if the currently configured plugin supports markers or not. + * + * @section sentenceparsing Sentence Parsing + * + * Not all speech engines provide robust capabilities for stopping synthesis that is in progress. + * To compensate for this, %KTTSD parses text jobs given to it by the setText and + * appendText methods into sentences and sends the sentences to the speech + * plugin one at a time. In this way, should the user wish to stop the speech + * output, they can do so, and the worst that will happen is that the last sentence + * will be completed. This is called Sentence Boundary Detection (SBD). + * + * Sentence Boundary Detection also permits the user to rewind by sentences. + * + * The default sentence delimiter used for plain text is as follows: + * + * - A period (.), question mark (?), exclamation mark (!), colon (:), or + * semi-colon (;) followed by whitespace (including newline), or + * - Two newlines in a row separated by optional whitespace, or + * - The end of the text. + * + * When given text containing speech markup, %KTTSD automatically determines the markup type + * and parses based on the sentence semantics of the markup language. + * + * An application may change the sentence delimiter by calling setSentenceDelimiter + * prior to calling setText. Changing the delimiter does not affect other + * applications. + * + * Text given to %KTTSD via the sayWarning, sayMessage, and sayScreenReaderOutput + * methods is @e not parsed into sentences. For this reason, applications + * should @e not send long messages with these methods. + * + * Sentence Boundary Detection is implemented as a plugin SBD filter. See + * filters for more information. + * + * @section filters Filters + * + * Users may specify filters in the kttsmgr GUI. Filters are plugins that modify the text + * to be spoken or change other characteristics of jobs. Currently, the following filter plugins + * are available: + * + * - String Replacer. Permits users to substitute for mispoken words, or vocalize chat + * emoticons. + * - XML Transformer. Given a particular XML or XHTML format, permits conversion of the + * XML to SSML (Speech Synthesis Markup Language) using XSLT (XML Style Language - Transforms) + * stylesheets. + * - Talker Chooser. Permits users to redirect jobs from one configured Talker to another + * based on the contents of the job or application that sent it. + * + * Additional plugins may be available in the future. + * + * In additional to these regular filters, KTTS also implements Sentence Boundary Detection (SBD) + * as a plugin filter. See sentenceparsing for more information. + * + * Regular filters are applied to Warnings, Messages, and Text jobs. SBD filters are + * only applied to regular Text jobs; they are not applied to Warnings and Messages. Screen + * Reader Outputs are never filtered. + * + * @section authors Authors + * + * @author José Pablo Ezequiel "Pupeno" Fernández + * @author Gary Cramblitt + * @author Olaf Schmidt + * @author Gunnar Schmi Dt + */ + +// Qt includes +#include +#include +#include +#include + +class KSpeechPrivate; +class KSpeech : public QObject +{ +Q_OBJECT + +public: + /** + * @enum JobPriority + */ + enum JobPriority + { + jpAll = 0, /**< All priorities. Used for information retrieval only. */ + jpScreenReaderOutput = 1, /**< Screen Reader job. */ + jpWarning = 2, /**< Warning job. */ + jpMessage = 3, /**< Message job.*/ + jpText = 4 /**< Text job. */ + }; + + /** + * @enum JobState + * Job states returned by method getJobState. + */ + enum JobState + { + jsQueued = 0, /**< Job has been queued but is not yet speakable. */ + jsFiltering = 1, /**< Job is being filtered. */ + jsSpeakable = 2, /**< Job is speakable, but is not speaking. */ + jsSpeaking = 3, /**< Job is currently speaking. */ + jsPaused = 4, /**< Job is paused. */ + jsInterrupted = 5, /**< Job is paused because it has been interrupted by another job. */ + jsFinished = 6, /**< Job is finished and is deleteable. */ + jsDeleted = 7 /**< Job is deleted from the queue. */ + }; + + /** + * @enum SayOptions + */ + enum SayOptions + { + soNone = 0x0000, /**< No options specified. Autodetected. */ + soPlainText = 0x0001, /**< The text contains plain text. */ + soHtml = 0x0002, /**< The text contains HTML markup. */ + soSsml = 0x0004, /**< The text contains SSML markup. */ + // FUTURE: + soChar = 0x0008, /**< The text should be spoken as individual characters. */ + soKey = 0x0010, /**< The text contains a keyboard symbolic key name. */ + soSoundIcon = 0x0020 /**< The text is the name of a sound icon. */ + }; + + /** + * @enum TalkerCapabilities1 + * All items marked FALSE are hard-coded off at this time. + */ + enum TalkerCapabilities1 + { + tcCanListVoices = 0x00000001, + tcCanSetVoiceByProperties = 0x00000002, + tcCanGetCurrentVoice = 0x00000004, + tcCanSetRateRelative = 0x00000008, /**< FALSE */ + tcCanSetRateAbsolute = 0x00000010, + tcCanGetRateDefault = 0x00000020, + tcCanSetPitchRelative = 0x00000040, /**< FALSE */ + tcCanSetPitchAbsolute = 0x00000080, + tcCanGetPitchDefault = 0x00000100, + tcCanSetPitchRangeRelative = 0x00000200, /**< FALSE */ + tcCanSetPitchRangeAbsolute = 0x00000400, /**< FALSE */ + tcCanGetPitchRangeDefault = 0x00000800, /**< FALSE */ + tcCanSetVolumeRelative = 0x00001000, /**< FALSE */ + tcCanSetVolumeAbsolute = 0x00002000, + tcCanGetVolumeDefault = 0x00004000, + tcCanSetPunctuationModeAll = 0x00008000, /**< FALSE */ + tcCanSetPunctuationModeNone = 0x00010000, /**< FALSE */ + tcCanSetPunctuationModeSome = 0x00020000, /**< FALSE */ + tcCanSetPunctuationDetail = 0x00040000, /**< FALSE */ + tcCanSetCapitalLettersModeSpelling = 0x00080000, /**< FALSE */ + tcCanSetCapitalLettersModeIcon = 0x00100000, /**< FALSE */ + tcCanSetCapitalLettersModePitch = 0x00200000, /**< FALSE */ + tcCanSetNumberGrouping = 0x00400000, /**< FALSE */ + tcCanSayTextFromPosition = 0x00800000, /**< FALSE */ + tcCanSayChar = 0x01000000, /**< FALSE */ + tcCanSayKey = 0x02000000, /**< FALSE */ + tcCanSayIcon = 0x04000000, /**< FALSE */ + tcCanSetDictionary = 0x08000000, /**< FALSE */ + tcCanRetrieveAudio = 0x10000000, /**< FALSE */ + tcCanPlayAudio = 0x20000000 /**< FALSE */ + }; + + /** + * @enum TalkerCapabilities2 + * All items marked FALSE are hard-coded off at this time. + */ + enum TalkerCapabilities2 + { + tcCanReportEventsBySentences = 0x00000001, + tcCanReportEventsByWords = 0x00000002, /**< FALSE */ + tcCanReportCustomIndexMarks = 0x00000004, /**< FALSE */ + tcHonorsPerformanceGuidelines1 = 0x00000008, /**< FALSE */ + tcHonorsPerformanceGuidelines2 = 0x00000010, /**< FALSE */ + tcHonorsPerformanceGuidelines = 0x00000018, /**< FALSE */ + tcCanDeferMessage = 0x00000020, /**< FALSE */ + tcCanParseSsml = 0x00000040, + tcSupportsMultilingualUtterances = 0x00000080, /**< FALSE */ + tcCanParseHtml = 0x00000100 + }; + + /** + * @enum MarkerType + */ + enum MarkerType + { + mtSentenceBegin = 0, + mtSentenceEnd = 1, + mtWordBegin = 2, + mtPhonemeBegin = 3, + mtCustom = 4 + }; + + /** + * Constructor. + * + * Create objects, speechData and speaker. + * Start thread + */ + KSpeech(QObject *parent=0); + + /** + * Destructor. + * + * Terminate speaker thread. + */ + ~KSpeech(); + +public: // PROPERTIES + Q_PROPERTY(bool isSpeaking READ isSpeaking) + /** + * Returns true if KTTSD is currently speaking. + * @return True if currently speaking. + */ + bool isSpeaking() const; + + Q_PROPERTY(QString version READ version) + /* + * Returns the version number of KTTSD. + * @return Version number string. + */ + QString version() const; + +public Q_SLOTS: // METHODS + /** + * Returns the friendly display name for the application. + * @return Application display name. + * + * If application has not provided a friendly name, the DBUS sender id is returned. + */ + QString applicationName(); + + /** + * Sets a friendly display name for the application. + * @param applicationName Friendly name for the application. + */ + void setApplicationName(const QString &applicationName); + + /** + * Returns the default talker for the application. + * @return Talker. + * + * The default is "", which uses the default talker configured by user. + */ + QString defaultTalker(); + + /** + * Sets the default talker for the application. + * @param defaultTalker Default talker. Example: "en". + */ + void setDefaultTalker(const QString &defaultTalker); + + /** + * Returns the default priority for speech jobs submitted by the application. + * @return Default job priority. + * + * @see KSpeech::JobPriority + */ + int defaultPriority(); + + /** + * Sets the default priority for speech jobs submitted by the application. + * @param defaultPriority Default job priority. + * + * @see KSpeech::JobPriority + */ + void setDefaultPriority(int defaultPriority); + + /** + * Returns the regular expression used to perform Sentence Boundary + * Detection (SBD) for the application. + * @return Sentence delimiter regular expression. + * + * The default sentence delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n)) + @endverbatim + * + * Note that backward slashes must be escaped. + * + * @see sentenceparsing + */ + QString sentenceDelimiter(); + + /** + * Sets the regular expression used to perform Sentence Boundary + * Detection (SBD) for the application. + * @param sentenceDelimiter Sentence delimiter regular expression. + */ + void setSentenceDelimiter(const QString &sentenceDelimiter); + + /** + * Returns whether speech jobs for this application are filtered using configured + * filter plugins. + * @return True if filtering is on. + * + * Filtering is on by default. + */ + bool filteringOn(); + + /** + * Sets whether speech jobs for this application are filtered using configured + * filter plugins. + * @param filteringOn True to set filtering on. + */ + void setFilteringOn(bool filteringOn); + + /** + * Returns whether KTTSD will automatically attempt to configure new + * talkers to meet required talker attributes. + * @return True if KTTSD will autoconfigure talkers. + * + * @see defaultTalker + */ + bool autoConfigureTalkersOn(); + + /** Sets whether KTTSD will automatically attempt to configure new + * talkers to meet required talker attributes. + * @param autoConfigureTalkersOn True to enable auto configuration. + */ + void setAutoConfigureTalkersOn(bool autoConfigureTalkersOn); + + /** + * Returns whether application is paused. + * @return True if application is paused. + */ + bool isApplicationPaused(); + + /** + * Returns the full path name to XSLT file used to convert HTML + * markup to speakable form. + * @return XSLT filename. + */ + QString htmlFilterXsltFile(); + + /** + * Sets the full path name to an XSLT file used to convert HTML + * markup to speakable form. + * @param htmlFilterXsltFile XSLT filename. + */ + void setHtmlFilterXsltFile(const QString &htmlFilterXsltFile); + + /** + * Returns the full path name to XSLT file used to convert SSML + * markup to a speakable form. + * @return XSLT filename. + */ + QString ssmlFilterXsltFile(); + + /** + * Sets the full path name to XSLT file used to convert SSML + * markup to a speakable form. + * @param ssmlFilterXsltFile XSLT filename. + */ + void setSsmlFilterXsltFile(const QString &ssmlFilterXsltFile); + + /** + * Returns whether this is a System Manager application. + * @return True if the application is a System Manager. + */ + bool isSystemManager(); + + /** + * Sets whether this is a System Manager application. + * @param isSystemManager True if this is a System Manager. + * + * System Managers are used to control and configure overall TTS output. + * When True, many of the KSpeech methods alter their behavior. + */ + void setIsSystemManager(bool isSystemManager); + + /** + * Creates and starts a speech job. The job is created at the application's + * default job priority using the default talker. + * @param text The text to be spoken. + * @param options Speech options. + * @return Job Number for the new job. + * + * @see KSpeech::JobPriority + * @see KSpeech::JobOptions + */ + int say(const QString &text, int options); + + /** + * Creates and starts a speech job from a specified file. + * @param filename Full path name of the file. + * @param encoding The encoding of the file. Default UTF-8. + * @return Job Number for the new job. + * + * The job is spoken using application's default talker. + * @see defaultTalker + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling sayFile. + * Call @ref getSentenceCount to retrieve the sentence count after calling sayFile. + * + * The text may contain speech mark language, such as SMML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + */ + int sayFile(const QString &filename, const QString &encoding); + + /** + * Submits a speech job from the contents of the clipboard. + * The job is spoken using application's default talker. + * @return Job Number for the new job. + * + * @see defaultTalker + */ + int sayClipboard(); + + /** + * Pauses speech jobs belonging to the application. + * When called by a System Manager, pauses all jobs of all applications. + */ + void pause(); + + /** + * Resumes speech jobs belonging to the application. + * When called by a System Manager, resumes all jobs of all applications. + */ + void resume(); + + /** + * Removes the specified job. If the job is speaking, it is stopped. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + */ + void removeJob(int jobNum); + + /** + * Removes all jobs belonging to the application. + * When called from a System Manager, removes all jobs of all applications. + */ + void removeAllJobs(); + + /** + * Returns the number of sentences in a job. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * @return Number of sentences in the job. + */ + int getSentenceCount(int jobNum); + + /** + * Returns the job number of the currently speaking job (any application). + * @param Job Number + */ + int getCurrentJob(); + + /** + * Returns the number of jobs belonging to the application + * with the specified job priority. + * @param priority Job Priority. + * @return Number of jobs. + * + * If priority is KSpeech::jpAll, returns the number of jobs belonging + * to the application (all priorities). + * + * When called from a System Manager, returns count of all jobs of the + * specified priority for all applications. + * + * @see KSpeech::JobPriority + */ + int getJobCount(int priority); + + /** + * Returns a list job numbers for the jobs belonging to the + * application with the specified priority. + * @param priority Job Priority. + * @return List of job numbers. Note that the numbers + * are strings. + * + * If priority is KSpeech::jpAll, returns the job numbers belonging + * to the application (all priorities). + * + * When called from a System Manager, returns job numbers of the + * specified priority for all applications. + * + * @see KSpeech::JobPriority + */ + QStringList getJobNumbers(int priority); + + /** + * Returns the state of a job. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * @return Job state. + * + * @see KSpeech::JobState + */ + int getJobState(int jobNum); + + /** + * Get information about a job. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * @return A QDataStream containing information about the job. + * Blank if no such job. + * + * The stream contains the following elements: + * - int priority Job Type. + * - int state Job state. + * - QString appId DBUS senderId of the application that requested the speech job. + * - QString talker Talker code as requested by application. + * - int seq Current sentence being spoken. Sentences are numbered starting at 1. + * - int sentenceCount Total number of sentences in the job. + * - QString applicationName Application's friendly name (if provided by app) + * + * If the job is currently filtering, waits for that to finish before returning. + * + * The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = m_kspeech->getJobInfo(jobNum); + if (jobInfo != QByteArray()) { + QDataStream stream(&jobInfo, QIODevice::ReadOnly); + qint32 priority; + qint32 state; + QString talker; + qint32 sentenceNum; + qint32 sentenceCount; + QString applicationName; + stream >> priority; + stream >> state; + stream >> appId; + stream >> talker; + stream >> sentenceNum; + stream >> sentenceCount; + stream >> applicationName; + }; + @endverbatim + */ + QByteArray getJobInfo(int jobNum); + + /** + * Return a sentence of a job. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * @param seq Sequence number of the sentence. Sequence numbers + * start at 1. + * @return The specified sentence in the specified job. If no such + * job or sentence, returns "". + */ + QString getJobSentence(int jobNum, int seq); + + /** + * Return a list of full Talker Codes for configured talkers. + * @return List of Talker codes. + */ + QStringList getTalkerCodes(); + + /** + * Given a talker, returns the Talker ID for the talker. + * that will speak the job. + * @param talker Talker. Example: "en". + * @return Talker ID. A Talker ID is an internally-assigned + * identifier for a talker. + * + * This method is normally used only by System Managers. + */ + QString talkerToTalkerId(const QString &talker); + + /** + * Returns a bitarray giving the capabilities of a talker. + * @param talker Talker. Example: "en". + * @return A word with bits set according to the capabilities + * of the talker. + * + * @see KSpeech::TalkerCapabilities1 + * @see getTalkerCapabilities2 + */ + int getTalkerCapabilities1(const QString &talker); + + /** + * Returns a bitarray giving the capabilities of a talker. + * @param talker Talker. Example: "en". + * @return A word with bits set according to the capabilities + * of the talker. + * + * @see KSpeech::TalkerCapabilities2 + * @see getTalkerCapabilities1 + */ + int getTalkerCapabilities2(const QString &talker); + + /** + * Return a list of the voice codes of voices available in the synthesizer + * corresponding to a talker. + * @param talker Talker. Example: "synthesizer='Festival'" + * @return List of voice codes. + * + * Voice codes are synthesizer specific. + */ + QStringList getTalkerVoices(const QString &talker); + + /** + * Change the talker of an already-submitted job. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * @param talker Desired new talker. + */ + void changeJobTalker(int jobNum, const QString &talker); + + /** + * Move a job one position down in the queue so that it is spoken later. + * If the job is already speaking, it is stopped and will resume + * when processing next gets to it. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * + * Since there is only one ScreenReaderOutput, this method is meaningless + * for ScreenReaderOutput jobs. + */ + void moveJobLater(int jobNum); + + /** + * Advance or rewind N sentences in a job. + * @param jobNum Job Number. If 0, the last job submitted by + * the application. + * @param n Number of sentences to advance (positive) or rewind (negative) + * in the job. + * @return Sequence number of the sentence actually moved to. Sequence numbers + * are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + * + * Since ScreenReaderOutput jobs are not split into sentences, this method + * is meaningless for ScreenReaderOutput jobs. + */ + int moveRelSentence(int jobNum, int n); + + /** + * Display the KttsMgr program so that user can configure KTTS options. + * Only one instance of KttsMgr is displayed. + */ + void showManagerDialog(); + + /** + * Shuts down KTTSD. Do not call this! + */ + void kttsdExit(); + + /** + * Cause KTTSD to re-read its configuration. + */ + void reinit(); + + // Called by DBusAdaptor so that KTTSD knows the application that + // called it. + void setCallingAppId(const QString& appId); + +Q_SIGNALS: // SIGNALS + /** + * This signal is emitted when KTTSD starts. + */ + void kttsdStarted(); + + /** + * This signal is emitted just before KTTS exits. + */ + void kttsdExiting(); + + /** + * This signal is emitted each time the state of a job changes. + * @param appId The DBUS sender ID of the application that + * submitted the job. + * @param jobNum Job Number. + * @param state Job state. @see KSpeech::JobState. + */ + void jobStateChanged(const QString &appId, int jobNum, int state); + + /** + * This signal is emitted when a marker is processed. + * Currently only emits mtSentenceBegin and mtSentenceEnd. + * @param appId The DBUS sender ID of the application that submitted the job. + * @param jobNum Job Number of the job emitting the marker. + * @param markerType The type of marker. + * Currently either mtSentenceBegin or mtSentenceEnd. + * @param markerData Data for the marker. + * Currently, this is the sequence number of the sentence + * begun or ended. Sequence numbers begin at 1. + */ + void marker(const QString &appId, int jobNum, int markerType, const QString &markerData); + +protected: + + /** + * This signal is emitted by KNotify when a notification event occurs. + */ + void notificationSignal(const QString &event, const QString &fromApp, + const QString &text, const QString &sound, const QString &file, + const int present, const int level, const int winId, const int eventId ); + +private slots: + void slotJobStateChanged(const QString& appId, int jobNum, KSpeech::JobState state); + void slotMarker(const QString& appId, int jobNum, KSpeech::MarkerType markerType, const QString& markerData); + void slotFilteringFinished(); + +private: + /** + * The DBUS sender id of the last application that called KTTSD. + */ + QString callingAppId(); + + /* + * Checks if KTTSD is ready to speak and at least one talker is configured. + * If not, user is prompted to display the configuration dialog. + */ + bool ready(); + + /** + * Create and initialize the Configuration Data object. + */ + bool initializeConfigData(); + + /* + * Create and initialize the SpeechData object. + */ + bool initializeSpeechData(); + + /* + * Create and initialize the TalkerMgr object. + */ + bool initializeTalkerMgr(); + + /* + * Create and initialize the speaker. + */ + bool initializeSpeaker(); + + /* + * If a job number is 0, returns the default job number for a command. + * Returns the job number of the last job queued by the application, or if + * no such job, the current job number. + * @param jobNum The job number passed in the DBUS message. May be 0. + * @return Default job number. 0 if no such job. + */ + int applyDefaultJobNum(int jobNum); + + /* + * Announces an event. + */ + void announceEvent(const QString& slotName, const QString& eventName); + void announceEvent(const QString& slotName, const QString& eventName, const QString& appId, + int jobNum, MarkerType markerType, const QString& markerData); + void announceEvent(const QString& slotName, const QString& eventName, const QString& appId, + int jobNum, JobState state); + +private: + KSpeechPrivate* d; +}; + +#endif // _KSPEECH_H_ diff --git a/kttsd/kttsd/kspeechadaptor_p.cpp b/kttsd/kttsd/kspeechadaptor_p.cpp dissimilarity index 81% index f1e14d2f..060fae2a 100644 --- a/kttsd/kttsd/kspeechadaptor_p.cpp +++ b/kttsd/kttsd/kspeechadaptor_p.cpp @@ -1,427 +1,525 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - KTTSD DBUS Adaptor class - ------------------------ - Copyright: - (C) 2006 by Gary Cramblitt - ------------------- - Original author: Gary Cramblitt - Current Maintainer: Gary Cramblitt - - This file was created by customizing output from dbusidl2cpp, which - was run on org.kde.KSpeech.xml. - ******************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; version 2 of the License. * - * * - ***************************************************************************/ - -#include - -#include "kspeechadaptor_p.h" -#include -#include -#include -#include -#include -#include -#include - -/* - * Implementation of adaptor class KSpeechAdaptor - */ - -KSpeechAdaptor::KSpeechAdaptor(QObject *parent) - : QDBusAbstractAdaptor(parent) -{ - // constructor - setAutoRelaySignals(true); -} - -KSpeechAdaptor::~KSpeechAdaptor() -{ - // destructor -} - -int KSpeechAdaptor::appendText(const QString &text, uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.appendText - QString appId = msg.sender(); - int out0; - QMetaObject::invokeMethod(parent(), "appendText", Q_RETURN_ARG(int, out0), Q_ARG(QString, text), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->appendText(text, jobNum); - return out0; -} - -void KSpeechAdaptor::changeTextTalker(const QString &talker, uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.changeTextTalker - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "changeTextTalker", Q_ARG(QString, talker), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->changeTextTalker(talker, jobNum); -} - -uint KSpeechAdaptor::getCurrentTextJob() -{ - // handle method call org.kde.KSpeech.getCurrentTextJob - uint out0; - QMetaObject::invokeMethod(parent(), "getCurrentTextJob", Q_RETURN_ARG(uint, out0)); - - // Alternative: - //out0 = static_cast(parent())->getCurrentTextJob(); - return out0; -} - -QStringList KSpeechAdaptor::getTalkers() -{ - // handle method call org.kde.KSpeech.getTalkers - QStringList out0; - QMetaObject::invokeMethod(parent(), "getTalkers", Q_RETURN_ARG(QStringList, out0)); - - // Alternative: - //out0 = static_cast(parent())->getTalkers(); - return out0; -} - -int KSpeechAdaptor::getTextCount(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.getTextCount - QString appId = msg.sender(); - int out0; - QMetaObject::invokeMethod(parent(), "getTextCount", Q_RETURN_ARG(int, out0), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //out0 = static_cast(parent())->getTextCount(jobNum); - return out0; -} - -uint KSpeechAdaptor::getTextJobCount() -{ - // handle method call org.kde.KSpeech.getTextJobCount - uint out0; - QMetaObject::invokeMethod(parent(), "getTextJobCount", Q_RETURN_ARG(uint, out0)); - - // Alternative: - //out0 = static_cast(parent())->getTextJobCount(); - return out0; -} - -QByteArray KSpeechAdaptor::getTextJobInfo(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.getTextJobInfo - QString appId = msg.sender(); - QByteArray out0; - QMetaObject::invokeMethod(parent(), "getTextJobInfo", Q_RETURN_ARG(QByteArray, out0), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //out0 = static_cast(parent())->getTextJobInfo(jobNum); - return out0; -} - -QString KSpeechAdaptor::getTextJobNumbers() -{ - // handle method call org.kde.KSpeech.getTextJobNumbers - QString out0; - QMetaObject::invokeMethod(parent(), "getTextJobNumbers", Q_RETURN_ARG(QString, out0)); - - // Alternative: - //out0 = static_cast(parent())->getTextJobNumbers(); - return out0; -} - -QString KSpeechAdaptor::getTextJobSentence(uint jobNum, uint seq, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.getTextJobSentence - QString appId = msg.sender(); - QString out0; - QMetaObject::invokeMethod(parent(), "getTextJobSentence", Q_RETURN_ARG(QString, out0), Q_ARG(uint, jobNum), Q_ARG(uint, seq), Q_ARG(QString, appId)); - - // Alternative: - //out0 = static_cast(parent())->getTextJobSentence(jobNum, seq); - return out0; -} - -int KSpeechAdaptor::getTextJobState(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.getTextJobState - QString appId = msg.sender(); - int out0; - QMetaObject::invokeMethod(parent(), "getTextJobState", Q_RETURN_ARG(int, out0), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //out0 = static_cast(parent())->getTextJobState(jobNum); - return out0; -} - -bool KSpeechAdaptor::isSpeakingText() -{ - // handle method call org.kde.KSpeech.isSpeakingText - bool out0; - QMetaObject::invokeMethod(parent(), "isSpeakingText", Q_RETURN_ARG(bool, out0)); - - // Alternative: - //out0 = static_cast(parent())->isSpeakingText(); - return out0; -} - -int KSpeechAdaptor::jumpToTextPart(int partNum, uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.jumpToTextPart - QString appId = msg.sender(); - int out0; - QMetaObject::invokeMethod(parent(), "jumpToTextPart", Q_RETURN_ARG(int, out0), Q_ARG(int, partNum), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //out0 = static_cast(parent())->jumpToTextPart(partNum, jobNum); - return out0; -} - -void KSpeechAdaptor::kttsdExit() -{ - // handle method call org.kde.KSpeech.kttsdExit - QMetaObject::invokeMethod(parent(), "kttsdExit"); - - // Alternative: - //static_cast(parent())->kttsdExit(); -} - -uint KSpeechAdaptor::moveRelTextSentence(int n, uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.moveRelTextSentence - QString appId = msg.sender(); - uint out0; - QMetaObject::invokeMethod(parent(), "moveRelTextSentence", Q_RETURN_ARG(uint, out0), Q_ARG(int, n), Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //out0 = static_cast(parent())->moveRelTextSentence(n, jobNum); - return out0; -} - -void KSpeechAdaptor::moveTextLater(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.moveTextLater - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "moveTextLater", Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->moveTextLater(jobNum); -} - -void KSpeechAdaptor::pauseText(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.pauseText - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "pauseText", Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->pauseText(jobNum); -} - -void KSpeechAdaptor::reinit() -{ - // handle method call org.kde.KSpeech.reinit - QMetaObject::invokeMethod(parent(), "reinit"); - - // Alternative: - //static_cast(parent())->reinit(); -} - -void KSpeechAdaptor::removeText(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.removeText - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "removeText", Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->removeText(jobNum); -} - -void KSpeechAdaptor::resumeText(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.resumeText - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "resumeText", Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->resumeText(jobNum); -} - -void KSpeechAdaptor::sayMessage(const QString &message, const QString &talker, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.sayMessage - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "sayMessage", Q_ARG(QString, message), Q_ARG(QString, talker), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->sayMessage(message, talker); -} - -void KSpeechAdaptor::sayScreenReaderOutput(const QString &message, const QString &talker, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.sayScreenReaderOutput - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "sayScreenReaderOutput", Q_ARG(QString, message), Q_ARG(QString, talker), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->sayScreenReaderOutput(msg, talker); -} - -uint KSpeechAdaptor::sayText(const QString &text, const QString &talker, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.sayText - QString appId = msg.sender(); - uint out0; - QMetaObject::invokeMethod(parent(), "sayText", Q_RETURN_ARG(uint, out0), Q_ARG(QString, text), Q_ARG(QString, talker), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->sayText(text, talker); - return out0; -} - -void KSpeechAdaptor::sayWarning(const QString &warning, const QString &talker, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.sayWarning - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "sayWarning", Q_ARG(QString, warning), Q_ARG(QString, talker), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->sayWarning(warning, talker); -} - -uint KSpeechAdaptor::setFile(const QString &filename, const QString &talker, const QString &encoding, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.setFile - QString appId = msg.sender(); - uint out0; - QMetaObject::invokeMethod(parent(), "setFile", Q_RETURN_ARG(uint, out0), Q_ARG(QString, filename), Q_ARG(QString, talker), Q_ARG(QString, encoding), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->setFile(filename, talker, encoding); - return out0; -} - -void KSpeechAdaptor::setSentenceDelimiter(const QString &delimiter, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.setSentenceDelimiter - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "setSentenceDelimiter", Q_ARG(QString, delimiter), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->setSentenceDelimiter(delimiter); -} - -uint KSpeechAdaptor::setText(const QString &text, const QString &talker, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.setText - QString appId = msg.sender(); - uint out0; - QMetaObject::invokeMethod(parent(), "setText", Q_RETURN_ARG(uint, out0), Q_ARG(QString, text), Q_ARG(QString, talker), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->setText(text, talker); - return out0; -} - -void KSpeechAdaptor::showDialog() -{ - // handle method call org.kde.KSpeech.showDialog - QMetaObject::invokeMethod(parent(), "showDialog"); - - // Alternative: - //static_cast(parent())->showDialog(); -} - -void KSpeechAdaptor::speakClipboard(const QDBusMessage &msg) -{ - QString appId = msg.sender(); - // handle method call org.kde.KSpeech.speakClipboard - QMetaObject::invokeMethod(parent(), "speakClipboard", Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->speakClipboard(); -} - -void KSpeechAdaptor::startText(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.startText - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "startText", Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->startText(jobNum); -} - -void KSpeechAdaptor::stopText(uint jobNum, const QDBusMessage &msg) -{ - // handle method call org.kde.KSpeech.stopText - QString appId = msg.sender(); - QMetaObject::invokeMethod(parent(), "stopText", Q_ARG(uint, jobNum), Q_ARG(QString, appId)); - - // Alternative: - //static_cast(parent())->stopText(jobNum); -} - -bool KSpeechAdaptor::supportsMarkers(const QString &talker) -{ - // handle method call org.kde.KSpeech.supportsMarkers - bool out0; - QMetaObject::invokeMethod(parent(), "supportsMarkers", Q_RETURN_ARG(bool, out0), Q_ARG(QString, talker)); - - // Alternative: - //out0 = static_cast(parent())->supportsMarkers(talker); - return out0; -} - -bool KSpeechAdaptor::supportsMarkup(const QString &talker, uint markupType) -{ - // handle method call org.kde.KSpeech.supportsMarkup - bool out0; - QMetaObject::invokeMethod(parent(), "supportsMarkup", Q_RETURN_ARG(bool, out0), Q_ARG(QString, talker), Q_ARG(uint, markupType)); - - // Alternative: - //out0 = static_cast(parent())->supportsMarkup(talker, markupType); - return out0; -} - -QString KSpeechAdaptor::talkerCodeToTalkerId(const QString &talkerCode) -{ - // handle method call org.kde.KSpeech.talkerCodeToTalkerId - QString out0; - QMetaObject::invokeMethod(parent(), "talkerCodeToTalkerId", Q_RETURN_ARG(QString, out0), Q_ARG(QString, talkerCode)); - - // Alternative: - //out0 = static_cast(parent())->talkerCodeToTalkerId(talkerCode); - return out0; -} - -QString KSpeechAdaptor::userDefaultTalker() -{ - // handle method call org.kde.KSpeech.userDefaultTalker - QString out0; - QMetaObject::invokeMethod(parent(), "userDefaultTalker", Q_RETURN_ARG(QString, out0)); - - // Alternative: - //out0 = static_cast(parent())->userDefaultTalker(); - return out0; -} - -QString KSpeechAdaptor::version() -{ - // handle method call org.kde.KSpeech.version - QString out0; - QMetaObject::invokeMethod(parent(), "version", Q_RETURN_ARG(QString, out0)); - - // Alternative: - //out0 = static_cast(parent())->version(); - return out0; -} - - -#include "kspeechadaptor_p.moc" +/* + * This file was generated by dbusidl2cpp version 0.6 + * Command line was: dbusidl2cpp -m -a kspeechadaptor -p kspeechinterface -- org.kde.KSpeech.xml + * + * dbusidl2cpp is Copyright (C) 2006 Trolltech AS. All rights reserved. + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#include "kspeechadaptor_p.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * Implementation of adaptor class KSpeechAdaptor + */ + +KSpeechAdaptor::KSpeechAdaptor(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ + // constructor + setAutoRelaySignals(true); +} + +KSpeechAdaptor::~KSpeechAdaptor() +{ + // destructor +} + +bool KSpeechAdaptor::isSpeaking() const +{ + // get the value of property isSpeaking + return qvariant_cast< bool >(parent()->property("isSpeaking")); +} + +QString KSpeechAdaptor::version() const +{ + // get the value of property version + return qvariant_cast< QString >(parent()->property("version")); +} + +QString KSpeechAdaptor::applicationName(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.applicationName + QString out0; + QMetaObject::invokeMethod(parent(), "applicationName", Q_RETURN_ARG(QString, out0)); + + // Alternative: + //out0 = static_cast(parent())->applicationName(); + return out0; +} + +bool KSpeechAdaptor::autoConfigureTalkersOn(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.autoConfigureTalkersOn + bool out0; + QMetaObject::invokeMethod(parent(), "autoConfigureTalkersOn", Q_RETURN_ARG(bool, out0)); + + // Alternative: + //out0 = static_cast(parent())->autoConfigureTalkersOn(); + return out0; +} + +void KSpeechAdaptor::changeJobTalker(int jobNum, const QString &talker) +{ + // handle method call org.kde.KSpeech.changeJobTalker + QMetaObject::invokeMethod(parent(), "changeJobTalker", Q_ARG(int, jobNum), Q_ARG(QString, talker)); + + // Alternative: + //static_cast(parent())->changeJobTalker(jobNum, talker); +} + +int KSpeechAdaptor::defaultPriority(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.defaultPriority + int out0; + QMetaObject::invokeMethod(parent(), "defaultPriority", Q_RETURN_ARG(int, out0)); + + // Alternative: + //out0 = static_cast(parent())->defaultPriority(); + return out0; +} + +QString KSpeechAdaptor::defaultTalker(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.defaultTalker + QString out0; + QMetaObject::invokeMethod(parent(), "defaultTalker", Q_RETURN_ARG(QString, out0)); + + // Alternative: + //out0 = static_cast(parent())->defaultTalker(); + return out0; +} + +bool KSpeechAdaptor::filteringOn(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.filteringOn + bool out0; + QMetaObject::invokeMethod(parent(), "filteringOn", Q_RETURN_ARG(bool, out0)); + + // Alternative: + //out0 = static_cast(parent())->filteringOn(); + return out0; +} + +int KSpeechAdaptor::getCurrentJob() +{ + // handle method call org.kde.KSpeech.getCurrentJob + int out0; + QMetaObject::invokeMethod(parent(), "getCurrentJob", Q_RETURN_ARG(int, out0)); + + // Alternative: + //out0 = static_cast(parent())->getCurrentJob(); + return out0; +} + +int KSpeechAdaptor::getJobCount(int priority, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.getJobCount + int out0; + QMetaObject::invokeMethod(parent(), "getJobCount", Q_RETURN_ARG(int, out0), Q_ARG(int, priority)); + + // Alternative: + //out0 = static_cast(parent())->getJobCount(priority); + return out0; +} + +QByteArray KSpeechAdaptor::getJobInfo(int jobNum) +{ + // handle method call org.kde.KSpeech.getJobInfo + QByteArray out0; + QMetaObject::invokeMethod(parent(), "getJobInfo", Q_RETURN_ARG(QByteArray, out0), Q_ARG(int, jobNum)); + + // Alternative: + //out0 = static_cast(parent())->getJobInfo(jobNum); + return out0; +} + +QStringList KSpeechAdaptor::getJobNumbers(int priority, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.getJobNumbers + QStringList out0; + QMetaObject::invokeMethod(parent(), "getJobNumbers", Q_RETURN_ARG(QStringList, out0), Q_ARG(int, priority)); + + // Alternative: + //out0 = static_cast(parent())->getJobNumbers(priority); + return out0; +} + +QString KSpeechAdaptor::getJobSentence(int jobNum, int seq) +{ + // handle method call org.kde.KSpeech.getJobSentence + QString out0; + QMetaObject::invokeMethod(parent(), "getJobSentence", Q_RETURN_ARG(QString, out0), Q_ARG(int, jobNum), Q_ARG(int, seq)); + + // Alternative: + //out0 = static_cast(parent())->getJobSentence(jobNum, seq); + return out0; +} + +int KSpeechAdaptor::getJobState(int jobNum) +{ + // handle method call org.kde.KSpeech.getJobState + int out0; + QMetaObject::invokeMethod(parent(), "getJobState", Q_RETURN_ARG(int, out0), Q_ARG(int, jobNum)); + + // Alternative: + //out0 = static_cast(parent())->getJobState(jobNum); + return out0; +} + +int KSpeechAdaptor::getSentenceCount(int jobNum) +{ + // handle method call org.kde.KSpeech.getSentenceCount + int out0; + QMetaObject::invokeMethod(parent(), "getSentenceCount", Q_RETURN_ARG(int, out0), Q_ARG(int, jobNum)); + + // Alternative: + //out0 = static_cast(parent())->getSentenceCount(jobNum); + return out0; +} + +int KSpeechAdaptor::getTalkerCapabilities1(const QString &talker) +{ + // handle method call org.kde.KSpeech.getTalkerCapabilities1 + int out0; + QMetaObject::invokeMethod(parent(), "getTalkerCapabilities1", Q_RETURN_ARG(int, out0), Q_ARG(QString, talker)); + + // Alternative: + //out0 = static_cast(parent())->getTalkerCapabilities1(talker); + return out0; +} + +int KSpeechAdaptor::getTalkerCapabilities2(const QString &talker) +{ + // handle method call org.kde.KSpeech.getTalkerCapabilities2 + int out0; + QMetaObject::invokeMethod(parent(), "getTalkerCapabilities2", Q_RETURN_ARG(int, out0), Q_ARG(QString, talker)); + + // Alternative: + //out0 = static_cast(parent())->getTalkerCapabilities2(talker); + return out0; +} + +QStringList KSpeechAdaptor::getTalkerCodes() +{ + // handle method call org.kde.KSpeech.getTalkerCodes + QStringList out0; + QMetaObject::invokeMethod(parent(), "getTalkerCodes", Q_RETURN_ARG(QStringList, out0)); + + // Alternative: + //out0 = static_cast(parent())->getTalkerCodes(); + return out0; +} + +QStringList KSpeechAdaptor::getTalkerVoices(const QString &talker) +{ + // handle method call org.kde.KSpeech.getTalkerVoices + QStringList out0; + QMetaObject::invokeMethod(parent(), "getTalkerVoices", Q_RETURN_ARG(QStringList, out0), Q_ARG(QString, talker)); + + // Alternative: + //out0 = static_cast(parent())->getTalkerVoices(talker); + return out0; +} + +QString KSpeechAdaptor::htmlFilterXsltFile(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.htmlFilterXsltFile + QString out0; + QMetaObject::invokeMethod(parent(), "htmlFilterXsltFile", Q_RETURN_ARG(QString, out0)); + + // Alternative: + //out0 = static_cast(parent())->htmlFilterXsltFile(); + return out0; +} + +bool KSpeechAdaptor::isApplicationPaused(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.isApplicationPaused + bool out0; + QMetaObject::invokeMethod(parent(), "isApplicationPaused", Q_RETURN_ARG(bool, out0)); + + // Alternative: + //out0 = static_cast(parent())->isApplicationPaused(); + return out0; +} + +bool KSpeechAdaptor::isSystemManager(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.isSystemManager + bool out0; + QMetaObject::invokeMethod(parent(), "isSystemManager", Q_RETURN_ARG(bool, out0)); + + // Alternative: + //out0 = static_cast(parent())->isSystemManager(); + return out0; +} + +void KSpeechAdaptor::kttsdExit() +{ + // handle method call org.kde.KSpeech.kttsdExit + QMetaObject::invokeMethod(parent(), "kttsdExit"); + + // Alternative: + //static_cast(parent())->kttsdExit(); +} + +void KSpeechAdaptor::moveJobLater(int jobNum) +{ + // handle method call org.kde.KSpeech.moveJobLater + QMetaObject::invokeMethod(parent(), "moveJobLater", Q_ARG(int, jobNum)); + + // Alternative: + //static_cast(parent())->moveJobLater(jobNum); +} + +int KSpeechAdaptor::moveRelSentence(int jobNum, int n) +{ + // handle method call org.kde.KSpeech.moveRelSentence + int out0; + QMetaObject::invokeMethod(parent(), "moveRelSentence", Q_RETURN_ARG(int, out0), Q_ARG(int, jobNum), Q_ARG(int, n)); + + // Alternative: + //out0 = static_cast(parent())->moveRelSentence(jobNum, n); + return out0; +} + +void KSpeechAdaptor::pause(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.pause + QMetaObject::invokeMethod(parent(), "pause"); + + // Alternative: + //static_cast(parent())->pause(); +} + +void KSpeechAdaptor::reinit() +{ + // handle method call org.kde.KSpeech.reinit + QMetaObject::invokeMethod(parent(), "reinit"); + + // Alternative: + //static_cast(parent())->reinit(); +} + +void KSpeechAdaptor::removeAllJobs(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.removeAllJobs + QMetaObject::invokeMethod(parent(), "removeAllJobs"); + + // Alternative: + //static_cast(parent())->removeAllJobs(); +} + +void KSpeechAdaptor::removeJob(int jobNum) +{ + // handle method call org.kde.KSpeech.removeJob + QMetaObject::invokeMethod(parent(), "removeJob", Q_ARG(int, jobNum)); + + // Alternative: + //static_cast(parent())->removeJob(jobNum); +} + +void KSpeechAdaptor::resume(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.resume + QMetaObject::invokeMethod(parent(), "resume"); + + // Alternative: + //static_cast(parent())->resume(); +} + +int KSpeechAdaptor::say(const QString &text, int options, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.say + int out0; + QMetaObject::invokeMethod(parent(), "say", Q_RETURN_ARG(int, out0), Q_ARG(QString, text), Q_ARG(int, options)); + + // Alternative: + //out0 = static_cast(parent())->say(text, options); + return out0; +} + +int KSpeechAdaptor::sayClipboard(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.sayClipboard + int out0; + QMetaObject::invokeMethod(parent(), "sayClipboard", Q_RETURN_ARG(int, out0)); + + // Alternative: + //out0 = static_cast(parent())->sayClipboard(); + return out0; +} + +int KSpeechAdaptor::sayFile(const QString &filename, const QString &encoding, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.sayFile + int out0; + QMetaObject::invokeMethod(parent(), "sayFile", Q_RETURN_ARG(int, out0), Q_ARG(QString, filename), Q_ARG(QString, encoding)); + + // Alternative: + //out0 = static_cast(parent())->sayFile(filename, encoding); + return out0; +} + +QString KSpeechAdaptor::sentenceDelimiter(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.sentenceDelimiter + QString out0; + QMetaObject::invokeMethod(parent(), "sentenceDelimiter", Q_RETURN_ARG(QString, out0)); + + // Alternative: + //out0 = static_cast(parent())->sentenceDelimiter(); + return out0; +} + +void KSpeechAdaptor::setApplicationName(const QString &applicationName, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setApplicationName + QMetaObject::invokeMethod(parent(), "setApplicationName", Q_ARG(QString, applicationName)); + + // Alternative: + //static_cast(parent())->setApplicationName(applicationName); +} + +void KSpeechAdaptor::setAutoConfigureTalkersOn(bool autoConfigureTalkersOn, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setAutoConfigureTalkersOn + QMetaObject::invokeMethod(parent(), "setAutoConfigureTalkersOn", Q_ARG(bool, autoConfigureTalkersOn)); + + // Alternative: + //static_cast(parent())->setAutoConfigureTalkersOn(autoConfigureTalkersOn); +} + +void KSpeechAdaptor::setDefaultPriority(int defaultPriority, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setDefaultPriority + QMetaObject::invokeMethod(parent(), "setDefaultPriority", Q_ARG(int, defaultPriority)); + + // Alternative: + //static_cast(parent())->setDefaultPriority(defaultPriority); +} + +void KSpeechAdaptor::setDefaultTalker(const QString &defaultTalker, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setDefaultTalker + QMetaObject::invokeMethod(parent(), "setDefaultTalker", Q_ARG(QString, defaultTalker)); + + // Alternative: + //static_cast(parent())->setDefaultTalker(defaultTalker); +} + +void KSpeechAdaptor::setFilteringOn(bool filteringOn, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setFilteringOn + QMetaObject::invokeMethod(parent(), "setFilteringOn", Q_ARG(bool, filteringOn)); + + // Alternative: + //static_cast(parent())->setFilteringOn(filteringOn); +} + +void KSpeechAdaptor::setHtmlFilterXsltFile(const QString &htmlFilterXsltFile, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setHtmlFilterXsltFile + QMetaObject::invokeMethod(parent(), "setHtmlFilterXsltFile", Q_ARG(QString, htmlFilterXsltFile)); + + // Alternative: + //static_cast(parent())->setHtmlFilterXsltFile(htmlFilterXsltFile); +} + +void KSpeechAdaptor::setIsSystemManager(bool isSystemManager, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setIsSystemManager + QMetaObject::invokeMethod(parent(), "setIsSystemManager", Q_ARG(bool, isSystemManager)); + + // Alternative: + //static_cast(parent())->setIsSystemManager(isSystemManager); +} + +void KSpeechAdaptor::setSentenceDelimiter(const QString &sentenceDelimiter, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setSentenceDelimiter + QMetaObject::invokeMethod(parent(), "setSentenceDelimiter", Q_ARG(QString, sentenceDelimiter)); + + // Alternative: + //static_cast(parent())->setSentenceDelimiter(sentenceDelimiter); +} + +void KSpeechAdaptor::setSsmlFilterXsltFile(const QString &ssmlFilterXsltFile, const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.setSsmlFilterXsltFile + QMetaObject::invokeMethod(parent(), "setSsmlFilterXsltFile", Q_ARG(QString, ssmlFilterXsltFile)); + + // Alternative: + //static_cast(parent())->setSsmlFilterXsltFile(ssmlFilterXsltFile); +} + +void KSpeechAdaptor::showManagerDialog() +{ + // handle method call org.kde.KSpeech.showManagerDialog + QMetaObject::invokeMethod(parent(), "showManagerDialog"); + + // Alternative: + //static_cast(parent())->showManagerDialog(); +} + +QString KSpeechAdaptor::ssmlFilterXsltFile(const QDBusMessage &msg) +{ + QMetaObject::invokeMethod(parent(), "setCallingAppId", Q_ARG(QString, msg.service())); + // handle method call org.kde.KSpeech.ssmlFilterXsltFile + QString out0; + QMetaObject::invokeMethod(parent(), "ssmlFilterXsltFile", Q_RETURN_ARG(QString, out0)); + + // Alternative: + //out0 = static_cast(parent())->ssmlFilterXsltFile(); + return out0; +} + +QString KSpeechAdaptor::talkerToTalkerId(const QString &talker) +{ + // handle method call org.kde.KSpeech.talkerToTalkerId + QString out0; + QMetaObject::invokeMethod(parent(), "talkerToTalkerId", Q_RETURN_ARG(QString, out0), Q_ARG(QString, talker)); + + // Alternative: + //out0 = static_cast(parent())->talkerToTalkerId(talker); + return out0; +} + + +#include "kspeechadaptor_p.moc" diff --git a/kttsd/kttsd/kspeechadaptor_p.h b/kttsd/kttsd/kspeechadaptor_p.h dissimilarity index 67% index bb789bf7..f14afcdd 100644 --- a/kttsd/kttsd/kspeechadaptor_p.h +++ b/kttsd/kttsd/kspeechadaptor_p.h @@ -1,293 +1,278 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - KTTSD DBUS Adaptor class - ------------------------ - Copyright: - (C) 2006 by Gary Cramblitt - ------------------- - Original author: Gary Cramblitt - Current Maintainer: Gary Cramblitt - - This file was created by customizing output from dbusidl2cpp, which - was run on org.kde.KSpeech.xml. - ******************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; version 2 of the License. * - * * - ***************************************************************************/ - -#ifndef KSPEECHADAPTOR_P_H -#define KSPEECHADAPTOR_P_H - -#include -#include -class QByteArray; -template class QList; -template class QMap; -class QString; -class QStringList; -class QVariant; - -/* - * Adaptor class for interface org.kde.KSpeech - */ -class KSpeechAdaptor: public QDBusAbstractAdaptor -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.KSpeech") - Q_CLASSINFO("D-Bus Introspection", "" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" - "") -public: - KSpeechAdaptor(QObject *parent); - virtual ~KSpeechAdaptor(); - -public: // PROPERTIES -public Q_SLOTS: // METHODS - int appendText(const QString &text, uint jobNum, const QDBusMessage &msg); - Q_ASYNC void changeTextTalker(const QString &talker, uint jobNum, const QDBusMessage &msg); - uint getCurrentTextJob(); - QStringList getTalkers(); - int getTextCount(uint jobNum, const QDBusMessage &msg); - uint getTextJobCount(); - QByteArray getTextJobInfo(uint jobNum, const QDBusMessage &msg); - QString getTextJobNumbers(); - QString getTextJobSentence(uint jobNum, uint seq, const QDBusMessage &msg); - int getTextJobState(uint jobNum, const QDBusMessage &msg); - bool isSpeakingText(); - int jumpToTextPart(int partNum, uint jobNum, const QDBusMessage &msg); - Q_ASYNC void kttsdExit(); - uint moveRelTextSentence(int n, uint jobNum, const QDBusMessage &msg); - Q_ASYNC void moveTextLater(uint jobNum, const QDBusMessage &msg); - Q_ASYNC void pauseText(uint jobNum, const QDBusMessage &msg); - Q_ASYNC void reinit(); - Q_ASYNC void removeText(uint jobNum, const QDBusMessage &msg); - Q_ASYNC void resumeText(uint jobNum, const QDBusMessage &msg); - Q_ASYNC void sayMessage(const QString &message, const QString &talker, const QDBusMessage &msg); - Q_ASYNC void sayScreenReaderOutput(const QString &message, const QString &talker, const QDBusMessage &msg); - uint sayText(const QString &text, const QString &talker, const QDBusMessage &msg); - Q_ASYNC void sayWarning(const QString &warning, const QString &talker, const QDBusMessage &msg); - uint setFile(const QString &filename, const QString &talker, const QString &encoding, const QDBusMessage &msg); - Q_ASYNC void setSentenceDelimiter(const QString &delimiter, const QDBusMessage &msg); - uint setText(const QString &text, const QString &talker, const QDBusMessage &msg); - Q_ASYNC void showDialog(); - Q_ASYNC void speakClipboard(const QDBusMessage &msg); - Q_ASYNC void startText(uint jobNum, const QDBusMessage &msg); - Q_ASYNC void stopText(uint jobNum, const QDBusMessage &msg); - bool supportsMarkers(const QString &talker); - bool supportsMarkup(const QString &talker, uint markupType); - QString talkerCodeToTalkerId(const QString &talkerCode); - QString userDefaultTalker(); - QString version(); -Q_SIGNALS: // SIGNALS - void kttsdExiting(); - void kttsdStarted(); - void markerSeen(const QString &appId, const QString &markerName); - void sentenceFinished(const QString &appId, uint jobNum, uint seq); - void sentenceStarted(const QString &appId, uint jobNum, uint seq); - void textAppended(const QString &appId, uint jobNum, int partNum); - void textFinished(const QString &appId, uint jobNum); - void textPaused(const QString &appId, uint jobNum); - void textRemoved(const QString &appId, uint jobNum); - void textResumed(const QString &appId, uint jobNum); - void textSet(const QString &appId, uint jobNum); - void textStarted(const QString &appId, uint jobNum); - void textStopped(const QString &appId, uint jobNum); -}; - -#endif +/* + * This file was generated by dbusidl2cpp version 0.6 + * Command line was: dbusidl2cpp -m -a kspeechadaptor -p kspeechinterface -- org.kde.KSpeech.xml + * + * dbusidl2cpp is Copyright (C) 2006 Trolltech AS. All rights reserved. + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +// HAND-EDIT: QDBUSMESSAGE arguments added so that dbus sender can be passed +// to kttsd for those methods that require an appId. + +#ifndef KSPEECHADAPTOR_H_70821151966344 +#define KSPEECHADAPTOR_H_70821151966344 + +#include +#include +class QByteArray; +template class QList; +template class QMap; +class QString; +class QStringList; +class QVariant; + +/* + * Adaptor class for interface org.kde.KSpeech + */ +class KSpeechAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KSpeech") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + KSpeechAdaptor(QObject *parent); + virtual ~KSpeechAdaptor(); + +public: // PROPERTIES + Q_PROPERTY(bool isSpeaking READ isSpeaking) + bool isSpeaking() const; + + Q_PROPERTY(QString version READ version) + QString version() const; + +public Q_SLOTS: // METHODS + QString applicationName(const QDBusMessage &msg); + bool autoConfigureTalkersOn(const QDBusMessage &msg); + Q_NOREPLY void changeJobTalker(int jobNum, const QString &talker); + int defaultPriority(const QDBusMessage &msg); + QString defaultTalker(const QDBusMessage &msg); + bool filteringOn(const QDBusMessage &msg); + int getCurrentJob(); + int getJobCount(int priority, const QDBusMessage &msg); + QByteArray getJobInfo(int jobNum); + QStringList getJobNumbers(int priority, const QDBusMessage &msg); + QString getJobSentence(int jobNum, int seq); + int getJobState(int jobNum); + int getSentenceCount(int jobNum); + int getTalkerCapabilities1(const QString &talker); + int getTalkerCapabilities2(const QString &talker); + QStringList getTalkerCodes(); + QStringList getTalkerVoices(const QString &talker); + QString htmlFilterXsltFile(const QDBusMessage &msg); + bool isApplicationPaused(const QDBusMessage &msg); + bool isSystemManager(const QDBusMessage &msg); + Q_NOREPLY void kttsdExit(); + Q_NOREPLY void moveJobLater(int jobNum); + int moveRelSentence(int jobNum, int n); + Q_NOREPLY void pause(const QDBusMessage &msg); + Q_NOREPLY void reinit(); + Q_NOREPLY void removeAllJobs(const QDBusMessage &msg); + Q_NOREPLY void removeJob(int jobNum); + Q_NOREPLY void resume(const QDBusMessage &msg); + int say(const QString &text, int options, const QDBusMessage &msg); + int sayClipboard(const QDBusMessage &msg); + int sayFile(const QString &filename, const QString &encoding, const QDBusMessage &msg); + QString sentenceDelimiter(const QDBusMessage &msg); + Q_NOREPLY void setApplicationName(const QString &applicationName, const QDBusMessage &msg); + Q_NOREPLY void setAutoConfigureTalkersOn(bool autoConfigureTalkersOn, const QDBusMessage &msg); + Q_NOREPLY void setDefaultPriority(int defaultPriority, const QDBusMessage &msg); + Q_NOREPLY void setDefaultTalker(const QString &defaultTalker, const QDBusMessage &msg); + Q_NOREPLY void setFilteringOn(bool filteringOn, const QDBusMessage &msg); + Q_NOREPLY void setHtmlFilterXsltFile(const QString &htmlFilterXsltFile, const QDBusMessage &msg); + Q_NOREPLY void setIsSystemManager(bool isSystemManager, const QDBusMessage &msg); + Q_NOREPLY void setSentenceDelimiter(const QString &sentenceDelimiter, const QDBusMessage &msg); + Q_NOREPLY void setSsmlFilterXsltFile(const QString &ssmlFilterXsltFile, const QDBusMessage &msg); + Q_NOREPLY void showManagerDialog(); + QString ssmlFilterXsltFile(const QDBusMessage &msg); + QString talkerToTalkerId(const QString &talker); +Q_SIGNALS: // SIGNALS + void jobStateChanged(const QString &appId, int jobNum, int state); + void kttsdExiting(); + void kttsdStarted(); + void marker(const QString &appId, int jobNum, int markerType, const QString &markerData); +}; + +#endif diff --git a/kttsd/kttsd/kttsd.cpp b/kttsd/kttsd/kttsd.cpp deleted file mode 100644 index 1c2b2592..00000000 --- a/kttsd/kttsd/kttsd.cpp +++ /dev/null @@ -1,1114 +0,0 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - KTTSD main class - ------------------- - Copyright: - (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt - (C) 2004 by Gary Cramblitt - ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - Current Maintainer: Gary Cramblitt - ******************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; version 2 of the License. * - * * - ***************************************************************************/ - -// Qt includes. -#include -#include -#include -#include - -// KDE includes. -#include -#include -#include -#include -#include -#include -#include -#include - -// KTTS includes. -#include "notify.h" -#include "kttsd.h" -#include "kspeechadaptor_p.h" - -/** -* This is the "main" module of KTTSD. It performs the following functions: -* - Creates and destroys SpeechData and Speaker objects. -* - Receives DBUS calls and dispatches them to SpeechData and Speaker. -* - Receives signals from SpeechData and Speaker and converts them to DBUS signals. -* -* Note that most of the real tts work occurs in Speaker. -*/ - -KTTSD::KTTSD(QObject *parent, const char *name) : - QObject(parent) -{ - setObjectName(name); - kDebug() << "KTTSD::KTTSD Running" << endl; - new KSpeechAdaptor(this); - QDBus::sessionBus().registerObject("/org/kde/KSpeech", this, QDBusConnection::ExportAdaptors); - m_speaker = 0; - m_talkerMgr = 0; - m_speechData = 0; - ready(); - kDebug() << "KTTSD::KTTSD Exiting" << endl; -} - -/* -* Create and initialize the SpeechData object. -*/ -bool KTTSD::initializeSpeechData() -{ - // Create speechData object. - if (!m_speechData) - { - m_speechData = new SpeechData(); - connect (m_speechData, SIGNAL(textSet(const QString&, const uint)), - this, SLOT(slotTextSet(const QString&, const uint))); - connect (m_speechData, SIGNAL(textAppended(const QString&, const uint, const int)), - this, SLOT(slotTextAppended(const QString&, const uint, const int))); - connect (m_speechData, SIGNAL(textRemoved(const QString&, const uint)), - this, SLOT(slotTextRemoved(const QString&, const uint))); - - // TODO: Hook KNotify signal. - // if (!connectDCOPSignal(0, 0, - // "notifySignal(QString,QString,QString,QString,QString,int,int,int,int)", - // "notificationSignal(QString,QString,QString,QString,QString,int,int,int,int)", - // false)) kDebug() << "KTTSD:initializeSpeechData: connectDCOPSignal for knotify failed" << endl; - } - // Load configuration. - m_speechData->readConfig(); - - return true; -} - -/* -* Create and initialize the TalkerMgr object. -*/ -bool KTTSD::initializeTalkerMgr() -{ - if (!m_talkerMgr) - { - if (!m_speechData) initializeSpeechData(); - - m_talkerMgr = new TalkerMgr(this, "kttsdtalkermgr"); - int load = m_talkerMgr->loadPlugIns(m_speechData->config); - // If no Talkers configured, try to autoconfigure one, first in the user's - // desktop language, but if that fails, fallback to English. - if (load < 0) - { - QString languageCode = KGlobal::locale()->language(); - if (m_talkerMgr->autoconfigureTalker(languageCode, m_speechData->config)) - load = m_talkerMgr->loadPlugIns(m_speechData->config); - else - { - if (m_talkerMgr->autoconfigureTalker("en", m_speechData->config)) - load = m_talkerMgr->loadPlugIns(m_speechData->config); - } - } - if (load < 0) - { - // TODO: Would really like to eliminate ALL GUI stuff from kttsd. Find - // a better way to do this. - delete m_speaker; - m_speaker = 0; - delete m_talkerMgr; - m_talkerMgr = 0; - delete m_speechData; - m_speechData = 0; - kDebug() << "KTTSD::initializeTalkerMgr: no Talkers have been configured." << endl; - // Ask if user would like to run configuration dialog, but don't bug user unnecessarily. - QString dontAskConfigureKTTS = "DontAskConfigureKTTS"; - KMessageBox::ButtonCode msgResult; - if (KMessageBox::shouldBeShownYesNo(dontAskConfigureKTTS, msgResult)) - { - if (KMessageBox::questionYesNo( - 0, - i18n("KTTS has not yet been configured. At least one Talker must be configured. " - "Would you like to configure it now?"), - i18n("KTTS Not Configured"), - i18n("Configure"), - i18n("Do Not Configure"), - dontAskConfigureKTTS) == KMessageBox::Yes) msgResult = KMessageBox::Yes; - } - if (msgResult == KMessageBox::Yes) showDialog(); - return false; - } - } - m_speechData->setTalkerMgr(m_talkerMgr); - return true; -} - -/* -* Create and initialize the Speaker object. -*/ -bool KTTSD::initializeSpeaker() -{ - // kDebug() << "KTTSD::initializeSpeaker: Instantiating Speaker" << endl; - - if (!m_talkerMgr) initializeTalkerMgr(); - - // Create speaker object and load plug ins, checking for the return - m_speaker = new Speaker(m_speechData, m_talkerMgr); - connect (m_speaker, SIGNAL(textStarted(const QString&, const uint)), - this, SLOT(slotTextStarted(const QString&, const uint))); - connect (m_speaker, SIGNAL(textFinished(const QString&, const uint)), - this, SLOT(slotTextFinished(const QString&, const uint))); - connect (m_speaker, SIGNAL(textResumed(const QString&, const uint)), - this, SLOT(slotTextResumed(const QString&, const uint))); - connect (m_speaker, SIGNAL(sentenceStarted(QString, QString, const QString&, const uint, const uint)), - this, SLOT(slotSentenceStarted(QString, QString, const QString&, const uint, const uint))); - connect (m_speaker, SIGNAL(sentenceFinished(const QString&, const uint, const uint)), this, - SLOT(slotSentenceFinished(const QString&, const uint, const uint))); - connect (m_speaker, SIGNAL(textStopped(const QString&, const uint)), - this, SLOT(slotTextStopped(const QString&, const uint))); - connect (m_speaker, SIGNAL(textPaused(const QString&, const uint)), - this, SLOT(slotTextPaused(const QString&, const uint))); - - return true; -} - -/** - * Destructor - * Terminate speaker thread - */ -KTTSD::~KTTSD(){ - kDebug() << "KTTSD::~KTTSD:: Stopping KTTSD service" << endl; - if (m_speaker) m_speaker->requestExit(); - delete m_speaker; - delete m_talkerMgr; - delete m_speechData; - announceEvent("~KTTS", "kttsdExiting"); - kttsdExiting(); -} - -/***** DBUS exported functions *****/ - -/** -* Determine whether the currently-configured speech plugin supports a speech markup language. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* @param markupType The kttsd code for the desired speech markup language. -* @return True if the plugin currently configured for the indicated -* talker supports the indicated speech markup language. -* @see kttsdMarkupType -*/ -bool KTTSD::supportsMarkup(const QString& talker /*=NULL*/, const uint markupType /*=0*/) const -{ - if (markupType == KSpeech::mtHtml) - { - if (!m_speechData) return false; - return m_speechData->supportsHTML; - } - if (markupType != KSpeech::mtSsml) return false; - if (!m_talkerMgr) return false; - return m_talkerMgr->supportsMarkup(fixNullString(talker), markupType); -} - -/** -* Determine whether the currently-configured speech plugin supports markers in speech markup. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* @return True if the plugin currently configured for the indicated -* talker supports markers. -* TODO: Waiting on plugin API. -*/ -bool KTTSD::supportsMarkers(const QString& /*talker=NULL*/) const { return false; } - -/** -* Say a message as soon as possible, interrupting any other speech in progress. -* IMPORTANT: This method is reserved for use by Screen Readers and should not be used -* by any other applications. -* @param msg The message to be spoken. -* @param talker Code for the to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -* -* If an existing Screen Reader output is in progress, it is stopped and discarded and -* replaced with this new message. -*/ -void KTTSD::sayScreenReaderOutput(const QString &msg, const QString &talker /*=NULL*/, const QString &appId) -{ - if (!m_speaker) return; - m_speechData->setScreenReaderOutput(msg, fixNullString(talker), appId); - m_speaker->doUtterances(); -} - -/** -* Say a warning. The warning will be spoken when the current sentence -* stops speaking and takes precedence over Messages and regular text. Warnings should only -* be used for high-priority messages requiring immediate user attention, such as -* "WARNING. CPU is overheating." -* @param warning The warning to be spoken. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -*/ -void KTTSD::sayWarning(const QString &warning, const QString &talker /*=NULL*/, const QString &appId){ - // kDebug() << "KTTSD::sayWarning: Running" << endl; - if (!m_speaker) return; - kDebug() << "KTTSD::sayWarning: Adding '" << warning << "' to warning queue." << endl; - m_speechData->enqueueWarning(warning, fixNullString(talker), appId); - m_speaker->doUtterances(); -} - -/** -* Say a message. The message will be spoken when the current sentence stops speaking -* but after any warnings have been spoken. -* Messages should be used for one-shot messages that can't wait for -* normal text messages to stop speaking, such as "You have mail.". -* @param message The message to be spoken. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no talker has been configured for the specified Talker code, -* defaults to the closest matching talker. -*/ -void KTTSD::sayMessage(const QString &message, const QString &talker /*=NULL*/, const QString &appId) -{ - // kDebug() << "KTTSD::sayMessage: Running" << endl; - if (!m_speaker) return; - kDebug() << "KTTSD::sayMessage: Adding '" << message << "' to message queue." << endl; - m_speechData->enqueueMessage(message, fixNullString(talker), appId); - m_speaker->doUtterances(); -} - -/** -* Sets the GREP pattern that will be used as the sentence delimiter. -* @param delimiter A valid GREP pattern. -* -* The default sentence delimiter is - @verbatim - ([\\.\\?\\!\\:\\;])\\s - @endverbatim -* -* Note that backward slashes must be escaped. -* -* Changing the sentence delimiter does not affect other applications. -* @see sentenceparsing -*/ -void KTTSD::setSentenceDelimiter(const QString &delimiter, const QString &appId) -{ - if (!m_speaker) return; - m_speechData->setSentenceDelimiter(fixNullString(delimiter), appId); -} - -/** -* Queue a text job. Does not start speaking the text. -* @param text The message to be spoken. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default plugin. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -* @return Job number. -* -* Plain text is parsed into individual sentences using the current sentence delimiter. -* Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. -* Call @ref getTextCount to retrieve the sentence count after calling setText. -* -* The text may contain speech mark language, such as Sable, JSML, or SMML, -* provided that the speech plugin/engine support it. In this case, -* sentence parsing follows the semantics of the markup language. -* -* Call @ref startText to mark the job as speakable and if the -* job is the first speakable job in the queue, speaking will begin. -* @see getTextCount -* @see startText -*/ -uint KTTSD::setText(const QString &text, const QString &talker /*=NULL*/, const QString &appId) -{ - // kDebug() << "KTTSD::setText: Running" << endl; - if (!m_speaker) return 0; - // kDebug() << "KTTSD::setText: Setting text: '" << text << "'" << endl; - uint jobNum = m_speechData->setText(text, fixNullString(talker), appId); - return jobNum; -} - -/** -* Say a plain text job. This is a convenience method that -* combines @ref setText and @ref startText into a single call. -* @param text The message to be spoken. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default plugin. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -* @return Job number. -* -* Plain text is parsed into individual sentences using the current sentence delimiter. -* Call @ref setSentenceDelimiter to change the sentence delimiter prior to -* calling setText. -* Call @ref getTextCount to retrieve the sentence count after calling setText. -* -* The text may contain speech mark language, such as Sable, JSML, or SSML, -* provided that the speech plugin/engine support it. In this case, -* sentence parsing follows the semantics of the markup language. -* -* The job is marked speakable. -* If there are other speakable jobs preceeding this one in the queue, -* those jobs continue speaking and when finished, this job will begin speaking. -* If there are no other speakable jobs preceeding this one, it begins speaking. -* -* @see getTextCount -* -* @since KDE 3.5 -*/ -uint KTTSD::sayText(const QString &text, const QString &talker, const QString &appId) -{ - kDebug() << "KTTSD:sayText: Running with text = " << text << " and talker = " << talker << endl; - uint jobNum = setText(text, talker, appId); - if (jobNum) startText(jobNum); - return jobNum; -} - -/** -* Adds another part to a text job. Does not start speaking the text. -* (thread safe) -* @param text The message to be spoken. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application, -* but if no such job, applies to the last job queued by any application. -* @return Part number for the added part. Parts are numbered starting at 1. -* -* The text is parsed into individual sentences. Call getTextCount to retrieve -* the sentence count. Call startText to mark the job as speakable and if the -* job is the first speakable job in the queue, speaking will begin. -* @see setText. -* @see startText. -*/ -int KTTSD::appendText(const QString &text, const uint jobNum /*=0*/, const QString &appId) -{ - if (!m_speaker) return 0; - return m_speechData->appendText(text, applyDefaultJobNum(jobNum, appId)); -} - -/** -* Queue a text job from the contents of a file. Does not start speaking the text. -* @param filename Full path to the file to be spoken. May be a URL. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -* @param encoding Name of the encoding to use when reading the file. If -* NULL or Empty, uses default stream encoding. -* @return Job number. 0 if an error occurs. -* -* Plain text is parsed into individual sentences using the current sentence delimiter. -* Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. -* Call @ref getTextCount to retrieve the sentence count after calling setText. -* -* The text may contain speech mark language, such as Sable, JSML, or SMML, -* provided that the speech plugin/engine support it. In this case, -* sentence parsing follows the semantics of the markup language. -* -* Call @ref startText to mark the job as speakable and if the -* job is the first speakable job in the queue, speaking will begin. -* @see getTextCount -* @see startText -*/ -uint KTTSD::setFile(const QString &filename, const QString &talker /*=NULL*/, - const QString &encoding /*=NULL*/, const QString &appId) -{ - // kDebug() << "KTTSD::setFile: Running" << endl; - if (!m_speaker) return 0; - QFile file(filename); - uint jobNum = 0; - if ( file.open(QIODevice::ReadOnly) ) - { - QTextStream stream(&file); - QString enc = fixNullString(encoding); - if (!enc.isEmpty()) - { - QTextCodec* codec = QTextCodec::codecForName(enc.toLatin1()); - if (codec) stream.setCodec(codec); - } - jobNum = m_speechData->setText(stream.readAll(), fixNullString(talker), appId); - file.close(); - } - return jobNum; -} - -/** -* Get the number of sentences in a text job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* @return The number of sentences in the job. -1 if no such job. -* -* The sentences of a job are given sequence numbers from 1 to the number returned by this -* method. The sequence numbers are emitted in the @ref sentenceStarted and -* @ref sentenceFinished signals. -*/ -int KTTSD::getTextCount(const uint jobNum /*=0*/, const QString &appId) -{ - if (!m_speaker) return -1; - return m_speechData->getTextCount(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Get the job number of the current text job. -* @return Job number of the current text job. 0 if no jobs. -* -* Note that the current job may not be speaking. See @ref isSpeakingText. -* @see getTextJobState. -* @see isSpeakingText -*/ -uint KTTSD::getCurrentTextJob() -{ - if (!m_speaker) return 0; - return m_speaker->getCurrentTextJob(); -} - -/** -* Get the number of jobs in the text job queue. -* @return Number of text jobs in the queue. 0 if none. -*/ -uint KTTSD::getTextJobCount() -{ - if (!m_speaker) return 0; - return m_speechData->getTextJobCount(); -} - -/** -* Get a comma-separated list of text job numbers in the queue. -* @return Comma-separated list of text job numbers in the queue. -*/ -QString KTTSD::getTextJobNumbers() -{ - if (!m_speaker) return QString(); - return m_speechData->getTextJobNumbers(); -} - -/** -* Get the state of a text job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* @return State of the job. -1 if invalid job number. -* -* @see kttsdJobState -*/ -int KTTSD::getTextJobState(const uint jobNum /*=0*/, const QString &appId) -{ - if (!m_speaker) return -1; - return m_speechData->getTextJobState(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Get information about a text job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* @return A QDataStream containing information about the job. -* Blank if no such job. -* -* The stream contains the following elements: -* - int state Job state. -* - QString appId DBUS senderId of the application that requested the speech job. -* - QString talker Language code in which to speak the text. -* - int seq Current sentence being spoken. Sentences are numbered starting at 1. -* - int sentenceCount Total number of sentences in the job. -* -* The following sample code will decode the stream: - @verbatim - QByteArray jobInfo = getTextJobInfo(jobNum); - QDataStream stream(jobInfo, QIODevice::ReadOnly); - int state; - QString appId; - QString talker; - int seq; - int sentenceCount; - stream >> state; - stream >> appId; - stream >> talker; - stream >> seq; - stream >> sentenceCount; - @endverbatim -*/ -QByteArray KTTSD::getTextJobInfo(const uint jobNum /*=0*/, const QString &appId) -{ - return m_speechData->getTextJobInfo(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Given a Talker Code, returns the Talker ID of the talker that would speak -* a text job with that Talker Code. -* @param talkerCode Talker Code. -* @return Talker ID of the talker that would speak the text job. -*/ -QString KTTSD::talkerCodeToTalkerId(const QString& talkerCode) -{ - if (!m_talkerMgr) return QString(); - return m_talkerMgr->talkerCodeToTalkerId(fixNullString(talkerCode)); -} - -/** -* Return a sentence of a job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* @param seq Sequence number of the sentence. -* @return The specified sentence in the specified job. If no such -* job or sentence, returns "". -*/ -QString KTTSD::getTextJobSentence(const uint jobNum /*=0*/, const uint seq /*=1*/, const QString &appId) -{ - return m_speechData->getTextJobSentence(applyDefaultJobNum(jobNum, appId), seq); -} - -/** -* Determine if kttsd is currently speaking any text jobs. -* @return True if currently speaking any text jobs. -*/ -bool KTTSD::isSpeakingText() const -{ - if (!m_speaker) return false; - return m_speaker->isSpeakingText(); -} - -/** -* Remove a text job from the queue. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* -* The job is deleted from the queue and the @ref textRemoved signal is emitted. -* -* If there is another job in the text queue, and it is marked speakable, -* that job begins speaking. -*/ -void KTTSD::removeText(const uint jobNum /*=0*/, const QString &appId) -{ - kDebug() << "KTTSD::removeText: Running" << endl; - if (!m_speaker) return; - m_speaker->removeText(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Start a text job at the beginning. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* -* Rewinds the job to the beginning. -* -* The job is marked speakable. -* If there are other speakable jobs preceeding this one in the queue, -* those jobs continue speaking and when finished, this job will begin speaking. -* If there are no other speakable jobs preceeding this one, it begins speaking. -* -* The @ref textStarted signal is emitted when the text job begins speaking. -* When all the sentences of the job have been spoken, the job is marked for deletion from -* the text queue and the @ref textFinished signal is emitted. -*/ -void KTTSD::startText(const uint jobNum /*=0*/, const QString &appId) -{ - kDebug() << "KTTSD::startText: Running" << endl; - if (!m_speaker) return; - // Determine if we are starting speech. - bool startingSpeech = !isSpeakingText(); - uint jNum = applyDefaultJobNum(jobNum, appId); - m_speaker->startText(jNum); - // If this has started speech output, determine whether to autostart KTTSMgr. - if (startingSpeech) - { - if (m_speechData->autoStartManager) - { - // Do not start KTTSMgr unless at least 5 sentences are queued. - if (getTextCount(jNum) > 4) - { - QString cmd = "kttsmgr"; - if (m_speechData->autoExitManager) cmd.append(" --autoexit"); - // Notice this fails if KTTSMgr is already running, which is what we want. - KRun::runCommand(cmd); - } - } - } -} - -/** -* Stop a text job and rewind to the beginning. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* -* The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText -* is called. -* -* If there are speaking jobs preceeding this one in the queue, they continue speaking. -* If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. -* Depending upon the speech engine and plugin used, speeking may not stop immediately -* (it might finish the current sentence). -*/ -void KTTSD::stopText(const uint jobNum /*=0*/, const QString &appId) -{ - kDebug() << "KTTSD::stopText: Running" << endl; - if (!m_speaker) return; - m_speaker->stopText(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Pause a text job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* -* The job is marked as paused and will not be speakable until @ref resumeText or -* @ref startText is called. -* -* If there are speaking jobs preceeding this one in the queue, they continue speaking. -* If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. -* Depending upon the speech engine and plugin used, speeking may not stop immediately -* (it might finish the current sentence). -* @see resumeText -*/ -void KTTSD::pauseText(const uint jobNum /*=0*/, const QString &appId) -{ - kDebug() << "KTTSD::pauseText: Running" << endl; - if (!m_speaker) return; - m_speaker->pauseText(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Start or resume a text job where it was paused. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* -* The job is marked speakable. -* -* If the job is currently speaking, or is waiting to be spoken (speakable -* state), the resumeText() call is ignored. -* -* If the job is currently queued, or is finished, it is the same as calling -* @ref startText . -* -* If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, -* when finished this job will begin speaking where it left off. -* -* The @ref textResumed signal is emitted when the job resumes. -* @see pauseText -*/ -void KTTSD::resumeText(const uint jobNum /*=0*/, const QString &appId) -{ - kDebug() << "KTTSD::resumeText: Running" << endl; - if (!m_speaker) return; - m_speaker->resumeText(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Get a list of the talkers configured in KTTS. -* @return A QStringList of fully-specified talker codes, one -* for each talker user has configured. -* -* @see talkers -*/ -QStringList KTTSD::getTalkers() -{ - if (!m_talkerMgr) return QStringList(); - return m_talkerMgr->getTalkers(); -} - -/** -* Change the talker for a text job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* @param talker New code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -*/ -void KTTSD::changeTextTalker(const QString &talker, uint jobNum, const QString &appId) -{ - m_speechData->changeTextTalker(fixNullString(talker), applyDefaultJobNum(jobNum, appId)); -} - -/** -* Get the user's default talker. -* @return A fully-specified talker code. -* -* @see talkers -* @see getTalkers -*/ -QString KTTSD::userDefaultTalker() -{ - if (!m_talkerMgr) return QString(); - return m_talkerMgr->userDefaultTalker(); -} - -/** -* Move a text job down in the queue so that it is spoken later. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application. -* -* If the job is currently speaking, it is paused. -* If the next job in the queue is speakable, it begins speaking. -*/ -void KTTSD::moveTextLater(const uint jobNum /*=0*/, const QString &appId) -{ - if (!m_speaker) return; - m_speaker->moveTextLater(applyDefaultJobNum(jobNum, appId)); -} - -/** -* Jump to the first sentence of a specified part of a text job. -* @param partNum Part number of the part to jump to. Parts are numbered starting at 1. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application, -* but if no such job, applies to the last job queued by any application. -* @return Part number of the part actually jumped to. -* -* If partNum is greater than the number of parts in the job, jumps to last part. -* If partNum is 0, does nothing and returns the current part number. -* If no such job, does nothing and returns 0. -* Does not affect the current speaking/not-speaking state of the job. -*/ -int KTTSD::jumpToTextPart(const int partNum, const uint jobNum /*=0*/, const QString &appId) -{ - if (!m_speaker) return 0; - return m_speaker->jumpToTextPart(partNum, applyDefaultJobNum(jobNum, appId)); -} - -/** -* Advance or rewind N sentences in a text job. -* @param n Number of sentences to advance (positive) or rewind (negative) in the job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application, -* but if no such job, applies to the last job queued by any application. -* @return Sequence number of the sentence actually moved to. Sequence numbers -* are numbered starting at 1. -* -* If no such job, does nothing and returns 0. -* If n is zero, returns the current sequence number of the job. -* Does not affect the current speaking/not-speaking state of the job. -*/ -uint KTTSD::moveRelTextSentence(const int n, const uint jobNum /*=0*/, const QString &appId) -{ - if (!m_speaker) return 0; - return m_speaker->moveRelTextSentence(n, applyDefaultJobNum(jobNum, appId)); -} - -/** -* Add the clipboard contents to the text queue and begin speaking it. -*/ -void KTTSD::speakClipboard(const QString &appId) -{ - // Get the clipboard object. - QClipboard *cb = kapp->clipboard(); - - // Copy text from the clipboard. - QString text = cb->text(); - - // Speak it. - if ( !text.isNull() ) - { - setText(text, NULL, appId); - startText(); - } -} - -/** -* Displays the %KTTS Manager dialog. In this dialog, the user may backup or skip forward in -* any text job by sentence or paragraph, rewind jobs, pause or resume jobs, or -* delete jobs. -*/ -void KTTSD::showDialog() -{ - QString cmd = "kcmshell kcmkttsd --caption "; - cmd += "'" + i18n("KDE Text-to-Speech") + "'"; - KRun::runCommand(cmd); -} - -/** -* Stop the service. -*/ -void KTTSD::kttsdExit() -{ - stopText(); - announceEvent("kttsdExit", "kttsdExiting"); - kttsdExiting(); - kapp->quit(); -} - -/** -* Re-start %KTTSD. -*/ -void KTTSD::reinit() -{ - // Restart ourself. - kDebug() << "KTTSD::reinit: Running" << endl; - if (m_speaker) - { - kDebug() << "KTTSD::reinit: Stopping KTTSD service" << endl; - if (m_speaker->isSpeakingText()) pauseText(); - m_speaker->requestExit(); - } - delete m_speaker; - m_speaker = 0; - delete m_talkerMgr; - m_talkerMgr = 0; - ready(); -} - -/** - * Return KTTSD daemon version number. - */ -QString KTTSD::version() { return kapp->aboutData()->version(); } - -/* -* Checks if KTTSD is ready to speak and at least one talker is configured. -* If not, user is prompted to display the configuration dialog. -*/ -bool KTTSD::ready() -{ - if (m_speaker) return true; - kDebug() << "KTTSD::ready: Starting KTTSD service" << endl; - if (!initializeSpeechData()) return false; - if (!initializeTalkerMgr()) return false; - if (!initializeSpeaker()) return false; - m_speaker->doUtterances(); - announceEvent("ready", "kttsdStarted"); - kttsdStarted(); - return true; -} - -void KTTSD::configCommitted() { - if (m_speaker) reinit(); -} - -/** -* This signal is emitted by KNotify when a notification event occurs. -* ds << event << fromApp << text << sound << file << present << level -* << winId << eventId; -* default_presentation contains these ORed events: None=0, Sound=1, Messagebox=2, Logfile=4, Stderr=8, -* PassivePopup=16, Execute=32, Taskbar=64 -*/ -void KTTSD::notificationSignal( const QString& event, const QString& fromApp, - const QString &text, const QString& sound, const QString& /*file*/, - const int present, const int /*level*/, const int /*windId*/, const int /*eventId*/) -{ - if (!m_speaker) return; - // kDebug() << "KTTSD:notificationSignal: event: " << event << " fromApp: " << fromApp << - // " text: " << text << " sound: " << sound << " file: " << file << " present: " << present << - // " level: " << level << " windId: " << windId << " eventId: " << eventId << endl; - if ( m_speechData->notify ) - if ( !m_speechData->notifyExcludeEventsWithSound || sound.isEmpty() ) - { - bool found = false; - NotifyOptions notifyOptions; - QString msg; - QString talker; - // Check for app-specific action. - if ( m_speechData->notifyAppMap.contains( fromApp ) ) - { - NotifyEventMap notifyEventMap = m_speechData->notifyAppMap[ fromApp ]; - if ( notifyEventMap.contains( event ) ) - { - found = true; - notifyOptions = notifyEventMap[ event ]; - } else { - // Check for app-specific default. - if ( notifyEventMap.contains( "default" ) ) - { - found = true; - notifyOptions = notifyEventMap[ "default" ]; - notifyOptions.eventName.clear(); - } - } - } - // If no app-specific action, try default. - if ( !found ) - { - switch ( m_speechData->notifyDefaultPresent ) - { - case NotifyPresent::None: - found = false; - break; - case NotifyPresent::Dialog: - found = ( - (present & KNotifyClient::Messagebox) - && - !(present & KNotifyClient::PassivePopup) - ); - break; - case NotifyPresent::Passive: - found = ( - !(present & KNotifyClient::Messagebox) - && - (present & KNotifyClient::PassivePopup) - ); - break; - case NotifyPresent::DialogAndPassive: - found = ( - (present & KNotifyClient::Messagebox) - && - (present & KNotifyClient::PassivePopup) - ); - break; - case NotifyPresent::All: - found = true; - break; - } - if ( found ) - notifyOptions = m_speechData->notifyDefaultOptions; - } - if ( found ) - { - int action = notifyOptions.action; - talker = notifyOptions.talker; - switch ( action ) - { - case NotifyAction::DoNotSpeak: - break; - case NotifyAction::SpeakEventName: - if (notifyOptions.eventName.isEmpty()) - msg = NotifyEvent::getEventName( fromApp, event ); - else - msg = notifyOptions.eventName; - break; - case NotifyAction::SpeakMsg: - msg = text; - break; - case NotifyAction::SpeakCustom: - msg = notifyOptions.customMsg; - msg.replace( "%a", fromApp ); - msg.replace( "%m", text ); - if ( msg.contains( "%e" ) ) - { - if ( notifyOptions.eventName.isEmpty() ) - msg.replace( "%e", NotifyEvent::getEventName( fromApp, event ) ); - else - msg.replace( "%e", notifyOptions.eventName ); - } - break; - } - } - // Queue msg if we should speak something. - if ( !msg.isEmpty() ) - { - QString fromApps = fromApp + ",knotify"; - m_speechData->enqueueMessage( msg, talker, fromApps.toUtf8() ); - m_speaker->doUtterances(); - } - } -} - -// Slots for the speaker object -void KTTSD::slotSentenceStarted(QString, QString, const QString& appId, - const uint jobNum, const uint seq) { - // Emit DBUS signal. - announceEvent("slotSentenceStarted", "sentenceStarted", appId, jobNum, seq); - sentenceStarted(appId, jobNum, seq); -} - -void KTTSD::slotSentenceFinished(const QString& appId, const uint jobNum, const uint seq){ - // Emit DBUS signal. - announceEvent("slotSentenceFinished", "sentenceFinished", appId, jobNum, seq); - sentenceFinished(appId, jobNum, seq); -} - -// Slots for the speechData and speaker objects. -void KTTSD::slotTextStarted(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextStarted", "textStarted", appId, jobNum); - textStarted(appId, jobNum); -} - -void KTTSD::slotTextFinished(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextFinished", "textFinished", appId, jobNum); - textFinished(appId, jobNum); -} - -void KTTSD::slotTextStopped(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextStopped", "textStopped", appId, jobNum); - textStopped(appId, jobNum); -} - -void KTTSD::slotTextPaused(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextPaused", "textPaused", appId, jobNum); - textPaused(appId, jobNum); -} - -void KTTSD::slotTextResumed(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextResumed", "textResumed", appId, jobNum); - textResumed(appId, jobNum); -} - -void KTTSD::slotTextSet(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextSet", "textSet", appId, jobNum); - textSet(appId, jobNum); -} - -void KTTSD::slotTextAppended(const QString& appId, const uint jobNum, const int partNum){ - // Emit DBUS signal. - announceEvent("slotTextAppended", "textAppended", appId, jobNum, partNum); - textAppended(appId, jobNum, partNum); -} - -void KTTSD::slotTextRemoved(const QString& appId, const uint jobNum){ - // Emit DBUS signal. - announceEvent("slotTextRemoved", "textRemoved", appId, jobNum); - textRemoved(appId, jobNum); -} - -/** -* If a job number is 0, returns the default job number for a command. -* Returns the job number of the last job queued by the application, or if -* no such job, the current job number. -* @param jobNum The job number passed in the DBUS message. May be 0. -* @param appId The DBUS sender ID. -* @return Default job number. 0 if no such job. -*/ -uint KTTSD::applyDefaultJobNum(const uint jobNum, const QString &appId) -{ - uint jNum = jobNum; - if (!jNum) - { - jNum = m_speechData->findAJobNumByAppId(appId); - if (!jNum) jNum = getCurrentTextJob(); - if (!jNum) jNum = m_speechData->findAJobNumByAppId(0); - } - return jNum; -} - -/* -* Fixes a string argument passed in via dbus. -* If NULL or "0" return QString(). -*/ -QString KTTSD::fixNullString(const QString &talker) const -{ - if (talker == 0) return QString(); - if (talker == "0") return QString(); - return talker; -} - -void KTTSD::announceEvent(const QString& slotName, const QString& eventName) { - kDebug() << "KTTSD::" << slotName << ": emitting DBUS signal " << eventName << endl; -} - -void KTTSD::announceEvent(const QString& slotName, const QString& eventName, const QString appId) { - QString appIdStr(appId); - kDebug() << "KTTSD::" << slotName << ": emitting DBUS signal " << eventName << - " with appId " << appIdStr << endl; -} - -void KTTSD::announceEvent(const QString& slotName, const QString& eventName, const QString appId, - uint jobNum) { - QString appIdStr(appId); - kDebug() << "KTTSD::" << slotName << ": emitting DBUS signal " << eventName << - " with appId " << appIdStr << " and job number " << jobNum << endl; -} - -void KTTSD::announceEvent(const QString& slotName, const QString& eventName, const QString appId, - uint jobNum, uint seq) { - QString appIdStr(appId); - kDebug() << "KTTSD::" << slotName << ": emitting DBUS signal " << eventName << - " with appId " << appIdStr << " job number " << jobNum << " and seq/part number " << seq << endl; -} - -#include "kttsd.moc" - diff --git a/kttsd/kttsd/kttsd.h b/kttsd/kttsd/kttsd.h deleted file mode 100644 index c71a2dfb..00000000 --- a/kttsd/kttsd/kttsd.h +++ /dev/null @@ -1,655 +0,0 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - KTTSD main class - ------------------- - Copyright: - (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt - (C) 2004 by Gary Cramblitt - ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - ******************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; version 2 of the License. * - * * - ***************************************************************************/ - -#ifndef _KTTSD_H_ -#define _KTTSD_H_ - -// Qt includes -#include - -// KTTS includes -#include "speechdata.h" -#include "talkermgr.h" -#include "speaker.h" -#include "kspeechdef.h" - -/** -* KTTSD - the KDE Text-to-speech Deamon. -* -* Provides the capability for applications to speak text. -* Applications may speak text by sending DCOP messages to application "kttsd" object "KSpeech". -* -* @author José Pablo Ezequiel "Pupeno" Fernández -* @author Olaf Schmidt -* @author Gary Cramblitt -*/ - -class KTTSD : public QObject -{ - Q_OBJECT - - public: - /** - * Constructor. - * - * Create objects, speechData and speaker. - * Start thread - */ - KTTSD(QObject *parent=0, const char *name=0); - - /** - * Destructor. - * - * Terminate speaker thread. - */ - ~KTTSD(); - - /** DCOP exported functions for kspeech interface **/ - - public Q_SLOTS: - /** - * Determine whether the currently-configured speech plugin supports a speech markup language. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * @param markupType The kttsd code for the desired speech markup language. - * @return True if the plugin currently configured for the indicated - * talker supports the indicated speech markup language. - * @see kttsdMarkupType - */ - bool supportsMarkup(const QString &talker=NULL, const uint markupType = 0) const; - - /** - * Determine whether the currently-configured speech plugin supports markers in speech markup. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * @return True if the plugin currently configured for the indicated - * talker supports markers. - */ - bool supportsMarkers(const QString &talker=NULL) const; - - /** - * Say a message as soon as possible, interrupting any other speech in progress. - * IMPORTANT: This method is reserved for use by Screen Readers and should not be used - * by any other applications. - * @param msg The message to be spoken. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - * - * If an existing Screen Reader output is in progress, it is stopped and discarded and - * replaced with this new message. - */ - void sayScreenReaderOutput(const QString &msg, const QString &talker=NULL, const QString &appId=NULL); - - /** - * Say a warning. The warning will be spoken when the current sentence - * stops speaking and takes precedence over Messages and regular text. Warnings should only - * be used for high-priority messages requiring immediate user attention, such as - * "WARNING. CPU is overheating." - * @param warning The warning to be spoken. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - */ - void sayWarning(const QString &warning, const QString &talker=NULL, const QString &appId=NULL); - - /** - * Say a message. The message will be spoken when the current sentence stops speaking - * but after any warnings have been spoken. - * Messages should be used for one-shot messages that can't wait for - * normal text messages to stop speaking, such as "You have mail.". - * @param message The message to be spoken. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * If no talker has been configured for the specified Talker code, - * defaults to the closest matching talker. - */ - void sayMessage(const QString &message, const QString &talker=NULL, const QString &appId=NULL); - - /** - * Sets the GREP pattern that will be used as the sentence delimiter. - * @param delimiter A valid GREP pattern. - * - * The default sentence delimiter is - @verbatim - ([\\.\\?\\!\\:\\;])\\s - @endverbatim - * - * Note that backward slashes must be escaped. - * - * Changing the sentence delimiter does not affect other applications. - * @see sentenceparsing - */ - void setSentenceDelimiter(const QString &delimiter, const QString &appId=NULL); - - /** - * Queue a text job. Does not start speaking the text. - * @param text The message to be spoken. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default plugin. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - * @return Job number. - * - * Plain text is parsed into individual sentences using the current sentence delimiter. - * Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. - * Call @ref getTextCount to retrieve the sentence count after calling setText. - * - * The text may contain speech mark language, such as Sable, JSML, or SMML, - * provided that the speech plugin/engine support it. In this case, - * sentence parsing follows the semantics of the markup language. - * - * Call @ref startText to mark the job as speakable and if the - * job is the first speakable job in the queue, speaking will begin. - * @see getTextCount - * @see startText - */ - uint setText(const QString &text, const QString &talker=NULL, const QString &appId=NULL); - - /** - * Say a plain text job. This is a convenience method that - * combines @ref setText and @ref startText into a single call. - * @param text The message to be spoken. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default plugin. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - * @return Job number. - * - * Plain text is parsed into individual sentences using the current sentence delimiter. - * Call @ref setSentenceDelimiter to change the sentence delimiter prior to - * calling setText. - * Call @ref getTextCount to retrieve the sentence count after calling setText. - * - * The text may contain speech mark language, such as Sable, JSML, or SSML, - * provided that the speech plugin/engine support it. In this case, - * sentence parsing follows the semantics of the markup language. - * - * The job is marked speakable. - * If there are other speakable jobs preceeding this one in the queue, - * those jobs continue speaking and when finished, this job will begin speaking. - * If there are no other speakable jobs preceeding this one, it begins speaking. - * - * @see getTextCount - * - * @since KDE 3.5 - */ - uint sayText(const QString &text, const QString &talker, const QString &appId); - - /** - * Adds another part to a text job. Does not start speaking the text. - * (thread safe) - * @param text The message to be spoken. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @return Part number for the added part. Parts are numbered starting at 1. - * - * The text is parsed into individual sentences. Call getTextCount to retrieve - * the sentence count. Call startText to mark the job as speakable and if the - * job is the first speakable job in the queue, speaking will begin. - * @see setText. - * @see startText. - */ - int appendText(const QString &text, const uint jobNum=0, const QString &appId=NULL); - - /** - * Queue a text job from the contents of a file. Does not start speaking the text. - * @param filename Full path to the file to be spoken. May be a URL. - * @param talker Code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - * @param encoding Name of the encoding to use when reading the file. If - * NULL or Empty, uses default stream encoding. - * @return Job number. 0 if an error occurs. - * - * Plain text is parsed into individual sentences using the current sentence delimiter. - * Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. - * Call @ref getTextCount to retrieve the sentence count after calling setText. - * - * The text may contain speech mark language, such as Sable, JSML, or SMML, - * provided that the speech plugin/engine support it. In this case, - * sentence parsing follows the semantics of the markup language. - * - * Call @ref startText to mark the job as speakable and if the - * job is the first speakable job in the queue, speaking will begin. - * @see getTextCount - * @see startText - */ - uint setFile(const QString &filename, const QString &talker=NULL, - const QString& encoding=NULL, const QString &appId=NULL); - - /** - * Get the number of sentences in a text job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @return The number of sentences in the job. -1 if no such job. - * - * The sentences of a job are given sequence numbers from 1 to the number returned by this - * method. The sequence numbers are emitted in the @ref sentenceStarted and - * @ref sentenceFinished signals. - */ - int getTextCount(const uint jobNum=0, const QString &appId=NULL); - - /** - * Get the job number of the current text job. - * @return Job number of the current text job. 0 if no jobs. - * - * Note that the current job may not be speaking. See @ref isSpeakingText. - * @see getTextJobState. - * @see isSpeakingText - */ - uint getCurrentTextJob(); - - /** - * Get the number of jobs in the text job queue. - * @return Number of text jobs in the queue. 0 if none. - */ - uint getTextJobCount(); - - /** - * Get a comma-separated list of text job numbers in the queue. - * @return Comma-separated list of text job numbers in the queue. - */ - QString getTextJobNumbers(); - - /** - * Get the state of a text job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @return State of the job. -1 if invalid job number. - * - * @see kttsdJobState - */ - int getTextJobState(const uint jobNum=0, const QString &appId=NULL); - - /** - * Get information about a text job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @return A QDataStream containing information about the job. - * Blank if no such job. - * - * The stream contains the following elements: - * - int state Job state. - * - QString appId DBUS senderId of the application that requested the speech job. - * - QString talker Language code in which to speak the text. - * - int seq Current sentence being spoken. Sentences are numbered starting at 1. - * - int sentenceCount Total number of sentences in the job. - * - int partNum Current part of the job begin spoken. Parts are numbered starting at 1. - * - int partCount Total number of parts in the job. - * - * Note that sequence numbers apply to the entire job. They do not start from 1 at the beginning of - * each part. - * - * The following sample code will decode the stream: - @verbatim - QByteArray jobInfo = getTextJobInfo(jobNum); - QDataStream stream(jobInfo, QIODevice::ReadOnly); - int state; - QString appId; - QString talker; - int seq; - int sentenceCount; - int partNum; - int partCount; - stream >> state; - stream >> appId; - stream >> talker; - stream >> seq; - stream >> sentenceCount; - stream >> partNum; - stream >> partCount; - @endverbatim - */ - QByteArray getTextJobInfo(const uint jobNum=0, const QString &appId=NULL); - - /** - * Given a Talker Code, returns the Talker ID of the talker that would speak - * a text job with that Talker Code. - * @param talkerCode Talker Code. - * @return Talker ID of the talker that would speak the text job. - */ - QString talkerCodeToTalkerId(const QString& talkerCode); - - /** - * Return a sentence of a job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @param seq Sequence number of the sentence. - * @return The specified sentence in the specified job. If not such - * job or sentence, returns "". - */ - QString getTextJobSentence(const uint jobNum=0, const uint seq=1, const QString &appId=NULL); - - /** - * Determine if kttsd is currently speaking any text jobs. - * @return True if currently speaking any text jobs. - */ - bool isSpeakingText() const; - - /** - * Remove a text job from the queue. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * - * The job is deleted from the queue and the @ref textRemoved signal is emitted. - * - * If there is another job in the text queue, and it is marked speakable, - * that job begins speaking. - */ - void removeText(const uint jobNum=0, const QString &appId=NULL); - - /** - * Start a text job at the beginning. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * - * Rewinds the job to the beginning. - * - * The job is marked speakable. - * If there are other speakable jobs preceeding this one in the queue, - * those jobs continue speaking and when finished, this job will begin speaking. - * If there are no other speakable jobs preceeding this one, it begins speaking. - * - * The @ref textStarted signal is emitted when the text job begins speaking. - * When all the sentences of the job have been spoken, the job is marked for deletion from - * the text queue and the @ref textFinished signal is emitted. - */ - void startText(const uint jobNum=0, const QString &appId=NULL); - - /** - * Stop a text job and rewind to the beginning. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * - * The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText - * is called. - * - * If there are speaking jobs preceeding this one in the queue, they continue speaking. - * If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. - * Depending upon the speech engine and plugin used, speeking may not stop immediately - * (it might finish the current sentence). - */ - void stopText(const uint jobNum=0, const QString &appId=NULL); - - /** - * Pause a text job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * - * The job is marked as paused and will not be speakable until @ref resumeText or - * @ref startText is called. - * - * If there are speaking jobs preceeding this one in the queue, they continue speaking. - * If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. - * Depending upon the speech engine and plugin used, speeking may not stop immediately - * (it might finish the current sentence). - * @see resumeText - */ - void pauseText(const uint jobNum=0, const QString &appId=NULL); - - /** - * Start or resume a text job where it was paused. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * - * The job is marked speakable. - * - * If the job is currently speaking, or is waiting to be spoken (speakable - * state), the resumeText() call is ignored. - * - * If the job is currently queued, or is finished, it is the same as calling - * @ref startText . - * - * If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, - * when finished this job will begin speaking where it left off. - * - * The @ref textResumed signal is emitted when the job resumes. - * @see pauseText - */ - void resumeText(const uint jobNum=0, const QString &appId=NULL); - - /** - * Get a list of the talkers configured in KTTS. - * @return A QStringList of fully-specified talker codes, one - * for each talker user has configured. - * - * @see talkers - */ - QStringList getTalkers(); - - /** - * Change the talker for a text job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @param talker New code for the talker to do the speaking. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - */ - void changeTextTalker(const QString &talker, uint jobNum=0, const QString &appId=NULL); - - /** - * Get the user's default talker. - * @return A fully-specified talker code. - * - * @see talkers - * @see getTalkers - */ - QString userDefaultTalker(); - - /** - * Move a text job down in the queue so that it is spoken later. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * - * If the job is currently speaking, it is paused. - * If the next job in the queue is speakable, it begins speaking. - */ - void moveTextLater(const uint jobNum=0, const QString &appId=NULL); - - /** - * Jump to the first sentence of a specified part of a text job. - * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @return Part number of the part actually jumped to. - * - * If partNum is greater than the number of parts in the job, jumps to last part. - * If partNum is 0, does nothing and returns the current part number. - * If no such job, does nothing and returns 0. - * Does not affect the current speaking/not-speaking state of the job. - */ - int jumpToTextPart(const int partNum, const uint jobNum=0, const QString &appId=NULL); - - /** - * Advance or rewind N sentences in a text job. - * @param n Number of sentences to advance (positive) or rewind (negative) in the job. - * @param jobNum Job number of the text job. - * If zero, applies to the last job queued by the application, - * but if no such job, applies to the current job (if any). - * @return Sequence number of the sentence actually moved to. Sequence numbers - * are numbered starting at 1. - * - * If no such job, does nothing and returns 0. - * If n is zero, returns the current sequence number of the job. - * Does not affect the current speaking/not-speaking state of the job. - */ - uint moveRelTextSentence(const int n, const uint jobNum=0, const QString &appId=NULL); - - /** - * Add the clipboard contents to the text queue and begin speaking it. - */ - void speakClipboard(const QString &appId=NULL); - - /** - * Displays the %KTTS Manager dialog. In this dialog, the user may backup or skip forward in - * any text job by sentence or paragraph, rewind jobs, pause or resume jobs, or - * delete jobs. - */ - void showDialog(); - - /** - * Stop the service. - */ - void kttsdExit(); - - /** - * Re-start %KTTSD. - */ - void reinit(); - - /** - * Return the KTTSD deamon version number. - * @since KDE 3.5.1 - */ - QString version(); - - protected: - - /** - * This signal is emitted by KNotify when a notification event occurs. - */ - void notificationSignal(const QString &event, const QString &fromApp, - const QString &text, const QString &sound, const QString &file, - const int present, const int level, const int winId, const int eventId ); - - private slots: - /* - * These functions are called whenever - * the status of the speaker object has changed - */ - void slotSentenceStarted(QString text, QString language, - const QString& appId, const uint jobNum, const uint seq); - void slotSentenceFinished(const QString& appId, const uint jobNum, const uint seq); - void slotTextStarted(const QString& appId, const uint jobNum); - void slotTextFinished(const QString& appId, const uint jobNum); - void slotTextStopped(const QString& appId, const uint jobNum); - void slotTextPaused(const QString& appId, const uint jobNum); - void slotTextResumed(const QString& appId, const uint jobNum); - - /* - * These functions are called whenever - * the status of the speechData or speaker objects has changed - */ - void slotTextSet(const QString& appId, const uint jobNum); - void slotTextAppended(const QString& appId, const uint jobNum, const int partNum); - void slotTextRemoved(const QString& appId, const uint jobNum); - - /* - * Fires whenever user clicks Apply or OK buttons in Settings dialog. - */ - void configCommitted(); - - private: - /* - * Checks if KTTSD is ready to speak and at least one talker is configured. - * If not, user is prompted to display the configuration dialog. - */ - bool ready(); - - /* - * Create and initialize the SpeechData object. - */ - bool initializeSpeechData(); - - /* - * Create and initialize the TalkerMgr object. - */ - bool initializeTalkerMgr(); - - /* - * Create and initialize the speaker. - */ - bool initializeSpeaker(); - - /* - * If a job number is 0, returns the default job number for a command. - * Returns the job number of the last job queued by the application, or if - * no such job, the current job number. - * @param jobNum The job number passed in the DBUS message. May be 0. - * @param appId The DBUS sender ID. - * @return Default job number. 0 if no such job. - */ - uint applyDefaultJobNum(const uint jobNum, const QString &appId); - - /* - * Fixes a talker argument passed in via dcop. - * If NULL or "0" return QString(). - */ - QString fixNullString(const QString &talker) const; - - /* - * Announces an event. - */ - void announceEvent(const QString& slotName, const QString& eventName); - void announceEvent(const QString& slotName, const QString& eventName, const QString appId); - void announceEvent(const QString& slotName, const QString& eventName, const QString appId, - uint jobNum); - void announceEvent(const QString& slotName, const QString& eventName, const QString appId, - uint jobNum, uint seq); - - /* - * SpeechData containing all the data and the manipulating methods for all KTTSD - */ - SpeechData* m_speechData; - - /* - * TalkerMgr keeps a list of all the Talkers (synth plugins). - */ - TalkerMgr* m_talkerMgr; - - /* - * Speaker that will be run as another thread, actually saying the messages, warnings, and texts - */ - Speaker* m_speaker; - -Q_SIGNALS: // SIGNALS - void kttsdExiting(); - void kttsdStarted(); - void markerSeen(const QString &appId, const QString &markerName); - void sentenceFinished(const QString &appId, uint jobNum, uint seq); - void sentenceStarted(const QString &appId, uint jobNum, uint seq); - void textAppended(const QString &appId, uint jobNum, int partNum); - void textFinished(const QString &appId, uint jobNum); - void textPaused(const QString &appId, uint jobNum); - void textRemoved(const QString &appId, uint jobNum); - void textResumed(const QString &appId, uint jobNum); - void textSet(const QString &appId, uint jobNum); - void textStarted(const QString &appId, uint jobNum); - void textStopped(const QString &appId, uint jobNum); -}; - -#endif // _KTTSD_H_ diff --git a/kttsd/kttsd/main.cpp b/kttsd/kttsd/main.cpp index 4bbcad3a..33c68554 100644 --- a/kttsd/kttsd/main.cpp +++ b/kttsd/kttsd/main.cpp @@ -1,28 +1,38 @@ /***************************************************** vim:set ts=4 sw=4 sts=4: - Where the main function for KTTSD resides. - ------------------- + KTTSD + + The KDE Text-to-Speech Daemon. + ----------------------------- Copyright: (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt + (C) 2006 by Gary Cramblitt ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - ******************************************************************************/ + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; version 2 of the License. * - * * - ***************************************************************************/ + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ +// KDE Includes. #include #include #include #include #include -#include "kttsd.h" +// KTTSD includes. +#include "kspeech.h" int main (int argc, char *argv[]){ KLocale::setMainCatalog("kttsd"); @@ -53,7 +63,7 @@ int main (int argc, char *argv[]){ // This app is started automatically, no need for session management app.disableSessionManagement(); kDebug() << "main: Creating KTTSD Service" << endl; - KTTSD *service = new KTTSD(); + KSpeech* service = new KSpeech(); // kDebug() << "Entering event loop." << endl; return app.exec(); diff --git a/kttsd/kttsd/speaker.cpp b/kttsd/kttsd/speaker.cpp dissimilarity index 71% index 6f1e5e2d..daaad08f 100644 --- a/kttsd/kttsd/speaker.cpp +++ b/kttsd/kttsd/speaker.cpp @@ -1,1689 +1,1177 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - Speaker class. - This class is in charge of getting the messages, warnings and text from - the queue and call the plug ins function to actually speak the texts. - ------------------- - Copyright: - (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt - (C) 2004 by Gary Cramblitt - ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - ******************************************************************************/ - -/****************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License. * - * * - ******************************************************************************/ - -#include - -// Qt includes. -#include -#include -#include - -// KDE includes. -#include -#include -#include -#include -#include -#include -#include -//#include - -// KTTSD includes. -#include "player.h" -#include "speaker.h" -#include "speaker.moc" -#include "talkermgr.h" -#include "utils.h" - -/** -* The Speaker class takes sentences from the text queue, messages from the -* messages queue, warnings from the warnings queue, and Screen Reader -* output and places them into an internal "utterance queue". It then -* loops through this queue, farming the work off to the plugins. -* It tries to optimize processing so as to keep the plugins busy as -* much as possible, while ensuring that only one stream of audio is -* heard at any one time. -* -* The message queues are maintained in the SpeechData class. -* -* Text jobs in the text queue each have a state (queued, speakable, -* speaking, paused, finished). Each plugin has a state (idle, saying, synthing, -* or finished). And finally, each utterance has a state (waiting, saying, -* synthing, playing, finished). It can be confusing if you are not aware -* of all these states. -* -* Speaker takes some pains to ensure speech is spoken in the correct order, -* namely Screen Reader Output has the highest priority, Warnings are next, -* Messages are next, and finally regular text jobs. Since Screen Reader -* Output, Warnings, and Messages can be queued in the middle of a text -* job, Speaker must be prepared to reorder utterances in its queue. -* -* At the same time, it must issue the signals to inform programs -* what is happening. -* -* Finally, users can pause, restart, delete, advance, or rewind text jobs -* and Speaker must respond to these commands. In some cases, utterances that -* have already been synthesized and are ready for audio output must be -* discarded in response to these commands. -* -* Some general guidelines for programmers modifying this code: -* - Avoid blocking at all cost. If a plugin won't stopText, keep going. -* You might have to wait for the plugin to complete before asking it -* to perform the next operation, but in the meantime, there might be -* other useful work that can be performed. -* - In no case allow the main thread Qt event loop to block. -* - Plugins that do not have asynchronous support are wrapped in the -* ThreadedPlugin class, which attempts to make them as asynchronous as -* it can, but there are limits. -* - doUtterances is the main worker method. If in doubt, call doUtterances. -* - Because Speaker controls the ordering of utterances, most sequence-related -* signals must be emitted by Speaker; not SpeechData. Only the add -* and delete job-related signals eminate from SpeechData. -* - The states of the 3 types of objects mentioned above (jobs, utterances, -* and plugins) can interact in subtle ways. Test fully. For example, while -* a text job might be in a paused state, the plugin could be synthesizing -* in anticipation of resume, or sythesizing a Warning, Message, or -* Screen Reader Output. Meanwhile, while one of the utterances might -* have a paused state, others from the same job could be synthing, waiting, -* or finished. -* - There can be more than one Audio Player object in existence at one time, although -* there must never be more than one actually playing at one time. For -* example, an Audio Player playing an utterance from a text job can be -* in a paused state, while another Audio Player is playing a Screen Reader -* Output utterance. Finally, since some plugins do their own audio, it -* might be that none of the Audio Player objects are playing. -*/ - -/* Public Methods ==========================================================*/ - -/** -* Constructor. -* Loads plugins. -*/ -Speaker::Speaker( SpeechData*speechData, TalkerMgr* talkerMgr, - QObject *parent, const char *name) : - QObject(parent), - m_speechData(speechData), - m_talkerMgr(talkerMgr) -{ - Q_UNUSED(name); - // kDebug() << "Running: Speaker::Speaker()" << endl; - m_exitRequested = false; - m_textInterrupted = false; - m_currentJobNum = 0; - m_lastJobNum = 0; - m_lastSeq = 0; - m_timer = new QTimer(this); - m_speechData->config->setGroup("General"); - // Default to Phonon (0). - m_playerOption = m_speechData->config->readEntry("AudioOutputMethod", 0); - // Map 50% to 100% onto 2.0 to 0.5. - m_audioStretchFactor = 1.0/(float(m_speechData->config->readEntry("AudioStretchFactor", 100))/100.0); - switch (m_playerOption) - { - case 0: - case 1: break; - case 2: - m_speechData->config->setGroup("ALSAPlayer"); - m_sinkName = m_speechData->config->readEntry("PcmName", "default"); - if ("custom" == m_sinkName) - m_sinkName = m_speechData->config->readEntry("CustomPcmName", "default"); - m_periodSize = m_speechData->config->readEntry("PeriodSize", 128); - m_periods = m_speechData->config->readEntry("Periods", 8); - m_playerDebugLevel = m_speechData->config->readEntry("DebugLevel", 1); - break; - } - // Connect timer timeout signal. - connect(m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); - - // Connect plugins to slots. - PlugInList plugins = m_talkerMgr->getLoadedPlugIns(); - const int pluginsCount = plugins.count(); - for (int ndx = 0; ndx < pluginsCount; ++ndx) - { - PlugInProc* speech = plugins.at(ndx); - connect(speech, SIGNAL(synthFinished()), - this, SLOT(slotSynthFinished())); - connect(speech, SIGNAL(sayFinished()), - this, SLOT(slotSayFinished())); - connect(speech, SIGNAL(stopped()), - this, SLOT(slotStopped())); - connect(speech, SIGNAL(error(bool, const QString&)), - this, SLOT(slotError(bool, const QString&))); - } -} - -/** -* Destructor. -*/ -Speaker::~Speaker(){ - // kDebug() << "Running: Speaker::~Speaker()" << endl; - m_timer->stop(); - delete m_timer; - if (!m_uttQueue.isEmpty()) - { - uttIterator it; - for (it = m_uttQueue.begin(); it != m_uttQueue.end(); ) - it = deleteUtterance(it); - } -} - -/** - * Tells the speaker it is requested to exit. - * TODO: I don't think this actually accomplishes anything. - */ -void Speaker::requestExit(){ - // kDebug() << "Speaker::requestExit: Running" << endl; - m_exitRequested = true; -} - -/** - * Main processing loop. Dequeues utterances and sends them to the - * plugins and/or Audio Player. - */ -void Speaker::doUtterances() -{ - // kDebug() << "Running: Speaker::doUtterances()" << endl; - - // Used to prevent exiting prematurely. - m_again = true; - - while(m_again && !m_exitRequested) - { - m_again = false; - - if (m_exitRequested) - { - // kDebug() << "Speaker::run: exiting due to request 1." << endl; - return; - } - - uttIterator it; - uttIterator itBegin; - uttIterator itEnd = 0; // Init to zero to avoid compiler warning. - - // If Screen Reader Output is waiting, we need to process it ASAP. - if (m_speechData->screenReaderOutputReady()) - { - m_again = getNextUtterance(); - } - kDebug() << "Speaker::doUtterances: queue dump:" << endl; - for (it = m_uttQueue.begin(); it != m_uttQueue.end(); ++it) - { - QString pluginState = "no plugin"; - if (it->plugin) pluginState = pluginStateToStr(it->plugin->getState()); - QString jobState = - jobStateToStr(m_speechData->getTextJobState(it->sentence->jobNum)); - kDebug() << - " State: " << uttStateToStr(it->state) << - "," << pluginState << - "," << jobState << - " Type: " << uttTypeToStr(it->utType) << - " Text: " << it->sentence->text << endl; - } - - if (!m_uttQueue.isEmpty()) - { - // Delete utterances that are finished. - it = m_uttQueue.begin(); - while (it != m_uttQueue.end()) - { - if (it->state == usFinished) - it = deleteUtterance(it); - else - ++it; - } - // Loop through utterance queue. - int waitingCnt = 0; - int waitingMsgCnt = 0; - int transformingCnt = 0; - bool playing = false; - int synthingCnt = 0; - itEnd = m_uttQueue.end(); - itBegin = m_uttQueue.begin(); - for (it = itBegin; it != itEnd; ++it) - { - uttState utState = it->state; - uttType utType = it->utType; - switch (utState) - { - case usNone: - { - setInitialUtteranceState(*it); - m_again = true; - break; - } - case usWaitingTransform: - { - // Create an XSLT transformer and transform the text. - it->transformer = new SSMLConvert(); - connect(it->transformer, SIGNAL(transformFinished()), - this, SLOT(slotTransformFinished())); - if (it->transformer->transform(it->sentence->text, - it->plugin->getSsmlXsltFilename())) - { - it->state = usTransforming; - ++transformingCnt; - } - else - { - // If an error occurs transforming, skip it. - it->state = usTransforming; - setInitialUtteranceState(*it); - } - m_again = true; - break; - } - case usTransforming: - { - // See if transformer is finished. - if (it->transformer->getState() == SSMLConvert::tsFinished) - { - // Get the transformed text. - it->sentence->text = it->transformer->getOutput(); - // Set next state (usWaitingSynth or usWaitingSay) - setInitialUtteranceState(*it); - m_again = true; - --transformingCnt; - } - break; - } - case usWaitingSignal: - { - kDebug() << "Speaker::doUtterances: state usWaitingSignal" << endl; - // If first in queue, emit signal. - if (it == itBegin) - { - if (utType == utStartOfJob) - { - m_speechData->setTextJobState( - it->sentence->jobNum, KSpeech::jsSpeaking); - if (it->sentence->seq == 1) - emit textStarted(it->sentence->appId, - it->sentence->jobNum); - else - emit textResumed(it->sentence->appId, - it->sentence->jobNum); - } else { - m_speechData->setTextJobState( - it->sentence->jobNum, KSpeech::jsFinished); - emit textFinished(it->sentence->appId, it->sentence->jobNum); - } - it->state = usFinished; - m_again = true; - } - break; - } - case usSynthed: - { - // Don't bother stretching if factor is 1.0. - // Don't bother stretching if SSML. - // TODO: This is because sox mangles SSML pitch settings. Would be nice - // to figure out how to avoid this. - kDebug() << "Speaker::doUtterances: state usSynthed" << endl; - if (m_audioStretchFactor == 1.0 || it->isSSML) - { - it->state = usStretched; - m_again = true; - } - else - { - it->audioStretcher = new Stretcher(); - connect(it->audioStretcher, SIGNAL(stretchFinished()), - this, SLOT(slotStretchFinished())); - if (it->audioStretcher->stretch(it->audioUrl, makeSuggestedFilename(), - m_audioStretchFactor)) - { - it->state = usStretching; - m_again = true; // Is this needed? - } - else - { - // If stretch failed, it is most likely caused by sox not being - // installed. Just skip it. - it->state = usStretched; - m_again = true; - delete it->audioStretcher; - it->audioStretcher= 0; - } - } - break; - } - case usStretching: - { - // See if Stretcher is finished. - if (it->audioStretcher->getState() == Stretcher::ssFinished) - { - QFile::remove(it->audioUrl); - it->audioUrl = it->audioStretcher->getOutFilename(); - it->state = usStretched; - delete it->audioStretcher; - it->audioStretcher = 0; - m_again = true; - } - break; - } - case usStretched: - { - kDebug() << "Speaker::doUtterances: state usStretched" << endl; - // If first in queue, start playback. - if (it == itBegin) - { - if (startPlayingUtterance(it)) - { - playing = true; - m_again = true; - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - break; - } - case usPlaying: - { - kDebug() << "Speaker::doUtterances: state usPlaying" << endl; - playing = true; - break; - } - case usPaused: - case usPreempted: - { - if (!playing) - { - if (startPlayingUtterance(it)) - { - playing = true; - m_again = true; - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - break; - } - case usWaitingSay: - { - // If first in queue, start it. - if (it == itBegin) - { - int jobState = - m_speechData->getTextJobState(it->sentence->jobNum); - if ((jobState == KSpeech::jsSpeaking) || - (jobState == KSpeech::jsSpeakable)) - { - if (it->plugin->getState() == psIdle) - { - // Set job to speaking state and set sequence number. - mlText* sentence = it->sentence; - m_currentJobNum = sentence->jobNum; - m_speechData->setTextJobState(m_currentJobNum, KSpeech::jsSpeaking); - m_speechData->setJobSequenceNum(m_currentJobNum, sentence->seq); - prePlaySignals(it); - // kDebug() << "Async synthesis and audibilizing." << endl; - it->state = usSaying; - playing = true; - it->plugin->sayText(it->sentence->text); - m_again = true; - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - } else { - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - break; - } - case usWaitingSynth: - { - // TODO: If the synth is busy and the waiting text is screen - // reader output, it would be nice to call the synth's - // stopText() method. However, some of the current plugins - // have horrible startup times, so we won't do that for now. - kDebug() << "Speaker::doUtterances: state usWaitingSynth" << endl; - if (it->plugin->getState() == psIdle) - { - // kDebug() << "Async synthesis." << endl; - it->state = usSynthing; - ++synthingCnt; - it->plugin->synthText(it->sentence->text, - makeSuggestedFilename()); - m_again = true; - } - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - break; - } - case usSaying: - { - kDebug() << "Speaker::doUtterances: state usSaying" << endl; - // See if synthesis and audibilizing is finished. - if (it->plugin->getState() == psFinished) - { - it->plugin->ackFinished(); - it->state = usFinished; - m_again = true; - } else { - playing = true; - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - } - break; - } - case usSynthing: - { - kDebug() << "Speaker::doUtterances: state usSynthing" << endl; - // See if synthesis is completed. - if (it->plugin->getState() == psFinished) - { - it->audioUrl = KStandardDirs::realFilePath(it->plugin->getFilename()); - kDebug() << "Speaker::doUtterances: synthesized filename: " << it->audioUrl << endl; - it->plugin->ackFinished(); - it->state = usSynthed; - m_again = true; - } else ++synthingCnt; - ++waitingCnt; - if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; - break; - } - case usFinished: break; - } - } - // See if there are any messages or warnings to process. - // We keep up to 2 such utterances in the queue. - if ((waitingMsgCnt < 2) && (transformingCnt < 3)) - { - if (m_speechData->warningInQueue() || m_speechData->messageInQueue()) - { - getNextUtterance(); - m_again = true; - } - } - // Try to keep at least two utterances in the queue waiting to be played, - // and no more than 3 transforming at one time. - if ((waitingCnt < 2) && (transformingCnt < 3)) - if (getNextUtterance()) m_again = true; - } else { - // See if another utterance is ready to be worked on. - // If so, loop again since we've got work to do. - m_again = getNextUtterance(); - } - } - // kDebug() << "Speaker::doUtterances: exiting." << endl; -} - -/** - * Determine if kttsd is currently speaking any text jobs. - * @return True if currently speaking any text jobs. - */ -bool Speaker::isSpeakingText() -{ - return (m_speechData->getTextJobState(m_currentJobNum) == KSpeech::jsSpeaking); -} - -/** - * Get the job number of the current text job. - * @return Job number of the current text job. 0 if no jobs. - * - * Note that the current job may not be speaking. See @ref isSpeakingText. - */ -uint Speaker::getCurrentTextJob() { return m_currentJobNum; } - -/** - * Remove a text job from the queue. - * @param jobNum Job number of the text job. - * - * The job is deleted from the queue and the @ref textRemoved signal is emitted. - * - * If there is another job in the text queue, and it is marked speakable, - * that job begins speaking. - */ -void Speaker::removeText(const uint jobNum) -{ - deleteUtteranceByJobNum(jobNum); - m_speechData->removeText(jobNum); - doUtterances(); -} - -/** - * Start a text job at the beginning. - * @param jobNum Job number of the text job. - * - * Rewinds the job to the beginning. - * - * The job is marked speakable. - * If there are other speakable jobs preceeding this one in the queue, - * those jobs continue speaking and when finished, this job will begin speaking. - * If there are no other speakable jobs preceeding this one, it begins speaking. - * - * The @ref textStarted signal is emitted when the text job begins speaking. - * When all the sentences of the job have been spoken, the job is marked for deletion from - * the text queue and the @ref textFinished signal is emitted. - */ -void Speaker::startText(const uint jobNum) -{ - deleteUtteranceByJobNum(jobNum); - m_speechData->setJobSequenceNum(jobNum, 1); - m_speechData->setTextJobState(jobNum, KSpeech::jsSpeakable); - if (m_lastJobNum == jobNum) - { - // kDebug() << "Speaker::startText: startText called on speaking job " << jobNum << endl; - m_lastJobNum = 0; - m_lastAppId.clear(); - m_lastSeq = 0; - } - doUtterances(); -} - -/** - * Stop a text job and rewind to the beginning. - * @param jobNum Job number of the text job. - * - * The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText - * is called. - * - * If there are speaking jobs preceeding this one in the queue, they continue speaking. - * If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. - * Depending upon the speech engine and plugin used, speeking may not stop immediately - * (it might finish the current sentence). - */ -void Speaker::stopText(const uint jobNum) -{ - bool emitSignal = (m_speechData->getTextJobState(jobNum) == KSpeech::jsSpeaking); - deleteUtteranceByJobNum(jobNum); - m_speechData->setJobSequenceNum(jobNum, 1); - m_speechData->setTextJobState(jobNum, KSpeech::jsQueued); - if (emitSignal) textStopped(m_speechData->getAppIdByJobNum(jobNum), jobNum); - // Call doUtterances to process other jobs. - doUtterances(); -} - -/** - * Pause a text job. - * @param jobNum Job number of the text job. - * - * The job is marked as paused and will not be speakable until @ref resumeText or - * @ref startText is called. - * - * If there are speaking jobs preceeding this one in the queue, they continue speaking. - * If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. - * Depending upon the speech engine and plugin used, speeking may not stop immediately - * (it might finish the current sentence). - * @see resumeText - */ -void Speaker::pauseText(const uint jobNum) -{ - bool emitSignal = (m_speechData->getTextJobState(jobNum) == KSpeech::jsSpeaking); - pauseUtteranceByJobNum(jobNum); - kDebug() << "Speaker::pauseText: setting Job State of job " << jobNum << " to jsPaused" << endl; - m_speechData->setTextJobState(jobNum, KSpeech::jsPaused); - if (emitSignal) textPaused(m_speechData->getAppIdByJobNum(jobNum),jobNum); -} - -/** - * Start or resume a text job where it was paused. - * @param jobNum Job number of the text job. - * - * The job is marked speakable. - * - * If the job is currently speaking, or is waiting to be spoken (speakable - * state), the resumeText() call is ignored. - * - * If the job is currently queued, or is finished, it is the same as calling - * @ref startText . - * - * If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, - * when finished this job will begin speaking where it left off. - * - * The @ref textResumed signal is emitted when the job resumes. - * @see pauseText - */ -void Speaker::resumeText(const uint jobNum) -{ - int state = m_speechData->getTextJobState(jobNum); - switch (state) - { - case KSpeech::jsQueued: - case KSpeech::jsFinished: - startText(jobNum); - break; - case KSpeech::jsSpeakable: - case KSpeech::jsSpeaking: - doUtterances(); - break; - case KSpeech::jsPaused: - if (jobNum == m_currentJobNum) - m_speechData->setTextJobState(jobNum, KSpeech::jsSpeaking); - else - m_speechData->setTextJobState(jobNum, KSpeech::jsSpeakable); - doUtterances(); - break; - } -} - -/** - * Move a text job down in the queue so that it is spoken later. - * @param jobNum Job number of the text job. - * - * If the job is currently speaking, it is paused. - * If the next job in the queue is speakable, it begins speaking. - */ -void Speaker::moveTextLater(const uint jobNum) -{ - if (m_speechData->getTextJobState(jobNum) == KSpeech::jsSpeaking) - m_speechData->setTextJobState(jobNum, KSpeech::jsPaused); - deleteUtteranceByJobNum(jobNum); - m_speechData->moveTextLater(jobNum); - doUtterances(); -} - -/** - * Jump to the first sentence of a specified part of a text job. - * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. - * @param jobNum Job number of the text job. - * @return Part number of the part actually jumped to. - * - * If partNum is greater than the number of parts in the job, jumps to last part. - * If partNum is 0, does nothing and returns the current part number. - * If no such job, does nothing and returns 0. - * Does not affect the current speaking/not-speaking state of the job. - */ -int Speaker::jumpToTextPart(const int partNum, const uint jobNum) -{ - if (partNum == 0) return m_speechData->jumpToTextPart(partNum, jobNum); - deleteUtteranceByJobNum(jobNum); - int pNum = m_speechData->jumpToTextPart(partNum, jobNum); - if (pNum) - { - uint seq = m_speechData->getJobSequenceNum(jobNum); - if (jobNum == m_lastJobNum) - { - if (seq == 0) - m_lastSeq = seq; - else - m_lastSeq = seq - 1; - } - if (jobNum == m_currentJobNum) - { - m_lastJobNum = jobNum; - if (seq == 0) - m_lastSeq = 0; - else - m_lastSeq = seq - 1; - doUtterances(); - } - } - return pNum; -} - -/** - * Advance or rewind N sentences in a text job. - * @param n Number of sentences to advance (positive) or rewind (negative) - * in the job. - * @param jobNum Job number of the text job. - * @return Sequence number of the sentence actually moved to. - * Sequence numbers are numbered starting at 1. - * - * If no such job, does nothing and returns 0. - * If n is zero, returns the current sequence number of the job. - * Does not affect the current speaking/not-speaking state of the job. - */ -uint Speaker::moveRelTextSentence(const int n, const uint jobNum) -{ - if (0 == n) - return m_speechData->getJobSequenceNum(jobNum); - else { - deleteUtteranceByJobNum(jobNum); - // TODO: More efficient way to advance one or two sentences, since there is a - // good chance those utterances are already in the queue and synthesized. - uint seq = m_speechData->moveRelTextSentence(n, jobNum); - kDebug() << "Speaker::moveRelTextSentence: job num: " << jobNum << " moved to seq: " << seq << endl; - if (jobNum == m_lastJobNum) - { - if (0 == seq) - m_lastSeq = seq; - else - m_lastSeq = seq - 1; - } - if (jobNum == m_currentJobNum) - { - m_lastJobNum = jobNum; - if (0 == seq) - m_lastSeq = 0; - else - m_lastSeq = seq - 1; - doUtterances(); - } - return seq; - } -} - -/* Private Methods ==========================================================*/ - -/** - * Converts an utterance state enumerator to a displayable string. - * @param state Utterance state. - */ -QString Speaker::uttStateToStr(uttState state) -{ - switch (state) - { - case usNone: return "usNone"; - case usWaitingTransform: return "usWaitingTransform"; - case usTransforming: return "usTransforming"; - case usWaitingSay: return "usWaitingSay"; - case usWaitingSynth: return "usWaitingSynth"; - case usWaitingSignal: return "usWaitingSignal"; - case usSaying: return "usSaying"; - case usSynthing: return "usSynthing"; - case usSynthed: return "usSynthed"; - case usStretching: return "usStretching"; - case usStretched: return "usStretched"; - case usPlaying: return "usPlaying"; - case usPaused: return "usPaused"; - case usPreempted: return "usPreempted"; - case usFinished: return "usFinished"; - } - return QString(); -} - -/** - * Converts an utterance type enumerator to a displayable string. - * @param utType Utterance type. - * @return Displayable string for utterance type. - */ -QString Speaker::uttTypeToStr(uttType utType) -{ - switch (utType) - { - case utText: return "utText"; - case utInterruptMsg: return "utInterruptMsg"; - case utInterruptSnd: return "utInterruptSnd"; - case utResumeMsg: return "utResumeMsg"; - case utResumeSnd: return "utResumeSnd"; - case utMessage: return "utMessage"; - case utWarning: return "utWarning"; - case utScreenReader: return "utScreenReader"; - case utStartOfJob: return "utStartOfJob"; - case utEndOfJob: return "utEndOfJob"; - } - return QString(); -} - -/** - * Converts a plugin state enumerator to a displayable string. - * @param state Plugin state. - * @return Displayable string for plugin state. - */ -QString Speaker::pluginStateToStr(pluginState state) -{ - switch( state ) - { - case psIdle: return "psIdle"; - case psSaying: return "psSaying"; - case psSynthing: return "psSynthing"; - case psFinished: return "psFinished"; - } - return QString(); -} - -/** - * Converts a job state enumerator to a displayable string. - * @param state Job state. - * @return Displayable string for job state. - */ -QString Speaker::jobStateToStr(int state) -{ - switch ( state ) - { - case KSpeech::jsQueued: return "jsQueued"; - case KSpeech::jsSpeakable: return "jsSpeakable"; - case KSpeech::jsSpeaking: return "jsSpeaking"; - case KSpeech::jsPaused: return "jsPaused"; - case KSpeech::jsFinished: return "jsFinished"; - } - return QString(); -} - -/** - * Delete any utterances in the queue with this jobNum. - * @param jobNum Job Number of the utterances to delete. - * If currently processing any deleted utterances, stop them. - */ -void Speaker::deleteUtteranceByJobNum(const uint jobNum) -{ - uttIterator it = m_uttQueue.begin(); - while (it != m_uttQueue.end()) - { - if (it->sentence) - { - if (it->sentence->jobNum == jobNum) - it = deleteUtterance(it); - else - ++it; - } else - ++it; - } -} - -/** - * Pause the utterance with this jobNum if it is playing on the Audio Player. - * @param jobNum The Job Number of the utterance to pause. - */ -void Speaker::pauseUtteranceByJobNum(const uint jobNum) -{ - uttIterator itEnd = m_uttQueue.end(); - for (uttIterator it = m_uttQueue.begin(); it != itEnd; ++it) - { - if (it->sentence) // TODO: Why is this necessary? - if (it->sentence->jobNum == jobNum) - if (it->state == usPlaying) - { - if (it->audioPlayer) - if (it->audioPlayer->playing()) - { - m_timer->stop(); - kDebug() << "Speaker::pauseUtteranceByJobNum: pausing audio player" << endl; - it->audioPlayer->pause(); - kDebug() << "Speaker::pauseUtteranceByJobNum: Setting utterance state to usPaused" << endl; - it->state = usPaused; - return; - } - // Audio player has finished, but timeout hasn't had a chance - // to clean up. So do nothing, and let timeout do the cleanup. - } - } -} - -/** - * Determines whether the given text is SSML markup. - */ -bool Speaker::isSsml(const QString &text) -{ - return KttsUtils::hasRootElement( text, "speak" ); -} - -/** - * Determines the initial state of an utterance. If the utterance contains - * SSML, the state is set to usWaitingTransform. Otherwise, if the plugin - * supports async synthesis, sets to usWaitingSynth, otherwise usWaitingSay. - * If an utterance has already been transformed, usWaitingTransform is - * skipped to either usWaitingSynth or usWaitingSay. - * @param utt The utterance. - */ -void Speaker::setInitialUtteranceState(Utt &utt) -{ - if ((utt.state != usTransforming) && utt.isSSML) -{ - utt.state = usWaitingTransform; - return; -} - if (utt.plugin->supportsSynth()) - utt.state = usWaitingSynth; - else - utt.state = usWaitingSay; -} - -/** - * Returns true if the given job and sequence number are already in the utterance queue. - */ -bool Speaker::isInUtteranceQueue(uint jobNum, uint seqNum) -{ - uttIterator itEnd = m_uttQueue.end(); - for (uttIterator it = m_uttQueue.begin(); it != itEnd; ++it) - { - if (it->sentence) - { - if (it->sentence->jobNum == jobNum && it->sentence->seq == seqNum) return true; - } - } - return false; -} - -/** - * Gets the next utterance to be spoken from speechdata and adds it to the queue. - * @return True if one or more utterances were added to the queue. - * - * Checks for waiting ScreenReaderOutput, Warnings, Messages, or Text, - * in that order. - * If Warning or Message and interruption messages have been configured, - * adds those to the queue as well. - * Determines which plugin should be used for the utterance. - */ -bool Speaker::getNextUtterance() -{ - bool gotOne = false; - Utt* utt = 0; - if (m_speechData->screenReaderOutputReady()) { - utt = new Utt; - utt->utType = utScreenReader; - utt->sentence = m_speechData->getScreenReaderOutput(); - } else { - if (m_speechData->warningInQueue()) { - utt = new Utt; - utt->utType = utWarning; - utt->sentence = m_speechData->dequeueWarning(); - } else { - if (m_speechData->messageInQueue()) { - utt = new Utt; - utt->utType = utMessage; - utt->sentence = m_speechData->dequeueMessage(); - } else { - uint jobNum = m_lastJobNum; - uint seq = m_lastSeq; - mlText* sentence = m_speechData->getNextSentenceText(jobNum, seq); - // Skip over blank lines. - while (sentence && sentence->text.isEmpty()) - { - jobNum = sentence->jobNum; - seq = sentence->seq; - sentence = m_speechData->getNextSentenceText(jobNum, seq); - } - // If this utterance is already in the queue, it means we have run out of - // stuff to say and are trying to requeue already queued (and waiting stuff). - if (sentence && !isInUtteranceQueue(sentence->jobNum, sentence->seq)) - { - utt = new Utt; - utt->utType = utText; - utt->sentence = sentence; - } - } - } - } - if (utt) - { - gotOne = true; - utt->isSSML = isSsml(utt->sentence->text); - utt->state = usNone; - utt->audioPlayer = 0; - utt->audioStretcher = 0; - utt->audioUrl.clear(); - utt->plugin = m_talkerMgr->talkerToPlugin(utt->sentence->talker); - // Save some time by setting initial state now. - setInitialUtteranceState(*utt); - // Screen Reader Outputs need to be processed ASAP. - if (utt->utType == utScreenReader) - { - m_uttQueue.insert(m_uttQueue.begin(), *utt); - // Delete any other Screen Reader Outputs in the queue. - // Only one Screen Reader Output at a time. - uttIterator it = m_uttQueue.begin(); - ++it; - while (it != m_uttQueue.end()) - { - if (it->utType == utScreenReader) - it = deleteUtterance(it); - else - ++it; - } - } - // If the new utterance is a Warning or Message... - if ((utt->utType == utWarning) || (utt->utType == utMessage)) - { - uttIterator itEnd = m_uttQueue.end(); - uttIterator it = m_uttQueue.begin(); - bool interrupting = false; - if (it != itEnd) - { - // New Warnings go after Screen Reader Output, other Warnings, - // Interruptions, and in-process text, - // but before Resumes, waiting text or signals. - if (utt->utType == utWarning) - while ( it != itEnd && - ((it->utType == utScreenReader) || - (it->utType == utWarning) || - (it->utType == utInterruptMsg) || - (it->utType == utInterruptSnd))) ++it; - // New Messages go after Screen Reader Output, Warnings, other Messages, - // Interruptions, and in-process text, - // but before Resumes, waiting text or signals. - if (utt->utType == utMessage) - while ( it != itEnd && - ((it->utType == utScreenReader) || - (it->utType == utWarning) || - (it->utType == utMessage) || - (it->utType == utInterruptMsg) || - (it->utType == utInterruptSnd))) ++it; - if (it != itEnd) - if (it->utType == utText && - ((it->state == usPlaying) || - (it->state == usSaying))) ++it; - // If now pointing at a text message, we are interrupting. - // Insert optional Interruption message and sound. - if (it != itEnd) interrupting = (it->utType == utText && it->state != usPaused); - if (interrupting) - { - if (m_speechData->textPreSndEnabled) - { - Utt intrUtt; - intrUtt.sentence = new mlText; - intrUtt.sentence->text.clear(); - intrUtt.sentence->talker = utt->sentence->talker; - intrUtt.sentence->appId = utt->sentence->appId; - intrUtt.sentence->jobNum = utt->sentence->jobNum; - intrUtt.sentence->seq = 0; - intrUtt.audioUrl = m_speechData->textPreSnd; - intrUtt.audioPlayer = 0; - intrUtt.utType = utInterruptSnd; - intrUtt.isSSML = false; - intrUtt.state = usStretched; - intrUtt.plugin = 0; - it = m_uttQueue.insert(it, intrUtt); - ++it; - } - if (m_speechData->textPreMsgEnabled) - { - Utt intrUtt; - intrUtt.sentence = new mlText; - intrUtt.sentence->text = m_speechData->textPreMsg; - // Interruptions are spoken using default Talker. - intrUtt.sentence->talker.clear(); - intrUtt.sentence->appId = utt->sentence->appId; - intrUtt.sentence->jobNum = utt->sentence->jobNum; - intrUtt.sentence->seq = 0; - intrUtt.audioUrl.clear(); - intrUtt.audioPlayer = 0; - intrUtt.utType = utInterruptMsg; - intrUtt.isSSML = isSsml(intrUtt.sentence->text); - intrUtt.plugin = m_talkerMgr->talkerToPlugin(intrUtt.sentence->talker); - intrUtt.state = usNone; - setInitialUtteranceState(intrUtt); - it = m_uttQueue.insert(it, intrUtt); - ++it; - } - } - } - // Insert the new message or warning. - it = m_uttQueue.insert(it, *utt); - ++it; - // Resumption message and sound. - if (interrupting) - { - if (m_speechData->textPostSndEnabled) - { - Utt resUtt; - resUtt.sentence = new mlText; - resUtt.sentence->text.clear(); - resUtt.sentence->talker = utt->sentence->talker; - resUtt.sentence->appId = utt->sentence->appId; - resUtt.sentence->jobNum = utt->sentence->jobNum; - resUtt.sentence->seq = 0; - resUtt.audioUrl = m_speechData->textPostSnd; - resUtt.audioPlayer = 0; - resUtt.utType = utResumeSnd; - resUtt.isSSML = false; - resUtt.state = usStretched; - resUtt.plugin = 0; - it = m_uttQueue.insert(it, resUtt); - ++it; - } - if (m_speechData->textPostMsgEnabled) - { - Utt resUtt; - resUtt.sentence = new mlText; - resUtt.sentence->text = m_speechData->textPostMsg; - resUtt.sentence->talker.clear(); - resUtt.sentence->appId = utt->sentence->appId; - resUtt.sentence->jobNum = utt->sentence->jobNum; - resUtt.sentence->seq = 0; - resUtt.audioUrl.clear(); - resUtt.audioPlayer = 0; - resUtt.utType = utResumeMsg; - resUtt.isSSML = isSsml(resUtt.sentence->text); - resUtt.plugin = m_talkerMgr->talkerToPlugin(resUtt.sentence->talker); - resUtt.state = usNone; - setInitialUtteranceState(resUtt); - it = m_uttQueue.insert(it, resUtt); - } - } - } - // If a text message... - if (utt->utType == utText) - { - // If job number has changed... - if (utt->sentence->jobNum != m_lastJobNum) - { - // If we finished the last job, append End-of-job to the queue, - // which will become a textFinished signal when it is processed. - if (m_lastJobNum) - { - if (m_lastSeq == static_cast(m_speechData->getTextCount(m_lastJobNum))) - { - Utt jobUtt; - jobUtt.sentence = new mlText; - jobUtt.sentence->text.clear(); - jobUtt.sentence->talker.clear(); - jobUtt.sentence->appId = m_lastAppId; - jobUtt.sentence->jobNum = m_lastJobNum; - jobUtt.sentence->seq = 0; - jobUtt.audioUrl.clear(); - jobUtt.utType = utEndOfJob; - jobUtt.isSSML = false; - jobUtt.plugin = 0; - jobUtt.state = usWaitingSignal; - m_uttQueue.append(jobUtt); - } - } - m_lastJobNum = utt->sentence->jobNum; - m_lastAppId = utt->sentence->appId; - // If we are at beginning of new job, append Start-of-job to queue, - // which will become a textStarted signal when it is processed. - if (utt->sentence->seq == 1) - { - Utt jobUtt; - jobUtt.sentence = new mlText; - jobUtt.sentence->text.clear(); - jobUtt.sentence->talker.clear(); - jobUtt.sentence->appId = m_lastAppId; - jobUtt.sentence->jobNum = m_lastJobNum; - jobUtt.sentence->seq = utt->sentence->seq; - jobUtt.audioUrl.clear(); - jobUtt.utType = utStartOfJob; - jobUtt.isSSML = false; - jobUtt.plugin = 0; - jobUtt.state = usWaitingSignal; - m_uttQueue.append(jobUtt); - } - } - m_lastSeq = utt->sentence->seq; - // Add the new utterance to the queue. - m_uttQueue.append(*utt); - } - delete utt; - } else { - // If no more text to speak, and we finished the last job, issue textFinished signal. - if (m_lastJobNum) - { - if (m_lastSeq == static_cast(m_speechData->getTextCount(m_lastJobNum))) - { - Utt jobUtt; - jobUtt.sentence = new mlText; - jobUtt.sentence->text.clear(); - jobUtt.sentence->talker.clear(); - jobUtt.sentence->appId = m_lastAppId; - jobUtt.sentence->jobNum = m_lastJobNum; - jobUtt.sentence->seq = 0; - jobUtt.audioUrl.clear(); - jobUtt.utType = utEndOfJob; - jobUtt.isSSML = false; - jobUtt.plugin = 0; - jobUtt.state = usWaitingSignal; - m_uttQueue.append(jobUtt); - gotOne = true; - ++m_lastSeq; // Don't append another End-of-job - } - } - } - - return gotOne; -} - -/** - * Given an iterator pointing to the m_uttQueue, deletes the utterance - * from the queue. If the utterance is currently being processed by a - * plugin or the Audio Player, halts that operation and deletes Audio Player. - * Also takes care of deleting temporary audio file. - * @param it Iterator pointer to m_uttQueue. - * @return Iterator pointing to the next utterance in the - * queue, or m_uttQueue.end(). - */ -uttIterator Speaker::deleteUtterance(uttIterator it) -{ - switch (it->state) - { - case usNone: - case usWaitingTransform: - case usWaitingSay: - case usWaitingSynth: - case usWaitingSignal: - case usSynthed: - case usFinished: - case usStretched: - break; - - case usTransforming: - { - delete it->transformer; - it->transformer = 0; - break; - } - case usSaying: - case usSynthing: - { - // If plugin supports asynchronous mode, and it is busy, halt it. - PlugInProc* plugin = it->plugin; - if (it->plugin->supportsAsync()) - if ((plugin->getState() == psSaying) || (plugin->getState() == psSynthing)) - { - kDebug() << "Speaker::deleteUtterance calling stopText" << endl; - plugin->stopText(); - } - break; - } - case usStretching: - { - delete it->audioStretcher; - it->audioStretcher = 0; - break; - } - case usPlaying: - { - m_timer->stop(); - it->audioPlayer->stop(); - delete it->audioPlayer; - break; - } - case usPaused: - case usPreempted: - { - // Note: Must call stop(), even if player not currently playing. Why? - it->audioPlayer->stop(); - delete it->audioPlayer; - break; - } - } - if (!it->audioUrl.isNull()) - { - // If the audio file was generated by a plugin, delete it. - if (it->plugin) - { - if (m_speechData->keepAudio) - { - QString seqStr; - seqStr.sprintf("%08i", it->sentence->seq); // Zero-fill to 8 chars. - QString jobStr; - jobStr.sprintf("%08i", it->sentence->jobNum); - QString dest = m_speechData->keepAudioPath + "/kttsd-" + - QString("%1-%2").arg(jobStr).arg(seqStr) + ".wav"; - QFile::remove(dest); - QDir d; - d.rename(it->audioUrl, dest); - // TODO: This is always producing the following. Why and how to fix? - // It moves the files just fine. - // kio (KIOJob): stat file:///home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav - // kio (KIOJob): error 11 /home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav - // kio (KIOJob): This seems to be a suitable case for trying to rename before stat+[list+]copy+del - // KIO::move(it->audioUrl, dest, false); - } - else - QFile::remove(it->audioUrl); - } - } - // Delete the utterance from queue. - delete it->sentence; - return m_uttQueue.erase(it); -} - -/** - * Given an iterator pointing to the m_uttQueue, starts playing audio if - * 1) An audio file is ready to be played, and - * 2) It is not already playing. - * If another audio player is already playing, pauses it before starting - * the new audio player. - * @param it Iterator pointer to m_uttQueue. - * @return True if an utterance began playing or resumed. - */ -bool Speaker::startPlayingUtterance(uttIterator it) -{ - // kDebug() << "Speaker::startPlayingUtterance running" << endl; - if (it->state == usPlaying) return false; - if (it->audioUrl.isNull()) return false; - bool started = false; - // Pause (preempt) any other utterance currently being spoken. - // If any plugins are audibilizing, must wait for them to finish. - uttIterator itEnd = m_uttQueue.end(); - for (uttIterator it2 = m_uttQueue.begin(); it2 != itEnd; ++it2) - if (it2 != it) - { - if (it2->state == usPlaying) - { - m_timer->stop(); - it2->audioPlayer->pause(); - it2->state = usPreempted; - } - if (it2->state == usSaying) return false; - } - uttState utState = it->state; - switch (utState) - { - case usNone: - case usWaitingTransform: - case usTransforming: - case usWaitingSay: - case usWaitingSynth: - case usWaitingSignal: - case usSaying: - case usSynthing: - case usSynthed: - case usStretching: - case usPlaying: - case usFinished: - break; - - case usStretched: - { - // Don't start playback yet if text job is paused. - if ((it->utType != utText) || - (m_speechData->getTextJobState(it->sentence->jobNum) != KSpeech::jsPaused)) - { - - it->audioPlayer = createPlayerObject(); - if (it->audioPlayer) - { - it->audioPlayer->startPlay(it->audioUrl); - // Set job to speaking state and set sequence number. - mlText* sentence = it->sentence; - m_currentJobNum = sentence->jobNum; - m_speechData->setTextJobState(m_currentJobNum, KSpeech::jsSpeaking); - m_speechData->setJobSequenceNum(m_currentJobNum, sentence->seq); - prePlaySignals(it); - it->state = usPlaying; - m_timer->start(timerInterval); - started = true; - } else { - // If could not create audio player object, best we can do is silence. - it->state = usFinished; - } - } - break; - } - case usPaused: - { - // Unpause playback only if user has resumed. - // kDebug() << "Speaker::startPlayingUtterance: checking whether to resume play" << endl; - if ((it->utType != utText) || - (m_speechData->getTextJobState(it->sentence->jobNum) != KSpeech::jsPaused)) - { - // kDebug() << "Speaker::startPlayingUtterance: resuming play" << endl; - it->audioPlayer->startPlay(QString()); // resume - it->state = usPlaying; - m_timer->start(timerInterval); - started = true; - } - break; - } - - case usPreempted: - { - // Preempted playback automatically resumes. - // Note: Must call stop(), even if player not currently playing. Why? - it->audioPlayer->startPlay(QString()); // resume - it->state = usPlaying; - m_timer->start(timerInterval); - started = true; - break; - } - } - return started; -} - -/** - * Takes care of emitting reading interrupted/resumed and sentence started signals. - * Should be called just before audibilizing an utterance. - * @param it Iterator pointer to m_uttQueue. - * It also makes sure the job state is set to jsSpeaking. - */ -void Speaker::prePlaySignals(uttIterator it) -{ - uttType utType = it->utType; - if (utType == utText) - { - // If this utterance is for a regular text message, - // and it was interrupted, emit reading resumed signal. - if (m_textInterrupted) - { - m_textInterrupted = false; - emit readingResumed(); - } - // Set job to speaking state and set sequence number. - mlText* sentence = it->sentence; - // Emit sentence started signal. - emit sentenceStarted(sentence->text, - sentence->talker, sentence->appId, - m_currentJobNum, sentence->seq); - } else { - // If this utterance is not a regular text message, - // and we are doing a text job, emit reading interrupted signal. - if (isSpeakingText()) - { - m_textInterrupted = true; - emit readingInterrupted(); - } - } -} - -/** - * Takes care of emitting sentenceFinished signal. - * Should be called immediately after an utterance has completed playback. - * @param it Iterator pointer to m_uttQueue. - */ -void Speaker::postPlaySignals(uttIterator it) -{ - uttType utType = it->utType; - if (utType == utText) - { - // If this utterance is for a regular text message, - // emit sentence finished signal. - mlText* sentence = it->sentence; - emit sentenceFinished(sentence->appId, - sentence->jobNum, sentence->seq); - } -} - -/** - * Constructs a temporary filename for plugins to use as a suggested filename - * for synthesis to write to. - * @return Full pathname of suggested file. - */ -QString Speaker::makeSuggestedFilename() -{ - QString tmpDir = locateLocal("tmp", "kttsd-"); - kDebug() << "Speaker::makeSuggestedFilename: tmpDir = " << tmpDir << endl; - KTempFile tempFile (tmpDir, ".wav"); - // TODO: This not working. Why? - // QString waveFile = tempFile.file()->name(); - // QString waveFile = mFile.name(); - // tempFile.close(); - QString waveFile = tempFile.name(); - QFile::remove(waveFile); - kDebug() << "Speaker::makeSuggestedFilename: Suggesting filename: " << waveFile << endl; - return KStandardDirs::realFilePath(waveFile); -} - -/** - * Creates and returns a player object based on user option. - */ -Player* Speaker::createPlayerObject() -{ - Player* player = 0; - QString plugInName; - switch(m_playerOption) - { - case 0 : - { - plugInName = "kttsd_phononplugin"; - break; - } - case 2 : - { - plugInName = "kttsd_alsaplugin"; - break; - } - default: - { - // TODO: Default to Phonon. - plugInName = "kttsd_phononplugin"; - break; - } - } - KService::List offers = KServiceTypeTrader::self()->query( - "KTTSD/AudioPlugin", QString("DesktopEntryName == '%1'").arg(plugInName)); - - if(offers.count() == 1) - { - kDebug() << "Speaker::createPlayerObject: Loading " << offers[0]->library() << endl; - KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().toLatin1()); - if (factory) - player = - KLibLoader::createInstance( - offers[0]->library().toLatin1(), this, QStringList(offers[0]->library().toLatin1())); - } - if (player == 0) - { - // If we failed, fall back to default plugin. - // Default to Phonon. - if (m_playerOption != 0) - { - kDebug() << "Speaker::createPlayerObject: Could not load " + plugInName + - " plugin. Falling back to KDE (Phonon)." << endl; - m_playerOption = 0; - return createPlayerObject(); - } - else - kDebug() << "Speaker::createPlayerObject: Could not load KDE (Phonon) plugin. Is KDEDIRS set correctly?" << endl; - } - if (player) { - player->setSinkName(m_sinkName); - player->setPeriodSize(m_periodSize); - player->setPeriods(m_periodSize); - player->setDebugLevel(m_playerDebugLevel); - } - return player; -} - - -/* Slots ==========================================================*/ - -/** -* Received from PlugIn objects when they finish asynchronous synthesis -* and audibilizing. -* TODO: In Qt4, custom events are no longer necessary as events may pass -* through thread boundaries now. -*/ -void Speaker::slotSayFinished() -{ - // Since this signal handler may be running from a plugin's thread, - // convert to postEvent and return immediately. - QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 101)); - QApplication::postEvent(this, ev); -} - -/** -* Received from PlugIn objects when they finish asynchronous synthesis. -*/ -void Speaker::slotSynthFinished() -{ - // Since this signal handler may be running from a plugin's thread, - // convert to postEvent and return immediately. - QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 102)); - QApplication::postEvent(this, ev); -} - -/** -* Received from PlugIn objects when they asynchronously stopText. -*/ -void Speaker::slotStopped() -{ - // Since this signal handler may be running from a plugin's thread, - // convert to postEvent and return immediately. - QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 103)); - QApplication::postEvent(this, ev); -} - -/** -* Received from audio stretcher when stretching (speed adjustment) is finished. -*/ -void Speaker::slotStretchFinished() -{ - // Convert to postEvent and return immediately. - QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 104)); - QApplication::postEvent(this, ev); -} - -/** -* Received from transformer (SSMLConvert) when transforming is finished. -*/ -void Speaker::slotTransformFinished() -{ - // Convert to postEvent and return immediately. - QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 105)); - QApplication::postEvent(this, ev); -} - -/** Received from PlugIn object when they encounter an error. -* @param keepGoing True if the plugin can continue processing. -* False if the plugin cannot continue, for example, -* the speech engine could not be started. -* @param msg Error message. -*/ -void Speaker::slotError(bool /*keepGoing*/, const QString& /*msg*/) -{ - // Since this signal handler may be running from a plugin's thread, - // convert to postEvent and return immediately. - // TODO: Do something with error messages. - /* - if (keepGoing) - QCustomEvent* ev = new QCustomEvent(QEvent::User + 106); - else - QCustomEvent* ev = new QCustomEvent(QEvent::User + 107); - QApplication::postEvent(this, ev); - */ -} - -/** -* Received from Timer when it fires. -* Check audio player to see if it is finished. -*/ -void Speaker::slotTimeout() -{ - uttIterator itEnd = m_uttQueue.end(); - for (uttIterator it = m_uttQueue.begin(); it != itEnd; ++it) - { - if (it->state == usPlaying) - { - if (it->audioPlayer->playing()) return; // Still playing. - m_timer->stop(); - postPlaySignals(it); - deleteUtterance(it); - doUtterances(); - return; - } - } -} - -/** -* Processes events posted by plugins. When asynchronous plugins emit signals -* they are converted into these events. -*/ -bool Speaker::event ( QEvent * e ) -{ - // TODO: Do something with event numbers 106 (error; keepGoing=True) - // and 107 (error; keepGoing=False). - if ((e->type() >= (QEvent::User + 101)) && (e->type() <= (QEvent::User + 105))) - { - // kDebug() << "Speaker::event: received event." << endl; - doUtterances(); - return TRUE; - } - else return FALSE; -} - +/***************************************************** vim:set ts=4 sw=4 sts=4: + Speaker class. + + This class is in charge of getting the messages, warnings and text from + the queue and calling the plugins to actually speak the texts. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// System includes. +#include + +// Qt includes. +#include +#include +#include + +// KDE includes. +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +// KTTS includes. +#include "pluginproc.h" +#include "player.h" +#include "utils.h" +#include "talkercode.h" +#include "stretcher.h" + +// KTTSD includes. +#include "speechdata.h" +#include "talkermgr.h" +#include "ssmlconvert.h" + +// Speaker includes. +#include "speaker.h" +#include "speaker.moc" + + +/** +* The Speaker class takes sentences from the text queue, messages from the +* messages queue, warnings from the warnings queue, and Screen Reader +* output and places them into an internal "utterance queue". It then +* loops through this queue, farming the work off to the plugins. +* It tries to optimize processing so as to keep the plugins busy as +* much as possible, while ensuring that only one stream of audio is +* heard at any one time. +* +* The message queues are maintained in the SpeechData class. +* +* Text jobs in the text queue each have a state (queued, speakable, +* speaking, paused, finished). Each plugin has a state (idle, saying, synthing, +* or finished). And finally, each utterance has a state (waiting, saying, +* synthing, playing, finished). It can be confusing if you are not aware +* of all these states. +* +* Speaker takes some pains to ensure speech is spoken in the correct order, +* namely Screen Reader Output has the highest priority, Warnings are next, +* Messages are next, and finally regular text jobs. Since Screen Reader +* Output, Warnings, and Messages can be queued in the middle of a text +* job, Speaker must be prepared to reorder utterances in its queue. +* +* At the same time, it must issue the signals to inform programs +* what is happening. +* +* Finally, users can pause, restart, delete, advance, or rewind text jobs +* and Speaker must respond to these commands. In some cases, utterances that +* have already been synthesized and are ready for audio output must be +* discarded in response to these commands. +* +* Some general guidelines for programmers modifying this code: +* - Avoid blocking at all cost. If a plugin won't stopText, keep going. +* You might have to wait for the plugin to complete before asking it +* to perform the next operation, but in the meantime, there might be +* other useful work that can be performed. +* - In no case allow the main thread Qt event loop to block. +* - Plugins that do not have asynchronous support are wrapped in the +* ThreadedPlugin class, which attempts to make them as asynchronous as +* it can, but there are limits. +* - doUtterances is the main worker method. If in doubt, call doUtterances. +* - Because Speaker controls the ordering of utterances, most sequence-related +* signals must be emitted by Speaker; not SpeechData. Only the add +* and delete job-related signals eminate from SpeechData. +* - The states of the 3 types of objects mentioned above (jobs, utterances, +* and plugins) can interact in subtle ways. Test fully. For example, while +* a text job might be in a paused state, the plugin could be synthesizing +* in anticipation of resume, or sythesizing a Warning, Message, or +* Screen Reader Output. Meanwhile, while one of the utterances might +* have a paused state, others from the same job could be synthing, waiting, +* or finished. +* - There can be more than one Audio Player object in existence at one time, although +* there must never be more than one actually playing at one time. For +* example, an Audio Player playing an utterance from a text job can be +* in a paused state, while another Audio Player is playing a Screen Reader +* Output utterance. Finally, since some plugins do their own audio, it +* might be that none of the Audio Player objects are playing. +*/ + + +class SpeakerPrivate +{ + SpeakerPrivate(SpeechData* speechData, TalkerMgr* talkerMgr) : + speechData(speechData), + talkerMgr(talkerMgr), + configData(NULL), + exitRequested(false), + again(false), + timer(NULL), + currentJobNum(0) + { + } + + ~SpeakerPrivate() { } + + friend class Speaker; + +protected: + /** + * SpeechData local pointer + */ + SpeechData* speechData; + + /** + * TalkerMgr local pointer. + */ + TalkerMgr* talkerMgr; + + /** + * Configuration Data object. + */ + ConfigData* configData; + + /** + * True if the speaker was requested to exit. + */ + volatile bool exitRequested; + + /** + * Queue of utterances we are currently processing. + */ + QList uttQueue; + + /** + * Used to prevent doUtterances from prematurely exiting. + */ + bool again; + + /** + * Timer for monitoring audio player. + */ + QTimer* timer; + + /** + * Current Text job being played. + */ + int currentJobNum; + + QMap currentJobs; +}; + +/* Public Methods ==========================================================*/ + +Speaker::Speaker( + SpeechData*speechData, + TalkerMgr* talkerMgr, + QObject *parent) : + + QObject(parent), + d(new SpeakerPrivate(speechData, talkerMgr)) +{ + // kDebug() << "Running: Speaker::Speaker()" << endl; + d->timer = new QTimer(this); + // Connect timer timeout signal. + connect(d->timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); + + // Connect plugins to slots. + PlugInList plugins = d->talkerMgr->getLoadedPlugIns(); + const int pluginsCount = plugins.count(); + for (int ndx = 0; ndx < pluginsCount; ++ndx) + { + PlugInProc* speech = plugins.at(ndx); + connect(speech, SIGNAL(synthFinished()), + this, SLOT(slotSynthFinished())); + connect(speech, SIGNAL(sayFinished()), + this, SLOT(slotSayFinished())); + connect(speech, SIGNAL(stopped()), + this, SLOT(slotStopped())); + connect(speech, SIGNAL(error(bool, const QString&)), + this, SLOT(slotError(bool, const QString&))); + } + + d->currentJobs.insert(KSpeech::jpScreenReaderOutput, NULL); + d->currentJobs.insert(KSpeech::jpWarning, NULL); + d->currentJobs.insert(KSpeech::jpMessage, NULL); + d->currentJobs.insert(KSpeech::jpText, NULL); +} + +Speaker::~Speaker(){ + // kDebug() << "Running: Speaker::~Speaker()" << endl; + d->timer->stop(); + delete d->timer; + if (!d->uttQueue.isEmpty()) + { + uttIterator it; + for (it = d->uttQueue.begin(); it != d->uttQueue.end(); ) + it = deleteUtterance(it); + } + delete d; +} + +void Speaker::setConfigData(ConfigData* configData) +{ + d->configData = configData; +} + +void Speaker::requestExit(){ + // kDebug() << "Speaker::requestExit: Running" << endl; + d->exitRequested = true; +} + +void Speaker::doUtterances() +{ + // kDebug() << "Running: Speaker::doUtterances()" << endl; + + // Used to prevent exiting prematurely. + d->again = true; + + while(d->again && !d->exitRequested) + { + d->again = false; + + if (d->exitRequested) + { + // kDebug() << "Speaker::run: exiting due to request 1." << endl; + return; + } + + uttIterator it; + uttIterator itBegin; + uttIterator itEnd = 0; // Init to zero to avoid compiler warning. + + // If Screen Reader Output is waiting, we need to process it ASAP. + d->again = getNextUtterance(KSpeech::jpScreenReaderOutput); + + kDebug() << "Speaker::doUtterances: queue dump:" << endl; + for (it = d->uttQueue.begin(); it != d->uttQueue.end(); ++it) + { + QString pluginState = "no plugin"; + if (it->plugin()) pluginState = pluginStateToStr(it->plugin()->getState()); + QString jobState = "no job"; + if (it->job()) + jobState = SpeechJob::jobStateToStr(it->job()->state()); + kDebug() << + " State: " << Utt::uttStateToStr(it->state()) << + "," << pluginState << + "," << jobState << + " Type: " << Utt::uttTypeToStr(it->utType()) << + " Text: " << it->sentence() << endl; + } + + if (!d->uttQueue.isEmpty()) + { + // Delete utterances that are finished. + it = d->uttQueue.begin(); + while (it != d->uttQueue.end()) + { + if (Utt::usFinished == it->state()) + it = deleteUtterance(it); + else + ++it; + } + // Loop through utterance queue. + int waitingCnt = 0; + int waitingMsgCnt = 0; + int transformingCnt = 0; + bool playing = false; + int synthingCnt = 0; + itEnd = d->uttQueue.end(); + itBegin = d->uttQueue.begin(); + for (it = itBegin; it != itEnd; ++it) + { + // Skip the utterance if application is paused. + if (!d->speechData->isApplicationPaused(it->appId())) + { + Utt::uttState utState = it->state(); + Utt::uttType utType = it->utType(); + switch (utState) + { + case Utt::usNone: + { + it->setInitialState(); + d->again = true; + break; + } + case Utt::usWaitingTransform: + { + // Create an XSLT transformer and transform the text. + SSMLConvert* transformer = new SSMLConvert(); + it->setTransformer(transformer); + connect(transformer, SIGNAL(transformFinished()), + this, SLOT(slotTransformFinished())); + if (transformer->transform(it->sentence(), + it->plugin()->getSsmlXsltFilename())) + { + it->setState(Utt::usTransforming); + ++transformingCnt; + } + else + { + // If an error occurs transforming, skip it. + it->setState(Utt::usTransforming); + it->setInitialState(); + } + d->again = true; + break; + } + case Utt::usTransforming: + { + // See if transformer is finished. + if (it->transformer()->getState() == SSMLConvert::tsFinished) + { + // Get the transformed text. + it->setSentence(it->transformer()->getOutput()); + // Set next state (usWaitingSynth or usWaitingSay) + it->setInitialState(); + d->again = true; + --transformingCnt; + } + break; + } + case Utt::usSynthed: + { + // Don't bother stretching if factor is 1.0. + // Don't bother stretching if SSML. + // TODO: This is because sox mangles SSML pitch settings. Would be nice + // to figure out how to avoid this. + kDebug() << "Speaker::doUtterances: state usSynthed" << endl; + if (d->configData->audioStretchFactor == 1.0 || it->isSsml()) + { + it->setState(Utt::usStretched); + d->again = true; + } + else + { + Stretcher* stretcher = new Stretcher(); + it->setAudioStretcher(stretcher); + connect(stretcher, SIGNAL(stretchFinished()), + this, SLOT(slotStretchFinished())); + if (stretcher->stretch(it->audioUrl(), makeSuggestedFilename(), + d->configData->audioStretchFactor)) + { + it->setState(Utt::usStretching); + d->again = true; // Is this needed? + } + else + { + // If stretch failed, it is most likely caused by sox not being + // installed. Just skip it. + it->setState(Utt::usStretched); + d->again = true; + delete stretcher; + it->setAudioStretcher(NULL); + } + } + break; + } + case Utt::usStretching: + { + // See if Stretcher is finished. + Stretcher* stretcher = it->audioStretcher(); + if (stretcher->getState() == Stretcher::ssFinished) + { + QFile::remove(it->audioUrl()); + it->setAudioUrl(stretcher->getOutFilename()); + it->setState(Utt::usStretched); + delete stretcher; + it->setAudioStretcher(NULL); + d->again = true; + } + break; + } + case Utt::usStretched: + { + kDebug() << "Speaker::doUtterances: state usStretched" << endl; + // If first in queue, start playback. + if (it == itBegin) + { + if (startPlayingUtterance(it)) + { + playing = true; + d->again = true; + } else { + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + break; + } + case Utt::usPlaying: + { + kDebug() << "Speaker::doUtterances: state usPlaying" << endl; + playing = true; + break; + } + case Utt::usPaused: + case Utt::usPreempted: + { + if (!playing) + { + if (startPlayingUtterance(it)) + { + playing = true; + d->again = true; + } else { + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + break; + } + case Utt::usWaitingSay: + { + // If first in queue, start it. + if (it == itBegin) + { + if (it->plugin()->getState() == psIdle) + { + // Set job to speaking state and set sequence number. + d->currentJobNum = it->job()->jobNum(); + it->setState(Utt::usSaying); + prePlaySignals(it); + // kDebug() << "Async synthesis and audibilizing." << endl; + playing = true; + it->plugin()->sayText(it->sentence()); + d->again = true; + } else { + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + break; + } + case Utt::usWaitingSynth: + { + // TODO: If the synth is busy and the waiting text is screen + // reader output, it would be nice to call the synth's + // stopText() method. However, some of the current plugins + // have horrible startup times, so we won't do that for now. + kDebug() << "Speaker::doUtterances: state usWaitingSynth" << endl; + if (it->plugin()->getState() == psIdle) + { + // kDebug() << "Async synthesis." << endl; + it->setState(Utt::usSynthing); + ++synthingCnt; + it->plugin()->synthText(it->sentence(), + makeSuggestedFilename()); + d->again = true; + } + ++waitingCnt; + if (Utt::utMessage == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + break; + } + case Utt::usSaying: + { + kDebug() << "Speaker::doUtterances: state usSaying" << endl; + // See if synthesis and audibilizing is finished. + if (it->plugin()->getState() == psFinished) + { + it->plugin()->ackFinished(); + it->setState(Utt::usFinished); + d->again = true; + } else { + playing = true; + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + } + break; + } + case Utt::usSynthing: + { + kDebug() << "Speaker::doUtterances: state usSynthing" << endl; + // See if synthesis is completed. + if (it->plugin()->getState() == psFinished) + { + it->setAudioUrl(KStandardDirs::realFilePath(it->plugin()->getFilename())); + kDebug() << "Speaker::doUtterances: synthesized filename: " << it->audioUrl() << endl; + it->plugin()->ackFinished(); + it->setState(Utt::usSynthed); + d->again = true; + } else ++synthingCnt; + ++waitingCnt; + if (Utt::utWarning == utType || Utt::utMessage == utType) + ++waitingMsgCnt; + break; + } + case Utt::usFinished: break; + } + } + } + // See if there are any messages or warnings to process. + // We keep up to 2 such utterances in the queue. + if ((waitingMsgCnt < 2) && (transformingCnt < 3)) + { + if (getNextUtterance(KSpeech::jpWarning)) + d->again = true; + else + if (getNextUtterance(KSpeech::jpMessage)) + d->again = true; + } + // Try to keep at least two utterances in the queue waiting to be played, + // and no more than 3 transforming at one time. + if ((waitingCnt < 2) && (transformingCnt < 3)) + if (getNextUtterance(KSpeech::jpAll)) + d->again = true; + } else { + // See if another utterance is ready to be worked on. + // If so, loop again since we've got work to do. + d->again = getNextUtterance(KSpeech::jpAll); + } + } + // kDebug() << "Speaker::doUtterances: exiting." << endl; +} + +bool Speaker::isSpeaking() +{ + return (KSpeech::jsSpeaking == d->speechData->jobState(d->currentJobNum)); +} + +int Speaker::getCurrentJobNum() { return d->currentJobNum; } + +void Speaker::removeJob(int jobNum) +{ + deleteUtteranceByJobNum(jobNum); + doUtterances(); +} + +void Speaker::removeAllJobs(const QString& appId) +{ + AppData* appData = d->speechData->getAppData(appId); + if (appData->isSystemManager()) { + uttIterator it = d->uttQueue.begin(); + while (it != d->uttQueue.end()) + it = deleteUtterance(it); + } else { + uttIterator it = d->uttQueue.begin(); + while (it != d->uttQueue.end()) { + if (it->appId() == appId) + deleteUtterance(it); + else + ++it; + } + } + doUtterances(); +} + +void Speaker::pause(const QString& appId) +{ + Utt* pausedUtt = NULL; + AppData* appData = d->speechData->getAppData(appId); + if (appData->isSystemManager()) { + uttIterator it = d->uttQueue.begin(); + while (it != d->uttQueue.end()) + if (Utt::usPlaying == it->state()) { + pausedUtt = &(*it); + break; + } else + ++it; + } else { + uttIterator it = d->uttQueue.begin(); + while (it != d->uttQueue.end()) + if (it->appId() == appId && Utt::usPlaying == it->state()) { + pausedUtt = &(*it); + break; + } else + ++it; + } + + if (pausedUtt) { + Q_ASSERT(d->speechData->isApplicationPaused(pausedUtt->appId())); + if (pausedUtt->audioPlayer() && pausedUtt->audioPlayer()->playing()) { + d->timer->stop(); + kDebug() << "Speaker::pause: pausing audio player" << endl; + pausedUtt->audioPlayer()->pause(); + pausedUtt->setState(Utt::usPaused); + kDebug() << "Speaker::pause: Setting utterance state to usPaused" << endl; + return; + } + // Audio player has finished, but timeout hasn't had a chance + // to clean up. So do nothing, and let timeout do the cleanup. + doUtterances(); + } +} + +void Speaker::moveJobLater(int jobNum) +{ + SpeechJob* job = d->speechData->findJobByJobNum(jobNum); + if (job) + pause(job->appId()); + deleteUtteranceByJobNum(jobNum); + d->speechData->moveJobLater(jobNum); + doUtterances(); +} + +int Speaker::moveRelSentence(int jobNum, int n) +{ + if (0 == n) + return d->speechData->jobSequenceNum(jobNum); + else { + // TODO: If the job is speaking, we must establish the current + // sequence number from the speaking utterance and move relative + // to that. Actually, this is a design flaw, as the job's seq + // number doesn't match what is speaking. + deleteUtteranceByJobNum(jobNum); + // TODO: More efficient way to advance one or two sentences, since there is a + // good chance those utterances are already in the queue and synthesized. + int seq = d->speechData->moveRelSentence(jobNum, n); + kDebug() << "Speaker::moveRelTextSentence: job num: " << jobNum << " moved to seq: " << seq << endl; + doUtterances(); + return seq; + } +} + +/* Private Methods ==========================================================*/ + +QString Speaker::pluginStateToStr(pluginState state) +{ + switch( state ) + { + case psIdle: return "psIdle"; + case psSaying: return "psSaying"; + case psSynthing: return "psSynthing"; + case psFinished: return "psFinished"; + } + return QString(); +} + +void Speaker::deleteUtteranceByJobNum(int jobNum) +{ + uttIterator it = d->uttQueue.begin(); + while (it != d->uttQueue.end()) + { + if (it->job() && it->job()->jobNum() == jobNum) + it = deleteUtterance(it); + else + ++it; + } +} + +bool Speaker::getNextUtterance(KSpeech::JobPriority requestedPriority) +{ + Utt* utt = NULL; + QString appId; + QString sentence; + Utt::uttType utType; + KSpeech::JobPriority priority; + if (KSpeech::jpAll == requestedPriority) + foreach (priority, d->currentJobs.keys()) { + d->currentJobs[priority] = d->speechData->getNextSpeakableJob(priority); + if (d->currentJobs[priority]) + sentence = d->currentJobs[priority]->getNextSentence(); + if (!sentence.isEmpty()) + break; + } + else { + priority = requestedPriority; + d->currentJobs[priority] = d->speechData->getNextSpeakableJob(priority); + if (d->currentJobs[priority]) + sentence = d->currentJobs[priority]->getNextSentence(); + } + if (!sentence.isEmpty()) { + switch (priority) { + case KSpeech::jpAll: // should not happen. + break; + case KSpeech::jpScreenReaderOutput: + utType = Utt::utScreenReader; + break; + case KSpeech::jpWarning: + utType = Utt::utWarning; + break; + case KSpeech::jpMessage: + utType = Utt::utMessage; + break; + case KSpeech::jpText: + utType = Utt::utText; + break; + } + appId = d->currentJobs[priority]->appId(); + SpeechJob* job = d->currentJobs[priority]; + utt = new Utt(utType, appId, job, sentence, d->talkerMgr->talkerToPlugin(job->talker())); + utt->setSeq(job->seq()); + } + + if (utt) + { + // Screen Reader Outputs need to be processed ASAP. + if (Utt::utScreenReader == utType) + { + d->uttQueue.insert(d->uttQueue.begin(), *utt); + // Delete any other Screen Reader Outputs in the queue. + // Only one Screen Reader Output at a time. + uttIterator it = d->uttQueue.begin(); + ++it; + while (it != d->uttQueue.end()) + { + if (Utt::utScreenReader == it->utType()) + it = deleteUtterance(it); + else + ++it; + } + } + // If the new utterance is a Warning or Message... + if ((Utt::utWarning == utType) || (Utt::utMessage == utType)) + { + uttIterator itEnd = d->uttQueue.end(); + uttIterator it = d->uttQueue.begin(); + bool interrupting = false; + if (it != itEnd) + { + // New Warnings go after Screen Reader Output, other Warnings, + // Interruptions, and in-process text, + // but before Resumes, waiting text or signals. + if (Utt::utWarning == utType) + while ( it != itEnd && + ((Utt::utScreenReader == it->utType()) || + (Utt::utWarning == it->utType()) || + (Utt::utInterruptMsg == it->utType()) || + (Utt::utInterruptSnd == it->utType()))) ++it; + // New Messages go after Screen Reader Output, Warnings, other Messages, + // Interruptions, and in-process text, + // but before Resumes, waiting text or signals. + if (Utt::utMessage == utType) + while ( it != itEnd && + ((Utt::utScreenReader == it->utType()) || + (Utt::utWarning == it->utType()) || + (Utt::utMessage == it->utType()) || + (Utt::utInterruptMsg == it->utType()) || + (Utt::utInterruptSnd == it->utType()))) ++it; + if (it != itEnd) + if (Utt::utText == it->utType() && + ((Utt::usPlaying == it->state()) || + (Utt::usSaying == it->state()))) ++it; + // If now pointing at a text message, we are interrupting. + // Insert optional Interruption message and sound. + if (it != itEnd) interrupting = (Utt::utText == it->utType() && Utt::usPaused != it->state()); + if (interrupting) + { + if (d->configData->textPreSndEnabled) + { + Utt intrUtt(Utt::utInterruptSnd, appId, d->configData->textPreSnd); + it = d->uttQueue.insert(it, intrUtt); + ++it; + } + if (d->configData->textPreMsgEnabled) + { + Utt intrUtt(Utt::utInterruptMsg, appId, NULL, d->configData->textPreMsg, d->talkerMgr->talkerToPlugin(""));; + it = d->uttQueue.insert(it, intrUtt); + ++it; + } + } + } + // Insert the new message or warning. + it = d->uttQueue.insert(it, *utt); + ++it; + // Resumption message and sound. + if (interrupting) + { + if (d->configData->textPostSndEnabled) + { + Utt resUtt(Utt::utResumeSnd, appId, d->configData->textPostSnd); + it = d->uttQueue.insert(it, resUtt); + ++it; + } + if (d->configData->textPostMsgEnabled) + { + Utt resUtt(Utt::utResumeMsg, appId, NULL, d->configData->textPostMsg, d->talkerMgr->talkerToPlugin("")); + it = d->uttQueue.insert(it, resUtt); + } + } + } + // If a text message... + if (Utt::utText == utt->utType()) + d->uttQueue.append(*utt); + } + + return (NULL != utt); +} + +uttIterator Speaker::deleteUtterance(uttIterator it) +{ + switch (it->state()) + { + case Utt::usNone: + case Utt::usWaitingTransform: + case Utt::usWaitingSay: + case Utt::usWaitingSynth: + case Utt::usSynthed: + case Utt::usFinished: + case Utt::usStretched: + break; + + case Utt::usTransforming: + delete it->transformer(); + break; + case Utt::usSaying: + case Utt::usSynthing: + { + // If plugin supports asynchronous mode, and it is busy, halt it. + PlugInProc* plugin = it->plugin(); + if (plugin->supportsAsync()) + if ((plugin->getState() == psSaying) || (plugin->getState() == psSynthing)) + { + kDebug() << "Speaker::deleteUtterance calling stopText" << endl; + plugin->stopText(); + } + break; + } + case Utt::usStretching: + delete it->audioStretcher(); + break; + case Utt::usPlaying: + d->timer->stop(); + it->audioPlayer()->stop(); + delete it->audioPlayer(); + break; + case Utt::usPaused: + case Utt::usPreempted: + // Note: Must call stop(), even if player not currently playing. Why? + it->audioPlayer()->stop(); + delete it->audioPlayer(); + break; + } + if (!it->audioUrl().isNull()) + { + // If the audio file was generated by a plugin, delete it. + if (it->plugin()) + { + if (d->configData->keepAudio) + { + QString seqStr; + seqStr.sprintf("%08i", it->seq()); // Zero-fill to 8 chars. + QString jobStr; + jobStr.sprintf("%08i", it->job()->jobNum()); + QString dest = d->configData->keepAudioPath + "/kttsd-" + + QString("%1-%2").arg(jobStr).arg(seqStr) + ".wav"; + QFile::remove(dest); + QDir d; + d.rename(it->audioUrl(), dest); + // TODO: This is always producing the following. Why and how to fix? + // It moves the files just fine. + // kio (KIOJob): stat file:///home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav + // kio (KIOJob): error 11 /home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav + // kio (KIOJob): This seems to be a suitable case for trying to rename before stat+[list+]copy+del + // KIO::move(it->audioUrl, dest, false); + } + else + QFile::remove(it->audioUrl()); + } + } + // Delete the utterance from queue. + return d->uttQueue.erase(it); +} + +bool Speaker::startPlayingUtterance(uttIterator it) +{ + // kDebug() << "Speaker::startPlayingUtterance running" << endl; + if (Utt::usPlaying == it->state()) return false; + if (it->audioUrl().isEmpty()) return false; + bool started = false; + // Pause (preempt) any other utterance currently being spoken. + // If any plugins are audibilizing, must wait for them to finish. + uttIterator itEnd = d->uttQueue.end(); + for (uttIterator it2 = d->uttQueue.begin(); it2 != itEnd; ++it2) + if (it2 != it) + { + if (Utt::usPlaying == it2->state()) + { + d->timer->stop(); + it2->audioPlayer()->pause(); + it2->setState(Utt::usPreempted); + } + if (Utt::usSaying == it2->state()) return false; + } + Utt::uttState utState = it->state(); + switch (utState) + { + case Utt::usNone: + case Utt::usWaitingTransform: + case Utt::usTransforming: + case Utt::usWaitingSay: + case Utt::usWaitingSynth: + case Utt::usSaying: + case Utt::usSynthing: + case Utt::usSynthed: + case Utt::usStretching: + case Utt::usPlaying: + case Utt::usFinished: + break; + + case Utt::usStretched: + it->setAudioPlayer(createPlayerObject()); + if (it->audioPlayer()) { + it->audioPlayer()->startPlay(it->audioUrl()); + // Set job to speaking state and set sequence number. + d->currentJobNum = it->job()->jobNum(); + it->setState(Utt::usPlaying); + prePlaySignals(it); + d->timer->start(timerInterval); + started = true; + } else { + // If could not create audio player object, best we can do is silence. + it->setState(Utt::usFinished); + } + break; + + case Utt::usPaused: + // kDebug() << "Speaker::startPlayingUtterance: resuming play" << endl; + it->audioPlayer()->startPlay(QString()); // resume + it->setState(Utt::usPlaying); + d->timer->start(timerInterval); + started = true; + break; + + case Utt::usPreempted: + // Preempted playback automatically resumes. + it->audioPlayer()->startPlay(QString()); // resume + it->setState(Utt::usPlaying); + d->timer->start(timerInterval); + started = true; + break; + } + return started; +} + +void Speaker::prePlaySignals(uttIterator it) +{ + if (it->job()) + emit marker(it->job()->appId(), it->job()->jobNum(), KSpeech::mtSentenceBegin, QString::number(it->seq())); +} + +void Speaker::postPlaySignals(uttIterator it) +{ + if (it->job()) + emit marker(it->job()->appId(), it->job()->jobNum(), KSpeech::mtSentenceEnd, QString::number(it->seq())); +} + +QString Speaker::makeSuggestedFilename() +{ + QString tmpDir = KStandardDirs::locateLocal("tmp", "kttsd-"); + kDebug() << "Speaker::makeSuggestedFilename: tmpDir = " << tmpDir << endl; + KTempFile tempFile (tmpDir, ".wav"); + // TODO: This not working. Why? + // QString waveFile = tempFile.file()->name(); + // QString waveFile = mFile.name(); + // tempFile.close(); + QString waveFile = tempFile.name(); + QFile::remove(waveFile); + kDebug() << "Speaker::makeSuggestedFilename: Suggesting filename: " << waveFile << endl; + return KStandardDirs::realFilePath(waveFile); +} + +Player* Speaker::createPlayerObject() +{ + Player* player = 0; + QString plugInName; + switch(d->configData->playerOption) + { + case 0 : + { + plugInName = "kttsd_phononplugin"; + break; + } + case 2 : + { + plugInName = "kttsd_alsaplugin"; + break; + } + default: + { + // TODO: Default to Phonon. + plugInName = "kttsd_phononplugin"; + break; + } + } + KService::List offers = KServiceTypeTrader::self()->query( + "KTTSD/AudioPlugin", QString("DesktopEntryName == '%1'").arg(plugInName)); + + if(offers.count() == 1) + { + kDebug() << "Speaker::createPlayerObject: Loading " << offers[0]->library() << endl; + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().toLatin1()); + if (factory) + player = + KLibLoader::createInstance( + offers[0]->library().toLatin1(), this, QStringList(offers[0]->library().toLatin1())); + } + if (player == 0) + { + // If we failed, fall back to default plugin. + // Default to Phonon. + if (d->configData->playerOption != 0) + { + kDebug() << "Speaker::createPlayerObject: Could not load " + plugInName + + " plugin. Falling back to KDE (Phonon)." << endl; + d->configData->playerOption = 0; + return createPlayerObject(); + } + else + kDebug() << "Speaker::createPlayerObject: Could not load KDE (Phonon) plugin. Is KDEDIRS set correctly?" << endl; + } + if (player) { + player->setSinkName(d->configData->sinkName); + player->setPeriodSize(d->configData->periodSize); + player->setPeriods(d->configData->periods); + player->setDebugLevel(d->configData->playerDebugLevel); + } + return player; +} + + +/* Slots ==========================================================*/ + +/** +* Received from PlugIn objects when they finish asynchronous synthesis +* and audibilizing. +* TODO: In Qt4, custom events are no longer necessary as events may pass +* through thread boundaries now. +*/ +void Speaker::slotSayFinished() +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 101)); + QApplication::postEvent(this, ev); +} + +/** +* Received from PlugIn objects when they finish asynchronous synthesis. +*/ +void Speaker::slotSynthFinished() +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 102)); + QApplication::postEvent(this, ev); +} + +/** +* Received from PlugIn objects when they asynchronously stopText. +*/ +void Speaker::slotStopped() +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 103)); + QApplication::postEvent(this, ev); +} + +/** +* Received from audio stretcher when stretching (speed adjustment) is finished. +*/ +void Speaker::slotStretchFinished() +{ + // Convert to postEvent and return immediately. + QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 104)); + QApplication::postEvent(this, ev); +} + +/** +* Received from transformer (SSMLConvert) when transforming is finished. +*/ +void Speaker::slotTransformFinished() +{ + // Convert to postEvent and return immediately. + QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 105)); + QApplication::postEvent(this, ev); +} + +/** Received from PlugIn object when they encounter an error. +* @param keepGoing True if the plugin can continue processing. +* False if the plugin cannot continue, for example, +* the speech engine could not be started. +* @param msg Error message. +*/ +void Speaker::slotError(bool /*keepGoing*/, const QString& /*msg*/) +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + // TODO: Do something with error messages. + /* + if (keepGoing) + QCustomEvent* ev = new QCustomEvent(QEvent::User + 106); + else + QCustomEvent* ev = new QCustomEvent(QEvent::User + 107); + QApplication::postEvent(this, ev); + */ +} + +/** +* Received from Timer when it fires. +* Check audio player to see if it is finished. +*/ +void Speaker::slotTimeout() +{ + uttIterator itEnd = d->uttQueue.end(); + for (uttIterator it = d->uttQueue.begin(); it != itEnd; ++it) + { + if (Utt::usPlaying == it->state()) + { + if (it->audioPlayer()->playing()) + return; // Still playing. + else { + d->timer->stop(); + postPlaySignals(it); + it->setState(Utt::usFinished); + doUtterances(); + return; + } + } + } +} + +/** +* Processes events posted by plugins. When asynchronous plugins emit signals +* they are converted into these events. +*/ +bool Speaker::event ( QEvent * e ) +{ + // TODO: Do something with event numbers 106 (error; keepGoing=True) + // and 107 (error; keepGoing=False). + if ((e->type() >= (QEvent::User + 101)) && (e->type() <= (QEvent::User + 105))) + { + // kDebug() << "Speaker::event: received event." << endl; + doUtterances(); + return TRUE; + } + else return FALSE; +} diff --git a/kttsd/kttsd/speaker.h b/kttsd/kttsd/speaker.h dissimilarity index 96% index e772878e..4ddb9eae 100644 --- a/kttsd/kttsd/speaker.h +++ b/kttsd/kttsd/speaker.h @@ -1,600 +1,300 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - Speaker class. - - This class is in charge of getting the messages, warnings and text from - the queue and call the plug ins function to actually speak the texts. - ------------------- - Copyright: - (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt - (C) 2004 by Gary Cramblitt - ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - ******************************************************************************/ - -/****************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License. * - * * - ******************************************************************************/ - -#ifndef _SPEAKER_H_ -#define _SPEAKER_H_ - -// Qt includes. -#include -#include -#include -#include - -// KTTSD includes. -#include -#include -#include -#include -#include - -class Player; -class QTimer; -class TalkerMgr; - -/** -* Type of utterance. -*/ -enum uttType -{ - utText, /**< Text */ - utInterruptMsg, /**< Interruption text message */ - utInterruptSnd, /**< Interruption sound file */ - utResumeMsg, /**< Resume text message */ - utResumeSnd, /**< Resume sound file */ - utMessage, /**< Message */ - utWarning, /**< Warning */ - utScreenReader, /**< Screen Reader Output */ - utStartOfJob, /**< Start-of-job */ - utEndOfJob /**< End-of-job */ -}; - -/** -* Processing state of an utterance. -*/ -enum uttState -{ - usNone, /**< Null state. Brand new utterance. */ - usWaitingTransform, /**< Waiting to be transformed (XSLT) */ - usTransforming, /**< Transforming the utterance (XSLT). */ - usWaitingSay, /**< Waiting to start synthesis. */ - usWaitingSynth, /**< Waiting to be synthesized and audibilized. */ - usWaitingSignal, /**< Waiting to emit a textStarted or textFinished signal. */ - usSaying, /**< Plugin is synthesizing and audibilizing. */ - usSynthing, /**< Plugin is synthesizing only. */ - usSynthed, /**< Plugin has finished synthesizing. Ready for stretch. */ - usStretching, /**< Adjusting speed. */ - usStretched, /**< Speed adjustment finished. Ready for playback. */ - usPlaying, /**< Playing on Audio Player. */ - usPaused, /**< Paused on Audio Player due to user action. */ - usPreempted, /**< Paused on Audio Player due to Screen Reader Output. */ - usFinished /**< Ready for deletion. */ -}; - -/** -* Structure containing an utterance being synthesized or audibilized. -*/ -struct Utt{ - mlText* sentence; /* The text, talker, appId, and sequence num. */ - uttType utType; /* The type of utterance (text, msg, screen reader) */ - bool isSSML; /* True if the utterance contains SSML markup. */ - uttState state; /* Processing state of the utterance. */ - SSMLConvert* transformer; /* XSLT transformer. */ - PlugInProc* plugin; /* The plugin that synthesizes the utterance. */ - Stretcher* audioStretcher; /* Audio stretcher object. Adjusts speed. */ - QString audioUrl; /* Filename containing synthesized audio. Null if - plugin has not yet synthesized the utterance, or if - plugin does not support synthesis. */ - Player* audioPlayer; /* The audio player audibilizing the utterance. Null - if not currently audibilizing or if plugin doesn't - support synthesis. */ -}; - -/** -* Iterator for queue of utterances. -*/ -typedef QVector::iterator uttIterator; - -// Timer interval for checking whether audio playback is finished. -const int timerInterval = 500; - -/** - * This class is in charge of getting the messages, warnings and text from - * the queue and call the plug ins function to actually speak the texts. - */ -class Speaker : public QObject{ - Q_OBJECT - - public: - /** - * Constructor - * Calls load plug ins - */ - Speaker(SpeechData* speechData, TalkerMgr* talkerMgr, - QObject *parent = 0, const char *name = 0); - - /** - * Destructor - */ - ~Speaker(); - - /** - * Tells the thread to exit - */ - void requestExit(); - - /** - * Main processing loop. Dequeues utterances and sends them to the - * plugins and/or Audio Player. - */ - void doUtterances(); - - /** - * Determine if kttsd is currently speaking any text jobs. - * @return True if currently speaking any text jobs. - */ - bool isSpeakingText(); - - /** - * Get the job number of the current text job. - * @return Job number of the current text job. 0 if no jobs. - * - * Note that the current job may not be speaking. See @ref isSpeakingText. - * @see getTextJobState. - * @see isSpeakingText - */ - uint getCurrentTextJob(); - - /** - * Remove a text job from the queue. - * @param jobNum Job number of the text job. - * - * The job is deleted from the queue and the @ref textRemoved signal is emitted. - * - * If there is another job in the text queue, and it is marked speakable, - * that job begins speaking. - */ - void removeText(const uint jobNum); - - /** - * Start a text job at the beginning. - * @param jobNum Job number of the text job. - * - * Rewinds the job to the beginning. - * - * The job is marked speakable. - * If there are other speakable jobs preceeding this one in the queue, - * those jobs continue speaking and when finished, this job will begin speaking. - * If there are no other speakable jobs preceeding this one, it begins speaking. - * - * The @ref textStarted signal is emitted when the text job begins speaking. - * When all the sentences of the job have been spoken, the job is marked for deletion from - * the text queue and the @ref textFinished signal is emitted. - */ - void startText(const uint jobNum); - - /** - * Stop a text job and rewind to the beginning. - * @param jobNum Job number of the text job. - * - * The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText - * is called. - * - * If there are speaking jobs preceeding this one in the queue, they continue speaking. - * If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. - * Depending upon the speech engine and plugin used, speeking may not stop immediately - * (it might finish the current sentence). - */ - void stopText(const uint jobNum); - - /** - * Pause a text job. - * @param jobNum Job number of the text job. - * - * The job is marked as paused and will not be speakable until @ref resumeText or - * @ref startText is called. - * - * If there are speaking jobs preceeding this one in the queue, they continue speaking. - * If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. - * Depending upon the speech engine and plugin used, speeking may not stop immediately - * (it might finish the current sentence). - * @see resumeText - */ - void pauseText(const uint jobNum); - - /** - * Start or resume a text job where it was paused. - * @param jobNum Job number of the text job. - * - * The job is marked speakable. - * - * If the job is currently speaking, or is waiting to be spoken (speakable - * state), the resumeText() call is ignored. - * - * If the job is currently queued, or is finished, it is the same as calling - * @ref startText . - * - * If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, - * when finished this job will begin speaking where it left off. - * - * The @ref textResumed signal is emitted when the job resumes. - * @see pauseText - */ - void resumeText(const uint jobNum); - - /** - * Move a text job down in the queue so that it is spoken later. - * @param jobNum Job number of the text job. - * - * If the job is currently speaking, it is paused. - * If the next job in the queue is speakable, it begins speaking. - */ - void moveTextLater(const uint jobNum); - - /** - * Jump to the first sentence of a specified part of a text job. - * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. - * @param jobNum Job number of the text job. - * @return Part number of the part actually jumped to. - * - * If partNum is greater than the number of parts in the job, jumps to last part. - * If partNum is 0, does nothing and returns the current part number. - * If no such job, does nothing and returns 0. - * Does not affect the current speaking/not-speaking state of the job. - */ - int jumpToTextPart(const int partNum, const uint jobNum); - - /** - * Advance or rewind N sentences in a text job. - * @param n Number of sentences to advance (positive) or rewind (negative) - * in the job. - * @param jobNum Job number of the text job. - * @return Sequence number of the sentence actually moved to. - * Sequence numbers are numbered starting at 1. - * - * If no such job, does nothing and returns 0. - * If n is zero, returns the current sequence number of the job. - * Does not affect the current speaking/not-speaking state of the job. - */ - uint moveRelTextSentence(const int n, const uint jobNum); - - signals: - /** - * Emitted whenever reading a text was started or resumed - */ - void readingStarted(); - - /** - * Emitted whenever reading a text was finished, - * or paused, or stopped before it was finished - */ - void readingStopped(); - - /** - * Emitted whenever a message or warning interrupts reading a text - */ - void readingInterrupted(); - - /** - * Emitted whenever reading a text is resumed after it was interrupted - * Note: In function resumeText, readingStarted is called instead - */ - void readingResumed(); - - /* The following signals correspond to the signals in the KSpeech interface. */ - - /** - * This signal is emitted when the speech engine/plugin encounters a marker in the text. - * @param appId DCOP application ID of the application that queued the text. - * @param markerName The name of the marker seen. - * @see markers - */ - void markerSeen(const QString& appId, const QString& markerName); - - /** - * This signal is emitted whenever a sentence begins speaking. - * @param appId DCOP application ID of the application that queued the text. - * @param jobNum Job number of the text job. - * @param seq Sequence number of the text. - */ - void sentenceStarted(QString text, QString language, const QString& appId, - const uint jobNum, const uint seq); - - /** - * This signal is emitted when a sentence has finished speaking. - * @param appId DCOP application ID of the application that queued the text. - * @param jobNum Job number of the text job. - * @param seq Sequence number of the text. - */ - void sentenceFinished(const QString& appId, const uint jobNum, const uint seq); - - /** - * This signal is emitted whenever speaking of a text job begins. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. - */ - void textStarted(const QString& appId, const uint jobNum); - - /** - * This signal is emitted whenever a text job is finished. The job has - * been marked for deletion from the queue and will be deleted when another - * job reaches the Finished state. (Only one job in the text queue may be - * in state Finished at one time.) If @ref startText or @ref resumeText is - * called before the job is deleted, it will remain in the queue for speaking. - * @param appId The DCOP senderId of the application that created the job. - * @param jobNum Job number of the text job. - */ - void textFinished(const QString& appId, const uint jobNum); - - /** - * This signal is emitted whenever a speaking text job stops speaking. - * @param appId The DCOP senderId of the application that created the job. - * @param jobNum Job number of the text job. - */ - void textStopped(const QString& appId, const uint jobNum); - /** - * This signal is emitted whenever a speaking text job is paused. - * @param appId The DCOP senderId of the application that created the job. - * @param jobNum Job number of the text job. - */ - void textPaused(const QString& appId, const uint jobNum); - /** - * This signal is emitted when a text job, that was previously paused, resumes speaking. - * @param appId The DCOP senderId of the application that created the job. - * @param jobNum Job number of the text job. - */ - void textResumed(const QString& appId, const uint jobNum); - - protected: - /** - * Processes events posted by ThreadedPlugIns. - */ - virtual bool event ( QEvent * e ); - - private slots: - /** - * Received from PlugIn objects when they finish asynchronous synthesis. - */ - void slotSynthFinished(); - /** - * Received from PlugIn objects when they finish asynchronous synthesis - * and audibilizing. - */ - void slotSayFinished(); - /** - * Received from PlugIn objects when they asynchronously stopText. - */ - void slotStopped(); - /** - * Received from audio stretcher when stretching (speed adjustment) is finished. - */ - void slotStretchFinished(); - /** - * Received from transformer (SSMLConvert) when transforming is finished. - */ - void slotTransformFinished(); - /** Received from PlugIn object when they encounter an error. - * @param keepGoing True if the plugin can continue processing. - * False if the plugin cannot continue, for example, - * the speech engine could not be started. - * @param msg Error message. - */ - void slotError(bool keepGoing, const QString &msg); - /** - * Received from Timer when it fires. - * Check audio player to see if it is finished. - */ - void slotTimeout(); - - private: - - /** - * Converts an utterance state enumerator to a displayable string. - * @param state Utterance state. - * @return Displayable string for utterance state. - */ - QString uttStateToStr(uttState state); - - /** - * Converts an utterance type enumerator to a displayable string. - * @param utType Utterance type. - * @return Displayable string for utterance type. - */ - QString uttTypeToStr(uttType utType); - - /** - * Converts a plugin state enumerator to a displayable string. - * @param state Plugin state. - * @return Displayable string for plugin state. - */ - QString pluginStateToStr(pluginState state); - - /** - * Converts a job state enumerator to a displayable string. - * @param state Job state. - * @return Displayable string for job state. - */ - QString jobStateToStr(int state); - - /** - * Determines whether the given text is SSML markup. - */ - bool isSsml(const QString &text); - - /** - * Determines the initial state of an utterance. If the utterance contains - * SSML, the state is set to usWaitingTransform. Otherwise, if the plugin - * supports async synthesis, sets to usWaitingSynth, otherwise usWaitingSay. - * If an utterance has already been transformed, usWaitingTransform is - * skipped to either usWaitingSynth or usWaitingSay. - * @param utt The utterance. - */ - void setInitialUtteranceState(Utt &utt); - - /** - * Returns true if the given job and sequence number is already in the utterance queue. - */ - bool isInUtteranceQueue(uint jobNum, uint seqNum); - - /** - * Gets the next utterance to be spoken from speechdata and adds it to the queue. - * @return True if one or more utterances were added to the queue. - * - * Checks for waiting ScreenReaderOutput, Warnings, Messages, or Text, - * in that order. - * If Warning or Message and interruption messages have been configured, - * adds those to the queue as well. - * Determines which plugin should be used for the utterance. - */ - bool getNextUtterance(); - - /** - * Given an iterator pointing to the m_uttQueue, deletes the utterance - * from the queue. If the utterance is currently being processed by a - * plugin or the Audio Player, halts that operation and deletes Audio Player. - * Also takes care of deleting temporary audio file. - * @param it Iterator pointer to m_uttQueue. - * @return Iterator pointing to the next utterance in the - * queue, or m_uttQueue.end(). - */ - uttIterator deleteUtterance(uttIterator it); - - /** - * Given an iterator pointing to the m_uttQueue, starts playing audio if - * 1) An audio file is ready to be played, and - * 2) It is not already playing. - * If another audio player is already playing, pauses it before starting - * the new audio player. - * @param it Iterator pointer to m_uttQueue. - * @return True if an utterance began playing or resumed. - */ - bool startPlayingUtterance(uttIterator it); - - /** - * Delete any utterances in the queue with this jobNum. - * @param jobNum The Job Number of the utterance(s) to delete. - * If currently processing any deleted utterances, stop them. - */ - void deleteUtteranceByJobNum(const uint jobNum); - - /** - * Pause the utterance with this jobNum and if it is playing on the Audio Player, - * pause the Audio Player. - * @param jobNum The Job Number of the utterance to pause. - */ - void pauseUtteranceByJobNum(const uint jobNum); - - /** - * Takes care of emitting reading interrupted/resumed and sentence started signals. - * Should be called just before audibilizing an utterance. - * @param it Iterator pointer to m_uttQueue. - */ - void prePlaySignals(uttIterator it); - - /** - * Takes care of emitting sentenceFinished signal. - * Should be called immediately after an utterance has completed playback. - * @param it Iterator pointer to m_uttQueue. - */ - void postPlaySignals(uttIterator it); - - /** - * Constructs a temporary filename for plugins to use as a suggested filename - * for synthesis to write to. - * @return Full pathname of suggested file. - */ - QString makeSuggestedFilename(); - - /** - * Creates and returns a player object based on user option. - */ - Player* createPlayerObject(); - - /** - * SpeechData local pointer - */ - SpeechData* m_speechData; - - /** - * TalkerMgr local pointer. - */ - TalkerMgr* m_talkerMgr; - - /** - * True if the speaker was requested to exit. - */ - volatile bool m_exitRequested; - - /** - * Queue of utterances we are currently processing. - */ - QVector m_uttQueue; - - /** - * True when text job reading has been interrupted. - */ - bool m_textInterrupted; - - /** - * Used to prevent doUtterances from prematurely exiting. - */ - bool m_again; - - /** - * Which audio player to use. - * 0 = aRts - * 1 = gstreamer - * 2 = ALSA - */ - int m_playerOption; - - /** - * Audio stretch factor (Speed). - */ - float m_audioStretchFactor; - - /** - * GStreamer sink name to use, or ALSA PCM device name. - */ - QString m_sinkName; - - /** - * Timer for monitoring audio player. - */ - QTimer* m_timer; - - /** - * Current Text job being processed. - */ - uint m_currentJobNum; - - /** - * Job Number, appId, and sequence number of the last text sentence queued. - */ - uint m_lastJobNum; - QString m_lastAppId; - uint m_lastSeq; - - /** - * Some parameters used by ALSA plugin. - * Size of buffer interrupt period (in frames) - * Number of periods in buffer. - */ - uint m_periodSize; - uint m_periods; - - /** - * Debug level in players. - */ - uint m_playerDebugLevel; -}; - -#endif // _SPEAKER_H_ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Speaker class. + + This class is in charge of getting the messages, warnings and text from + the queue and calling the plugins to actually speak the texts. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _SPEAKER_H_ +#define _SPEAKER_H_ + +// Qt includes. +#include +#include +#include +#include + +// KTTSD includes. +#include "utt.h" + +class SpeechData; +class Player; +class QTimer; +class TalkerMgr; + +/** +* Iterator for queue of utterances. +*/ +typedef QList::iterator uttIterator; + +// Timer interval for checking whether audio playback is finished. +const int timerInterval = 500; + +/** + * This class is in charge of getting the messages, warnings and text from + * the queue and call the plug ins function to actually speak the texts. + */ +class SpeakerPrivate; +class Speaker : public QObject +{ +Q_OBJECT + +public: + /** + * Constructor. + * @param speechData Pointer to SpeechData object. + * @param talkerMgr Pointer to TalkerMgr object. + */ + Speaker( + SpeechData* speechData, + TalkerMgr* talkerMgr, + QObject *parent = 0); + + /** + * Destructor. + */ + ~Speaker(); + + /** + * Sets pointer to the configuration data object. + */ + void setConfigData(ConfigData* configData); + + /** + * Tells the thread to exit. + * TODO: Is this used anymore? + */ + void requestExit(); + + /** + * Main processing loop. Dequeues utterances and sends them to the + * plugins and/or Audio Player. + */ + void doUtterances(); + + /** + * Determine if kttsd is currently speaking any jobs. + * @return True if currently speaking any jobs. + */ + bool isSpeaking(); + + /** + * Get the job number of the current job speaking. + * @return Job number of the current job. 0 if no jobs. + * + * @see isSpeakingText + */ + int getCurrentJobNum(); + + /** + * Delete all utterances for a specified job number. + * @param jobNum Job number of the job. + * + * If there is another job in the text queue, and it is marked speakable, + * that job begins speaking. + */ + void removeJob(int jobNum); + + /** + * Delete all utterances for all jobs for a specified application. + * @param appId DBUS sender ID of the application. + * + * If an utterance is playing, it is stopped. + */ + void removeAllJobs(const QString& appId); + + /** + * Pause any playing utterances for an application. + * @param appId DBUS sender id of the application. + * + * The application must be paused before calling this method. + * See SpeechData::pause(). + */ + void pause(const QString& appId); + + /** + * Move a job down in the queue so that it is spoken later. + * @param jobNum Job number. + * + * If the job is currently speaking, it is paused. + * If the next job in the queue is speakable, it begins speaking. + */ + void moveJobLater(int jobNum); + + /** + * Advance or rewind N sentences in a job. + * @param jobNum Job number of the job. + * @param n Number of sentences to advance (positive) or rewind (negative) + * in the job. + * @return Sequence number of the sentence actually moved to. + * Sequence numbers are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + */ + int moveRelSentence(int jobNum, int n); + +signals: + /** + * Emitted when a marker is processed. + * Currently only emits mtSentenceBegin and mtSentenceEnd. + * @param appId The DBUS sender ID of the application that queued the job. + * @param jobNum Job Number of the job emitting the marker. + * @param markerType The type of marker. + * Currently either mtSentenceBegin or mtSentenceEnd. + * @param markerData Data for the marker. + * Currently, this is the sequence number of the sentence + * begun or ended. Sequence numbers begin at 1. + */ + void marker(const QString& appId, int jobNum, KSpeech::MarkerType markerType, const QString& markerData); + +protected: + /** + * Processes events posted by ThreadedPlugIns. + */ + virtual bool event ( QEvent * e ); + +private slots: + /** + * Received from PlugIn objects when they finish asynchronous synthesis. + */ + void slotSynthFinished(); + /** + * Received from PlugIn objects when they finish asynchronous synthesis + * and audibilizing. + */ + void slotSayFinished(); + /** + * Received from PlugIn objects when they asynchronously stopText. + */ + void slotStopped(); + /** + * Received from audio stretcher when stretching (speed adjustment) is finished. + */ + void slotStretchFinished(); + /** + * Received from transformer (SSMLConvert) when transforming is finished. + */ + void slotTransformFinished(); + /** Received from PlugIn object when they encounter an error. + * @param keepGoing True if the plugin can continue processing. + * False if the plugin cannot continue, for example, + * the speech engine could not be started. + * @param msg Error message. + */ + void slotError(bool keepGoing, const QString &msg); + /** + * Received from Timer when it fires. + * Check audio player to see if it is finished. + */ + void slotTimeout(); + +private: + /** + * Converts a plugin state enumerator to a displayable string. + * @param state Plugin state. + * @return Displayable string for plugin state. + */ + QString pluginStateToStr(pluginState state); + + /** + * Converts a job state enumerator to a displayable string. + * @param state Job state. + * @return Displayable string for job state. + */ + QString jobStateToStr(int state); + + /** + * Gets the next utterance of the specified priority to be spoken from + * speechdata and adds it to the queue. + * @param requestedPriority Job priority to check for. + * @return True if one or more utterances were added to the queue. + * + * If priority is KSpeech::jpAll, checks for waiting ScreenReaderOutput, + * Warnings, Messages, or Text, in that order. + * If Warning or Message and interruption messages have been configured, + * adds those to the queue as well. + * Determines which plugin should be used for the utterance. + */ + bool getNextUtterance(KSpeech::JobPriority requestedPriority); + + /** + * Given an iterator pointing to the m_uttQueue, deletes the utterance + * from the queue. If the utterance is currently being processed by a + * plugin or the Audio Player, halts that operation and deletes Audio Player. + * Also takes care of deleting temporary audio file. + * @param it Iterator pointer to m_uttQueue. + * @return Iterator pointing to the next utterance in the + * queue, or m_uttQueue.end(). + */ + uttIterator deleteUtterance(uttIterator it); + + /** + * Given an iterator pointing to the m_uttQueue, starts playing audio if + * 1) An audio file is ready to be played, and + * 2) It is not already playing. + * If another audio player is already playing, pauses it before starting + * the new audio player. + * @param it Iterator pointer to m_uttQueue. + * @return True if an utterance began playing or resumed. + */ + bool startPlayingUtterance(uttIterator it); + + /** + * Delete any utterances in the queue with this jobNum. + * @param jobNum The Job Number of the utterance(s) to delete. + * If currently processing any deleted utterances, stop them. + */ + void deleteUtteranceByJobNum(int jobNum); + + /** + * Takes care of emitting reading interrupted/resumed and sentence started signals. + * Should be called just before audibilizing an utterance. + * @param it Iterator pointer to m_uttQueue. + */ + void prePlaySignals(uttIterator it); + + /** + * Takes care of emitting sentenceFinished signal. + * Should be called immediately after an utterance has completed playback. + * @param it Iterator pointer to m_uttQueue. + */ + void postPlaySignals(uttIterator it); + + /** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ + QString makeSuggestedFilename(); + + /** + * Creates and returns a player object based on user option. + */ + Player* createPlayerObject(); + +private: + SpeakerPrivate* d; +}; + +#endif // _SPEAKER_H_ diff --git a/kttsd/kttsd/speechdata.cpp b/kttsd/kttsd/speechdata.cpp dissimilarity index 81% index 65a6f914..643f3517 100644 --- a/kttsd/kttsd/speechdata.cpp +++ b/kttsd/kttsd/speechdata.cpp @@ -1,1304 +1,779 @@ -/***************************************************** vim:set ts=4 sw=4 sts=4: - This contains the SpeechData class which is in charge of maintaining - all the data on the memory. - It maintains queues manages the text. - We could say that this is the common repository between the KTTSD class - (dcop service) and the Speaker class (speaker, loads plug ins, call plug in - functions) - ------------------- - Copyright: - (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt - (C) 2004-2005 by Gary Cramblitt - ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - ******************************************************************************/ - -// C++ includes. -#include - -// Qt includes. -#include -#include -#include -#include -#include - -// KDE includes. -#include -#include -#include -#include - -// KTTS includes. -#include "talkermgr.h" -#include "notify.h" - -// SpeechData includes. -#include "speechdata.h" -#include "speechdata.moc" - -// Set this to 1 to turn off filter support, including SBD as a plugin. -#define NO_FILTERS 0 - -/** -* Constructor -* Sets text to be stopped and warnings and messages queues to be autodelete. -*/ -SpeechData::SpeechData(){ - // kDebug() << "Running: SpeechData::SpeechData()" << endl; - // The text should be stoped at the beggining (thread safe) - jobCounter = 0; - config = 0; - supportsHTML = false; - - screenReaderOutput.jobNum = 0; - screenReaderOutput.text = ""; -} - -bool SpeechData::readConfig(){ - // Load configuration - delete config; - //config = KGlobal::config(); - config = new KConfig("kttsdrc"); - - // Set the group general for the configuration of KTTSD itself (no plug ins) - config->setGroup("General"); - - // Load the configuration of the text interruption messages and sound - textPreMsgEnabled = config->readEntry("TextPreMsgEnabled", QVariant(false)).toBool(); - textPreMsg = config->readEntry("TextPreMsg"); - - textPreSndEnabled = config->readEntry("TextPreSndEnabled", QVariant(false)).toBool(); - textPreSnd = config->readEntry("TextPreSnd"); - - textPostMsgEnabled = config->readEntry("TextPostMsgEnabled", QVariant(false)).toBool(); - textPostMsg = config->readEntry("TextPostMsg"); - - textPostSndEnabled = config->readEntry("TextPostSndEnabled", QVariant(false)).toBool(); - textPostSnd = config->readEntry("TextPostSnd"); - keepAudio = config->readEntry("KeepAudio", QVariant(false)).toBool(); - keepAudioPath = config->readEntry("KeepAudioPath", locateLocal("data", "kttsd/audio/")); - - // Notification (KNotify). - notify = config->readEntry("Notify", QVariant(false)).toBool(); - notifyExcludeEventsWithSound = config->readEntry("ExcludeEventsWithSound", QVariant(true)).toBool(); - loadNotifyEventsFromFile( locateLocal("config", "kttsd_notifyevents.xml"), true ); - - // KTTSMgr auto start and auto exit. - autoStartManager = config->readEntry("AutoStartManager", QVariant(false)).toBool(); - autoExitManager = config->readEntry("AutoExitManager", QVariant(false)).toBool(); - - // Clear the pool of filter managers so that filters re-init themselves. - QMultiHash::iterator it = m_pooledFilterMgrs.begin(); - while (it != m_pooledFilterMgrs.end()) { - PooledFilterMgr* pooledFilterMgr = it.value(); - delete pooledFilterMgr->filterMgr; - delete pooledFilterMgr->talkerCode; - delete pooledFilterMgr; - ++it; - } - m_pooledFilterMgrs.clear(); - - // Create an initial FilterMgr for the pool to save time later. - PooledFilterMgr* pooledFilterMgr = new PooledFilterMgr(); - FilterMgr* filterMgr = new FilterMgr(); - filterMgr->init(config, "General"); - supportsHTML = filterMgr->supportsHTML(); - pooledFilterMgr->filterMgr = filterMgr; - pooledFilterMgr->busy = false; - pooledFilterMgr->job = 0; - pooledFilterMgr->partNum = 0; - pooledFilterMgr->talkerCode = 0; - // Connect signals from FilterMgr. - connect (filterMgr, SIGNAL(filteringFinished()), this, SLOT(slotFilterMgrFinished())); - connect (filterMgr, SIGNAL(filteringStopped()), this, SLOT(slotFilterMgrStopped())); - m_pooledFilterMgrs.insert(0, pooledFilterMgr); - - return true; -} - -/** - * Loads notify events from a file. Clearing data if clear is True. - */ -void SpeechData::loadNotifyEventsFromFile( const QString& filename, bool clear) -{ - // Open existing event list. - QFile file( filename ); - if ( !file.open( QIODevice::ReadOnly ) ) - { - kDebug() << "SpeechData::loadNotifyEventsFromFile: Unable to open file " << filename << endl; - } - // QDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" ); - QDomDocument doc( "" ); - if ( !doc.setContent( &file ) ) { - file.close(); - kDebug() << "SpeechData::loadNotifyEventsFromFile: File not in proper XML format. " << filename << endl; - } - // kDebug() << "StringReplacerConf::load: document successfully parsed." << endl; - file.close(); - - if ( clear ) - { - notifyDefaultPresent = NotifyPresent::Passive; - notifyDefaultOptions.action = NotifyAction::SpeakMsg; - notifyDefaultOptions.talker.clear(); - notifyDefaultOptions.customMsg.clear(); - notifyAppMap.clear(); - } - - // Event list. - QDomNodeList eventList = doc.elementsByTagName("notifyEvent"); - const int eventListCount = eventList.count(); - for (int eventIndex = 0; eventIndex < eventListCount; ++eventIndex) - { - QDomNode eventNode = eventList.item(eventIndex); - QDomNodeList propList = eventNode.childNodes(); - QString eventSrc; - QString event; - QString actionName; - QString message; - TalkerCode talkerCode; - const int propListCount = propList.count(); - for (int propIndex = 0; propIndex < propListCount; ++propIndex) - { - QDomNode propNode = propList.item(propIndex); - QDomElement prop = propNode.toElement(); - if (prop.tagName() == "eventSrc") eventSrc = prop.text(); - if (prop.tagName() == "event") event = prop.text(); - if (prop.tagName() == "action") actionName = prop.text(); - if (prop.tagName() == "message") message = prop.text(); - if (prop.tagName() == "talker") talkerCode = TalkerCode(prop.text(), false); - } - NotifyOptions notifyOptions; - notifyOptions.action = NotifyAction::action( actionName ); - notifyOptions.talker = talkerCode.getTalkerCode(); - notifyOptions.customMsg = message; - if ( eventSrc != "default" ) - { - notifyOptions.eventName = NotifyEvent::getEventName( eventSrc, event ); - NotifyEventMap notifyEventMap = notifyAppMap[ eventSrc ]; - notifyEventMap[ event ] = notifyOptions; - notifyAppMap[ eventSrc ] = notifyEventMap; - } else { - notifyOptions.eventName.clear(); - notifyDefaultPresent = NotifyPresent::present( event ); - notifyDefaultOptions = notifyOptions; - } - } -} - -/** -* Destructor -*/ -SpeechData::~SpeechData(){ - // kDebug() << "Running: SpeechData::~SpeechData()" << endl; - // Walk through jobs and emit a textRemoved signal for each job. - while (!messages.isEmpty()) - delete messages.dequeue(); - while (!warnings.isEmpty()) - delete warnings.dequeue(); - - while (!textJobs.isEmpty()) { - mlJob* job = textJobs.takeFirst(); - emit textRemoved(job->appId, job->jobNum); - delete job; - } - - while (!m_pooledFilterMgrs.isEmpty()) { - PooledFilterMgr* pooledFilterMgr = *m_pooledFilterMgrs.begin(); - m_pooledFilterMgrs.erase(m_pooledFilterMgrs.begin()); - delete pooledFilterMgr->filterMgr; - delete pooledFilterMgr->talkerCode; - delete pooledFilterMgr; - } - - delete config; -} - -/** -* Say a message as soon as possible, interrupting any other speech in progress. -* IMPORTANT: This method is reserved for use by Screen Readers and should not be used -* by any other applications. -* @param msg The message to be spoken. -* @param talker Code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -* @param appId The DBUS senderId of the application. NULL if kttsd. -* -* If an existing Screen Reader output is in progress, it is stopped and discarded and -* replaced with this new message. -*/ -void SpeechData::setScreenReaderOutput(const QString &msg, const QString &talker, const QString &appId) -{ - screenReaderOutput.text = msg; - screenReaderOutput.talker = talker; - screenReaderOutput.appId = appId; - screenReaderOutput.seq = 1; -} - -/** -* Retrieves the Screen Reader Output. -*/ -mlText* SpeechData::getScreenReaderOutput() -{ - mlText* temp = new mlText(); - temp->text = screenReaderOutput.text; - temp->talker = screenReaderOutput.talker; - temp->appId = screenReaderOutput.appId; - temp->seq = screenReaderOutput.seq; - // Blank the Screen Reader to text to "empty" it. - screenReaderOutput.text = ""; - return temp; -} - -/** -* Returns true if Screen Reader Output is ready to be spoken. -*/ -bool SpeechData::screenReaderOutputReady() -{ - return !screenReaderOutput.text.isEmpty(); -} - -/** -* Add a new warning to the queue. -*/ -void SpeechData::enqueueWarning( const QString &warning, const QString &talker, const QString &appId){ - // kDebug() << "Running: SpeechData::enqueueWarning( const QString &warning )" << endl; - mlJob* job = new mlJob(); - ++jobCounter; - if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums. - uint jobNum = jobCounter; - job->jobNum = jobNum; - job->talker = talker; - job->appId = appId; - job->seq = 1; - job->partCount = 1; - job->partSeqNums = QList(); - warnings.enqueue( job ); - job->sentences = QStringList(); - // Do not apply Sentence Boundary Detection filters to warnings. - startJobFiltering( job, warning, true ); - // uint count = warnings.count(); - // kDebug() << "Adding '" << temp->text << "' with talker '" << temp->talker << "' from application " << appId << " to the warnings queue leaving a total of " << count << " items." << endl; -} - -/** -* Pop (get and erase) a warning from the queue. -* @return Pointer to mlText structure containing the message. -* -* Caller is responsible for deleting the structure. -*/ -mlText* SpeechData::dequeueWarning(){ - // kDebug() << "Running: SpeechData::dequeueWarning()" << endl; - mlJob* job = warnings.dequeue(); - waitJobFiltering(job); - mlText* temp = new mlText(); - temp->jobNum = job->jobNum; - temp->text = job->sentences.join(""); - temp->talker = job->talker; - temp->appId = job->appId; - temp->seq = 1; - delete job; - // uint count = warnings.count(); - // kDebug() << "Removing '" << temp->text << "' with talker '" << temp->talker << "' from the warnings queue leaving a total of " << count << " items." << endl; - return temp; -} - -/** -* Are there any Warnings? -*/ -bool SpeechData::warningInQueue(){ - // kDebug() << "Running: SpeechData::warningInQueue() const" << endl; - bool temp = !warnings.isEmpty(); - // if(temp){ - // kDebug() << "The warnings queue is NOT empty" << endl; - // } else { - // kDebug() << "The warnings queue is empty" << endl; - // } - return temp; -} - -/** -* Add a new message to the queue. -*/ -void SpeechData::enqueueMessage( const QString &message, const QString &talker, const QString& appId){ - // kDebug() << "Running: SpeechData::enqueueMessage" << endl; - mlJob* job = new mlJob(); - ++jobCounter; - if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums. - uint jobNum = jobCounter; - job->jobNum = jobNum; - job->talker = talker; - job->appId = appId; - job->seq = 1; - job->partCount = 1; - job->partSeqNums = QList(); - messages.enqueue( job ); - job->sentences = QStringList(); - // Do not apply Sentence Boundary Detection filters to messages. - startJobFiltering( job, message, true ); - // uint count = messages.count(); - // kDebug() << "Adding '" << temp->text << "' with talker '" << temp->talker << "' from application " << appId << " to the messages queue leaving a total of " << count << " items." << endl; -} - -/** -* Pop (get and erase) a message from the queue. -* @return Pointer to mlText structure containing the message. -* -* Caller is responsible for deleting the structure. -*/ -mlText* SpeechData::dequeueMessage(){ - // kDebug() << "Running: SpeechData::dequeueMessage()" << endl; - mlJob* job = messages.dequeue(); - waitJobFiltering(job); - mlText* temp = new mlText(); - temp->jobNum = job->jobNum; - temp->text = job->sentences.join(""); - temp->talker = job->talker; - temp->appId = job->appId; - temp->seq = 1; - delete job; - /* mlText *temp = messages.dequeue(); */ - // uint count = messages.count(); - // kDebug() << "Removing '" << temp->text << "' with talker '" << temp->talker << "' from the messages queue leaving a total of " << count << " items." << endl; - return temp; -} - -/** -* Are there any Messages? -*/ -bool SpeechData::messageInQueue(){ - // kDebug() << "Running: SpeechData::messageInQueue() const" << endl; - bool temp = !messages.isEmpty(); - // if(temp){ - // kDebug() << "The messages queue is NOT empty" << endl; - // } else { - // kDebug() << "The messages queue is empty" << endl; - // } - return temp; -} - -/** -* Determines whether the given text is SSML markup. -*/ -bool SpeechData::isSsml(const QString &text) -{ - /// This checks to see if the root tag of the text is a tag. - QDomDocument ssml; - ssml.setContent(text, false); // No namespace processing. - /// Check to see if this is SSML - QDomElement root = ssml.documentElement(); - return (root.tagName() == "speak"); -} - -/** -* Parses a block of text into sentences using the application-specified regular expression -* or (if not specified), the default regular expression. -* @param text The message to be spoken. -* @param appId The DBUS senderId of the application. NULL if kttsd. -* @return List of parsed sentences. -* -* If the text contains SSML, it is not parsed into sentences at all. -* TODO: Need a way to preserve SSML but still parse into sentences. -* We will walk before we run for now and not sentence parse. -*/ - -QStringList SpeechData::parseText(const QString &text, const QString &appId /*=NULL*/) -{ - // There has to be a better way - // kDebug() << "I'm getting: " << endl << text << " from application " << appId << endl; - if (isSsml(text)) - { - QStringList tempList(text); - return tempList; - } - // See if app has specified a custom sentence delimiter and use it, otherwise use default. - QRegExp sentenceDelimiter; - if (sentenceDelimiters.find(appId) != sentenceDelimiters.end()) - sentenceDelimiter = QRegExp(sentenceDelimiters[appId]); - else - sentenceDelimiter = QRegExp("([\\.\\?\\!\\:\\;]\\s)|(\\n *\\n)"); - QString temp = text; - // Replace spaces, tabs, and formfeeds with a single space. - temp.replace(QRegExp("[ \\t\\f]+"), " "); - // Replace sentence delimiters with tab. - temp.replace(sentenceDelimiter, "\\1\t"); - // Replace remaining newlines with spaces. - temp.replace("\n"," "); - temp.replace("\r"," "); - // Remove leading spaces. - temp.replace(QRegExp("\\t +"), "\t"); - // Remove trailing spaces. - temp.replace(QRegExp(" +\\t"), "\t"); - // Remove blank lines. - temp.replace(QRegExp("\t\t+"),"\t"); - // Split into sentences. - QStringList tempList = temp.split( "\t", QString::SkipEmptyParts); - -// for ( QStringList::Iterator it = tempList.begin(); it != tempList.end(); ++it ) { -// kDebug() << "'" << *it << "'" << endl; -// } - return tempList; -} - -/** -* Queues a text job. -*/ -uint SpeechData::setText( const QString &text, const QString &talker, const QString &appId) -{ - // kDebug() << "Running: SpeechData::setText" << endl; - mlJob* job = new mlJob; - ++jobCounter; - if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums. - uint jobNum = jobCounter; - job->jobNum = jobNum; - job->appId = appId; - job->talker = talker; - job->state = KSpeech::jsQueued; - job->seq = 0; - job->partCount = 1; -#if NO_FILTERS - QStringList tempList = parseText(text, appId); - job->sentences = tempList; - job->partSeqNums.append(tempList.count()); - textJobs.append(job); - emit textSet(appId, jobNum); -#else - job->sentences = QStringList(); - job->partSeqNums = QList(); - textJobs.append(job); - startJobFiltering(job, text, false); -#endif - return jobNum; -} - -/** -* Adds another part to a text job. Does not start speaking the text. -* (thread safe) -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application, -* but if no such job, applies to the last job queued by any application. -* @param text The message to be spoken. -* @return Part number for the added part. Parts are numbered starting at 1. -* -* The text is parsed into individual sentences. Call getTextCount to retrieve -* the sentence count. Call startText to mark the job as speakable and if the -* job is the first speakable job in the queue, speaking will begin. -* @see setText. -* @see startText. -*/ -int SpeechData::appendText(const QString &text, const uint jobNum) -{ - // kDebug() << "Running: SpeechData::appendText" << endl; - int newPartNum = 0; - mlJob* job = findJobByJobNum(jobNum); - if (job) - { - job->partCount++; -#if NO_FILTERS - QStringList tempList = parseText(text, appId); - int sentenceCount = job->sentences.count(); - job->sentences += tempList; - job->partSeqNums.append(sentenceCount + tempList.count()); - newPartNum = job->partSeqNums.count() + 1; - emit textAppended(job->appId, jobNum, newPartNum); -#else - newPartNum = job->partSeqNums.count() + 1; - startJobFiltering(job, text, false); -#endif - } - return newPartNum; -} - -/** -* Given an appId, returns the last (most recently queued) job with that appId. -* @param appId The DBUS senderId of the application. NULL if kttsd. -* @return Pointer to the text job. -* If no such job, returns 0. -* If appId is NULL, returns the last job in the queue. -* Does not change textJobs.current(). -*/ -mlJob* SpeechData::findLastJobByAppId(const QString& appId) -{ - if (textJobs.isEmpty()) return 0; - if (appId.isEmpty()) - return textJobs.last(); - else { - QList::const_iterator it = textJobs.constEnd(); - QList::const_iterator itBegin = textJobs.constBegin(); - while (it != itBegin) { - --it; - mlJob* job = *it; - if (job->appId == appId) return job; - } - return 0; - } -} - -/** -* Given an appId, returns the last (most recently queued) job with that appId, -* or if no such job, the last (most recent) job in the queue. -* @param appId The DBUS senderId of the application. NULL if kttsd. -* @return Pointer to the text job. -* If no such job, returns 0. -* If appId is NULL, returns the last job in the queue. -* Does not change textJobs.current(). -*/ -mlJob* SpeechData::findAJobByAppId(const QString& appId) -{ - mlJob* job = findLastJobByAppId(appId); - // if (!job) job = textJobs.getLast(); - return job; -} - -/** -* Given an appId, returns the last (most recently queued) Job Number with that appId, -* or if no such job, the Job Number of the last (most recent) job in the queue. -* @param appId The DBUS senderId of the application. NULL if kttsd. -* @return Job Number of the text job. -* If no such job, returns 0. -* If appId is NULL, returns the Job Number of the last job in the queue. -* Does not change textJobs.current(). -*/ -uint SpeechData::findAJobNumByAppId(const QString& appId) -{ - mlJob* job = findAJobByAppId(appId); - if (job) - return job->jobNum; - else - return 0; -} - -/** -* Given a jobNum, returns the first job with that jobNum. -* @return Pointer to the text job. -* If no such job, returns 0. -* Does not change textJobs.current(). -*/ -mlJob* SpeechData::findJobByJobNum(const uint jobNum) -{ - QList::const_iterator it = textJobs.constBegin(); - QList::const_iterator itEnd = textJobs.constEnd(); - for ( ; it != itEnd; ++it) - { - mlJob* job = *it; - if (job->jobNum == jobNum) - { - return job; - } - } - return 0; -} - -/** -* Given a jobNum, returns the appId of the application that owns the job. -* @param jobNum Job number of the text job. -* @return appId of the job. -* If no such job, returns "". -* Does not change textJobs.current(). -*/ -QString SpeechData::getAppIdByJobNum(const uint jobNum) -{ - QString appId; - mlJob* job = findJobByJobNum(jobNum); - if (job) appId = job->appId; - return appId; -} - -/** -* Sets pointer to the TalkerMgr object. -*/ -void SpeechData::setTalkerMgr(TalkerMgr* talkerMgr) { m_talkerMgr = talkerMgr; } - -/** -* Remove a text job from the queue. -* (thread safe) -* @param jobNum Job number of the text job. -* -* The job is deleted from the queue and the textRemoved signal is emitted. -*/ -void SpeechData::removeText(const uint jobNum) -{ - // kDebug() << "Running: SpeechData::removeText" << endl; - uint removeJobNum = 0; - QString removeAppId; // The appId of the removed (and stopped) job. - mlJob* removeJob = findJobByJobNum(jobNum); - if (removeJob) - { - removeAppId = removeJob->appId; - removeJobNum = removeJob->jobNum; - // If filtering on the job, cancel it (all parts). - if (m_pooledFilterMgrs.contains(removeJobNum)) - { - while (PooledFilterMgr* pooledFilterMgr = m_pooledFilterMgrs.take(removeJobNum)) { - pooledFilterMgr->busy = false; - pooledFilterMgr->job = 0; - pooledFilterMgr->partNum = 0; - delete pooledFilterMgr->talkerCode; - pooledFilterMgr->talkerCode = 0; - pooledFilterMgr->filterMgr->stopFiltering(); - m_pooledFilterMgrs.insert(0, pooledFilterMgr); - } - } - // Delete the job. - textJobs.removeAll(removeJob); - } - if (removeJobNum) emit textRemoved(removeAppId, removeJobNum); -} - -/** -* Given a job and a sequence number, returns the part that sentence is in. -* If no such job or sequence number, returns 0. -* @param job The text job. -* @param seq Sequence number of the sentence. Sequence numbers begin with 1. -* @return Part number of the part the sentence is in. Parts are numbered -* beginning with 1. If no such job or sentence, returns 0. -* -*/ -int SpeechData::getJobPartNumFromSeq(const mlJob& job, const int seq) -{ - int foundPartNum = 0; - int desiredSeq = seq; - int partNum = 0; - // Wait until all filtering has stopped for the job. - waitJobFiltering(&job); - while (partNum < job.partSeqNums.count()) - { - if (desiredSeq <= job.partSeqNums[partNum]) - { - foundPartNum = partNum + 1; - break; - } - desiredSeq = desiredSeq - job.partSeqNums[partNum]; - ++partNum; - } - return foundPartNum; -} - - -/** -* Delete expired jobs. At most, one finished job is kept on the queue. -* @param finishedJobNum Job number of a job that just finished. -* The just finished job is not deleted, but any other finished jobs are. -* Does not change the textJobs.current() pointer. -*/ -void SpeechData::deleteExpiredJobs(const uint finishedJobNum) -{ - // Save current pointer. - typedef QPair removedJob; - typedef QList removedJobsList; - removedJobsList removedJobs; - // Walk through jobs and delete any other finished jobs. - int ndx = 0; - while (ndx < textJobs.size()) { - mlJob* job = textJobs[ndx]; - if (job->jobNum != finishedJobNum && job->state == KSpeech::jsFinished) - { - removedJobs.append(removedJob(job->appId, job->jobNum)); - delete textJobs.takeAt(ndx); - } else - ndx++; - } - // Emit signals for removed jobs. - for (int i = 0; i < removedJobs.size(); ++i) - { - QString appId = removedJobs.at(i).first; - uint jobNum = removedJobs.at(i).second; - textRemoved(appId, jobNum); - } -} - -/** -* Given a Job Number, returns the next speakable text job on the queue. -* @param prevJobNum Current job number (which should not be returned). -* @return Pointer to mlJob structure of the first speakable job -* not equal prevJobNum. If no such job, returns null. -* -* Caller must not delete the job. -*/ -mlJob* SpeechData::getNextSpeakableJob(const uint prevJobNum) -{ - QList::const_iterator it = textJobs.constBegin(); - QList::const_iterator itEnd = textJobs.constEnd(); - for ( ; it != itEnd; ++it) - { - mlJob* job = *it; - if (job->jobNum != prevJobNum) - if (job->state == KSpeech::jsSpeakable) - { - waitJobFiltering(job); - return job; - } - } - return 0; -} - -/** -* Given previous job number and sequence number, returns the next sentence from the -* text queue. If no such sentence is available, either because we've run out of -* jobs, or because all jobs are paused, returns null. -* @param prevJobNum Previous Job Number. -* @param prevSeq Previous sequency number. -* @return Pointer to n mlText structure containing the next sentence. If no -* sentence, returns null. -* -* Caller is responsible for deleting the returned mlText structure (if not null). -*/ -mlText* SpeechData::getNextSentenceText(const uint prevJobNum, const uint prevSeq) -{ - // kDebug() << "SpeechData::getNextSentenceText running with prevJobNum " << prevJobNum << " prevSeq " << prevSeq << endl; - mlText* temp = 0; - uint jobNum = prevJobNum; - mlJob* job = 0; - int seq = prevSeq; - ++seq; - if (!jobNum) - { - job = getNextSpeakableJob(jobNum); - if (job) seq =+ job->seq; - } else - job = findJobByJobNum(prevJobNum); - if (!job) - { - job = getNextSpeakableJob(jobNum); - if (job) seq =+ job->seq; - } - else - { - if ((job->state != KSpeech::jsSpeakable) && (job->state != KSpeech::jsSpeaking)) - { - job = getNextSpeakableJob(job->jobNum); - if (job) seq =+ job->seq; - } - } - if (job) - { - // If we run out of sentences in the job, move on to next job. - jobNum = job->jobNum; - if (seq > job->sentences.count()) - { - job = getNextSpeakableJob(jobNum); - if (job) seq =+ job->seq; - } - } - if (job) - { - if (seq == 0) seq = 1; - temp = new mlText; - temp->text = job->sentences[seq - 1]; - temp->appId = job->appId; - temp->talker = job->talker; - temp->jobNum = job->jobNum; - temp->seq = seq; - // kDebug() << "SpeechData::getNextSentenceText: return job number " << temp->jobNum << " seq " << temp->seq << " sentence count = " << job->sentences.count() << endl; - } // else kDebug() << "SpeechData::getNextSentenceText: no more sentences in queue" << endl; - return temp; -} - -/** -* Given a Job Number, sets the current sequence number of the job. -* @param jobNum Job Number. -* @param seq Sequence number. -* If for some reason, the job does not exist, nothing happens. -*/ -void SpeechData::setJobSequenceNum(const uint jobNum, const uint seq) -{ - mlJob* job = findJobByJobNum(jobNum); - if (job) job->seq = seq; -} - -/** -* Given a Job Number, returns the current sequence number of the job. -* @param jobNum Job Number. -* @return Sequence number of the job. If no such job, returns 0. -*/ -uint SpeechData::getJobSequenceNum(const uint jobNum) -{ - mlJob* job = findJobByJobNum(jobNum); - if (job) - return job->seq; - else - return 0; -} - -/** -* Sets the GREP pattern that will be used as the sentence delimiter. -* @param delimiter A valid GREP pattern. -* @param appId The DBUS senderId of the application. NULL if kttsd. -* -* The default delimiter is - @verbatim - ([\\.\\?\\!\\:\\;])\\s - @endverbatim -* -* Note that backward slashes must be escaped. -* -* Changing the sentence delimiter does not affect other applications. -* @see sentenceparsing -*/ -void SpeechData::setSentenceDelimiter(const QString &delimiter, const QString appId) -{ - sentenceDelimiters[appId] = delimiter; -} - -/** -* Get the number of sentences in a text job. -* (thread safe) -* @param jobNum Job number of the text job. -* @return The number of sentences in the job. -1 if no such job. -* -* The sentences of a job are given sequence numbers from 1 to the number returned by this -* method. The sequence numbers are emitted in the sentenceStarted and sentenceFinished signals. -*/ -int SpeechData::getTextCount(const uint jobNum) -{ - mlJob* job = findJobByJobNum(jobNum); - int temp; - if (job) - { - waitJobFiltering(job); - temp = job->sentences.count(); - } else - temp = -1; - return temp; -} - -/** -* Get the number of jobs in the text job queue. -* (thread safe) -* @return Number of text jobs in the queue. 0 if none. -*/ -uint SpeechData::getTextJobCount() -{ - return textJobs.count(); -} - -/** -* Get a comma-separated list of text job numbers in the queue. -* @return Comma-separated list of text job numbers in the queue. -*/ -QString SpeechData::getTextJobNumbers() -{ - QString jobs; - QList::const_iterator it = textJobs.constBegin(); - QList::const_iterator itEnd = textJobs.constEnd(); - for (; it != itEnd; ++it) - { - mlJob* job = *it; - if (!jobs.isEmpty()) jobs.append(","); - jobs.append(QString::number(job->jobNum)); - } - return jobs; -} - -/** -* Get the state of a text job. -* (thread safe) -* @param jobNum Job number of the text job. -* @return State of the job. -1 if invalid job number. -*/ -int SpeechData::getTextJobState(const uint jobNum) -{ - mlJob* job = findJobByJobNum(jobNum); - int temp; - if (job) - temp = (int)job->state; - else - temp = -1; - return temp; -} - -/** -* Set the state of a text job. -* @param jobNum Job Number of the job. -* @param state New state for the job. -* -* If the new state is Finished, deletes other expired jobs. -* -**/ -void SpeechData::setTextJobState(const uint jobNum, const KSpeech::kttsdJobState state) -{ - mlJob* job = findJobByJobNum(jobNum); - if (job) - { - job->state = state; - if (state == KSpeech::jsFinished) deleteExpiredJobs(jobNum); - } -} - -/** -* Get information about a text job. -* @param jobNum Job number of the text job. -* @return A QDataStream containing information about the job. -* Blank if no such job. -* -* The stream contains the following elements: -* - int state Job state. -* - QString appId DBUS senderId of the application that requested the speech job. -* - QString talker Language code in which to speak the text. -* - int seq Current sentence being spoken. Sentences are numbered starting at 1. -* - int sentenceCount Total number of sentences in the job. -* - int partNum Current part of the job begin spoken. Parts are numbered starting at 1. -* - int partCount Total number of parts in the job. -* -* Note that sequence numbers apply to the entire job. -* They do not start from 1 at the beginning of each part. -* -* The following sample code will decode the stream: - @verbatim - QByteArray jobInfo = getTextJobInfo(jobNum); - QDataStream stream(jobInfo, QIODevice::ReadOnly); - int state; - QString appId; - QString talker; - int seq; - int sentenceCount; - int partNum; - int partCount; - stream >> state; - stream >> appId; - stream >> talker; - stream >> seq; - stream >> sentenceCount; - stream >> partNum; - stream >> partCount; - @endverbatim -*/ -QByteArray SpeechData::getTextJobInfo(const uint jobNum) -{ - mlJob* job = findJobByJobNum(jobNum); - QByteArray temp; - if (job) - { - waitJobFiltering(job); - QDataStream stream(&temp, QIODevice::WriteOnly); - stream << job->state; - stream << job->appId; - stream << job->talker; - stream << job->seq; - stream << job->sentences.count(); - stream << getJobPartNumFromSeq(*job, job->seq); - stream << job->partSeqNums.count(); - } - return temp; -} - -/** -* Return a sentence of a job. -* @param jobNum Job number of the text job. -* @param seq Sequence number of the sentence. -* @return The specified sentence in the specified job. If no such -* job or sentence, returns "". -*/ -QString SpeechData::getTextJobSentence(const uint jobNum, const uint seq /*=1*/) -{ - mlJob* job = findJobByJobNum(jobNum); - QString temp; - if (job) - { - waitJobFiltering(job); - temp = job->sentences[seq - 1]; - } - return temp; -} - -/** -* Change the talker for a text job. -* @param jobNum Job number of the text job. -* If zero, applies to the last job queued by the application, -* but if no such job, applies to the last job queued by any application. -* @param talker New code for the talker to do the speaking. Example "en". -* If NULL, defaults to the user's default talker. -* If no plugin has been configured for the specified Talker code, -* defaults to the closest matching talker. -*/ -void SpeechData::changeTextTalker(const QString &talker, uint jobNum) -{ - mlJob* job = findJobByJobNum(jobNum); - if (job) job->talker = talker; -} - -/** -* Move a text job down in the queue so that it is spoken later. -* @param jobNum Job number of the text job. -*/ -void SpeechData::moveTextLater(const uint jobNum) -{ - // kDebug() << "Running: SpeechData::moveTextLater" << endl; - mlJob* job = findJobByJobNum(jobNum); - if (job) - { - // Get index of the job. - uint index = textJobs.indexOf(job); - // Move job down one position in the queue. - // kDebug() << "In SpeechData::moveTextLater, moving jobNum " << movedJobNum << endl; - textJobs.insert(index + 2, job); - textJobs.takeAt(index); - } -} - -/** -* Jump to the first sentence of a specified part of a text job. -* @param partNum Part number of the part to jump to. Parts are numbered starting at 1. -* @param jobNum Job number of the text job. -* @return Part number of the part actually jumped to. -* -* If partNum is greater than the number of parts in the job, jumps to last part. -* If partNum is 0, does nothing and returns the current part number. -* If no such job, does nothing and returns 0. -* Does not affect the current speaking/not-speaking state of the job. -*/ -int SpeechData::jumpToTextPart(const int partNum, const uint jobNum) -{ - // kDebug() << "Running: SpeechData::jumpToTextPart" << endl; - int newPartNum = 0; - mlJob* job = findJobByJobNum(jobNum); - if (job) - { - waitJobFiltering(job); - if (partNum > 0) - { - newPartNum = partNum; - int partCount = job->partSeqNums.count(); - if (newPartNum > partCount) newPartNum = partCount; - if (newPartNum > 1) - job->seq = job->partSeqNums[newPartNum - 1]; - else - job->seq = 0; - } - else - newPartNum = getJobPartNumFromSeq(*job, job->seq); - } - return newPartNum; -} - -/** -* Advance or rewind N sentences in a text job. -* @param n Number of sentences to advance (positive) or rewind (negative) -* in the job. -* @param jobNum Job number of the text job. -* @return Sequence number of the sentence actually moved to. Sequence numbers -* are numbered starting at 1. -* -* If no such job, does nothing and returns 0. -* If n is zero, returns the current sequence number of the job. -* Does not affect the current speaking/not-speaking state of the job. -*/ -uint SpeechData::moveRelTextSentence(const int n, const uint jobNum /*=0*/) -{ - // kDebug() << "Running: SpeechData::moveRelTextSentence" << endl; - int newSeqNum = 0; - mlJob* job = findJobByJobNum(jobNum); - if (job) - { - waitJobFiltering(job); - int oldSeqNum = job->seq; - newSeqNum = oldSeqNum + n; - if (n != 0) - { - if (newSeqNum < 0) newSeqNum = 0; - int sentenceCount = job->sentences.count(); - if (newSeqNum > sentenceCount) newSeqNum = sentenceCount; - job->seq = newSeqNum; - } - } - return newSeqNum; -} - -/** -* Assigns a FilterMgr to a job and starts filtering on it. -*/ -void SpeechData::startJobFiltering(mlJob* job, const QString& text, bool noSBD) -{ - uint jobNum = job->jobNum; - int partNum = job->partCount; - // kDebug() << "SpeechData::startJobFiltering: jobNum = " << jobNum << " partNum = " << partNum << " text.left(500) = " << text.left(500) << endl; - PooledFilterMgr* pooledFilterMgr = 0; - if (m_pooledFilterMgrs.contains(jobNum)) return; - // Find an idle FilterMgr, if any. - // If filtering is already in progress for this job and part, do nothing. - QMultiHash::iterator it = m_pooledFilterMgrs.begin(); - while (it != m_pooledFilterMgrs.end()) { - if (!it.value()->busy) - { - if ((it.value()->job && it.value()->job->jobNum == jobNum) && (it.value()->partNum == partNum)) - return; - } else { - if (!it.value()->job && !pooledFilterMgr) - pooledFilterMgr = it.value(); - } - ++it; - } - // Create a new FilterMgr if needed and add to pool. - if (!pooledFilterMgr) - { - // kDebug() << "SpeechData::startJobFiltering: adding new pooledFilterMgr for job " << jobNum << " part " << partNum << endl; - pooledFilterMgr = new PooledFilterMgr(); - FilterMgr* filterMgr = new FilterMgr(); - filterMgr->init(config, "General"); - pooledFilterMgr->filterMgr = filterMgr; - // Connect signals from FilterMgr. - connect (filterMgr, SIGNAL(filteringFinished()), this, SLOT(slotFilterMgrFinished())); - connect (filterMgr, SIGNAL(filteringStopped()), this, SLOT(slotFilterMgrStopped())); - m_pooledFilterMgrs.insert(jobNum, pooledFilterMgr); - } - // else kDebug() << "SpeechData::startJobFiltering: re-using idle pooledFilterMgr for job " << jobNum << " part " << partNum << endl; - // Flag the FilterMgr as busy and set it going. - pooledFilterMgr->busy = true; - pooledFilterMgr->job = job; - pooledFilterMgr->partNum = partNum; - pooledFilterMgr->filterMgr->setNoSBD( noSBD ); - // Get TalkerCode structure of closest matching Talker. - pooledFilterMgr->talkerCode = m_talkerMgr->talkerToTalkerCode(job->talker); - // Pass Sentence Boundary regular expression (if app overrode default); - if (sentenceDelimiters.find(job->appId) != sentenceDelimiters.end()) - pooledFilterMgr->filterMgr->setSbRegExp(sentenceDelimiters[job->appId]); - pooledFilterMgr->filterMgr->asyncConvert(text, pooledFilterMgr->talkerCode, job->appId); -} - -/** -* Waits for filtering to be completed on a job. -* This is typically called because an app has requested job info that requires -* filtering to be completed, such as getJobInfo. -*/ -void SpeechData::waitJobFiltering(const mlJob* job) -{ -#if NO_FILTERS - return; -#endif - int jobNum = job->jobNum; - bool waited = false; - bool notOptimum = false; - if (!m_pooledFilterMgrs.contains(jobNum)) return; - QMultiHash::iterator it = m_pooledFilterMgrs.find(jobNum); - while (it != m_pooledFilterMgrs.end() && it.key() == jobNum) { - PooledFilterMgr* pooledFilterMgr = it.value(); - if (pooledFilterMgr->busy) - { - if (!pooledFilterMgr->filterMgr->noSBD()) - notOptimum = true; - pooledFilterMgr->filterMgr->waitForFinished(); - waited = true; - } - ++it; - } - if (waited) { - if (notOptimum) - kDebug() << "SpeechData::waitJobFiltering: Waited for filtering to finish on job " - << jobNum << ". Not optimium. " - << "Try waiting for textSet signal before querying for job information." << endl; - doFiltering(); - } -} - -/** -* Processes filters by looping across the pool of FilterMgrs. -* As each FilterMgr finishes, emits appropriate signals and flags it as no longer busy. -*/ -void SpeechData::doFiltering() -{ - // kDebug() << "SpeechData::doFiltering: Running." << endl; - kDebug() << "SpeechData::doFiltering: Scanning " << m_pooledFilterMgrs.count() << " pooled filter managers." << endl; - bool again = true; - while (again) - { - again = false; - QMultiHash::iterator it = m_pooledFilterMgrs.begin(); - QMultiHash::iterator nextIt; - while (it != m_pooledFilterMgrs.end()) { - nextIt = it; - ++nextIt; - PooledFilterMgr* pooledFilterMgr = it.value(); - // If FilterMgr is busy, see if it is now finished. - Q_ASSERT(pooledFilterMgr); - if (pooledFilterMgr->busy) - { - FilterMgr* filterMgr = pooledFilterMgr->filterMgr; - if (filterMgr->getState() == FilterMgr::fsFinished) - { - mlJob* job = pooledFilterMgr->job; - kDebug() << "SpeechData::doFiltering: filter finished, jobNum = " << job->jobNum << " partNum = " << pooledFilterMgr->partNum << endl; - // We have to retrieve parts in order, but parts may not be completed in order. - // See if this is the next part we need. - if ((int)job->partSeqNums.count() == (pooledFilterMgr->partNum - 1)) - { - pooledFilterMgr->busy = false; - // Retrieve text from FilterMgr. - QString text = filterMgr->getOutput(); - kDebug() << "SpeechData::doFiltering: text.left(500) = " << text.left(500) << endl; - // kDebug() << "SpeechData::doFiltering: filtered text: " << text << endl; - filterMgr->ackFinished(); - // Convert the TalkerCode back into string. - job->talker = pooledFilterMgr->talkerCode->getTalkerCode(); - // TalkerCode object no longer needed. - delete pooledFilterMgr->talkerCode; - pooledFilterMgr->talkerCode = 0; - if (filterMgr->noSBD()) { - job->sentences.clear(); - job->sentences.append(text); - } else { - // Split the text into sentences and store in the job. - // The SBD plugin does all the real sentence parsing, inserting tabs at each - // sentence boundary. - QStringList sentences = text.split( "\t", QString::SkipEmptyParts); - int sentenceCount = job->sentences.count(); - job->sentences += sentences; - job->partSeqNums.append(sentenceCount + sentences.count()); - } - int partNum = job->partSeqNums.count(); - // Clean up. - pooledFilterMgr->job = 0; - pooledFilterMgr->partNum = 0; - // Re-index pool of FilterMgrs; - m_pooledFilterMgrs.erase(it); - m_pooledFilterMgrs.insert(0, pooledFilterMgr); - // Emit signal. - if (!filterMgr->noSBD()) - { - if (partNum == 1) - emit textSet(job->appId, job->jobNum); - else - emit textAppended(job->appId, job->jobNum, partNum); - } - } else { - // A part is ready, but need to first process a finished preceeding part - // that follows this one in the pool of filter managers. - again = true; - kDebug() << "SpeechData::doFiltering: filter is finished, but must wait for earlier part to finish filter, job = " << pooledFilterMgr->job->jobNum << endl; - } - } - else kDebug() << "SpeechData::doFiltering: filter for job " << pooledFilterMgr->job->jobNum << " is busy." << endl; - } - else kDebug() << "SpeechData::doFiltering: filter is idle" << endl; - it = nextIt; - } - } -} - -void SpeechData::slotFilterMgrFinished() -{ - // kDebug() << "SpeechData::slotFilterMgrFinished: received signal FilterMgr finished signal." << endl; - doFiltering(); -} - -void SpeechData::slotFilterMgrStopped() -{ - doFiltering(); -} - +/***************************************************** vim:set ts=4 sw=4 sts=4: + This contains the SpeechData class which is in charge of maintaining + all the speech data. + We could say that this is the common repository between the KTTSD class + (dbus service) and the Speaker class (speaker, loads plug ins, call plug in + functions) + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// C++ includes. +#include + +// Qt includes. +#include +#include +#include +#include + +// KDE includes. +#include +#include +#include +#include + +// KTTS includes. +#include "talkermgr.h" +#include "notify.h" +#include "configdata.h" + +// SpeechData includes. +#include "speechdata.h" +#include "speechdata.moc" + +/* -------------------------------------------------------------------------- */ + +class SpeechDataPrivate +{ +public: + SpeechDataPrivate() : + configData(NULL), + lastJobNum(0), + talkerMgr(NULL), + jobCounter(0), + supportsHTML(false) + { + jobLists.insert(KSpeech::jpScreenReaderOutput, new TJobList()); + jobLists.insert(KSpeech::jpWarning, new TJobList()); + jobLists.insert(KSpeech::jpMessage, new TJobList()); + jobLists.insert(KSpeech::jpText, new TJobList()); + }; + + ~SpeechDataPrivate() + { + // kDebug() << "Running: SpeechDataPrivate::~SpeechDataPrivate" << endl; + // Walk through jobs and emit jobStateChanged signal for each job. + foreach (SpeechJob* job, allJobs) + delete job; + allJobs.clear(); + + foreach (TJobListPtr jobList, jobLists) + jobList->clear(); + jobLists.clear(); + + foreach (PooledFilterMgr* pooledFilterMgr, pooledFilterMgrs) { + delete pooledFilterMgr->filterMgr; + delete pooledFilterMgr->talkerCode; + delete pooledFilterMgr; + } + pooledFilterMgrs.clear(); + + foreach (AppData* appData, appData) + delete appData; + appData.clear(); + } + + friend class SpeechData; + +protected: + /** + * Configuration data. + */ + ConfigData* configData; + + /** + * All jobs. + */ + QHash allJobs; + + /** + * List of jobs for each job priority type. + */ + QMap jobLists; + + /** + * Application data. + */ + mutable QMap appData; + + /** + * The last job queued by any App. + */ + int lastJobNum; + + /** + * TalkerMgr object local pointer. + */ + TalkerMgr* talkerMgr; + + /** + * Pool of FilterMgrs. + */ + QMultiHash pooledFilterMgrs; + + /** + * Job counter. Each new job increments this counter. + */ + int jobCounter; + + /** + * True if at least one XML Transformer plugin for html is enabled. + */ + bool supportsHTML; +}; + +/* -------------------------------------------------------------------------- */ + +/** +* Constructor +*/ +SpeechData::SpeechData() +{ + d = new SpeechDataPrivate(); +} + +/** +* Destructor +*/ +SpeechData::~SpeechData() +{ + delete d; +} + +AppData* SpeechData::getAppData(const QString& appId) const +{ + if (!d->appData.contains(appId)) + d->appData.insert(appId, new AppData(appId)); + return d->appData[appId]; +} + +void SpeechData::releaseAppData(const QString& appId) +{ + if (d->appData.contains(appId)) + delete d->appData.take(appId); +} + +bool SpeechData::isSsml(const QString &text) +{ + /// This checks to see if the root tag of the text is a tag. + QDomDocument ssml; + ssml.setContent(text, false); // No namespace processing. + /// Check to see if this is SSML + QDomElement root = ssml.documentElement(); + return (root.tagName() == "speak"); +} + +QStringList SpeechData::parseText(const QString &text, const QString &appId /*=NULL*/) +{ + // There has to be a better way + // kDebug() << "I'm getting: " << endl << text << " from application " << appId << endl; + if (isSsml(text)) { + QStringList tempList(text); + return tempList; + } + // See if app has specified a custom sentence delimiter and use it, otherwise use default. + QRegExp sentenceDelimiter(getAppData(appId)->sentenceDelimiter()); + QString temp = text; + // Replace spaces, tabs, and formfeeds with a single space. + temp.replace(QRegExp("[ \\t\\f]+"), " "); + // Replace sentence delimiters with tab. + temp.replace(sentenceDelimiter, "\\1\t"); + // Replace remaining newlines with spaces. + temp.replace("\n"," "); + temp.replace("\r"," "); + // Remove leading spaces. + temp.replace(QRegExp("\\t +"), "\t"); + // Remove trailing spaces. + temp.replace(QRegExp(" +\\t"), "\t"); + // Remove blank lines. + temp.replace(QRegExp("\t\t+"),"\t"); + // Split into sentences. + QStringList tempList = temp.split( "\t", QString::SkipEmptyParts); + +// for ( QStringList::Iterator it = tempList.begin(); it != tempList.end(); ++it ) { +// kDebug() << "'" << *it << "'" << endl; +// } + return tempList; +} + +int SpeechData::say(const QString& appId, const QString& text, int sayOptions) +{ + // TODO: sayOptions + Q_UNUSED(sayOptions); + + // kDebug() << "Running: SpeechData::say" << endl; + AppData* appData = getAppData(appId); + KSpeech::JobPriority priority = appData->defaultPriority(); + QString talker = appData->defaultTalker(); + + // Screen Reader Outputs replace other Screen Reader Outputs not yet speaking. + if (KSpeech::jpScreenReaderOutput == priority) + foreach(int jobNum, *d->jobLists[priority]) { + SpeechJob* job = d->allJobs[jobNum]; + // TODO: OK to delete jobs while iterating inside foreach? + switch (job->state()) { + case KSpeech::jsQueued: + removeJob(jobNum); + break; + case KSpeech::jsFiltering: + waitJobFiltering(job); + removeJob(jobNum); + break; + case KSpeech::jsSpeakable: + removeJob(jobNum); + break; + case KSpeech::jsSpeaking: + break; + case KSpeech::jsPaused: + case KSpeech::jsInterrupted: + break; + case KSpeech::jsFinished: + removeJob(jobNum); + break; + case KSpeech::jsDeleted: + break; + } + }; + SpeechJob* job = new SpeechJob(priority); + connect(job, SIGNAL(jobStateChanged(const QString&, int, KSpeech::JobState)), + this, SIGNAL(jobStateChanged(const QString&, int, KSpeech::JobState))); + ++d->jobCounter; + if (d->jobCounter <= 0) d->jobCounter = 1; // Overflow is OK, but don't want any 0 jobNums. + int jobNum = d->jobCounter; + job->setJobNum(jobNum); + job->setAppId(appId); + job->setTalker(talker); + // Note: Set state last so job is fully populated when jobStateChanged signal is emitted. + d->allJobs.insert(jobNum, job); + d->jobLists[priority]->append(jobNum); + appData->jobList()->append(jobNum); + d->lastJobNum = jobNum; + if (!appData->filteringOn()) { + QStringList tempList = parseText(text, appId); + job->setSentences(tempList); + job->setState(KSpeech::jsSpeakable); + } else { + job->setSentences(QStringList()); + startJobFiltering(job, text, (KSpeech::jpScreenReaderOutput == priority)); + emit jobStateChanged(appId, jobNum, KSpeech::jsQueued); + } + return jobNum; +} + +SpeechJob* SpeechData::findLastJobByAppId(const QString& appId) const +{ + int jobNum = findJobNumByAppId(appId); + if (jobNum) + return d->allJobs[jobNum]; + else + return NULL; +} + +int SpeechData::findJobNumByAppId(const QString& appId) const +{ + if (appId.isEmpty()) + return d->lastJobNum; + else + return getAppData(appId)->lastJobNum(); +} + +/** +* Given a jobNum, returns the first job with that jobNum. +* @return Pointer to the job. +* If no such job, returns 0. +*/ +SpeechJob* SpeechData::findJobByJobNum(int jobNum) const +{ + if (d->allJobs.contains(jobNum)) + return d->allJobs[jobNum]; + else + return NULL; +} + +QString SpeechData::getAppIdByJobNum(int jobNum) const +{ + QString appId; + SpeechJob* job = findJobByJobNum(jobNum); + if (job) appId = job->appId(); + return appId; +} + +void SpeechData::setTalkerMgr(TalkerMgr* talkerMgr) +{ + d->talkerMgr = talkerMgr; +} + +void SpeechData::setConfigData(ConfigData* configData) +{ + d->configData = configData; + + // Clear the pool of filter managers so that filters re-init themselves. + QMultiHash::iterator it = d->pooledFilterMgrs.begin(); + while (it != d->pooledFilterMgrs.end()) { + PooledFilterMgr* pooledFilterMgr = it.value(); + delete pooledFilterMgr->filterMgr; + delete pooledFilterMgr->talkerCode; + delete pooledFilterMgr; + ++it; + } + d->pooledFilterMgrs.clear(); + + // Create an initial FilterMgr for the pool to save time later. + PooledFilterMgr* pooledFilterMgr = new PooledFilterMgr(); + FilterMgr* filterMgr = new FilterMgr(); + filterMgr->init(); + d->supportsHTML = filterMgr->supportsHTML(); + pooledFilterMgr->filterMgr = filterMgr; + pooledFilterMgr->busy = false; + pooledFilterMgr->job = 0; + pooledFilterMgr->talkerCode = 0; + // Connect signals from FilterMgr. + connect (filterMgr, SIGNAL(filteringFinished()), this, SLOT(slotFilterMgrFinished())); + connect (filterMgr, SIGNAL(filteringStopped()), this, SLOT(slotFilterMgrStopped())); + d->pooledFilterMgrs.insert(0, pooledFilterMgr); +} + +void SpeechData::deleteJob(int removeJobNum) +{ + if (d->allJobs.contains(removeJobNum)) { + SpeechJob* job = d->allJobs.take(removeJobNum); + KSpeech::JobPriority priority = job->jobPriority(); + QString appId = job->appId(); + delete job; + d->jobLists[priority]->removeAll(removeJobNum); + getAppData(appId)->jobList()->removeAll(removeJobNum); + } +} + +void SpeechData::removeJob(int jobNum) +{ + // kDebug() << "Running: SpeechData::removeJob" << endl; + SpeechJob* removeJob = findJobByJobNum(jobNum); + if (removeJob) { + QString removeAppId = removeJob->appId(); + int removeJobNum = removeJob->jobNum(); + // If filtering on the job, cancel it. + if (d->pooledFilterMgrs.contains(removeJobNum)) { + while (PooledFilterMgr* pooledFilterMgr = d->pooledFilterMgrs.take(removeJobNum)) { + pooledFilterMgr->busy = false; + pooledFilterMgr->job = 0; + delete pooledFilterMgr->talkerCode; + pooledFilterMgr->talkerCode = 0; + pooledFilterMgr->filterMgr->stopFiltering(); + d->pooledFilterMgrs.insert(0, pooledFilterMgr); + } + } + deleteJob(removeJobNum); + } +} + +void SpeechData::removeAllJobs(const QString& appId) +{ + AppData* appData = getAppData(appId); + if (appData->isSystemManager()) + foreach (SpeechJob* job, d->allJobs) + removeJob(job->jobNum()); + else + foreach (int jobNum, *appData->jobList()) + removeJob(jobNum); +} + +void SpeechData::deleteExpiredJobs(int finishedJobNum) +{ + // Walk through jobs and delete any other finished jobs. + foreach (TJobListPtr jobList, d->jobLists) { + foreach (int jobNum, *jobList) { + if (jobNum != finishedJobNum) { + SpeechJob* job = d->allJobs[jobNum]; + if (KSpeech::jsFinished == job->state()) + deleteJob(jobNum); + } + } + } +} + +SpeechJob* SpeechData::getNextSpeakableJob(KSpeech::JobPriority priority) +{ + foreach (int jobNum, *d->jobLists[priority]) { + SpeechJob* job = d->allJobs[jobNum]; + if (!d->appData[job->appId()]->isApplicationPaused()) { + switch (job->state()) { + case KSpeech::jsQueued: + break; + case KSpeech::jsFiltering: + waitJobFiltering(job); + return job; + case KSpeech::jsSpeakable: + return job; + case KSpeech::jsSpeaking: + if (job->seq() < job->sentenceCount()) + return job; + break; + case KSpeech::jsPaused: + case KSpeech::jsInterrupted: + case KSpeech::jsFinished: + case KSpeech::jsDeleted: + break; + } + } + } + return NULL; +} + +void SpeechData::setJobSequenceNum(int jobNum, int seq) +{ + SpeechJob* job = findJobByJobNum(jobNum); + if (job) job->setSeq(seq); +} + +int SpeechData::jobSequenceNum(int jobNum) const +{ + SpeechJob* job = findJobByJobNum(jobNum); + if (job) + return job->seq(); + else + return 0; +} + +int SpeechData::sentenceCount(int jobNum) +{ + SpeechJob* job = findJobByJobNum(jobNum); + int temp; + if (job) { + waitJobFiltering(job); + temp = job->sentenceCount(); + } else + temp = -1; + return temp; +} + +int SpeechData::jobCount(const QString& appId, KSpeech::JobPriority priority) const +{ + AppData* appData = getAppData(appId); + // If System Manager app, return count of all jobs for all apps. + if (appData->isSystemManager()) + return d->allJobs.count(); + else { + if (KSpeech::jpAll == priority) + // Return count of all jobs for this app. + return appData->jobList()->count(); + else { + // Return count of jobs for this app of the specified priority. + int cnt = 0; + foreach (int jobNum, *d->jobLists[priority]) { + SpeechJob* job = d->allJobs[jobNum]; + if (job->appId() == appId) + ++cnt; + } + return cnt; + } + } +} + +QStringList SpeechData::jobNumbers(const QString& appId, KSpeech::JobPriority priority) const +{ + QStringList jobs; + AppData* appData = getAppData(appId); + if (appData->isSystemManager()) { + if (KSpeech::jpAll == priority) { + // Return job numbers for all jobs. + foreach (int jobNum, d->allJobs.keys()) + jobs.append(QString::number(jobNum)); + } else { + // Return job numbers for all jobs of specified priority. + foreach (int jobNum, *d->jobLists[priority]) + jobs.append(QString::number(jobNum)); + } + } else { + if (KSpeech::jpAll == priority) { + // Return job numbers for all jobs for this app. + foreach (int jobNum, *appData->jobList()) + jobs.append(QString::number(jobNum)); + } else { + // Return job numbers for this app's jobs of the specified priority. + foreach (int jobNum, *d->jobLists[priority]) { + SpeechJob* job = d->allJobs[jobNum]; + if (job->appId() == appId) + jobs.append(QString::number(jobNum)); + } + } + } + // kDebug() << "SpeechData::jobNumbers: appId = " << appId << " priority = " << priority + // << " jobs = " << jobs << endl; + return jobs; +} + +int SpeechData::jobState(int jobNum) const +{ + SpeechJob* job = findJobByJobNum(jobNum); + int temp; + if (job) + temp = (int)job->state(); + else + temp = -1; + return temp; +} + +QByteArray SpeechData::jobInfo(int jobNum) +{ + SpeechJob* job = findJobByJobNum(jobNum); + if (job) { + waitJobFiltering(job); + QByteArray temp = job->serialize(); + QDataStream stream(&temp, QIODevice::Append); + stream << getAppData(job->appId())->applicationName(); + return temp; + } else { + kDebug() << "SpeechData::jobInfo: request for job info on non-existent jobNum = " << jobNum << endl; + return QByteArray(); + } +} + +QString SpeechData::jobSentence(int jobNum, int seq) +{ + SpeechJob* job = findJobByJobNum(jobNum); + if (job) { + waitJobFiltering(job); + if (seq <= job->sentenceCount()) + return job->sentences()[seq - 1]; + else + return QString(); + } else + return QString(); +} + +void SpeechData::setTalker(int jobNum, const QString &talker) +{ + SpeechJob* job = findJobByJobNum(jobNum); + if (job) job->setTalker(talker); +} + +void SpeechData::moveJobLater(int jobNum) +{ + // kDebug() << "Running: SpeechData::moveTextLater" << endl; + if (d->allJobs.contains(jobNum)) { + KSpeech::JobPriority priority = d->allJobs[jobNum]->jobPriority(); + TJobListPtr jobList = d->jobLists[priority]; + // Get index of the job. + uint index = jobList->indexOf(jobNum); + // Move job down one position in the queue. + // kDebug() << "In SpeechData::moveTextLater, moving jobNum " << movedJobNum << endl; + jobList->insert(index + 2, jobNum); + jobList->takeAt(index); + } +} + +int SpeechData::moveRelSentence(int jobNum, int n) +{ + // kDebug() << "Running: SpeechData::moveRelTextSentence" << endl; + int newSeqNum = 0; + SpeechJob* job = findJobByJobNum(jobNum); + if (job) { + waitJobFiltering(job); + int oldSeqNum = job->seq(); + newSeqNum = oldSeqNum + n; + if (0 != n) { + if (newSeqNum < 0) newSeqNum = 0; + int sentenceCount = job->sentenceCount() + 1; + if (newSeqNum > sentenceCount) newSeqNum = sentenceCount; + job->setSeq(newSeqNum); + // If job was previously finished, but is now rewound, set state to speakable. + // If job was not finished, but now is past end, set state to finished. + if (KSpeech::jsFinished == job->state()) { + if (newSeqNum < sentenceCount) + job->setState(KSpeech::jsSpeakable); + } else { + if (newSeqNum == sentenceCount) + job->setState(KSpeech::jsFinished); + } + } + } + return newSeqNum; +} + +void SpeechData::startJobFiltering(SpeechJob* job, const QString& text, bool noSBD) +{ + job->setState(KSpeech::jsFiltering); + int jobNum = job->jobNum(); + // kDebug() << "SpeechData::startJobFiltering: jobNum = " << jobNum << " text.left(500) = " << text.left(500) << endl; + PooledFilterMgr* pooledFilterMgr = 0; + if (d->pooledFilterMgrs.contains(jobNum)) return; + // Find an idle FilterMgr, if any. + // If filtering is already in progress for this job, do nothing. + QMultiHash::iterator it = d->pooledFilterMgrs.begin(); + while (it != d->pooledFilterMgrs.end()) { + if (!it.value()->busy) { + if (it.value()->job && it.value()->job->jobNum() == jobNum) + return; + } else { + if (!it.value()->job && !pooledFilterMgr) + pooledFilterMgr = it.value(); + } + ++it; + } + // Create a new FilterMgr if needed and add to pool. + if (!pooledFilterMgr) { + // kDebug() << "SpeechData::startJobFiltering: adding new pooledFilterMgr for job " << jobNum << endl; + pooledFilterMgr = new PooledFilterMgr(); + FilterMgr* filterMgr = new FilterMgr(); + filterMgr->init(); + pooledFilterMgr->filterMgr = filterMgr; + // Connect signals from FilterMgr. + connect (filterMgr, SIGNAL(filteringFinished()), this, SLOT(slotFilterMgrFinished())); + connect (filterMgr, SIGNAL(filteringStopped()), this, SLOT(slotFilterMgrStopped())); + d->pooledFilterMgrs.insert(jobNum, pooledFilterMgr); + } + // else kDebug() << "SpeechData::startJobFiltering: re-using idle pooledFilterMgr for job " << jobNum << endl; + // Flag the FilterMgr as busy and set it going. + pooledFilterMgr->busy = true; + pooledFilterMgr->job = job; + pooledFilterMgr->filterMgr->setNoSBD( noSBD ); + // Get TalkerCode structure of closest matching Talker. + pooledFilterMgr->talkerCode = d->talkerMgr->talkerToTalkerCode(job->talker()); + // Pass Sentence Boundary regular expression. + AppData* appData = getAppData(job->appId()); + pooledFilterMgr->filterMgr->setSbRegExp(appData->sentenceDelimiter()); + kDebug() << "SpeechData::startJobFiltering: job = " << job->jobNum() << " NoSBD = " + << noSBD << " sentenceDelimiter = " << appData->sentenceDelimiter() << endl; + pooledFilterMgr->filterMgr->asyncConvert(text, pooledFilterMgr->talkerCode, appData->applicationName()); +} + +void SpeechData::waitJobFiltering(const SpeechJob* job) +{ + int jobNum = job->jobNum(); + bool waited = false; + bool notOptimum = false; + if (!d->pooledFilterMgrs.contains(jobNum)) return; + QMultiHash::iterator it = d->pooledFilterMgrs.find(jobNum); + while (it != d->pooledFilterMgrs.end() && it.key() == jobNum) { + PooledFilterMgr* pooledFilterMgr = it.value(); + if (pooledFilterMgr->busy) { + if (!pooledFilterMgr->filterMgr->noSBD()) + notOptimum = true; + pooledFilterMgr->filterMgr->waitForFinished(); + waited = true; + } + ++it; + } + if (waited) { + if (notOptimum) + kDebug() << "SpeechData::waitJobFiltering: Waited for filtering to finish on job " + << jobNum << ". Not optimium. " + << "Try waiting for jobStateChanged signal with jsSpeakable before querying for job information." << endl; + doFiltering(); + } +} + +void SpeechData::doFiltering() +{ + // kDebug() << "SpeechData::doFiltering: Running." << endl; + kDebug() << "SpeechData::doFiltering: Scanning " << d->pooledFilterMgrs.count() << " pooled filter managers." << endl; + bool again = true; + bool filterFinished = false; + while (again) { + again = false; + QMultiHash::iterator it = d->pooledFilterMgrs.begin(); + QMultiHash::iterator nextIt; + while (it != d->pooledFilterMgrs.end()) { + nextIt = it; + ++nextIt; + PooledFilterMgr* pooledFilterMgr = it.value(); + // If FilterMgr is busy, see if it is now finished. + Q_ASSERT(pooledFilterMgr); + if (pooledFilterMgr->busy) { + FilterMgr* filterMgr = pooledFilterMgr->filterMgr; + if (FilterMgr::fsFinished == filterMgr->getState()) { + filterFinished = true; + SpeechJob* job = pooledFilterMgr->job; + kDebug() << "SpeechData::doFiltering: filter finished, jobNum = " << job->jobNum() << endl; + pooledFilterMgr->busy = false; + // Retrieve text from FilterMgr. + QString text = filterMgr->getOutput(); + kDebug() << "SpeechData::doFiltering: text.left(500) = " << text.left(500) << endl; + // kDebug() << "SpeechData::doFiltering: filtered text: " << text << endl; + filterMgr->ackFinished(); + // Convert the TalkerCode back into string. + job->setTalker(pooledFilterMgr->talkerCode->getTalkerCode()); + // TalkerCode object no longer needed. + delete pooledFilterMgr->talkerCode; + pooledFilterMgr->talkerCode = 0; + if (filterMgr->noSBD()) { + job->setSentences(QStringList(text)); + } else { + // Split the text into sentences and store in the job. + // The SBD plugin does all the real sentence parsing, inserting tabs at each + // sentence boundary. + QStringList sentences = text.split( "\t", QString::SkipEmptyParts); + job->setSentences(sentences); + } + // Clean up. + pooledFilterMgr->job = 0; + // Re-index pool of FilterMgrs; + d->pooledFilterMgrs.erase(it); + d->pooledFilterMgrs.insert(0, pooledFilterMgr); + // Emit signal. + job->setState(KSpeech::jsSpeakable); + } + else kDebug() << "SpeechData::doFiltering: filter for job " << pooledFilterMgr->job->jobNum() << " is busy." << endl; + } + else kDebug() << "SpeechData::doFiltering: filter is idle" << endl; + it = nextIt; + } + } + if (filterFinished) + emit filteringFinished(); +} + +void SpeechData::pause(const QString& appId) +{ + AppData* appData = getAppData(appId); + if (appData->isSystemManager()) { + foreach (AppData* app, d->appData) + app->setIsApplicationPaused(true); + } else + appData->setIsApplicationPaused(true); +} + +void SpeechData::resume(const QString& appId) +{ + AppData* appData = getAppData(appId); + if (appData->isSystemManager()) { + foreach (AppData* app, d->appData) + app->setIsApplicationPaused(false); + } else + appData->setIsApplicationPaused(false); +} + +bool SpeechData::isApplicationPaused(const QString& appId) +{ + return getAppData(appId)->isApplicationPaused(); +} + +void SpeechData::slotFilterMgrFinished() +{ + // kDebug() << "SpeechData::slotFilterMgrFinished: received signal FilterMgr finished signal." << endl; + doFiltering(); +} + +void SpeechData::slotFilterMgrStopped() +{ + doFiltering(); +} diff --git a/kttsd/kttsd/speechdata.h b/kttsd/kttsd/speechdata.h dissimilarity index 92% index ace7f67c..9b9e7538 100644 --- a/kttsd/kttsd/speechdata.h +++ b/kttsd/kttsd/speechdata.h @@ -1,737 +1,419 @@ -/*************************************************** vim:set ts=4 sw=4 sts=4: - This contains the SpeechData class which is in charge of maintaining - all the data on the memory. - It maintains queues manages the text. - We could say that this is the common repository between the KTTSD class - (dcop service) and the Speaker class (speaker, loads plug ins, call plug in - functions) - ------------------- - Copyright: - (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández - (C) 2003-2004 by Olaf Schmidt - (C) 2004-2006 by Gary Cramblitt - ------------------- - Original author: José Pablo Ezequiel "Pupeno" Fernández - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - ******************************************************************************/ - -#ifndef _SPEECHDATA_H_ -#define _SPEECHDATA_H_ - -// Qt includes. -#include -#include -#include -#include -#include -#include - -// KDE includes. -#include - -// KTTS includes. -#include -#include -#include - -class TalkerMgr; - -/** -* Struct containing a text cell, for messages, warnings, and texts. -* Contains the text itself, the associated talker, -* the ID of the application that requested it be spoken, and a sequence number. -*/ -struct mlText{ - QString talker; /* Requested Talker code for the sentence. */ - QString text; /* Text of sentence. */ - QString appId; /* DBUS senderId of the application that requested the speech. */ - uint jobNum; /* Text jobNum. Only applies to text messages; not warning and messages. */ - uint seq; /* Sequence number. */ -}; - -/** - * Struct containing a text job. - */ -struct mlJob { - uint jobNum; /* Job number. */ - KSpeech::kttsdJobState state; /* Job state. */ - QString appId; /* DBUS senderId of the application that requested the speech job. */ - QString talker; /* Requested Talker code in which to speak the text. */ - int seq; /* Current sentence being spoken. */ - QList partSeqNums; /* List containing last sequence number for each part of a job. */ - QStringList sentences; /* List of sentences in the job. */ - int partCount; /* Number of parts in the job. */ -}; - -/** - * Struct used to keep a pool of FilterMgr objects. - */ -struct PooledFilterMgr { - FilterMgr* filterMgr; /* The FilterMgr object. */ - bool busy; /* True if the FilterMgr is busy. */ - mlJob* job; /* The job the FilterMgr is filtering. */ - int partNum; /* The part number of the job that is filtering. */ - TalkerCode* talkerCode; /* TalkerCode object passed to FilterMgr. */ -}; - -/** - * Struct used to keep notification options. - */ -struct NotifyOptions { - QString eventName; - int action; - QString talker; - QString customMsg; -}; - -/** - * A list of notification options for a single app, indexed by event. - */ -typedef QMap NotifyEventMap; - -/** - * A list of notification event maps for all apps, indexed by app. - */ -typedef QMap NotifyAppMap; - -/** - * SpeechData class which is in charge of maintaining all the data on the memory. - * It maintains queues and has methods to enque - * messages and warnings and manage the text queues. - * We could say that this is the common repository between the KTTSD class - * (dcop service) and the Speaker class (speaker, loads plug ins, call plug in - * functions) - */ -class SpeechData : public QObject { - Q_OBJECT - - public: - /** - * Constructor - * Sets text to be stopped and warnings and messages queues to be autodelete (thread safe) - */ - SpeechData(); - - /** - * Destructor - */ - ~SpeechData(); - - /** - * Read the configuration - */ - bool readConfig(); - - /** - * Say a message as soon as possible, interrupting any other speech in progress. - * IMPORTANT: This method is reserved for use by Screen Readers and should not be used - * by any other applications. - * @param msg The message to be spoken. - * @param talker Code for the talker to speak the message. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - * @param appId The DBUS senderId of the application. - * - * If an existing Screen Reader output is in progress, it is stopped and discarded and - * replaced with this new message. - */ - void setScreenReaderOutput(const QString &msg, const QString &talker, - const QString& appId); - - /** - * Given an appId, returns the last (most recently queued) Job Number with that appId, - * or if no such job, the Job Number of the last (most recent) job in the queue. - * @param appId The DBUS senderId of the application. - * @return Job Number of the text job. - * If no such job, returns 0. - * If appId is NULL, returns the Job Number of the last job in the queue. - * Does not change textJobs.current(). - */ - uint findAJobNumByAppId(const QString& appId); - - /** - * Retrieves the Screen Reader Output. - */ - mlText* getScreenReaderOutput(); - - /** - * Returns true if Screen Reader Output is ready to be spoken. - */ - bool screenReaderOutputReady(); - - /** - * Add a new warning to the queue. - */ - void enqueueWarning( const QString &, const QString &talker, - const QString& appId); - - /** - * Pop (get and erase) a warning from the queue. - * @return Pointer to mlText structure containing the warning. - * - * Caller is responsible for deleting the structure. - */ - mlText* dequeueWarning(); - - /** - * Are there any Warnings? - */ - bool warningInQueue(); - - /** - * Add a new message to the queue. - */ - void enqueueMessage( const QString &, const QString &talker, - const QString&); - - /** - * Pop (get and erase) a message from the queue. - * @return Pointer to mlText structure containing the message. - * - * Caller is responsible for deleting the structure. - */ - mlText* dequeueMessage(); - - /** - * Are there any Messages? - */ - bool messageInQueue(); - - /** - * Sets the GREP pattern that will be used as the sentence delimiter. - * @param delimiter A valid GREP pattern. - * @param appId The DBUS senderId of the application. - * - * The default delimiter is - @verbatim - ([\\.\\?\\!\\:\\;])\\s - @endverbatim - * - * Note that backward slashes must be escaped. - * - * Changing the sentence delimiter does not affect other applications. - * @see sentenceparsing - */ - void setSentenceDelimiter(const QString &delimiter, const QString appId); - - /* The following methods correspond to the methods in KSpeech interface. */ - - /** - * Queue a text job. Does not start speaking the text. - * (thread safe) - * @param text The message to be spoken. - * @param talker Code for the talker to speak the text. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - * @param appId The DBUS senderId of the application. - * @return Job number. - * - * The text is parsed into individual sentences. Call getTextCount to retrieve - * the sentence count. Call startText to mark the job as speakable and if the - * job is the first speakable job in the queue, speaking will begin. - * @see startText. - */ - uint setText(const QString &text, const QString &talker, const QString& appId); - - /** - * Adds another part to a text job. Does not start speaking the text. - * (thread safe) - * @param jobNum Job number of the text job. - * @param text The message to be spoken. - * @return Part number for the added part. Parts are numbered starting at 1. - * - * The text is parsed into individual sentences. Call getTextCount to retrieve - * the sentence count. Call startText to mark the job as speakable and if the - * job is the first speakable job in the queue, speaking will begin. - * @see setText. - * @see startText. - */ - int appendText(const QString &text, const uint jobNum); - - /** - * Get the number of sentences in a text job. - * (thread safe) - * @param jobNum Job number of the text job. - * @return The number of sentences in the job. -1 if no such job. - * - * The sentences of a job are given sequence numbers from 1 to the number returned by this - * method. The sequence numbers are emitted in the sentenceStarted and sentenceFinished signals. - */ - int getTextCount(const uint jobNum); - - /** - * Get the number of jobs in the text job queue. - * (thread safe) - * @return Number of text jobs in the queue. 0 if none. - */ - uint getTextJobCount(); - - /** - * Get a comma-separated list of text job numbers in the queue. - * @return Comma-separated list of text job numbers in the queue. - */ - QString getTextJobNumbers(); - - /** - * Get the state of a text job. - * (thread safe) - * @param jobNum Job number of the text job. - * @return State of the job. -1 if invalid job number. - */ - int getTextJobState(const uint jobNum); - - /** - * Set the state of a text job. - * @param jobNum Job Number of the job. - * @param state New state for the job. - * - **/ - void setTextJobState(const uint jobNum, const KSpeech::kttsdJobState state); - - /** - * Get information about a text job. - * @param jobNum Job number of the text job. - * @return A QDataStream containing information about the job. - * Blank if no such job. - * - * The stream contains the following elements: - * - int state Job state. - * - QString appId DBUS senderId of the application that requested the speech job. - * - QString talker Talker code as requested by application. - * - int seq Current sentence being spoken. Sentences are numbered starting at 1. - * - int sentenceCount Total number of sentences in the job. - * - int partNum Current part of the job begin spoken. Parts are numbered starting at 1. - * - int partCount Total number of parts in the job. - * - * Note that sequence numbers apply to the entire job. - * They do not start from 1 at the beginning of each part. - * - * The following sample code will decode the stream: - @verbatim - QByteArray jobInfo = getTextJobInfo(jobNum); - QDataStream stream(jobInfo, QIODevice::ReadOnly); - int state; - QString appId; - QString talker; - int seq; - int sentenceCount; - int partNum; - int partCount; - stream >> state; - stream >> appId; - stream >> talker; - stream >> seq; - stream >> sentenceCount; - stream >> partNum; - stream >> partCount; - @endverbatim - */ - QByteArray getTextJobInfo(const uint jobNum); - - /** - * Return a sentence of a job. - * @param jobNum Job number of the text job. - * @param seq Sequence number of the sentence. - * @return The specified sentence in the specified job. If no such - * job or sentence, returns "". - */ - QString getTextJobSentence(const uint jobNum, const uint seq=1); - - /** - * Remove a text job from the queue. - * (thread safe) - * @param jobNum Job number of the text job. - * - * The job is deleted from the queue and the textRemoved signal is emitted. - */ - void removeText(const uint jobNum); - - /** - * Change the talker for a text job. - * @param jobNum Job number of the text job. - * @param talker New code for the talker to do speaking. Example "en". - * If NULL, defaults to the user's default talker. - * If no plugin has been configured for the specified Talker code, - * defaults to the closest matching talker. - */ - void changeTextTalker(const QString &talker, uint jobNum); - - /** - * Move a text job down in the queue so that it is spoken later. - * @param jobNum Job number of the text job. - */ - void moveTextLater(const uint jobNum); - - /** - * Jump to the first sentence of a specified part of a text job. - * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. - * @param jobNum Job number of the text job. - * @return Part number of the part actually jumped to. - * - * If partNum is greater than the number of parts in the job, jumps to last part. - * If partNum is 0, does nothing and returns the current part number. - * If no such job, does nothing and returns 0. - * Does not affect the current speaking/not-speaking state of the job. - */ - int jumpToTextPart(const int partNum, const uint jobNum); - - /** - * Advance or rewind N sentences in a text job. - * @param n Number of sentences to advance (positive) or rewind (negative) - * in the job. - * @param jobNum Job number of the text job. - * @return Sequence number of the sentence actually moved to. Sequence numbers - * are numbered starting at 1. - * - * If no such job, does nothing and returns 0. - * If n is zero, returns the current sequence number of the job. - * Does not affect the current speaking/not-speaking state of the job. - */ - uint moveRelTextSentence(const int n, const uint jobNum); - - /** - * Given a jobNum, returns the first job with that jobNum. - * @return Pointer to the text job. - * If no such job, returns 0. - * Does not change textJobs.current(). - */ - mlJob* findJobByJobNum(const uint jobNum); - - /** - * Given a Job Number, returns the next speakable text job on the queue. - * @param prevJobNum Current job number (which should not be returned). - * @return Pointer to mlJob structure of the first speakable job - * not equal prevJobNum. If no such job, returns null. - * - * Caller must not delete the job. - */ - mlJob* getNextSpeakableJob(const uint prevJobNum); - - /** - * Given previous job number and sequence number, returns the next sentence from the - * text queue. If no such sentence is available, either because we've run out of - * jobs, or because all jobs are paused, returns null. - * @param prevJobNum Previous Job Number. - * @param prevSeq Previous sequency number. - * @return Pointer to n mlText structure containing the next sentence. If no - * sentence, returns null. - * - * Caller is responsible for deleting the returned mlText structure (if not null). - */ - mlText* getNextSentenceText(const uint prevJobNum, const uint prevSeq); - - /** - * Given a Job Number, sets the current sequence number of the job. - * @param jobNum Job Number. - * @param seq Sequence number. - * If for some reason, the job does not exist, nothing happens. - */ - void setJobSequenceNum(const uint jobNum, const uint seq); - - /** - * Given a Job Number, returns the current sequence number of the job. - * @param jobNum Job Number. - * @return Sequence number of the job. If no such job, returns 0. - */ - uint getJobSequenceNum(const uint jobNum); - - /** - * Given a jobNum, returns the appId of the application that owns the job. - * @param jobNum Job number of the text job. - * @return appId of the job. - * If no such job, returns "". - * Does not change textJobs.current(). - */ - QString getAppIdByJobNum(const uint jobNum); - - /** - * Sets pointer to the TalkerMgr object. - */ - void setTalkerMgr(TalkerMgr* talkerMgr); - - /* The following properties come from the configuration. */ - - /** - * Text pre message - */ - QString textPreMsg; - - /** - * Text pre message enabled ? - */ - bool textPreMsgEnabled; - - /** - * Text pre sound - */ - QString textPreSnd; - - /** - * Text pre sound enabled ? - */ - bool textPreSndEnabled; - - /** - * Text post message - */ - QString textPostMsg; - - /** - * Text post message enabled ? - */ - bool textPostMsgEnabled; - - /** - * Text post sound - */ - QString textPostSnd; - - /** - * Text post sound enabled ? - */ - bool textPostSndEnabled; - - /** - * Paragraph pre message - */ - QString parPreMsg; - - /** - * Paragraph pre message enabled ? - */ - bool parPreMsgEnabled; - - /** - * Paragraph pre sound - */ - QString parPreSnd; - - /** - * Paragraph pre sound enabled ? - */ - bool parPreSndEnabled; - - /** - * Paragraph post message - */ - QString parPostMsg; - - /** - * Paragraph post message enabled ? - */ - bool parPostMsgEnabled; - - /** - * Paragraph post sound - */ - QString parPostSnd; - - /** - * Paragraph post sound enabled ? - */ - bool parPostSndEnabled; - - /** - * Keep audio files. Do not delete generated tmp wav files. - */ - bool keepAudio; - QString keepAudioPath; - - /** - * Notification settings. - */ - bool notify; - bool notifyExcludeEventsWithSound; - NotifyAppMap notifyAppMap; - int notifyDefaultPresent; - NotifyOptions notifyDefaultOptions; - - /** - * Automatically start KTTSMgr whenever speaking. - */ - bool autoStartManager; - - /** - * Automatically exit auto-started KTTSMgr when speaking finishes. - */ - bool autoExitManager; - - /** - * Configuration - */ - KConfig *config; - - /** - * True if at least one XML Transformer plugin for html is enabled. - */ - bool supportsHTML; - - signals: - /** - * This signal is emitted whenever a new text job is added to the queue. - * @param appId The DBUS senderId of the application that created the job. - * @param jobNum Job number of the text job. - */ - void textSet(const QString& appId, const uint jobNum); - - /** - * This signal is emitted whenever a new part is appended to a text job. - * @param appId The DBUS senderId of the application that created the job. - * @param jobNum Job number of the text job. - * @param partNum Part number of the new part. Parts are numbered starting - * at 1. - */ - void textAppended(const QString& appId, const uint jobNum, const int partNum); - - /** - * This signal is emitted whenever a text job is deleted from the queue. - * The job is no longer in the queue when this signal is emitted. - * @param appId The DBUS senderId of the application that created the job. - * @param jobNum Job number of the text job. - */ - void textRemoved(const QString& appId, const uint jobNum); - - private: - /** - * Screen Reader Output. - */ - mlText screenReaderOutput; - - /** - * Queue of warnings - */ - QQueue warnings; - - /** - * Queue of messages - */ - QQueue messages; - - /** - * Queue of text jobs. - */ - QList textJobs; - - /** - * TalkerMgr object local pointer. - */ - TalkerMgr* m_talkerMgr; - - /** - * Pool of FilterMgrs. - */ - QMultiHash m_pooledFilterMgrs; - - /** - * Job counter. Each new job increments this counter. - */ - uint jobCounter; - - /** - * Talker of the text - */ - QString textTalker; - - /** - * Map of sentence delimiters. One per app. If none specified for an app, uses default. - */ - QMap sentenceDelimiters; - - /** - * Determines whether the given text is SSML markup. - */ - bool isSsml(const QString &text); - - /** - * Given an appId, returns the last (most recently queued) job with that appId. - * @param appId The DBUS senderId of the application. - * @return Pointer to the text job. - * If no such job, returns 0. - * If appId is NULL, returns the last job in the queue. - * Does not change textJobs.current(). - */ - mlJob* findLastJobByAppId(const QString& appId); - - /** - * Given an appId, returns the last (most recently queued) job with that appId, - * or if no such job, the last (most recent) job in the queue. - * @param appId The DBUS senderId of the application. - * @return Pointer to the text job. - * If no such job, returns 0. - * If appId is NULL, returns the last job in the queue. - * Does not change textJobs.current(). - */ - mlJob* findAJobByAppId(const QString& appId); - - /** - * Given a job and a sequence number, returns the part that sentence is in. - * If no such job or sequence number, returns 0. - * @param job The text job. - * @param seq Sequence number of the sentence. Sequence numbers begin with 1. - * @return Part number of the part the sentence is in. Parts are numbered - * beginning with 1. If no such job or sentence, returns 0. - */ - int getJobPartNumFromSeq(const mlJob& job, const int seq); - - /** - * Parses a block of text into sentences using the application-specified regular expression - * or (if not specified), the default regular expression. - * @param text The message to be spoken. - * @param appId The DBUS senderId of the application. - * @return List of parsed sentences. - */ - - QStringList parseText(const QString &text, const QString &appId); - - /** - * Delete expired jobs. At most, one finished job is kept on the queue. - * @param finishedJobNum Job number of a job that just finished - * The just finished job is not deleted, but any other finished jobs are. - * Does not change the textJobs.current() pointer. - */ - void deleteExpiredJobs(const uint finishedJobNum); - - /** - * Assigns a FilterMgr to a job and starts filtering on it. - */ - void startJobFiltering(mlJob* job, const QString& text, bool noSBD); - - /** - * Waits for filtering to be completed on a job. - * This is typically called because an app has requested job info that requires - * filtering to be completed, such as getJobInfo. - */ - void waitJobFiltering(const mlJob* job); - - /** - * Processes filters by looping across the pool of FilterMgrs. - * As each FilterMgr finishes, emits appropriate signals and flags it as no longer busy. - */ - void doFiltering(); - - /** - * Loads notify events from a file. Clearing data if clear is True. - */ - void loadNotifyEventsFromFile( const QString& filename, bool clear); - - private slots: - void slotFilterMgrFinished(); - void slotFilterMgrStopped(); -}; - -#endif // _SPEECHDATA_H_ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This contains the SpeechData class which is in charge of maintaining + all the speech data. + We could say that this is the common repository between the KTTSD class + (dbus service) and the Speaker class (speaker, loads plug ins, call plug in + functions) + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _SPEECHDATA_H_ +#define _SPEECHDATA_H_ + +// Qt includes. +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. +#include +#include + +// KTTSD includes. +#include "speechjob.h" +#include "talkercode.h" +#include "filtermgr.h" +#include "appdata.h" +#include "configdata.h" + +class TalkerMgr; + +/** + * Struct used to keep a pool of FilterMgr objects. + */ +struct PooledFilterMgr { + FilterMgr* filterMgr; /* The FilterMgr object. */ + bool busy; /* True if the FilterMgr is busy. */ + SpeechJob* job; /* The job the FilterMgr is filtering. */ + TalkerCode* talkerCode; /* TalkerCode object passed to FilterMgr. */ +}; + +class SpeechDataPrivate; +class SpeechData : public QObject { + Q_OBJECT + +public: + /** + * Constructor + */ + SpeechData(); + + /** + * Destructor + */ + ~SpeechData(); + + /** + * Get application data. + * If this is a new application, a new AppData object is created and initialized + * with defaults. + * Caller may set properties, but must not delete the returned AppData object. + * Use releaseAppData instead. + * @param appId The DBUS senderId of the application. + */ + AppData* getAppData(const QString& appId) const; + + /** + * Destroys the application data. + */ + void releaseAppData(const QString& appId); + + /** + * Queue and start a speech job. + * @param appId The DBUS senderId of the application. + * @param text The text to be spoken. + * @param options Option flags. @see SayOptions. Defaults to KSpeech::soNone. + * + * Based on the options, the text may contain the text to be spoken, with or withou + * markup, or it may contain characters to be spelled out, or it may contain + * the symbolic name of a keyboard key, or it may contain the name of a sound + * icon. + * + * The job is given the applications current defaultPriority. @see defaultPriority. + * The job is assigned the applications current defaultTalker. @see defaultTalker. + */ + int say(const QString& appId, const QString& text, int sayOptions); + + /** + * Remove a job from the queue. + * @param jobNum Job number. + * + * The job is deleted from the queue and the textRemoved signal is emitted. + */ + void removeJob(int jobNum); + + /** + * Remove all jobs owned by the application. + * @param appId The DBUS senderId of the application. + */ + void removeAllJobs(const QString& appId); + + /** + * Get the number of sentences in a job. + * (thread safe) + * @param jobNum Job number. + * @return The number of sentences in the job. -1 if no such job. + * + * The sentences of a job are given sequence numbers from 1 to the number returned by this + * method. The sequence numbers are emitted in the sentenceStarted and sentenceFinished signals. + * + * If the job is being filtered and split into sentences, waits until that is finished + * before returning. + */ + int sentenceCount(int jobNum); + + /** + * Get the number of jobs in the queue of the specified type owned by + * the application. + * @param appId The DBUS senderId of the application. + * @param priority Type of job. Text, Message, Warning, or ScreenReaderOutput. + * @return Number of jobs in the queue. 0 if none. + * + * A priority of KSpeech::jpAll will return all the job numbers + * owned by the application. + */ + int jobCount(const QString& appId, KSpeech::JobPriority priority) const; + + /** + * Get a list of job numbers in the queue of the + * specified type owned by the application. + * @param appId The DBUS senderId of the application. + * @param priority Type of job. Text, Message, Warning, or ScreenReaderOutput. + * @return List of job numbers in the queue. + */ + QStringList jobNumbers(const QString& appId, KSpeech::JobPriority priority) const; + + /** + * Get the state of a job. + * @param jobNum Job number. + * @return State of the job. -1 if invalid job number. + * + * @see setJobState + */ + int jobState(int jobNum) const; + + /** + * Get information about a job. + * @param jobNum Job number of the job. + * @return A QDataStream containing information about the job. + * Blank if no such job. + * + * The stream contains the following elements: + * - int priority Job Type. + * - int state Job state. + * - QString appId DBUS senderId of the application that requested the speech job. + * - QString talker Talker code as requested by application. + * - int seq Current sentence being spoken. Sentences are numbered starting at 1. + * - int sentenceCount Total number of sentences in the job. + * - QString applicationName Application's friendly name (if provided by app) + * + * If the job is currently filtering, waits for that to finish before returning. + * + * The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, QIODevice::ReadOnly); + qint32 priority; + qint32 state; + QString appId; + QString talker; + qint32 seq; + qint32 sentenceCount; + QString applicationName; + stream >> priority; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + stream >> applicationName; + @endverbatim + */ + QByteArray jobInfo(int jobNum); + + /** + * Return a sentence of a job. + * @param jobNum Job number. + * @param seq Sequence number of the sentence. + * @return The specified sentence in the specified job. If no such + * job or sentence, returns "". + */ + QString jobSentence(int jobNum, int seq=1); + + /** + * Return the talker code for a job. + * @param jobNum Job number of the job. + * + * @see setTalker + */ + QString talker(int jobNum); + + /** + * Change the talker for a job. + * @param jobNum Job number of the job. + * @param talker New code for the talker to do speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * + * @see talker + */ + void setTalker(int jobNum, const QString &talker); + + /** + * Move a job down in the queue so that it is spoken later. + * @param jobNum Job number. + * + * Since there is only one ScreenReaderOutput, this method is meaningless + * for ScreenReaderOutput jobs. + */ + void moveJobLater(int jobNum); + + /** + * Advance or rewind N sentences in a job. + * @param jobNum Job number of the job. + * @param n Number of sentences to advance (positive) or rewind (negative) + * in the job. + * @return Sequence number of the sentence actually moved to. Sequence numbers + * are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + * + * Since ScreenReaderOutput jobs are not split into sentences, this method + * is meaningless for ScreenReaderOutput jobs. + */ + int moveRelSentence(int jobNum, int n); + + /** + * Given an appId, returns the last (most recently queued) Job Number with that appId, + * or if no such job, the Job Number of the last (most recent) job in the queue. + * @param appId The DBUS senderId of the application. + * @return Job Number. + * If no such job, returns 0. + * If appId is NULL, returns the Job Number of the last job in the queue. + * Does not change textJobs.current(). + */ + int findJobNumByAppId(const QString& appId) const; + + /** + * Given a jobNum, returns the first job with that jobNum. + * @return Pointer to the job. + * If no such job, returns 0. + * Does not change textJobs.current(). + */ + SpeechJob* findJobByJobNum(int jobNum) const; + + /** + * Given an appId, returns the last (most recently queued) job with that appId. + * @param appId The DBUS senderId of the application. + * @return Pointer to the job. + * If no such job, returns 0. + * If appId is NULL, returns the last job in the queue. + * Does not change textJobs.current(). + */ + SpeechJob* findLastJobByAppId(const QString& appId) const; + + /** + * Given a Job Type and Job Number, returns the next speakable job on the queue. + * @param priority Type of job. Text, Message, Warning, or ScreenReaderOutput. + * @return Pointer to the speakable job. + * + * Caller must not delete the job. + */ + SpeechJob* getNextSpeakableJob(KSpeech::JobPriority priority); + + /** + * Given a Job Number, returns the current sequence number of the job. + * @param jobNum Job Number. + * @return Sequence number of the job. If no such job, returns 0. + */ + int jobSequenceNum(int jobNum) const; + + /** + * Given a Job Number, sets the current sequence number of the job. + * @param jobNum Job Number. + * @param seq Sequence number. + * If for some reason, the job does not exist, nothing happens. + */ + void setJobSequenceNum(int jobNum, int seq); + + /** + * Given a jobNum, returns the appId of the application that owns the job. + * @param jobNum Job number. + * @return appId of the job. + * If no such job, returns "". + * Does not change textJobs.current(). + */ + QString getAppIdByJobNum(int jobNum) const; + + /** + * Delete expired jobs. At most, one finished job is kept on the queue. + * @param finishedJobNum Job number of a job that just finished + * The just finished job is not deleted, but any other finished jobs are. + * Does not change the textJobs.current() pointer. + */ + void deleteExpiredJobs(int finishedJobNum); + + /** + * Return true if the application is paused. + */ + bool isApplicationPaused(const QString& appId); + + /** + * Pauses the application. + */ + void pause(const QString& appId); + + /** + * Resumes the application. + */ + void resume(const QString& appId); + + /** + * Sets pointer to the TalkerMgr object. + */ + void setTalkerMgr(TalkerMgr* talkerMgr); + + /** + * Sets pointer to the Configuration data object. + */ + void setConfigData(ConfigData* configData); + +Q_SIGNALS: + /** + * Emitted when the state of a job changes. + */ + void jobStateChanged(const QString& appId, int jobNum, KSpeech::JobState state); + + /** + * Emitted when job filtering completes. + */ + void filteringFinished(); + +private slots: + void slotFilterMgrFinished(); + void slotFilterMgrStopped(); + +private: + /** + * Determines whether the given text is SSML markup. + */ + bool isSsml(const QString &text); + + /** + * Parses a block of text into sentences using the application-specified regular expression + * or (if not specified), the default regular expression. + * @param text The message to be spoken. + * @param appId The DBUS senderId of the application. + * @return List of parsed sentences. + */ + + QStringList parseText(const QString &text, const QString &appId); + + /** + * Deletes job, removing it from all queues. + */ + void deleteJob(int removeJobNum); + + /** + * Assigns a FilterMgr to a job and starts filtering on it. + */ + void startJobFiltering(SpeechJob* job, const QString& text, bool noSBD); + + /** + * Waits for filtering to be completed on a job. + * This is typically called because an app has requested job info that requires + * filtering to be completed, such as getJobInfo. + */ + void waitJobFiltering(const SpeechJob* job); + + /** + * Processes filters by looping across the pool of FilterMgrs. + * As each FilterMgr finishes, emits appropriate signals and flags it as no longer busy. + */ + void doFiltering(); + + /** + * Loads notify events from a file. Clearing data if clear is True. + */ + void loadNotifyEventsFromFile( const QString& filename, bool clear); + +private: + SpeechDataPrivate* d; +}; + +#endif // _SPEECHDATA_H_ diff --git a/kttsd/kttsd/speechjob.cpp b/kttsd/kttsd/speechjob.cpp new file mode 100644 index 00000000..bfc4b9ee --- /dev/null +++ b/kttsd/kttsd/speechjob.cpp @@ -0,0 +1,145 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This class contains a single speech job. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include "speechjob.h" + +/* -------------------------------------------------------------------------- */ + +class SpeechJobPrivate +{ +public: + SpeechJobPrivate(KSpeech::JobPriority priority) : + jobNum(0), + appId(), + jobPriority(priority), + talker(), + state(KSpeech::jsQueued), + sentences(), + seq(0) + {}; + ~SpeechJobPrivate() {}; + friend class SpeechJob; + +protected: + /** Job number. */ + int jobNum; + /** DBUS senderId of the application that requested the speech job. */ + QString appId; + /** Priority of Job. Text, Message, Warning, or Screen Reader Output */ + KSpeech::JobPriority jobPriority; + /** Requested Talker code in which to speak the text. */ + QString talker; + /** Job state. */ + KSpeech::JobState state; + /** List of sentences in the job. */ + QStringList sentences; + /** Current sentence being spoken. + The first sentence is at seq 1, so if 0, not speaking. */ + int seq; +}; + +/* -------------------------------------------------------------------------- */ + +SpeechJob::SpeechJob(KSpeech::JobPriority priority) : + d(new SpeechJobPrivate(priority)) +{ +} + +SpeechJob::~SpeechJob() +{ + emit jobStateChanged(d->appId, d->jobNum, KSpeech::jsDeleted); + delete d; +} + +int SpeechJob::jobNum() const { return d->jobNum; } +void SpeechJob::setJobNum(int jobNum) { d->jobNum = jobNum; } + +QString SpeechJob::appId() const { return d->appId; } +void SpeechJob::setAppId(const QString &appId) { d->appId = appId; } + +KSpeech::JobPriority SpeechJob::jobPriority() const { return d->jobPriority; } +void SpeechJob::setJobPriority(KSpeech::JobPriority priority) { d->jobPriority = priority; } + +QString SpeechJob::talker() const { return d->talker; } +void SpeechJob::setTalker(const QString &talker) { d->talker = talker; } + +KSpeech::JobState SpeechJob::state() const { return d->state; } +void SpeechJob::setState(KSpeech::JobState state) +{ + bool emitSignal = (state != d->state); + d->state = state; + if (emitSignal) + emit jobStateChanged(d->appId, d->jobNum, state); +} + +QStringList SpeechJob::sentences() const { return d->sentences; } +void SpeechJob::setSentences(const QStringList &sentences) { d->sentences = sentences; } + +int SpeechJob::sentenceCount() const { return d->sentences.count(); } + +int SpeechJob::seq() const { return d->seq; } +void SpeechJob::setSeq(int seq) { d->seq = seq; } + +QString SpeechJob::getNextSentence() { + int newSeq = d->seq + 1; + if (newSeq > d->sentences.count()) + return QString(); + else { + QString sentence = d->sentences[d->seq]; + d->seq = newSeq; + return sentence; + } +}; + +QByteArray SpeechJob::serialize() const +{ + QByteArray temp; + QDataStream stream(&temp, QIODevice::WriteOnly); + stream << (qint32)d->jobPriority; + stream << (qint32)d->state; + stream << d->appId; + stream << d->talker; + stream << (qint32)d->seq; + stream << (qint32)(d->sentences.count()); + return temp; +} + +/*static*/ +QString SpeechJob::jobStateToStr(KSpeech::JobState state) +{ + switch ( state ) + { + case KSpeech::jsQueued: return "jsQueued"; + case KSpeech::jsFiltering: return "jsFiltering"; + case KSpeech::jsSpeakable: return "jsSpeakable"; + case KSpeech::jsSpeaking: return "jsSpeaking"; + case KSpeech::jsPaused: return "jsPaused"; + case KSpeech::jsInterrupted: return "jsInterrupted"; + case KSpeech::jsFinished: return "jsFinished"; + case KSpeech::jsDeleted: return "jsDeleted"; + } + return QString(); +} + +#include "speechjob.moc" + diff --git a/kttsd/kttsd/speechjob.h b/kttsd/kttsd/speechjob.h new file mode 100644 index 00000000..cda47e38 --- /dev/null +++ b/kttsd/kttsd/speechjob.h @@ -0,0 +1,144 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This class contains a single speech job. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _SPEECHJOB_H_ +#define _SPEECHJOB_H_ + +// Qt includes. +#include +#include +#include +#include + +// KDE includes. +#include + +/** + * @class SpeechJob + * + * Object containing a speech job. + */ + +class SpeechJobPrivate; +class SpeechJob : public QObject { + + Q_OBJECT + + Q_PROPERTY(int jobNum READ jobNum WRITE setJobNum) + Q_PROPERTY(QString appId READ appId WRITE setAppId) + Q_PROPERTY(KSpeech::JobPriority jobPriority READ jobPriority WRITE setJobPriority) + Q_PROPERTY(QString talker READ talker WRITE setTalker) + Q_PROPERTY(KSpeech::JobState state READ state WRITE setState) + Q_PROPERTY(QStringList sentences READ sentences WRITE setSentences) + Q_PROPERTY(int sentenceCount READ sentenceCount) + Q_PROPERTY(int seq READ seq WRITE setSeq) + Q_PROPERTY(QString getNextSentence READ getNextSentence) + Q_PROPERTY(QByteArray serialize READ serialize) + +public: + SpeechJob(KSpeech::JobPriority priority=KSpeech::jpText); + ~SpeechJob(); + + /** Job number. */ + int jobNum() const; + void setJobNum(int jobNum); + /** DBUS senderId of the application that requested the speech job. */ + QString appId() const; + void setAppId(const QString &appId); + /** Type of job. Text, Message, Warning, or Screen Reader Output */ + KSpeech::JobPriority jobPriority() const; + void setJobPriority(KSpeech::JobPriority jobPriority); + /** Requested Talker code in which to speak the text. */ + QString talker() const; + void setTalker(const QString &talker); + /** Job state. */ + KSpeech::JobState state() const; + void setState(KSpeech::JobState state); + /** List of sentences in the job. */ + QStringList sentences() const; + void setSentences(const QStringList &sentences); + /** Count of sentences in the job. */ + int sentenceCount() const; + /** Current sentence being spoken. + The first sentence is at seq 1, so if 0, not speaking. */ + int seq() const; + void setSeq(int seq); + + /** + * Returns the next sentence in the Job. + * Increments seq number. + * If we run out of sentences, returns QString(). + */ + QString getNextSentence(); + + /** + * Converts the job into a byte stream. + * @return A QDataStream containing information about the job. + * Blank if no such job. + * + * The stream contains the following elements: + * - int priority Job Priority + * - int state Job state. + * - QString appId DBUS senderId of the application that requested the speech job. + * - QString talker Language code in which to speak the text. + * - int seq Current sentence being spoken. Sentences are numbered starting at 1. + * - int sentenceCount Total number of sentences in the job. + * + * Note that sequence numbers apply to the entire job. + * They do not start from 1 at the beginning of each part. + * + * The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = serialize(); + QDataStream stream(jobInfo, QIODevice::ReadOnly); + qint32 priority; + qint32 state; + QString appId; + QString talker; + qint32 seq; + qint32 sentenceCount; + stream >> priority; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + @endverbatim + */ + QByteArray serialize() const; + + /** + * Converts a job state enumerator to a displayable string. + * @param state Job state. + * @return Displayable string for job state. + */ + static QString jobStateToStr(KSpeech::JobState state); + +Q_SIGNALS: + void jobStateChanged(const QString& appId, int jobNum, KSpeech::JobState state); + +private: + SpeechJobPrivate* d; +}; + +#endif // _SPEECHJOB_H_ diff --git a/kttsd/kttsd/ssmlconvert.cpp b/kttsd/kttsd/ssmlconvert.cpp index cc538d81..f4d80b8c 100644 --- a/kttsd/kttsd/ssmlconvert.cpp +++ b/kttsd/kttsd/ssmlconvert.cpp @@ -206,7 +206,7 @@ QString SSMLConvert::appropriateTalker(const QString &text) const { bool SSMLConvert::transform(const QString &text, const QString &xsltFilename) { m_xsltFilename = xsltFilename; /// Write @param text to a temporary file. - KTempFile inFile(locateLocal("tmp", "kttsd-"), ".ssml"); + KTempFile inFile(KStandardDirs::locateLocal("tmp", "kttsd-"), ".ssml"); m_inFilename = inFile.file()->fileName(); QTextStream* wstream = inFile.textStream(); if (wstream == 0) { @@ -225,7 +225,7 @@ bool SSMLConvert::transform(const QString &text, const QString &xsltFilename) { #endif // Get a temporary output file name. - KTempFile outFile(locateLocal("tmp", "kttsd-"), ".output"); + KTempFile outFile(KStandardDirs::locateLocal("tmp", "kttsd-"), ".output"); m_outFilename = outFile.file()->fileName(); outFile.close(); // outFile.unlink(); // only activate this if necessary. diff --git a/kttsd/kttsd/talkermgr.cpp b/kttsd/kttsd/talkermgr.cpp index b6faacad..1fb41dd9 100644 --- a/kttsd/kttsd/talkermgr.cpp +++ b/kttsd/kttsd/talkermgr.cpp @@ -28,6 +28,7 @@ #include #include #include + // KTTS includes. #include "pluginconf.h" #include "talkermgr.h" diff --git a/kttsd/kttsd/utt.cpp b/kttsd/kttsd/utt.cpp new file mode 100644 index 00000000..981a7f26 --- /dev/null +++ b/kttsd/kttsd/utt.cpp @@ -0,0 +1,247 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Speaker class. + + This class holds a single utterance for synthesis and playback. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. + +// KDE includes. + +// KTTS includes. +#include +#include + +// KTTSD includes. +#include "speechjob.h" + +// Utt includes. +#include "utt.h" + +class UttPrivate +{ +public: + UttPrivate() : + utType(Utt::utText), + appId(QString()), + job(NULL), + sentence(QString()), + seq(0), + isSsml(false), + state(Utt::usNone), + transformer(NULL), + plugin(NULL), + audioStretcher(NULL), + audioUrl(QString()), + audioPlayer(NULL) {} + + ~UttPrivate() {} + + friend class Utt; + +protected: + Utt::uttType utType; /* The type of utterance (text, msg, screen reader) */ + QString appId; /* The DBUS sender ID of the application for this utterance. */ + SpeechJob* job; /* The job from which utterance came. */ + QString sentence; /* Text of the utterance. */ + int seq; /* Sequence number of the sentence. */ + bool isSsml; /* True if the utterance contains SSML markup. */ + Utt::uttState state; /* Processing state of the utterance. */ + SSMLConvert* transformer; /* XSLT transformer. */ + PlugInProc* plugin; /* The plugin that synthesizes the utterance. */ + Stretcher* audioStretcher; /* Audio stretcher object. Adjusts speed. */ + QString audioUrl; /* Filename containing synthesized audio. Null if + plugin has not yet synthesized the utterance, or if + plugin does not support synthesis. */ + Player* audioPlayer; /* The audio player audibilizing the utterance. Null + if not currently audibilizing or if plugin doesn't + support synthesis. */ +}; + +Utt::Utt() : d(new UttPrivate()){} + +Utt::Utt(uttType utType, + const QString& appId, + SpeechJob* job, + const QString& sentence, + PlugInProc* pluginproc) : + + d(new UttPrivate()) +{ + d->utType = utType; + d->appId = appId; + d->job = job; + d->sentence = sentence; + d->plugin = pluginproc; + if (job) + d->seq = job->seq(); + if (sentence.isEmpty()) + d->isSsml = detectSsml(); + setInitialState(); +} + +/** +* Constructs a sound icon type of utterance. +*/ +Utt::Utt(uttType utType, const QString& appId, const QString& audioUrl) : + d(new UttPrivate()) +{ + d->utType = utType; + d->appId = appId; + d->audioUrl = audioUrl; + setInitialState(); +} + +Utt::~Utt() { delete d; } + +Utt::uttType Utt::utType() const { return d->utType; } +void Utt::setUtType(uttType type) { d->utType = type; } +QString Utt::appId() const { return d->appId; } +void Utt::setAppId(const QString& appId) { d->appId = appId; } +SpeechJob* Utt::job() const { return d->job; } +void Utt::setJob(SpeechJob* job) { d->job = job; } +QString Utt::sentence() const { return d->sentence; } +void Utt::setSentence(const QString& sentence) +{ + d->sentence = sentence; + d->isSsml = detectSsml(); +} +int Utt::seq() const { return d->seq; } +void Utt::setSeq(int seq) { d->seq = seq; } +bool Utt::isSsml() const { return d->isSsml; } +void Utt::setIsSsml(bool isSsml) { d->isSsml = isSsml; } +Utt::uttState Utt::state() const { return d->state; } + +void Utt::setState(uttState state) +{ + bool stateChanged = (state != d->state); + d->state = state; + if (stateChanged && d->job) + switch (state) + { + case usNone: + case usWaitingTransform: + case usTransforming: + case usWaitingSay: + case usWaitingSynth: + break; + case usSaying: + d->job->setState(KSpeech::jsSpeaking); + break; + case usSynthing: + case usSynthed: + case usStretching: + case usStretched: + break; + case usPlaying: + d->job->setState(KSpeech::jsSpeaking); + break; + case usPaused: + d->job->setState(KSpeech::jsPaused); + break; + case usPreempted: + d->job->setState(KSpeech::jsInterrupted); + break; + case usFinished: + if (d->seq == d->job->sentenceCount()) + d->job->setState(KSpeech::jsFinished); + break; + }; +} + +SSMLConvert* Utt::transformer() const { return d->transformer; } +void Utt::setTransformer(SSMLConvert* transformer) { d->transformer = transformer; } +PlugInProc* Utt::plugin() const { return d->plugin; } +void Utt::setPlugin(PlugInProc* plugin) { d->plugin = plugin; } +Stretcher* Utt::audioStretcher() const { return d->audioStretcher; } +void Utt::setAudioStretcher(Stretcher* stretcher) { d->audioStretcher = stretcher; } +QString Utt::audioUrl() const { return d->audioUrl; } +void Utt::setAudioUrl(const QString& audioUrl) { d->audioUrl = audioUrl; } + +Player* Utt::audioPlayer() const { return d->audioPlayer; } +void Utt::setAudioPlayer(Player* player) { d->audioPlayer = player; } + +bool Utt::detectSsml() +{ + if (d->sentence.isEmpty()) + return false; + else + return KttsUtils::hasRootElement( d->sentence, "speak" ); +} + +void Utt::setInitialState() +{ + if ((d->state != usTransforming) && d->isSsml) + { + d->state = usWaitingTransform; + return; + } + if (d->plugin) { + if (d->plugin->supportsSynth()) + d->state = usWaitingSynth; + else + d->state = usWaitingSay; + } else { + if (!d->audioUrl.isEmpty() && (utInterruptSnd == d->utType || utResumeSnd == d->utType)) + d->state = usStretched; + } +} + +/*static*/ +QString Utt::uttTypeToStr(uttType utType) +{ + switch (utType) + { + case utText: return "utText"; + case utInterruptMsg: return "utInterruptMsg"; + case utInterruptSnd: return "utInterruptSnd"; + case utResumeMsg: return "utResumeMsg"; + case utResumeSnd: return "utResumeSnd"; + case utMessage: return "utMessage"; + case utWarning: return "utWarning"; + case utScreenReader: return "utScreenReader"; + } + return QString(); +} + +/*static*/ +QString Utt::uttStateToStr(uttState state) +{ + switch (state) + { + case usNone: return "usNone"; + case usWaitingTransform: return "usWaitingTransform"; + case usTransforming: return "usTransforming"; + case usWaitingSay: return "usWaitingSay"; + case usWaitingSynth: return "usWaitingSynth"; + case usSaying: return "usSaying"; + case usSynthing: return "usSynthing"; + case usSynthed: return "usSynthed"; + case usStretching: return "usStretching"; + case usStretched: return "usStretched"; + case usPlaying: return "usPlaying"; + case usPaused: return "usPaused"; + case usPreempted: return "usPreempted"; + case usFinished: return "usFinished"; + } + return QString(); +} diff --git a/kttsd/kttsd/utt.h b/kttsd/kttsd/utt.h new file mode 100644 index 00000000..c136aa53 --- /dev/null +++ b/kttsd/kttsd/utt.h @@ -0,0 +1,214 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Speaker class. + + This class holds a single utterance for synthesis and playback. + ------------------- + Copyright: + (C) 2006 by Gary Cramblitt + ------------------- + Original author: Gary Cramblitt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _UTT_H_ +#define _UTT_H_ + +// Qt includes. +#include + +class SSMLConvert; +class PlugInProc; +class Player; +class SpeechJob; +class Stretcher; +class SpeechJob; + +class UttPrivate; +class Utt +{ +public: + /** + * Type of utterance. + */ + enum uttType + { + utText, /**< Text */ + utInterruptMsg, /**< Interruption text message */ + utInterruptSnd, /**< Interruption sound file */ + utResumeMsg, /**< Resume text message */ + utResumeSnd, /**< Resume sound file */ + utMessage, /**< Message */ + utWarning, /**< Warning */ + utScreenReader, /**< Screen Reader Output */ + }; + + /** + * Processing state of an utterance. + */ + enum uttState + { + usNone, /**< Null state. Brand new utterance. */ + usWaitingTransform, /**< Waiting to be transformed (XSLT) */ + usTransforming, /**< Transforming the utterance (XSLT). */ + usWaitingSay, /**< Waiting to start synthesis. */ + usWaitingSynth, /**< Waiting to be synthesized and audibilized. */ + usSaying, /**< Plugin is synthesizing and audibilizing. */ + usSynthing, /**< Plugin is synthesizing only. */ + usSynthed, /**< Plugin has finished synthesizing. Ready for stretch. */ + usStretching, /**< Adjusting speed. */ + usStretched, /**< Speed adjustment finished. Ready for playback. */ + usPlaying, /**< Playing on Audio Player. */ + usPaused, /**< Paused due to user action. */ + usPreempted, /**< Paused due to Screen Reader Output. */ + usFinished /**< Ready for deletion. */ + }; + + /** + * Constructs an empty utterance. + */ + Utt(); + + /** + * Constructs a message type of utterance that must be (optionally) + * transformed from SSML, synthesized, and played. + */ + Utt(uttType utType, + const QString& appId, + SpeechJob* job, + const QString& sentence, + PlugInProc* pluginproc); + + /** + * Constructs a sound icon type of utterance. + */ + Utt(uttType utType, const QString& appId, const QString& audioUrl); + + /** + * Destructor. + */ + ~Utt(); + + /** + * Type of utterance. + */ + uttType utType() const; + void setUtType(uttType type); + + /** + * AppId of utterance. + */ + QString appId() const; + void setAppId(const QString& appId); + + /** + * The job from which the utterance came. + */ + SpeechJob* job() const; + void setJob(SpeechJob* job); + + /** + * The text of the utterance. + */ + QString sentence() const; + void setSentence(const QString& sentence); + + /** + * Sequence number of the sentence. They start at 1. + */ + int seq() const; + void setSeq(int seq); + + /** + * True if the sentence contains SSML markup. + */ + bool isSsml() const; + void setIsSsml(bool isSsml); + + /** + * Processing state of the utterance. + */ + uttState state() const; + void setState(uttState state); + + /** + * XSLT Transformer. + */ + SSMLConvert* transformer() const; + void setTransformer(SSMLConvert* transformer); + + /** + * Synthesis plugin. + */ + PlugInProc* plugin() const; + void setPlugin(PlugInProc* plugin); + + /** + * Audio Stretcher. + */ + Stretcher* audioStretcher() const; + void setAudioStretcher(Stretcher* stretcher); + + /** + * Filename containing synthesized audio. Null if + * plugin has not yet synthesized the utterance, or if + * plugin does not support synthesis. */ + QString audioUrl() const; + void setAudioUrl(const QString& audioUrl); + + /** + * The audio player audibilizing the utterance. Null + * if not currently audibilizing or if plugin doesn't + * support synthesis. + */ + Player* audioPlayer() const; + void setAudioPlayer(Player* player); + + /** + * Determines the initial state of an utterance. If the utterance contains + * SSML, the state is set to usWaitingTransform. Otherwise, if the plugin + * supports async synthesis, sets to usWaitingSynth, otherwise usWaitingSay. + * If an utterance has already been transformed, usWaitingTransform is + * skipped to either usWaitingSynth or usWaitingSay. + */ + void setInitialState(); + + /** + * Converts an utterance type enumerator to a displayable string. + * Note this is not a translated string. + * @param utType Utterance type. + * @return Displayable string for utterance type. + */ + static QString uttTypeToStr(uttType utType); + + /** + * Converts an utterance state enumerator to a displayable string. + * Note this is not a translated string. + * @param state Utterance state. + */ + static QString uttStateToStr(uttState state); + + +private: + /** + * Determines whether the sentence contains SSML markup. + */ + bool detectSsml(); + +private: + UttPrivate* d; +}; + +#endif diff --git a/kttsd/kttsjobmgr/jobinfolistmodel.cpp b/kttsd/kttsjobmgr/jobinfolistmodel.cpp index 3dfdbeed..d3f86a95 100644 --- a/kttsd/kttsjobmgr/jobinfolistmodel.cpp +++ b/kttsd/kttsjobmgr/jobinfolistmodel.cpp @@ -26,11 +26,11 @@ // Qt includes. // KDE includes. -#include "klocale.h" -#include "kdebug.h" +#include +#include +#include // KTTS includes. -#include "kspeechdef.h" // JobInfoListModel includes. #include "jobinfolistmodel.h" @@ -95,13 +95,12 @@ QVariant JobInfoListModel::dataColumn(const JobInfo& job, int column) const switch (column) { case 0: return job.jobNum; break; - case 1: return job.appId; break; - case 2: return job.talkerID; break; - case 3: return stateToStr(job.state); break; - case 4: return job.sentenceNum; break; - case 5: return job.sentenceCount; break; - case 6: return job.partNum; break; - case 7: return job.partCount; break; + case 1: return job.applicationName; break; + case 2: return priorityToStr(job.priority); break; + case 3: return job.talkerID; break; + case 4: return stateToStr(job.state); break; + case 5: return job.sentenceNum; break; + case 6: return job.sentenceCount; break; } return QVariant(); } @@ -121,12 +120,11 @@ QVariant JobInfoListModel::headerData(int section, Qt::Orientation orientation, { case 0: return i18n("Job Num"); case 1: return i18n("Owner"); - case 2: return i18n("Talker ID"); - case 3: return i18n("State"); - case 4: return i18n("Position"); - case 5: return i18n("Sentences"); - case 6: return i18n("Part Num"); - case 7: return i18n("Parts"); + case 2: return i18n("Priority"); + case 3: return i18n("Talker ID"); + case 4: return i18n("State"); + case 5: return i18n("Position"); + case 6: return i18n("Sentences"); }; return QVariant(); @@ -180,7 +178,7 @@ void JobInfoListModel::clear() emit reset(); } -QModelIndex JobInfoListModel::jobNumToIndex(uint jobNum) +QModelIndex JobInfoListModel::jobNumToIndex(int jobNum) { for (int row = 0; row < m_jobs.count(); ++row) if (getRow(row).jobNum == jobNum) @@ -198,11 +196,31 @@ QString JobInfoListModel::stateToStr(int state) const switch( state ) { case KSpeech::jsQueued: return i18n("Queued"); + case KSpeech::jsFiltering: return i18n("Filtering"); case KSpeech::jsSpeakable: return i18n("Waiting"); case KSpeech::jsSpeaking: return i18n("Speaking"); case KSpeech::jsPaused: return i18n("Paused"); + case KSpeech::jsInterrupted: return i18n("Interrupted"); case KSpeech::jsFinished: return i18n("Finished"); default: return i18n("Unknown"); } } +/** +* Convert a KTTSD job priority into a display string. +* @param priority KTTSD job priority. +* @return Display string for priority. +*/ +QString JobInfoListModel::priorityToStr(int priority) const +{ + switch (priority) + { + case KSpeech::jpAll: return i18n("All"); + case KSpeech::jpScreenReaderOutput: return i18n("Screen Reader"); + case KSpeech::jpWarning: return i18n("Warning"); + case KSpeech::jpMessage: return i18n("Message"); + case KSpeech::jpText: return i18n("Text"); + default: return i18n("Unknown"); + } +} + diff --git a/kttsd/kttsjobmgr/jobinfolistmodel.h b/kttsd/kttsjobmgr/jobinfolistmodel.h index cb86d8ab..1e56da6f 100644 --- a/kttsd/kttsjobmgr/jobinfolistmodel.h +++ b/kttsd/kttsjobmgr/jobinfolistmodel.h @@ -37,14 +37,14 @@ class JobInfo { public: - uint jobNum; - QString appId; + int jobNum; + QString applicationName; + int priority; QString talkerID; int state; int sentenceNum; int sentenceCount; - int partNum; - int partCount; + QString appId; }; typedef QList JobInfoList; @@ -101,13 +101,19 @@ public: /** * Returns a QModelIndex to the row corresponding to a job number. */ - QModelIndex jobNumToIndex(uint jobNum); + QModelIndex jobNumToIndex(int jobNum); /** * Convert a KTTSD job state integer into a display string. * @param state KTTSD job state * @return Display string for the state. */ QString stateToStr(int state) const; + /** + * Convert a KTTSD job priority into a display string. + * @param priority KTTSD job priority. + * @return Display string for priority. + */ + QString priorityToStr(int priority) const; private: // Returns the displayable portion of the job corresponding to a column of the view. diff --git a/kttsd/kttsjobmgr/kttsjobmgr.cpp b/kttsd/kttsjobmgr/kttsjobmgr.cpp index 9db0c639..cf726ceb 100644 --- a/kttsd/kttsjobmgr/kttsjobmgr.cpp +++ b/kttsd/kttsjobmgr/kttsjobmgr.cpp @@ -30,6 +30,7 @@ #include #include #include +#include // KDE includes. #include @@ -44,10 +45,9 @@ #include #include #include -#include +#include // KTTS includes. -#include "kspeechdef.h" #include "talkercode.h" #include "selecttalkerdlg.h" #include "jobinfolistmodel.h" @@ -66,12 +66,17 @@ KAboutData* KttsJobMgrPart::createAboutData() } KttsJobMgrPart::KttsJobMgrPart(QWidget *parentWidget, QObject *parent, const QStringList& args) : - KParts::ReadOnlyPart(parent), - m_kspeech(QDBus::sessionBus().findInterface("org.kde.kttsd", "/org/kde/KSpeech")) + KParts::ReadOnlyPart(parent) +// m_kspeech(QDBus::sessionBus().findInterface("org.kde.kttsd", "/KSpeech")) { //DBusAbstractInterfacePrivate Q_UNUSED(args); + m_kspeech = new OrgKdeKSpeechInterface("org.kde.kttsd", "/KSpeech", QDBus::sessionBus()); m_kspeech->setParent(this); + + // Establish ourself as a System Manager. + m_kspeech->setApplicationName("kcmkttsmgr"); + m_kspeech->setIsSystemManager(true); // Initialize some variables. m_selectOnTextSet = false; @@ -147,8 +152,6 @@ KttsJobMgrPart::KttsJobMgrPart(QWidget *parentWidget, QObject *parent, const QSt // All the buttons with "job_" at start of their names will be enabled/disabled when a job is // selected in the Job List View. - // All the buttons with "part_" at the start of their names will be enabled/disabled when a - // job is selected in the Job List View that has multiple parts. QPushButton* btn; QString wt; @@ -190,12 +193,6 @@ KttsJobMgrPart::KttsJobMgrPart(QWidget *parentWidget, QObject *parent, const QSt btn->setWhatsThis(wt); connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_move())); - btn = new QPushButton(KIcon("2leftarrow"), i18n("Pre&vious Part"), hbox2); - btn->setObjectName("part_prevpart"); - wt = i18n( - "

Rewinds a multi-part job to the previous part.

"); - btn->setWhatsThis(wt); - connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_prev_par())); btn = new QPushButton(KIcon("1leftarrow"), i18n("&Previous Sentence"), hbox2); btn->setObjectName("job_prevsentence"); wt = i18n( @@ -208,12 +205,6 @@ KttsJobMgrPart::KttsJobMgrPart(QWidget *parentWidget, QObject *parent, const QSt "

Advances a job to the next sentence.

"); btn->setWhatsThis(wt); connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_next_sen())); - btn = new QPushButton(KIcon("2rightarrow"), i18n("Ne&xt Part"), hbox2); - btn->setObjectName("part_nextpart"); - wt = i18n( - "

Advances a multi-part job to the next part.

"); - btn->setWhatsThis(wt); - connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_next_par())); btn = new QPushButton(KIcon("klipper"), i18n("&Speak Clipboard"), hbox3); btn->setObjectName("speak_clipboard"); @@ -247,7 +238,6 @@ KttsJobMgrPart::KttsJobMgrPart(QWidget *parentWidget, QObject *parent, const QSt // Disable job buttons until a job is selected. enableJobActions(false); - enableJobPartActions(false); // Create a VBox for the current sentence and sentence label. KVBox* sentenceVBox = new KVBox(bottomBox); @@ -282,26 +272,10 @@ KttsJobMgrPart::KttsJobMgrPart(QWidget *parentWidget, QObject *parent, const QSt // Connect DBUS Signals emitted by KTTSD to our own slots. connect(m_kspeech, SIGNAL(kttsdStarted()), this, SLOT(kttsdStarted())); - connect(m_kspeech, SIGNAL(markerSeen(QString&,QString&)), - this, SLOT(markerSeen(QString&,QString&))); - connect(m_kspeech, SIGNAL(sentenceStarted(QString&,uint,uint)), - this, SLOT(sentenceStarted(QString&,uint,uint))); - connect(m_kspeech, SIGNAL(sentenceFinished(QString&,uint,uint)), - this, SLOT(sentenceFinished(QString&,uint,uint))); - connect(m_kspeech, SIGNAL(textSet(QString&,uint)), - this, SLOT(textSet(QString&,uint))); - connect(m_kspeech, SIGNAL(textStarted(QString&,uint)), - this, SLOT(textStarted(QString&,uint))); - connect(m_kspeech, SIGNAL(textFinished(QString&,uint)), - this, SLOT(textFinished(QString&,uint))); - connect(m_kspeech, SIGNAL(textStopped(QString&,uint)), - this, SLOT(textStopped(QString&,uint))); - connect(m_kspeech, SIGNAL(textPaused(QString&,uint)), - this, SLOT(textPaused(QString&,uint))); - connect(m_kspeech, SIGNAL(textResumed(QString&,uint)), - this, SLOT(textResumed(QString&,uint))); - connect(m_kspeech, SIGNAL(textRemoved(QString&,uint)), - this, SLOT(textRemoved(QString&,uint))); + connect(m_kspeech, SIGNAL(jobStateChanged(const QString&, int, int)), + this, SLOT(jobStateChanged(const QString&, int, int))); + connect(m_kspeech, SIGNAL(marker(const QString&, int, int, const QString&)), + this, SLOT(marker(const QString&, int, int, const QString&))); m_extension = new KttsJobMgrBrowserExtension(this); @@ -337,7 +311,6 @@ void KttsJobMgrPart::slot_jobListView_clicked() { // Enable job buttons. enableJobActions(true); - enableJobPartActions((getCurrentJobPartCount() > 1)); } /** @@ -345,95 +318,62 @@ void KttsJobMgrPart::slot_jobListView_clicked() */ void KttsJobMgrPart::slot_job_hold() { - uint jobNum = getCurrentJobNum(); - if (jobNum) - { - m_kspeech->pauseText(jobNum); - refreshJob(jobNum); - } + m_kspeech->pause(); } void KttsJobMgrPart::slot_job_resume() { - uint jobNum = getCurrentJobNum(); - if (jobNum) - { - m_kspeech->resumeText(jobNum); - refreshJob(jobNum); - } + m_kspeech->resume(); } void KttsJobMgrPart::slot_job_restart() { - uint jobNum = getCurrentJobNum(); + int jobNum = getCurrentJobNum(); // kDebug() << "KttsJobMgrPart::slot_job_restart: jobNum = " << jobNum << endl; if (jobNum) { - m_kspeech->startText(jobNum); - refreshJob(jobNum); - } -} - -void KttsJobMgrPart::slot_job_prev_par() -{ - uint jobNum = getCurrentJobNum(); - if (jobNum) - { - // Get current part number. - uint partNum = m_kspeech->jumpToTextPart(0, jobNum); - if (partNum > 1) m_kspeech->jumpToTextPart(--partNum, jobNum); + int seq = m_kspeech->moveRelSentence(jobNum, 0); + m_kspeech->moveRelSentence(jobNum, -seq); refreshJob(jobNum); } } void KttsJobMgrPart::slot_job_prev_sen() { - uint jobNum = getCurrentJobNum(); + int jobNum = getCurrentJobNum(); if (jobNum) { - m_kspeech->moveRelTextSentence(-1, jobNum); + m_kspeech->moveRelSentence(jobNum, -1); refreshJob(jobNum); } } void KttsJobMgrPart::slot_job_next_sen() { - uint jobNum = getCurrentJobNum(); - if (jobNum) - { - m_kspeech->moveRelTextSentence(1, jobNum); - refreshJob(jobNum); - } -} - -void KttsJobMgrPart::slot_job_next_par() -{ - uint jobNum = getCurrentJobNum(); + int jobNum = getCurrentJobNum(); if (jobNum) { - // Get current part number. - uint partNum = m_kspeech->jumpToTextPart(0, jobNum); - m_kspeech->jumpToTextPart(++partNum, jobNum); + m_kspeech->moveRelSentence(jobNum, 1); refreshJob(jobNum); } } void KttsJobMgrPart::slot_job_remove() { - uint jobNum = getCurrentJobNum(); + int jobNum = getCurrentJobNum(); if (jobNum) { - m_kspeech->removeText(jobNum); + m_kspeech->removeJob(jobNum); m_currentSentence->clear(); } } void KttsJobMgrPart::slot_job_move() { - uint jobNum = getCurrentJobNum(); + int jobNum = getCurrentJobNum(); if (jobNum) { - m_kspeech->moveTextLater(jobNum); + m_kspeech->moveJobLater(jobNum); refreshJobList(); // Select the job we just moved. QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); @@ -461,7 +401,7 @@ void KttsJobMgrPart::slot_job_change_talker() if (dlgResult != KDialog::Accepted) return; talkerCode = dlg.getSelectedTalkerCode(); int jobNum = job.jobNum; - m_kspeech->changeTextTalker(talkerCode, jobNum); + m_kspeech->changeJobTalker(jobNum, talkerCode); refreshJob(jobNum); } } @@ -475,32 +415,36 @@ void KttsJobMgrPart::slot_speak_clipboard() // Copy text from the clipboard. QString text; + KSpeech::SayOptions sayOptions = KSpeech::soNone; const QMimeData* data = cb->mimeData(); if (data) { if (data->hasFormat("text/html")) { - if (m_kspeech->supportsMarkup(NULL, KSpeech::mtHtml)) + // if (m_kspeech->supportsMarkup(NULL, KSpeech::mtHtml)) text = data->html(); + sayOptions = KSpeech::soHtml; } if (data->hasFormat("text/ssml")) { - if (m_kspeech->supportsMarkup(NULL, KSpeech::mtSsml)) + // if (m_kspeech->supportsMarkup(NULL, KSpeech::mtSsml)) { QByteArray d = data->data("text/ssml"); text = QString(d); + sayOptions = KSpeech::soSsml; } } } - if (text.isEmpty()) + if (text.isEmpty()) { text = cb->text(); + sayOptions = KSpeech::soPlainText; + } // Speak it. if ( !text.isEmpty() ) { - uint jobNum = m_kspeech->setText(text, ""); + m_kspeech->say(text, sayOptions); // kDebug() << "KttsJobMgrPart::slot_speak_clipboard: started jobNum " << jobNum << endl; - m_kspeech->startText(jobNum); // Set flag so that the job we just created will be selected when textSet signal is received. m_selectOnTextSet = true; } @@ -514,7 +458,7 @@ void KttsJobMgrPart::slot_speak_file() { // kDebug() << "KttsJobMgr::slot_speak_file: calling setFile with filename " << // result.fileNames[0] << " and encoding " << result.encoding << endl; - m_kspeech->setFile(result.fileNames[0], NULL, result.encoding); + m_kspeech->sayFile(result.fileNames[0], result.encoding); } } @@ -523,7 +467,7 @@ void KttsJobMgrPart::slot_refresh() // Clear TalkerID cache. m_talkerCodesToTalkerIDs.clear(); // Get current job number. - uint jobNum = getCurrentJobNum(); + int jobNum = getCurrentJobNum(); refreshJobList(); // Select the previously-selected job. if (jobNum) @@ -543,9 +487,9 @@ void KttsJobMgrPart::slot_refresh() * @return Job Number of currently-selected job. * 0 if no currently-selected job. */ -uint KttsJobMgrPart::getCurrentJobNum() +int KttsJobMgrPart::getCurrentJobNum() { - uint jobNum = 0; + int jobNum = 0; QModelIndex index = m_jobListView->currentIndex(); if (index.isValid()) jobNum = m_jobListModel->getRow(index.row()).jobNum; @@ -553,44 +497,52 @@ uint KttsJobMgrPart::getCurrentJobNum() } /** -* Get the number of parts in the currently-selected job in the Job List View. -* @return Number of parts in currently-selected job. -* 0 if no currently-selected job. +* Retrieves JobInfo from KTTSD, creates and fills JobInfo object. +* @param jobNum Job Number. */ -int KttsJobMgrPart::getCurrentJobPartCount() +JobInfo* KttsJobMgrPart::retrieveJobInfo(int jobNum) { - int partCount = 0; - QModelIndex index = m_jobListView->currentIndex(); - if (index.isValid()) - { - JobInfo job = m_jobListModel->getRow(index.row()); - partCount = job.partCount; - } - return partCount; + QByteArray jobInfo = m_kspeech->getJobInfo(jobNum); + if (jobInfo != QByteArray()) { + JobInfo* job = new JobInfo(); + QDataStream stream(&jobInfo, QIODevice::ReadOnly); + qint32 priority; + qint32 state; + QString talkerCode; + qint32 sentenceNum; + qint32 sentenceCount; + stream >> priority; + stream >> state; + stream >> job->appId; + stream >> talkerCode; + stream >> sentenceNum; + stream >> sentenceCount; + stream >> job->applicationName; + job->jobNum = jobNum; + job->priority = priority; + job->state = state; + job->sentenceNum = sentenceNum; + job->sentenceCount = sentenceCount; + job->talkerID = cachedTalkerCodeToTalkerID(talkerCode); + return job; + }; + return NULL; } /** * Refresh display of a single job in the JobListView. * @param jobNum Job Number. */ -void KttsJobMgrPart::refreshJob(uint jobNum) +void KttsJobMgrPart::refreshJob(int jobNum) { - QByteArray jobInfo = m_kspeech->getTextJobInfo(jobNum); - QDataStream stream(&jobInfo, QIODevice::ReadOnly); - JobInfo job; - QString talkerCode; - job.jobNum = jobNum; - stream >> job.state; - stream >> job.appId; - stream >> talkerCode; - stream >> job.sentenceNum; - stream >> job.sentenceCount; - stream >> job.partNum; - stream >> job.partCount; - job.talkerID = cachedTalkerCodeToTalkerID(talkerCode); QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - m_jobListModel->updateRow(index.row(), job); + if (index.isValid()) { + JobInfo* job = retrieveJobInfo(jobNum); + if (job) + m_jobListModel->updateRow(index.row(), *job); + else + m_jobListModel->removeRow(index.row()); + } } /** @@ -602,29 +554,16 @@ void KttsJobMgrPart::refreshJobList() m_jobListModel->clear(); JobInfoList jobInfoList; enableJobActions(false); - enableJobPartActions(false); - QString jobNumbers = m_kspeech->getTextJobNumbers(); - // kDebug() << "jobNumbers: " << jobNumbers << endl; - QStringList jobNums = jobNumbers.split(",", QString::SkipEmptyParts); + QStringList jobNums = m_kspeech->getJobNumbers(KSpeech::jpAll); for (int ndx = 0; ndx < jobNums.count(); ++ndx) { QString jobNumStr = jobNums[ndx]; - // kDebug() << "jobNumStr: " << jobNumStr << endl; - uint jobNum = jobNumStr.toUInt(0, 10); - QByteArray jobInfo = m_kspeech->getTextJobInfo(jobNum); - QDataStream stream(&jobInfo, QIODevice::ReadOnly); - JobInfo job; - job.jobNum = jobNum; - QString talkerCode; - stream >> job.state; - stream >> job.appId; - stream >> talkerCode; - stream >> job.sentenceNum; - stream >> job.sentenceCount; - stream >> job.partNum; - stream >> job.partCount; - job.talkerID = cachedTalkerCodeToTalkerID(talkerCode); - jobInfoList.append(job); + kDebug() << "jobNumStr = " << jobNumStr << endl; + int jobNum = jobNumStr.toInt(0, 10); + kDebug() << "jobNum = " << jobNum << endl; + JobInfo* job = retrieveJobInfo(jobNum); + if (job) + jobInfoList.append(*job); } m_jobListModel->setDatastore(jobInfoList); } @@ -640,10 +579,7 @@ void KttsJobMgrPart::autoSelectInJobListView() // If empty, disable job buttons. if (m_jobListModel->rowCount() == 0) - { enableJobActions(false); - enableJobPartActions(false); - } else { // Select first item. @@ -665,8 +601,9 @@ QString KttsJobMgrPart::cachedTalkerCodeToTalkerID(const QString& talkerCode) else { // Otherwise, retrieve Talker ID from KTTSD and cache it. - QString talkerID = m_kspeech->talkerCodeToTalkerId(talkerCode); + QString talkerID = m_kspeech->talkerToTalkerId(talkerCode); m_talkerCodesToTalkerIDs[talkerCode] = talkerID; + // kdDebug() << "KttsJobMgrPart::cachedTalkerCodeToTalkerID: talkerCode = " << talkerCode << " talkerID = " << talkerID << endl; return talkerID; } } @@ -710,232 +647,117 @@ void KttsJobMgrPart::enableJobActions(bool enable) } } -/** -* Enables or disables all the job part-related buttons. -* @param enable True to enable the job par-related butons. False to disable. -*/ -void KttsJobMgrPart::enableJobPartActions(bool enable) -{ - if (!m_buttonBox) return; -#if defined Q_CC_MSVC && _MSC_VER < 1300 - QList l = qfindChildren( m_buttonBox, QRegExp("part_*") ); -#else - QList l = m_buttonBox->findChildren( QRegExp("part_*") ); -#endif - QListIterator i(l); - - while (i.hasNext()) - (i.next())->setEnabled( enable ); -} - /** Slots connected to DBUS Signals emitted by KTTSD. */ /** * This signal is emitted when KTTSD starts or restarts after a call to reinit. */ -Q_ASYNC void KttsJobMgrPart::kttsdStarted() { slot_refresh(); }; - -/** -* This signal is emitted when the speech engine/plugin encounters a marker in the text. -* @param appId DCOP application ID of the application that queued the text. -* @param markerName The name of the marker seen. -* @see markers -*/ -Q_ASYNC void KttsJobMgrPart::markerSeen(const QString&, const QString&) -{ -} +Q_SCRIPTABLE void KttsJobMgrPart::kttsdStarted() { slot_refresh(); }; -/** - * This signal is emitted whenever a sentence begins speaking. - * @param appId DCOP application ID of the application that queued the text. - * @param jobNum Job number of the text job. - * @param seq Sequence number of the text. - * @see getTextCount - */ -Q_ASYNC void KttsJobMgrPart::sentenceStarted(const QString&, const uint jobNum, const uint seq) -{ - // kDebug() << "KttsJobMgrPart::sentencedStarted: jobNum = " << jobNum << " seq = " << seq << endl; - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - { - JobInfo job = m_jobListModel->getRow(index.row()); - job.state = KSpeech::jsSpeaking; - job.sentenceNum = seq; - m_jobListModel->updateRow(index.row(), job); - m_currentSentence->setPlainText(m_kspeech->getTextJobSentence(jobNum, seq)); - } -} - -/** -* This signal is emitted when a sentence has finished speaking. -* @param appId DCOP application ID of the application that queued the text. -* @param jobNum Job number of the text job. -* @param seq Sequence number of the text. -* @see getTextCount -*/ -Q_ASYNC void KttsJobMgrPart::sentenceFinished(const QString& /*appId*/, const uint /*jobNum*/, const uint /*seq*/) -{ - // kDebug() << "KttsJobMgrPart::sentencedFinished: jobNum = " << jobNum << endl; -/* - QListViewItem* item = findItemByJobNum(jobNum); - if (item) - { - item->setText(jlvcState, stateToStr(KSpeech::jsSpeaking)); - } -*/ -} /** -* This signal is emitted whenever a new text job is added to the queue. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -* -* If the job is already in the list, refreshes it. +* This signal is emitted each time the state of a job changes. +* @param appId The DBUS sender ID of the application that +* submitted the job. +* @param jobNum Job Number. +* @param state Job state. @see KSpeech::JobState. */ -Q_ASYNC void KttsJobMgrPart::textSet(const QString&, const uint jobNum) +Q_SCRIPTABLE void KttsJobMgrPart::jobStateChanged(const QString &appId, int jobNum, int state) { - kDebug() << "KttsJobMgrPart::textSet: jobNum = " << jobNum << endl; - - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - refreshJob(jobNum); - else + Q_UNUSED(appId); + switch (state) { - QByteArray jobInfo = m_kspeech->getTextJobInfo(jobNum); - QDataStream stream(&jobInfo, QIODevice::ReadOnly); - JobInfo job; - job.jobNum = jobNum; - QString talkerCode; - stream >> job.state; - stream >> job.appId; - stream >> talkerCode; - stream >> job.sentenceNum; - stream >> job.sentenceCount; - stream >> job.partNum; - stream >> job.partCount; - job.talkerID = cachedTalkerCodeToTalkerID(talkerCode); - m_jobListModel->appendRow(job); - // Should we select this job? - if (m_selectOnTextSet) + case KSpeech::jsQueued: { - m_jobListView->setCurrentIndex(m_jobListModel->jobNumToIndex(jobNum)); - m_selectOnTextSet = false; - slot_jobListView_clicked(); + QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); + if (index.isValid()) + refreshJob(jobNum); + else + { + JobInfo* job = retrieveJobInfo(jobNum); + if (job) { + m_jobListModel->appendRow(*job); + // Should we select this job? + if (m_selectOnTextSet) + { + m_jobListView->setCurrentIndex(m_jobListModel->jobNumToIndex(jobNum)); + m_selectOnTextSet = false; + slot_jobListView_clicked(); + } + } + } + // If a job not already selected, select this one. + autoSelectInJobListView(); + break; + } + case KSpeech::jsSpeakable: + { + QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); + if (index.isValid()) { + JobInfo* job = retrieveJobInfo(jobNum); + if (job) + m_jobListModel->updateRow(index.row(), *job); + else + m_jobListModel->removeRow(index.row()); + } + break; + } + case KSpeech::jsFiltering: + case KSpeech::jsSpeaking: + case KSpeech::jsPaused: + case KSpeech::jsInterrupted: + case KSpeech::jsFinished: + { + QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); + if (index.isValid()) + { + JobInfo job = m_jobListModel->getRow(index.row()); + job.state = state; + m_jobListModel->updateRow(index.row(), job); + } + break; + } + case KSpeech::jsDeleted: + { + QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); + if (index.isValid()) + m_jobListModel->removeRow(index.row()); + autoSelectInJobListView(); + break; } - } - // If a job not already selected, select this one. - autoSelectInJobListView(); -} - -/** -* This signal is emitted whenever a new part is appended to a text job. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -* @param partNum Part number of the new part. Parts are numbered starting -* at 1. -*/ -Q_ASYNC void KttsJobMgrPart::textAppended(const QString& appId, const uint jobNum, const int /*partNum*/) -{ - textSet(appId, jobNum); -} - -/** -* This signal is emitted whenever speaking of a text job begins. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -*/ -Q_ASYNC void KttsJobMgrPart::textStarted(const QString&, const uint jobNum) -{ - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - { - JobInfo job = m_jobListModel->getRow(index.row()); - job.state = KSpeech::jsSpeaking; - job.sentenceNum = 1; - m_jobListModel->updateRow(index.row(), job); - } -} - -/** -* This signal is emitted whenever a text job is finished. The job has -* been marked for deletion from the queue and will be deleted when another -* job reaches the Finished state. (Only one job in the text queue may be -* in state Finished at one time.) If @ref startText or @ref resumeText is -* called before the job is deleted, it will remain in the queue for speaking. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -*/ -Q_ASYNC void KttsJobMgrPart::textFinished(const QString&, const uint jobNum) -{ - // kDebug() << "KttsJobMgrPart::textFinished: jobNum = " << jobNum << endl; - refreshJob(jobNum); - m_currentSentence->setPlainText(QString()); -} - -/** -* This signal is emitted whenever a speaking text job stops speaking. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -*/ -Q_ASYNC void KttsJobMgrPart::textStopped(const QString&, const uint jobNum) -{ - // kDebug() << "KttsJobMgrPart::textStopped: jobNum = " << jobNum << endl; - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - { - JobInfo job = m_jobListModel->getRow(index.row()); - job.state = KSpeech::jsQueued; - job.sentenceNum = 1; - m_jobListModel->updateRow(index.row(), job); } } /** -* This signal is emitted whenever a speaking text job is paused. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. +* This signal is emitted when a marker is processed. +* Currently only emits mtSentenceBegin and mtSentenceEnd. +* @param appId The DBUS sender ID of the application that submitted the job. +* @param jobNum Job Number of the job emitting the marker. +* @param markerType The type of marker. +* Currently either mtSentenceBegin or mtSentenceEnd. +* @param markerData Data for the marker. +* Currently, this is the sequence number of the sentence +* begun or ended. Sequence numbers begin at 1. */ -Q_ASYNC void KttsJobMgrPart::textPaused(const QString&, const uint jobNum) +Q_SCRIPTABLE void KttsJobMgrPart::marker(const QString &appId, int jobNum, int markerType, const QString &markerData) { - // kDebug() << "KttsJobMgrPart::textPaused: jobNum = " << jobNum << endl; - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - { - JobInfo job = m_jobListModel->getRow(index.row()); - job.state = KSpeech::jsPaused; - m_jobListModel->updateRow(index.row(), job); + Q_UNUSED(appId); + if (KSpeech::mtSentenceBegin == markerType) { + QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); + if (index.isValid()) + { + JobInfo job = m_jobListModel->getRow(index.row()); + int seq = markerData.toInt(); + job.sentenceNum = seq; + m_jobListModel->updateRow(index.row(), job); + m_currentSentence->setPlainText(m_kspeech->getJobSentence(jobNum, seq)); + } } -} - -/** -* This signal is emitted when a text job, that was previously paused, resumes speaking. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -*/ -Q_ASYNC void KttsJobMgrPart::textResumed(const QString&, const uint jobNum) -{ - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - { - JobInfo job = m_jobListModel->getRow(index.row()); - job.state = KSpeech::jsSpeaking; - m_jobListModel->updateRow(index.row(), job); + if (KSpeech::mtSentenceEnd == markerType) { + m_currentSentence->setPlainText(QString()); } } -/** -* This signal is emitted whenever a text job is deleted from the queue. -* The job is no longer in the queue when this signal is emitted. -* @param appId The DCOP senderId of the application that created the job. -* @param jobNum Job number of the text job. -*/ -Q_ASYNC void KttsJobMgrPart::textRemoved(const QString&, const uint jobNum) -{ - QModelIndex index = m_jobListModel->jobNumToIndex(jobNum); - if (index.isValid()) - m_jobListModel->removeRow(index.row()); - autoSelectInJobListView(); -} KttsJobMgrBrowserExtension::KttsJobMgrBrowserExtension(KttsJobMgrPart *parent) : KParts::BrowserExtension(parent) diff --git a/kttsd/kttsjobmgr/kttsjobmgr.h b/kttsd/kttsjobmgr/kttsjobmgr.h index 0fe39676..26f2fce4 100644 --- a/kttsd/kttsjobmgr/kttsjobmgr.h +++ b/kttsd/kttsjobmgr/kttsjobmgr.h @@ -59,87 +59,28 @@ protected Q_SLOTS: * This signal is emitted when KTTSD starts or restarts after a call to reinit. */ Q_SCRIPTABLE void kttsdStarted(); - /** - * This signal is emitted when the speech engine/plugin encounters a marker in the text. - * @param appId DCOP application ID of the application that queued the text. - * @param markerName The name of the marker seen. - * @see markers - */ - Q_SCRIPTABLE void markerSeen(const QString& appId, const QString& markerName); - /** - * This signal is emitted whenever a sentence begins speaking. - * @param appId DCOP application ID of the application that queued the text. - * @param jobNum Job number of the text job. - * @param seq Sequence number of the text. - * @see getTextCount - */ - Q_SCRIPTABLE void sentenceStarted(const QString& appId, const uint jobNum, const uint seq); - /** - * This signal is emitted when a sentence has finished speaking. - * @param appId DCOP application ID of the application that queued the text. - * @param jobNum Job number of the text job. - * @param seq Sequence number of the text. - * @see getTextCount - */ - Q_SCRIPTABLE void sentenceFinished(const QString& appId, const uint jobNum, const uint seq); /** - * This signal is emitted whenever a new text job is added to the queue. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. + * This signal is emitted each time the state of a job changes. + * @param appId The DBUS sender ID of the application that + * submitted the job. + * @param jobNum Job Number. + * @param state Job state. @see KSpeech::JobState. */ - Q_SCRIPTABLE void textSet(const QString& appId, const uint jobNum); + Q_SCRIPTABLE void jobStateChanged(const QString &appId, int jobNum, int state); /** - * This signal is emitted whenever a new part is appended to a text job. - * @param appId The DCOP senderId of the application that created the job. - * @param jobNum Job number of the text job. - * @param partNum Part number of the new part. Parts are numbered starting - * at 1. - */ - Q_SCRIPTABLE void textAppended(const QString& appId, const uint jobNum, const int partNum); - - /** - * This signal is emitted whenever speaking of a text job begins. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. - */ - Q_SCRIPTABLE void textStarted(const QString& appId, const uint jobNum); - /** - * This signal is emitted whenever a text job is finished. The job has - * been marked for deletion from the queue and will be deleted when another - * job reaches the Finished state. (Only one job in the text queue may be - * in state Finished at one time.) If @ref startText or @ref resumeText is - * called before the job is deleted, it will remain in the queue for speaking. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. - */ - Q_SCRIPTABLE void textFinished(const QString& appId, const uint jobNum); - /** - * This signal is emitted whenever a speaking text job stops speaking. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. - */ - Q_SCRIPTABLE void textStopped(const QString& appId, const uint jobNum); - /** - * This signal is emitted whenever a speaking text job is paused. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. - */ - Q_SCRIPTABLE void textPaused(const QString& appId, const uint jobNum); - /** - * This signal is emitted when a text job, that was previously paused, resumes speaking. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. - */ - Q_SCRIPTABLE void textResumed(const QString& appId, const uint jobNum); - /** - * This signal is emitted whenever a text job is deleted from the queue. - * The job is no longer in the queue when this signal is emitted. - * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. - * @param jobNum Job number of the text job. + * This signal is emitted when a marker is processed. + * Currently only emits mtSentenceBegin and mtSentenceEnd. + * @param appId The DBUS sender ID of the application that submitted the job. + * @param jobNum Job Number of the job emitting the marker. + * @param markerType The type of marker. + * Currently either mtSentenceBegin or mtSentenceEnd. + * @param markerData Data for the marker. + * Currently, this is the sequence number of the sentence + * begun or ended. Sequence numbers begin at 1. */ - Q_SCRIPTABLE void textRemoved(const QString& appId, const uint jobNum); + Q_SCRIPTABLE void marker(const QString &appId, int jobNum, int markerType, const QString &markerData); private slots: /** @@ -159,10 +100,8 @@ private slots: void slot_speak_clipboard(); void slot_speak_file(); void slot_refresh(); - void slot_job_prev_par(); void slot_job_prev_sen(); void slot_job_next_sen(); - void slot_job_next_par(); private: /** @@ -170,14 +109,7 @@ private: * @return Job Number of currently-selected job. * 0 if no currently-selected job. */ - uint getCurrentJobNum(); - - /** - * Get the number of parts in the currently-selected job in the Job List View. - * @return Number of parts in currently-selected job. - * 0 if no currently-selected job. - */ - int getCurrentJobPartCount(); + int getCurrentJobNum(); /** * Enables or disables all the job-related buttons. @@ -186,16 +118,16 @@ private: void enableJobActions(bool enable); /** - * Enables or disables all the job part-related buttons. - * @param enable True to enable the job par-related butons. False to disable. + * Retrieves JobInfo from KTTSD, creates and fills JobInfo object. + * @param jobNum Job Number. */ - void enableJobPartActions(bool enable); + JobInfo* retrieveJobInfo(int jobNum); /** * Refresh display of a single job in the JobListView. * @param jobNum Job Number. */ - void refreshJob(uint jobNum); + void refreshJob(int jobNum); /** * Fill the Job List. diff --git a/kttsd/kttsjobmgr/org.kde.KSpeech.xml b/kttsd/kttsjobmgr/org.kde.KSpeech.xml index 6b4dc47f..eb99b787 100644 --- a/kttsd/kttsjobmgr/org.kde.KSpeech.xml +++ b/kttsd/kttsjobmgr/org.kde.KSpeech.xml @@ -4,169 +4,197 @@ + + - - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + - - - - - + + + + + + + - - - + + + + + + + + + - - - + + + + + + - - - - + + - - - - - + + + - + - + - - + + - - + - - + - + - - + + - - + + - - - + + + + - - - + + + + + + + - - - + + - - - + + + - - - - + + + - - + + + - - - + + + - - - + + + + - - - + + - - - - + + + + - - - + + + - - + + + + + + + + - + + - - - - - - - + + - - - - - - - + + - - - - - - + - + @@ -177,72 +205,23 @@ - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - + - - - - - - + + + - - - - - - - - - - - - - - - diff --git a/kttsd/kttsmgr/kttsmgr.cpp b/kttsd/kttsmgr/kttsmgr.cpp index bd776e8c..5b2ca99b 100644 --- a/kttsd/kttsmgr/kttsmgr.cpp +++ b/kttsd/kttsmgr/kttsmgr.cpp @@ -43,9 +43,9 @@ #include #include #include +#include // KTTSMgr includes. -#include "kspeechdef.h" #include "kttsmgr.h" static const KCmdLineOptions options[] = @@ -199,11 +199,11 @@ void KttsMgrTray::mousePressEvent(QMouseEvent* ev) int jobState = -1; if (kttsdRunning) { - uint job = m_kspeech->getCurrentTextJob(); - if (job != 0) + int jobNum = m_kspeech->getCurrentJob(); + if (jobNum != 0) { - // kDebug() << "KttsMgrTray::getStatus: job = " << job << endl; - jobState = m_kspeech->getTextJobState(job); + // kDebug() << "KttsMgrTray::getStatus: jobNum = " << jobNum << endl; + jobState = m_kspeech->getJobState(jobNum); } } actStop->setEnabled(jobState != -1); @@ -211,15 +211,14 @@ void KttsMgrTray::mousePressEvent(QMouseEvent* ev) actResume->setEnabled(jobState == KSpeech::jsPaused); actRepeat->setEnabled(jobState != -1); actSpeakClipboard->setEnabled(kttsdRunning); - bool configActive = (QDBus::sessionBus().busService()->nameHasOwner("org.kde.kcmshell_kcmkttsd")); + bool configActive = (QDBus::sessionBus().interface()->isServiceRegistered("org.kde.kcmshell_kcmkttsd")); actConfigure->setEnabled(!configActive); } void KttsMgrTray::exitWhenFinishedSpeaking() { // kDebug() << "KttsMgrTray::exitWhenFinishedSpeaking: running" << endl; - QString jobNums = m_kspeech->getTextJobNumbers(); - QStringList jobNumsList = jobNums.split(","); + QStringList jobNumsList = m_kspeech->getJobNumbers(KSpeech::jpAll); uint jobNumsListCount = jobNumsList.count(); // Since there can only be 2 Finished jobs at a time, more than 2 jobs means at least // one job is not Finished. @@ -227,15 +226,17 @@ void KttsMgrTray::exitWhenFinishedSpeaking() // Exit if all jobs are Finished or there are no jobs. for (uint ndx=0; ndx < jobNumsListCount; ++ndx) { - if (m_kspeech->getTextJobState(jobNumsList[ndx].toInt()) != KSpeech::jsFinished) return; + if (m_kspeech->getJobState(jobNumsList[ndx].toInt()) != KSpeech::jsFinished) return; } kapp->quit(); } -void KttsMgrTray::textFinished(const QString& /*appId*/, uint /*jobNum*/) +void KttsMgrTray::jobStateChanged(const QString &appId, int jobNum, int state) { - // kDebug() << "KttsMgrTray::textFinished: running" << endl; - exitWhenFinishedSpeaking(); + Q_UNUSED(appId); + Q_UNUSED(jobNum); + if (KSpeech::jsFinished == state) + exitWhenFinishedSpeaking(); } /** @@ -259,17 +260,17 @@ QString KttsMgrTray::stateToStr(int state) QString KttsMgrTray::getStatus() { if (!isKttsdRunning()) return i18n("Text-to-Speech System is not running"); - uint jobCount = m_kspeech->getTextJobCount(); + int jobCount = m_kspeech->getJobCount(KSpeech::jpAll); QString status = i18np("1 job", "%n jobs", jobCount); if (jobCount > 0) { - uint job = m_kspeech->getCurrentTextJob(); - if (job != 0) + int jobNum = m_kspeech->getCurrentJob(); + if (jobNum != 0) { // kDebug() << "KttsMgrTray::getStatus: job = " << job << endl; - int jobState = m_kspeech->getTextJobState(job); - int sentenceCount = m_kspeech->getTextCount(job); - uint seq = m_kspeech->moveRelTextSentence(0, job); + int jobState = m_kspeech->getJobState(jobNum); + int sentenceCount = m_kspeech->getSentenceCount(jobNum); + int seq = m_kspeech->moveRelSentence(jobNum, 0); status += i18n(", current job %1 at sentence %2 of %3 sentences" , stateToStr(jobState), seq, sentenceCount); } @@ -285,7 +286,7 @@ void KttsMgrTray::speakClipboardSelected() if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error) != 0) kError() << "Starting KTTSD failed with message " << error << endl; } - m_kspeech->speakClipboard(); + m_kspeech->sayClipboard(); } void KttsMgrTray::aboutSelected() @@ -308,36 +309,28 @@ void KttsMgrTray::quitSelected() void KttsMgrTray::stopSelected() { if (isKttsdRunning()) - { - uint jobNum = m_kspeech->getCurrentTextJob(); - m_kspeech->removeText(jobNum); - } + m_kspeech->removeAllJobs(); } void KttsMgrTray::pauseSelected() { if (isKttsdRunning()) - { - uint jobNum = m_kspeech->getCurrentTextJob(); - m_kspeech->pauseText(jobNum); - } + m_kspeech->pause(); } void KttsMgrTray::resumeSelected() { if (isKttsdRunning()) - { - uint jobNum = m_kspeech->getCurrentTextJob(); - m_kspeech->resumeText(jobNum); - } + m_kspeech->resume(); } void KttsMgrTray::repeatSelected() { if (isKttsdRunning()) { - uint jobNum = m_kspeech->getCurrentTextJob(); - m_kspeech->startText(jobNum); + int jobNum = m_kspeech->getCurrentJob(); + int seq = m_kspeech->moveRelSentence(jobNum, 0); + m_kspeech->moveRelSentence(jobNum, -seq); } } @@ -350,16 +343,18 @@ void KttsMgrTray::configureSelected() bool KttsMgrTray::isKttsdRunning() { - bool isRunning = (QDBus::sessionBus().busService()->nameHasOwner("org.kde.kttsd")); + bool isRunning = (QDBus::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")); if (isRunning) { if (!m_kspeech) { - m_kspeech = (org::kde::KSpeech*)(QDBus::sessionBus().findInterface("org.kde.kttsd", "/org/kde/KSpeech")); + m_kspeech = new OrgKdeKSpeechInterface("org.kde.kttsd", "/KSpeech", QDBus::sessionBus()); m_kspeech->setParent(this); + m_kspeech->setApplicationName("kttsmgr"); + m_kspeech->setIsSystemManager(true); // If --autoexit option given, exit when speaking stops. KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); if (args->isSet("autoexit")) - connect(m_kspeech, SIGNAL(textFinished(QString&,uint)), - this, SLOT(textFinished(QString&,uint))); + connect(m_kspeech, SIGNAL(jobStateChanged(const QString&, int, int)), + this, SLOT(jobStateChanged(const QString&, int, int))); } } else { delete m_kspeech; diff --git a/kttsd/kttsmgr/kttsmgr.h b/kttsd/kttsmgr/kttsmgr.h index 4944c82b..455b63f9 100644 --- a/kttsd/kttsmgr/kttsmgr.h +++ b/kttsd/kttsmgr/kttsmgr.h @@ -47,7 +47,8 @@ class KttsMgrTray: public KSystemTray QString getStatus(); protected Q_SLOTS: - Q_ASYNC void textFinished(const QString& appId, uint jobNum); + Q_SCRIPTABLE void jobStateChanged(const QString &appId, int jobNum, int state); + bool event(QEvent *event); void mousePressEvent(QMouseEvent* ev); virtual void contextMenuAboutToShow(KMenu* menu); diff --git a/kttsd/kttsmgr/org.kde.KSpeech.xml b/kttsd/kttsmgr/org.kde.KSpeech.xml index 6b4dc47f..eb99b787 100644 --- a/kttsd/kttsmgr/org.kde.KSpeech.xml +++ b/kttsd/kttsmgr/org.kde.KSpeech.xml @@ -4,169 +4,197 @@ + + - - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + - - - - - + + + + + + + - - - + + + + + + + + + - - - + + + + + + - - - - + + - - - - - + + + - + - + - - + + - - + - - + - + - - + + - - + + - - - + + + + - - - + + + + + + + - - - + + - - - + + + - - - - + + + - - + + + - - - + + + - - - + + + + - - - + + - - - - + + + + - - - + + + - - + + + + + + + + - + + - - - - - - - + + - - - - - - - + + - - - - - - + - + @@ -177,72 +205,23 @@ - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - + - - - - - - + + + - - - - - - - - - - - - - - - diff --git a/kttsd/plugins/command/commandconf.cpp b/kttsd/plugins/command/commandconf.cpp index 6a0e04a0..13088f03 100644 --- a/kttsd/plugins/command/commandconf.cpp +++ b/kttsd/plugins/command/commandconf.cpp @@ -143,7 +143,7 @@ void CommandConf::slotCommandTest_clicked() } // Create a temp file name for the wave file. - KTempFile tempFile (locateLocal("tmp", "commandplugin-"), ".wav"); + KTempFile tempFile (KStandardDirs::locateLocal("tmp", "commandplugin-"), ".wav"); QString tmpWaveFile = tempFile.file()->fileName(); tempFile.close(); diff --git a/kttsd/plugins/command/commandproc.cpp b/kttsd/plugins/command/commandproc.cpp index 504183d4..5b09cdca 100644 --- a/kttsd/plugins/command/commandproc.cpp +++ b/kttsd/plugins/command/commandproc.cpp @@ -148,7 +148,7 @@ void CommandProc::synth(const QString& inputText, const QString& suggestedFilena // 1.c) create a temporary file for the text, if %f macro is used. if (command.contains("%f")) { - KTempFile tempFile(locateLocal("tmp", "commandplugin-"), ".txt"); + KTempFile tempFile(KStandardDirs::locateLocal("tmp", "commandplugin-"), ".txt"); QTextStream* fs = tempFile.textStream(); fs->setCodec(codec); *fs << text; diff --git a/kttsd/plugins/epos/eposconf.cpp b/kttsd/plugins/epos/eposconf.cpp index 19109971..59ed51ad 100644 --- a/kttsd/plugins/epos/eposconf.cpp +++ b/kttsd/plugins/epos/eposconf.cpp @@ -202,7 +202,7 @@ void EposConf::slotEposTest_clicked() connect (m_eposProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); } // Create a temp file name for the wave file. - KTempFile tempFile (locateLocal("tmp", "eposplugin-"), ".wav"); + KTempFile tempFile (KStandardDirs::locateLocal("tmp", "eposplugin-"), ".wav"); QString tmpWaveFile = tempFile.file()->fileName(); tempFile.close(); diff --git a/kttsd/plugins/festivalint/festivalintconf.cpp b/kttsd/plugins/festivalint/festivalintconf.cpp index 1fc87886..be6ecd2d 100644 --- a/kttsd/plugins/festivalint/festivalintconf.cpp +++ b/kttsd/plugins/festivalint/festivalintconf.cpp @@ -549,7 +549,7 @@ void FestivalIntConf::slotTest_clicked() connect (m_festProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); } // Create a temp file name for the wave file. - KTempFile tempFile (locateLocal("tmp", "festivalintplugin-"), ".wav"); + KTempFile tempFile (KStandardDirs::locateLocal("tmp", "festivalintplugin-"), ".wav"); // FIXME: Temporary workaround for KTempFile problem. // QString tmpWaveFile = tempFile.file()->name(); // tempFile.close(); diff --git a/kttsd/plugins/flite/fliteconf.cpp b/kttsd/plugins/flite/fliteconf.cpp index 4b0d54c6..90714b24 100644 --- a/kttsd/plugins/flite/fliteconf.cpp +++ b/kttsd/plugins/flite/fliteconf.cpp @@ -134,7 +134,7 @@ void FliteConf::slotFliteTest_clicked() connect (m_fliteProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); } // Create a temp file name for the wave file. - KTempFile tempFile (locateLocal("tmp", "fliteplugin-"), ".wav"); + KTempFile tempFile (KStandardDirs::locateLocal("tmp", "fliteplugin-"), ".wav"); QString tmpWaveFile = tempFile.file()->fileName(); tempFile.close(); diff --git a/kttsd/plugins/freetts/freettsconf.cpp b/kttsd/plugins/freetts/freettsconf.cpp index 70895769..a320a13d 100644 --- a/kttsd/plugins/freetts/freettsconf.cpp +++ b/kttsd/plugins/freetts/freettsconf.cpp @@ -161,7 +161,7 @@ void FreeTTSConf::slotFreeTTSTest_clicked() connect (m_freettsProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); } // Create a temp file name for the wave file. - KTempFile tempFile (locateLocal("tmp", "freettsplugin-"), ".wav"); + KTempFile tempFile (KStandardDirs::locateLocal("tmp", "freettsplugin-"), ".wav"); QString tmpWaveFile = tempFile.file()->fileName(); tempFile.close(); diff --git a/kttsd/plugins/hadifix/hadifixconf.cpp b/kttsd/plugins/hadifix/hadifixconf.cpp index e0115f2c..510c586d 100644 --- a/kttsd/plugins/hadifix/hadifixconf.cpp +++ b/kttsd/plugins/hadifix/hadifixconf.cpp @@ -470,7 +470,7 @@ HadifixConf::HadifixConf( QWidget* parent, const QStringList &) : d = new HadifixConfPrivate(parent); - QString file = locate("data", "LICENSES/LGPL_V2"); + // QString file = locate("data", "LICENSES/LGPL_V2"); i18n("This plugin is distributed under the terms of the GPL v2 or later."); connect(d->voiceButton, SIGNAL(clicked()), this, SLOT(voiceButton_clicked())); @@ -606,7 +606,7 @@ void HadifixConf::testButton_clicked () { connect (d->hadifixProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); } // Create a temp file name for the wave file. - KTempFile tempFile (locateLocal("tmp", "hadifixplugin-"), ".wav"); + KTempFile tempFile (KStandardDirs::locateLocal("tmp", "hadifixplugin-"), ".wav"); QString tmpWaveFile = tempFile.file()->fileName(); tempFile.close(); -- 2.11.4.GIT