Remove this line
[kdeaccessibility.git] / kttsd / filters / sbd / sbdproc.cpp
blobe0ede0b72e46b110e102ad8ad2bb27b0f3ac87eb
1 /***************************************************** vim:set ts=4 sw=4 sts=4:
2 Sentence Boundary Detection Filter class.
3 -------------------
4 Copyright:
5 (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net>
6 -------------------
7 Original author: Gary Cramblitt <garycramblitt@comcast.net>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 ******************************************************************************/
24 // SdbProc includes.
25 #include "sbdproc.h"
27 // Qt includes.
28 #include <QtCore/QRegExp>
29 #include <QtXml/qdom.h>
30 #include <QtGui/QApplication>
31 #include <QtCore/QCustomEvent>
33 // KDE includes.
34 #include <kdebug.h>
35 #include <klocale.h>
36 #include <kconfig.h>
37 #include <kconfiggroup.h>
39 // KTTS includes.
40 #include "utils.h"
41 #include "talkercode.h"
43 /**
44 * Constructor.
46 SbdThread::SbdThread( QObject *parent) :
47 QThread( parent)
51 /**
52 * Destructor.
54 /*virtual*/ SbdThread::~SbdThread()
58 /**
59 * Get/Set text being processed.
61 void SbdThread::setText( const QString& text ) { m_text = text; }
62 QString SbdThread::text() { return m_text; }
64 /**
65 * Set/Get TalkerCode.
67 void SbdThread::setTalkerCode( TalkerCode* talkerCode ) { m_talkerCode = talkerCode; }
68 TalkerCode* SbdThread::talkerCode() { return m_talkerCode; }
70 /**
71 * Set Sentence Boundary Regular Expression.
72 * This method will only be called if the application overrode the default.
74 * @param re The sentence delimiter regular expression.
76 void SbdThread::setSbRegExp( const QString& re ) { m_re = re; }
78 /**
79 * The configured Sentence Boundary Regular Expression.
81 * @param re The sentence delimiter regular expression.
83 void SbdThread::setConfiguredSbRegExp( const QString& re ) { m_configuredRe = re; }
85 /**
86 * The configured Sentence Boundary that replaces SB regular expression.
88 * @param sb The sentence boundary replacement.
91 void SbdThread::setConfiguredSentenceBoundary( const QString& sb ) { m_configuredSentenceBoundary = sb; }
93 /**
94 * Did this filter do anything? If the filter returns the input as output
95 * unmolested, it should return False when this method is called.
97 void SbdThread::setWasModified(bool wasModified) { m_wasModified = wasModified; }
98 bool SbdThread::wasModified() { return m_wasModified; }
100 // Given a tag name, returns SsmlElemType.
101 SbdThread::SsmlElemType SbdThread::tagToSsmlElemType( const QString tagName )
103 if ( tagName == "speak" ) return etSpeak;
104 if ( tagName == "voice" ) return etVoice;
105 if ( tagName == "prosody" ) return etProsody;
106 if ( tagName == "emphasis" ) return etEmphasis;
107 if ( tagName == "break" ) return etBreak;
108 if ( tagName == "s" ) return etPS;
109 if ( tagName == "p" ) return etPS;
110 return etNotSsml;
113 // Parses an SSML element, pushing current settings onto the context stack.
114 void SbdThread::pushSsmlElem( SsmlElemType et, const QDomElement& elem )
116 // TODO: Need to convert relative values into absolute values and also convert
117 // only to values recognized by SSML2SABLE stylesheet. Either that or enhance all
118 // the synth stylesheets.
119 QDomNamedNodeMap attrList = elem.attributes();
120 int attrCount = attrList.count();
121 switch ( et )
123 case etSpeak: {
124 SpeakElem e = m_speakStack.top();
125 for ( int ndx=0; ndx < attrCount; ++ndx )
127 QDomAttr a = attrList.item( ndx ).toAttr();
128 if ( a.name() == "lang" ) e.lang = a.value();
130 m_speakStack.push( e );
131 break; }
132 case etVoice: {
133 VoiceElem e = m_voiceStack.top();
134 // TODO: Since Festival chokes on <voice> tags, don't output them at all.
135 // This means we can't support voice changes, and probably more irritatingly,
136 // gender changes either.
137 m_voiceStack.push( e );
138 break; }
139 case etProsody: {
140 ProsodyElem e = m_prosodyStack.top();
141 for ( int ndx=0; ndx < attrCount; ++ndx )
143 QDomAttr a = attrList.item( ndx ).toAttr();
144 if ( a.name() == "pitch" ) e.pitch = a.value();
145 if ( a.name() == "contour" ) e.contour = a.value();
146 if ( a.name() == "range" ) e.range = a.value();
147 if ( a.name() == "rate" ) e.rate = a.value();
148 if ( a.name() == "duration" ) e.duration = a.value();
149 if ( a.name() == "volume" ) e.volume = a.value();
151 m_prosodyStack.push( e );
152 break; }
153 case etEmphasis: {
154 EmphasisElem e = m_emphasisStack.top();
155 for ( int ndx=0; ndx < attrCount; ++ndx )
157 QDomAttr a = attrList.item( ndx ).toAttr();
158 if ( a.name() == "level" ) e.level = a.value();
160 m_emphasisStack.push( e );
161 break; }
162 case etPS: {
163 PSElem e = m_psStack.top();
164 for ( int ndx=0; ndx < attrCount; ++ndx )
166 QDomAttr a = attrList.item( ndx ).toAttr();
167 if ( a.name() == "lang" ) e.lang = a.value();
169 m_psStack.push( e );
170 break; }
171 default: break;
175 // Given an attribute name and value, constructs an XML representation of the attribute,
176 // i.e., name="value".
177 QString SbdThread::makeAttr( const QString& name, const QString& value )
179 if ( value.isEmpty() ) return QString();
180 return " " + name + "=\"" + value + "\"";
183 // Returns an XML representation of an SSML tag from the top of the context stack.
184 QString SbdThread::makeSsmlElem( SsmlElemType et )
186 QString s;
187 QString a;
188 switch ( et )
190 // Must always output speak tag, otherwise kttsd won't think each sentence is SSML.
191 // For all other tags, only output the tag if it contains at least one attribute.
192 case etSpeak: {
193 SpeakElem e = m_speakStack.top();
194 s = "<speak";
195 if ( !e.lang.isEmpty() ) s += makeAttr( "lang", e.lang );
196 s += '>';
197 break; }
198 case etVoice: {
199 VoiceElem e = m_voiceStack.top();
200 a += makeAttr( "lang", e.lang );
201 a += makeAttr( "gender", e.gender );
202 a += makeAttr( "age", QString::number(e.age) );
203 a += makeAttr( "name", e.name );
204 a += makeAttr( "variant", e.variant );
205 if ( !a.isEmpty() ) s = "<voice" + a + '>';
206 break; }
207 case etProsody: {
208 ProsodyElem e = m_prosodyStack.top();
209 a += makeAttr( "pitch", e.pitch );
210 a += makeAttr( "contour", e.contour );
211 a += makeAttr( "range", e.range );
212 a += makeAttr( "rate", e.rate );
213 a += makeAttr( "duration", e.duration );
214 a += makeAttr( "volume", e.volume );
215 if ( !a.isEmpty() ) s = "<prosody" + a + '>';
216 break; }
217 case etEmphasis: {
218 EmphasisElem e = m_emphasisStack.top();
219 a += makeAttr( "level", e.level );
220 if ( !a.isEmpty() ) s = "<emphasis" + a + '>';
221 break; }
222 case etPS: {
223 break; }
224 default: break;
226 return s;
229 // Pops element from the indicated context stack.
230 void SbdThread::popSsmlElem( SsmlElemType et )
232 switch ( et )
234 case etSpeak: m_speakStack.pop(); break;
235 case etVoice: m_voiceStack.pop(); break;
236 case etProsody: m_prosodyStack.pop(); break;
237 case etEmphasis: m_emphasisStack.pop(); break;
238 case etPS: m_psStack.pop(); break;
239 default: break;
243 // Returns an XML representation of a break element.
244 QString SbdThread::makeBreakElem( const QDomElement& e )
246 QString s = "<break";
247 QDomNamedNodeMap attrList = e.attributes();
248 int attrCount = attrList.count();
249 for ( int ndx=0; ndx < attrCount; ++ndx )
251 QDomAttr a = attrList.item( ndx ).toAttr();
252 s += makeAttr( a.name(), a.value() );
254 s += '>';
255 return s;
258 // Converts a text fragment into a CDATA section.
259 QString SbdThread::makeCDATA( const QString& text )
261 QString s = "<![CDATA[";
262 s += text;
263 s += "]]>";
264 return s;
267 // Returns an XML representation of an utterance node consisting of voice,
268 // prosody, and emphasis elements.
269 QString SbdThread::makeSentence( const QString& text )
271 QString s;
272 QString v = makeSsmlElem( etVoice );
273 QString p = makeSsmlElem( etProsody );
274 QString e = makeSsmlElem( etEmphasis );
275 // TODO: Lang settings from psStack.
276 if ( !v.isEmpty() ) s += v;
277 if ( !p.isEmpty() ) s += p;
278 if ( !e.isEmpty() ) s += e;
279 // Escape ampersands and less thans.
280 QString newText = text;
281 newText.replace(QRegExp("&(?!amp;)"), "&amp;");
282 newText.replace(QRegExp("<(?!lt;)"), "&lt;");
283 s += newText;
284 if ( !e.isEmpty() ) s += "</emphasis>";
285 if ( !p.isEmpty() ) s += "</prosody>";
286 if ( !v.isEmpty() ) s += "</voice>";
287 return s;
290 // Starts a sentence by returning a speak tag.
291 QString SbdThread::startSentence()
293 if ( m_sentenceStarted ) return QString();
294 QString s;
295 s += makeSsmlElem( etSpeak );
296 m_sentenceStarted = true;
297 return s;
300 // Ends a sentence and appends a Tab.
301 QString SbdThread::endSentence()
303 if ( !m_sentenceStarted ) return QString();
304 QString s = "</speak>";
305 s += '\t';
306 m_sentenceStarted = false;
307 return s;
310 // Parses a node of the SSML tree and recursively parses its children.
311 // Returns the filtered text with each sentence a complete ssml tree.
312 QString SbdThread::parseSsmlNode( QDomNode& n, const QString& re )
314 QString result;
315 switch ( n.nodeType() )
317 case QDomNode::ElementNode: { // = 1
318 QDomElement e = n.toElement();
319 QString tagName = e.tagName();
320 SsmlElemType et = tagToSsmlElemType( tagName );
321 switch ( et )
323 case etSpeak:
324 case etVoice:
325 case etProsody:
326 case etEmphasis:
327 case etPS:
329 pushSsmlElem( et, e );
330 QDomNode t = n.firstChild();
331 while ( !t.isNull() )
333 result += parseSsmlNode( t, re );
334 t = t.nextSibling();
336 popSsmlElem( et );
337 if ( et == etPS )
338 result += endSentence();
339 break;
341 case etBreak:
343 // Break elements are empty.
344 result += makeBreakElem( e );
346 // Ignore any elements we don't recognize.
347 default: break;
349 break; }
350 case QDomNode::AttributeNode: { // = 2
351 break; }
352 case QDomNode::TextNode: { // = 3
353 QString s = parsePlainText( n.toText().data(), re );
354 // QString d = s;
355 // d.replace("\t", "\\t");
356 // kDebug() << "SbdThread::parseSsmlNode: parsedPlainText = [" << d << "]";
357 QStringList sentenceList = s.split( '\t', QString::SkipEmptyParts);
358 int lastNdx = sentenceList.count() - 1;
359 for ( int ndx=0; ndx < lastNdx; ++ndx )
361 result += startSentence();
362 result += makeSentence( sentenceList[ndx] );
363 result += endSentence();
365 // Only output sentence boundary if last text fragment ended a sentence.
366 if ( lastNdx >= 0 )
368 result += startSentence();
369 result += makeSentence( sentenceList[lastNdx] );
370 if ( s.endsWith( '\t' ) ) result += endSentence();
372 break; }
373 case QDomNode::CDATASectionNode: { // = 4
374 QString s = parsePlainText( n.toCDATASection().data(), re );
375 QStringList sentenceList = s.split( '\t', QString::SkipEmptyParts);
376 int lastNdx = sentenceList.count() - 1;
377 for ( int ndx=0; ndx < lastNdx; ++ndx )
379 result += startSentence();
380 result += makeSentence( makeCDATA( sentenceList[ndx] ) );
381 result += endSentence();
383 // Only output sentence boundary if last text fragment ended a sentence.
384 if ( lastNdx >= 0 )
386 result += startSentence();
387 result += makeSentence( makeCDATA( sentenceList[lastNdx] ) );
388 if ( s.endsWith( '\t' ) ) result += endSentence();
390 break; }
391 case QDomNode::EntityReferenceNode: { // = 5
392 break; }
393 case QDomNode::EntityNode: { // = 6
394 break; }
395 case QDomNode::ProcessingInstructionNode: { // = 7
396 break; }
397 case QDomNode::CommentNode: { // = 8
398 break; }
399 case QDomNode::DocumentNode: { // = 9
400 break; }
401 case QDomNode::DocumentTypeNode: { // = 10
402 break; }
403 case QDomNode::DocumentFragmentNode: { // = 11
404 break; }
405 case QDomNode::NotationNode: { // = 12
406 break; }
407 case QDomNode::BaseNode: { // = 21
408 break; }
409 case QDomNode::CharacterDataNode: { // = 22
410 break; }
412 return result;
415 // Parses Ssml.
416 QString SbdThread::parseSsml( const QString& inputText, const QString& re )
418 QRegExp sentenceDelimiter = QRegExp( re );
420 // Read the text into xml dom tree.
421 QDomDocument doc( "" );
422 // If an error occurs parsing the SSML, return "invalid S S M L".
423 if ( !doc.setContent( inputText ) ) return i18n("Invalid S S M L.");
425 // Set up context stacks and set defaults for all element attributes.
426 m_speakStack.clear();
427 m_voiceStack.clear();
428 m_prosodyStack.clear();
429 m_emphasisStack.clear();
430 m_psStack.clear();
431 SpeakElem se = { "" };
432 m_speakStack.push ( se );
433 VoiceElem ve = {"", "neutral", 40, "", ""};
434 m_voiceStack.push( ve );
435 ProsodyElem pe = { "medium", "", "medium", "medium", "", "medium" };
436 m_prosodyStack.push( pe );
437 EmphasisElem em = { "" };
438 m_emphasisStack.push( em );
439 PSElem pse = { "" };
440 m_psStack.push ( pse );
442 // This flag is used to close out a previous sentence.
443 m_sentenceStarted = false;
445 // Get the root element (speak) and recursively process its children.
446 QDomElement docElem = doc.documentElement();
447 QDomNode n = docElem.firstChild();
448 QString ssml = parseSsmlNode( docElem, re );
450 // Close out last sentence.
451 if ( m_sentenceStarted ) ssml += "</speak>";
453 return ssml;
456 // Parses code. Each newline is converted into a tab character (\t).
457 QString SbdThread::parseCode( const QString& inputText )
459 QString temp = inputText;
460 // Replace newlines with tabs.
461 temp.replace('\n','\t');
462 // Remove leading spaces.
463 temp.replace(QRegExp("\\t +"), "\t");
464 // Remove trailing spaces.
465 temp.replace(QRegExp(" +\\t"), "\t");
466 // Remove blank lines.
467 temp.replace(QRegExp("\t\t+"),"\t");
468 return temp;
471 // Parses plain text.
472 QString SbdThread::parsePlainText( const QString& inputText, const QString& re )
474 // kDebug() << "SbdThread::parsePlainText: parsing " << inputText << " with re " << re;
475 QRegExp sentenceDelimiter = QRegExp( re );
476 QString temp = inputText;
477 // Replace sentence delimiters with tab.
478 temp.replace(sentenceDelimiter, m_configuredSentenceBoundary);
479 // Replace remaining newlines with spaces.
480 temp.replace('\n',' ');
481 temp.replace('\r',' ');
482 // Remove leading spaces.
483 temp.replace(QRegExp("\\t +"), "\t");
484 // Remove trailing spaces.
485 temp.replace(QRegExp(" +\\t"), "\t");
486 // Remove blank lines.
487 temp.replace(QRegExp("\t\t+"),"\t");
488 // kDebug() << "SbdThread::parsePlainText: returning " << temp;
489 return temp;
492 // This is where the real work takes place.
493 /*virtual*/ void SbdThread::run()
495 // kDebug() << "SbdThread::run: processing text = " << m_text;
497 // TODO: Determine if we should do anything or not.
498 m_wasModified = true;
500 // Determine what kind of input text we are dealing with.
501 int textType;
502 if ( KttsUtils::hasRootElement( m_text, "speak" ) )
503 textType = ttSsml;
504 else
506 // Examine just the first 500 chars to see if it is code.
507 QString p = m_text.left( 500 );
508 if ( p.contains( QRegExp( "(/\\*)|(if\\b\\()|(^#include\\b)" ) ) )
509 textType = ttCode;
510 else
511 textType = ttPlain;
514 // If application specified a sentence delimiter regular expression, use that,
515 // otherwise use configured default.
516 QString re = m_re;
517 if ( re.isEmpty() ) re = m_configuredRe;
519 // Replace spaces, tabs, and formfeeds with a single space.
520 m_text.replace(QRegExp("[ \\t\\f]+"), " ");
522 // kDebug() << "SbdThread::run: textType = " << textType;
524 // Perform the filtering based on type of text.
525 switch ( textType )
527 case ttSsml:
528 m_text = parseSsml( m_text, re );
529 break;
531 case ttCode:
532 m_text = parseCode( m_text );
533 break;
535 case ttPlain:
536 m_text = parsePlainText( m_text, re);
537 break;
540 // Clear app-specified sentence delimiter. App must call setSbRegExp for each conversion.
541 m_re.clear();
543 // kDebug() << "SbdThread::run: filtered text = " << m_text;
545 // Result is in m_text;
547 // Post an event. We need to emit filterFinished signal, but not from the
548 // separate thread.
549 QEvent* ev = new QEvent(QEvent::Type(QEvent::User + 301));
550 QApplication::postEvent(this, ev);
553 bool SbdThread::event ( QEvent * e )
555 if ( e->type() == (QEvent::User + 301) )
557 // kDebug() << "SbdThread::event: emitting filteringFinished signal.";
558 emit filteringFinished();
559 return true;
561 else return false;
564 // ----------------------------------------------------------------------------
567 * Constructor.
569 SbdProc::SbdProc( QObject *parent, const QVariantList& args) :
570 KttsFilterProc(parent, args)
572 // kDebug() << "SbdProc::SbdProc: Running";
573 m_sbdThread = new SbdThread(this);
574 connect( m_sbdThread, SIGNAL(filteringFinished()), this, SLOT(slotSbdThreadFilteringFinished()) );
578 * Destructor.
580 SbdProc::~SbdProc()
582 // kDebug() << "SbdProc::~SbdProc: Running";
583 if ( m_sbdThread )
585 if ( m_sbdThread->isRunning() )
587 m_sbdThread->terminate();
588 m_sbdThread->wait();
590 delete m_sbdThread;
595 * Initialize the filter.
596 * @param config Settings object.
597 * @param configGroup Settings Group.
598 * @return False if filter is not ready to filter.
600 * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain
601 * separate configuration files of their own.
603 bool SbdProc::init(KConfig* c, const QString& configGroup){
604 // kDebug() << "SbdProc::init: Running";
605 KConfigGroup config(c, configGroup );
606 // m_configuredRe = config.readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])\\s|(\\n *\\n)" );
607 m_configuredRe = config.readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))" );
608 m_sbdThread->setConfiguredSbRegExp( m_configuredRe );
609 QString sb = config.readEntry( "SentenceBoundary", "\\1\t" );
610 sb.replace( "\\t", "\t" );
611 m_sbdThread->setConfiguredSentenceBoundary( sb );
612 m_appIdList = config.readEntry( "AppID", QStringList() );
613 m_languageCodeList = config.readEntry( "LanguageCodes", QStringList() );
614 return true;
618 * Returns True if this filter is a Sentence Boundary Detector.
619 * If so, the filter should implement @ref setSbRegExp() .
620 * @return True if this filter is a SBD.
622 /*virtual*/ bool SbdProc::isSBD() { return true; }
625 * Returns True if the plugin supports asynchronous processing,
626 * i.e., supports asyncConvert method.
627 * @return True if this plugin supports asynchronous processing.
629 * If the plugin returns True, it must also implement @ref getState .
630 * It must also emit @ref filteringFinished when filtering is completed.
631 * If the plugin returns True, it must also implement @ref stopFiltering .
632 * It must also emit @ref filteringStopped when filtering has been stopped.
634 /*virtual*/ bool SbdProc::supportsAsync() { return true; }
637 * Convert input, returning output. Runs synchronously.
638 * @param inputText Input text.
639 * @param talkerCode TalkerCode structure for the talker that KTTSD intends to
640 * use for synthing the text. Useful for extracting hints about
641 * how to filter the text. For example, languageCode.
642 * @param appId The DCOP appId of the application that queued the text.
643 * Also useful for hints about how to do the filtering.
645 /*virtual*/ QString SbdProc::convert(const QString& inputText, TalkerCode* talkerCode,
646 const QString& appId)
648 if ( asyncConvert( inputText, talkerCode, appId) )
650 waitForFinished();
651 // kDebug() << "SbdProc::convert: returning " << getOutput();
652 return getOutput();
653 } else return inputText;
657 * Convert input. Runs asynchronously.
658 * @param inputText Input text.
659 * @param talkerCode TalkerCode structure for the talker that KTTSD intends to
660 * use for synthing the text. Useful for extracting hints about
661 * how to filter the text. For example, languageCode.
662 * @param appId The DCOP appId of the application that queued the text.
663 * Also useful for hints about how to do the filtering.
664 * @return False if the filter cannot perform the conversion.
666 * When conversion is completed, emits signal @ref filteringFinished. Calling
667 * program may then call @ref getOutput to retrieve converted text. Calling
668 * program must call @ref ackFinished to acknowledge the conversion.
670 /*virtual*/ bool SbdProc::asyncConvert(const QString& inputText, TalkerCode* talkerCode,
671 const QString& appId)
673 // kDebug() << "SbdProc::asyncConvert: running";
674 m_sbdThread->setWasModified( false );
675 // If language doesn't match, return input unmolested.
676 if ( !m_languageCodeList.isEmpty() )
678 QString languageCode = talkerCode->languageCode();
679 // kDebug() << "StringReplacerProc::convert: converting " << inputText <<
680 // " if language code " << languageCode << " matches " << m_languageCodeList << endl;
681 if ( !m_languageCodeList.contains( languageCode ) )
683 if ( !talkerCode->countryCode().isEmpty() )
685 languageCode += '_' + talkerCode->countryCode();
686 // kDebug() << "StringReplacerProc::convert: converting " << inputText <<
687 // " if language code " << languageCode << " matches " << m_languageCodeList << endl;
688 if ( !m_languageCodeList.contains( languageCode ) ) return false;
689 } else return false;
692 // If appId doesn't match, return input unmolested.
693 if ( !m_appIdList.isEmpty() )
695 // kDebug() << "SbdProc::convert: converting " << inputText << " if appId "
696 // << appId << " matches " << m_appIdList << endl;
697 bool found = false;
698 QString appIdStr = appId;
699 for ( int ndx=0; ndx < m_appIdList.count(); ++ndx )
701 if ( appIdStr.contains(m_appIdList[ndx]) )
703 found = true;
704 break;
707 if ( !found ) return false;
709 m_sbdThread->setText( inputText );
710 m_sbdThread->setTalkerCode( talkerCode );
711 m_state = fsFiltering;
712 m_sbdThread->start();
713 return true;
717 * Waits for a previous call to asyncConvert to finish.
719 /*virtual*/ void SbdProc::waitForFinished()
721 if ( m_sbdThread->isRunning() )
723 // kDebug() << "SbdProc::waitForFinished: waiting";
724 m_sbdThread->wait();
725 // kDebug() << "SbdProc::waitForFinished: finished waiting";
726 m_state = fsFinished;
731 * Returns the state of the Filter.
733 /*virtual*/ int SbdProc::getState() { return m_state; }
736 * Returns the filtered output.
738 /*virtual*/ QString SbdProc::getOutput() { return m_sbdThread->text(); }
741 * Acknowledges the finished filtering.
743 /*virtual*/ void SbdProc::ackFinished()
745 m_state = fsIdle;
746 m_sbdThread->setText( QString() );
750 * Stops filtering. The filteringStopped signal will emit when filtering
751 * has in fact stopped and state returns to fsIdle;
753 /*virtual*/ void SbdProc::stopFiltering()
755 if ( m_sbdThread->isRunning() )
757 m_sbdThread->terminate();
758 m_sbdThread->wait();
759 delete m_sbdThread;
760 m_sbdThread = new SbdThread(this);
761 m_sbdThread->setConfiguredSbRegExp( m_configuredRe );
762 connect( m_sbdThread, SIGNAL(filteringFinished()), this, SLOT(slotSbdThreadFilteringFinished()) );
763 m_state = fsIdle;
764 emit filteringStopped();
769 * Did this filter do anything? If the filter returns the input as output
770 * unmolested, it should return False when this method is called.
772 /*virtual*/ bool SbdProc::wasModified() { return m_sbdThread->wasModified(); }
775 * Set Sentence Boundary Regular Expression.
776 * This method will only be called if the application overrode the default.
778 * @param re The sentence delimiter regular expression.
780 /*virtual*/ void SbdProc::setSbRegExp(const QString& re) { m_sbdThread->setSbRegExp( re ); }
782 // Received when SBD Thread finishes.
783 void SbdProc::slotSbdThreadFilteringFinished()
785 m_state = fsFinished;
786 // kDebug() << "SbdProc::slotSbdThreadFilteringFinished: emitting filterFinished signal.";
787 emit filteringFinished();
790 #include "sbdproc.moc"