/***************************************************** vim:set ts=4 sw=4 sts=4: Description: Filters text, applying each configured Filter in turn. Runs asynchronously, emitting Finished() signal when all Filters have run. Copyright: (C) 2005 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. ******************************************************************************/ // KDE includes. #include #include #include #include #include // FilterMgr includes. #include "filtermgr.h" #include "filtermgr.moc" /** * Constructor. */ FilterMgr::FilterMgr( TQObject *parent, const char *name) : KttsFilterProc(parent, name) { // kdDebug() << "FilterMgr::FilterMgr: Running" << endl; m_state = fsIdle; m_noSBD = false; m_supportsHTML = false; m_talkerCode = 0; } /** * Destructor. */ FilterMgr::~FilterMgr() { // kdDebug() << "FilterMgr::~FilterMgr: Running" << endl; if ( m_state == fsFiltering ) stopFiltering(); m_filterList.setAutoDelete( TRUE ); m_filterList.clear(); } /** * Loads and initializes the filters. * @param config Settings object. * @return False if FilterMgr is not ready to filter. */ bool FilterMgr::init(TDEConfig *config, const TQString& /*configGroup*/) { // Load each of the filters and initialize. config->setGroup("General"); TQStringList filterIDsList = config->readListEntry("FilterIDs", ','); // kdDebug() << "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->readListEntry("FilterIDs", ','); } if ( !filterIDsList.isEmpty() ) { TQStringList::ConstIterator itEnd = filterIDsList.constEnd(); for (TQStringList::ConstIterator it = filterIDsList.constBegin(); it != itEnd; ++it) { TQString filterID = *it; TQString groupName = "Filter_" + filterID; config->setGroup( groupName ); TQString 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. // DesktopEntryNames are better because user can change their desktop language // and DesktopEntryName won't change. if (desktopEntryName.isEmpty()) { TQString filterPlugInName = config->readEntry("PlugInName", TQString()); // 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 (config->readBoolEntry("Enabled") || config->readBoolEntry("IsSBD")) { // kdDebug() << "FilterMgr::init: filterID = " << filterID << endl; KttsFilterProc* filterProc = loadFilterPlugin( desktopEntryName ); if ( filterProc ) { filterProc->init( config, groupName ); m_filterList.append( filterProc ); } if (config->readEntry("DocType").contains("html") || config->readEntry("RootElement").contains("html")) m_supportsHTML = true; } } } return true; } /** * Returns True if this filter is a Sentence Boundary Detector. * If so, the filter should implement @ref setSbRegExp() . * @return True if this filter is a SBD. */ /*virtual*/ bool FilterMgr::isSBD() { return true; } /** * Returns True if the plugin supports asynchronous processing, * i.e., supports asyncConvert method. * @return True if this plugin supports asynchronous processing. * * If the plugin returns True, it must also implement @ref getState . * It must also emit @ref filteringFinished when filtering is completed. * If the plugin returns True, it must also implement @ref stopFiltering . * It must also emit @ref filteringStopped when filtering has been stopped. */ /*virtual*/ bool FilterMgr::supportsAsync() { return true; } /** * Synchronously convert text. * @param inputText Input text. * @param talkerCode TalkerCode structure for the talker that KTTSD intends to * use for synthing the text. Useful for extracting hints about * how to filter the text. For example, languageCode. * @param appId The DCOP appId of the application that queued the text. * Also useful for hints about how to do the filtering. * @return Converted text. */ TQString FilterMgr::convert(const TQString& inputText, TalkerCode* talkerCode, const TQCString& appId) { m_text = inputText; m_talkerCode = talkerCode; m_appId = appId; m_filterIndex = -1; m_filterProc = 0; m_state = fsFiltering; m_async = false; while ( m_state == fsFiltering ) nextFilter(); return m_text; } /** * Aynchronously convert input. * @param inputText Input text. * @param talkerCode TalkerCode structure for the talker that KTTSD intends to * use for synthing the text. Useful for extracting hints about * how to filter the text. For example, languageCode. * @param appId The DCOP appId of the application that queued the text. * Also useful for hints about how to do the filtering. * * When the input text has been converted, filteringFinished signal will be emitted * and caller can retrieve using getOutput(); */ bool FilterMgr::asyncConvert(const TQString& inputText, TalkerCode* talkerCode, const TQCString& appId) { m_text = inputText; m_talkerCode = talkerCode; m_appId = appId; m_filterIndex = -1; m_filterProc = 0; m_state = fsFiltering; m_async = true; nextFilter(); return true; } // Finishes up with current filter (if any) and goes on to the next filter. void FilterMgr::nextFilter() { if ( m_filterProc ) { if ( m_filterProc->supportsAsync() ) { m_text = m_filterProc->getOutput(); m_filterProc->ackFinished(); disconnect( m_filterProc, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilteringFinished()) ); } // if ( m_filterProc->wasModified() ) // kdDebug() << "FilterMgr::nextFilter: Filter# " << m_filterIndex << " modified the text." << endl; if ( m_filterProc->wasModified() && m_filterProc->isSBD() ) { m_state = fsFinished; // Post an event which will be later emitted as a signal. TQCustomEvent* ev = new TQCustomEvent(TQEvent::User + 301); TQApplication::postEvent(this, ev); return; } } ++m_filterIndex; if ( m_filterIndex == static_cast(m_filterList.count()) ) { m_state = fsFinished; // Post an event which will be later emitted as a signal. TQCustomEvent* ev = new TQCustomEvent(TQEvent::User + 301); TQApplication::postEvent(this, ev); return; } m_filterProc = m_filterList.at(m_filterIndex); if ( m_noSBD && m_filterProc->isSBD() ) { m_state = fsFinished; // Post an event which will be later emitted as a signal. TQCustomEvent* ev = new TQCustomEvent(TQEvent::User + 301); TQApplication::postEvent(this, ev); return; } m_filterProc->setSbRegExp( m_re ); if ( m_async ) { if ( m_filterProc->supportsAsync() ) { // kdDebug() << "FilterMgr::nextFilter: calling asyncConvert on filter " << m_filterIndex << endl; connect( m_filterProc, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilteringFinished()) ); if ( !m_filterProc->asyncConvert( m_text, m_talkerCode, m_appId ) ) { disconnect( m_filterProc, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilteringFinished()) ); m_filterProc = 0; nextFilter(); } } else { m_text = m_filterProc->convert( m_text, m_talkerCode, m_appId ); nextFilter(); } } else m_text = m_filterProc->convert( m_text, m_talkerCode, m_appId ); } // Received when each filter finishes. void FilterMgr::slotFilteringFinished() { // kdDebug() << "FilterMgr::slotFilteringFinished: received signal from filter " << m_filterIndex << endl; nextFilter(); } bool FilterMgr::event ( TQEvent * e ) { if ( e->type() == (TQEvent::User + 301) ) { // kdDebug() << "FilterMgr::event: emitting filteringFinished signal." << endl; emit filteringFinished(); return true; } if ( e->type() == (TQEvent::User + 302) ) { // kdDebug() << "FilterMgr::event: emitting filteringStopped signal." << endl; emit filteringStopped(); return true; } else return false; } /** * Waits for filtering to finish. */ void FilterMgr::waitForFinished() { if ( m_state != fsFiltering ) return; disconnect(m_filterProc, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilteringFinished()) ); m_async = false; m_filterProc->waitForFinished(); while ( m_state == fsFiltering ) nextFilter(); } /** * Returns the state of the FilterMgr. */ int FilterMgr::getState() { return m_state; } /** * Returns the filtered output. */ TQString FilterMgr::getOutput() { return m_text; } /** * Acknowledges the finished filtering. */ void FilterMgr::ackFinished() { m_state = fsIdle; m_text = TQString(); } /** * Stops filtering. The filteringStopped signal will emit when filtering * has in fact stopped. */ void FilterMgr::stopFiltering() { if ( m_state != fsFiltering ) return; if ( m_async ) disconnect( m_filterProc, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilteringFinished()) ); m_filterProc->stopFiltering(); m_state = fsIdle; TQCustomEvent* ev = new TQCustomEvent(TQEvent::User + 302); TQApplication::postEvent(this, ev); } /** * Set Sentence Boundary Regular Expression. * This method will only be called if the application overrode the default. * * @param re The sentence delimiter regular expression. */ /*virtual*/ void FilterMgr::setSbRegExp(const TQString& re) { m_re = re; } /** * Do not call SBD filters. */ void FilterMgr::setNoSBD(bool noSBD) { m_noSBD = noSBD; } bool FilterMgr::noSBD() { return m_noSBD; } // Loads the processing plug in for a filter plug in given its DesktopEntryName. KttsFilterProc* FilterMgr::loadFilterPlugin(const TQString& desktopEntryName) { // kdDebug() << "FilterMgr::loadFilterPlugin: Running"<< endl; // Find the plugin. TDETrader::OfferList offers = TDETrader::self()->query("KTTSD/FilterPlugin", TQString("DesktopEntryName == '%1'").arg(desktopEntryName)); if (offers.count() == 1) { // When the entry is found, load the plug in // First create a factory for the library KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); if(factory){ // If the factory is created successfully, instantiate the KttsFilterConf class for the // specific plug in to get the plug in configuration object. int errorNo; KttsFilterProc *plugIn = KParts::ComponentFactory::createInstanceFromLibrary( offers[0]->library().latin1(), NULL, offers[0]->library().latin1(), TQStringList(), &errorNo); if(plugIn){ // If everything went ok, return the plug in pointer. return plugIn; } else { // Something went wrong, returning null. kdDebug() << "FilterMgr::loadFilterPlugin: Unable to instantiate KttsFilterProc class for plugin " << desktopEntryName << " error: " << errorNo << endl; return NULL; } } else { // Something went wrong, returning null. kdDebug() << "FilterMgr::loadFilterPlugin: Unable to create Factory object for plugin " << desktopEntryName << endl; return NULL; } } // The plug in was not found (unexpected behaviour, returns null). kdDebug() << "FilterMgr::loadFilterPlugin: TDETrader did not return an offer for plugin " << desktopEntryName << endl; return NULL; } /** * Uses TDETrader to convert a translated Filter Plugin Name to DesktopEntryName. * @param name The translated plugin name. From Name= line in .desktop file. * @return DesktopEntryName. The name of the .desktop file (less .desktop). * TQString() if not found. */ TQString FilterMgr::FilterNameToDesktopEntryName(const TQString& name) { if (name.isEmpty()) return TQString(); TDETrader::OfferList offers = TDETrader::self()->query("KTTSD/FilterPlugin"); for (uint ndx = 0; ndx < offers.count(); ++ndx) if (offers[ndx]->name() == name) return offers[ndx]->desktopEntryName(); return TQString(); }