1 /***************************************************** vim:set ts=4 sw=4 sts=4:
2 Sentence Boundary Detection Filter class.
5 (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net>
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 ******************************************************************************/
25 #include <QtCore/QRegExp>
26 #include <QtXml/qdom.h>
27 #include <QtGui/QApplication>
28 #include <QtCore/QCustomEvent>
37 #include "talkercode.h"
45 SbdThread::SbdThread( QObject
*parent
) :
53 /*virtual*/ SbdThread::~SbdThread()
58 * Get/Set text being processed.
60 void SbdThread::setText( const QString
& text
) { m_text
= text
; }
61 QString
SbdThread::text() { return m_text
; }
66 void SbdThread::setTalkerCode( TalkerCode
* talkerCode
) { m_talkerCode
= talkerCode
; }
67 TalkerCode
* SbdThread::talkerCode() { return m_talkerCode
; }
70 * Set Sentence Boundary Regular Expression.
71 * This method will only be called if the application overrode the default.
73 * @param re The sentence delimiter regular expression.
75 void SbdThread::setSbRegExp( const QString
& re
) { m_re
= re
; }
78 * The configured Sentence Boundary Regular Expression.
80 * @param re The sentence delimiter regular expression.
82 void SbdThread::setConfiguredSbRegExp( const QString
& re
) { m_configuredRe
= re
; }
85 * The configured Sentence Boundary that replaces SB regular expression.
87 * @param sb The sentence boundary replacement.
90 void SbdThread::setConfiguredSentenceBoundary( const QString
& sb
) { m_configuredSentenceBoundary
= sb
; }
93 * Did this filter do anything? If the filter returns the input as output
94 * unmolested, it should return False when this method is called.
96 void SbdThread::setWasModified(bool wasModified
) { m_wasModified
= wasModified
; }
97 bool SbdThread::wasModified() { return m_wasModified
; }
99 // Given a tag name, returns SsmlElemType.
100 SbdThread::SsmlElemType
SbdThread::tagToSsmlElemType( const QString tagName
)
102 if ( tagName
== "speak" ) return etSpeak
;
103 if ( tagName
== "voice" ) return etVoice
;
104 if ( tagName
== "prosody" ) return etProsody
;
105 if ( tagName
== "emphasis" ) return etEmphasis
;
106 if ( tagName
== "break" ) return etBreak
;
107 if ( tagName
== "s" ) return etPS
;
108 if ( tagName
== "p" ) return etPS
;
112 // Parses an SSML element, pushing current settings onto the context stack.
113 void SbdThread::pushSsmlElem( SsmlElemType et
, const QDomElement
& elem
)
115 // TODO: Need to convert relative values into absolute values and also convert
116 // only to values recognized by SSML2SABLE stylesheet. Either that or enhance all
117 // the synth stylesheets.
118 QDomNamedNodeMap attrList
= elem
.attributes();
119 int attrCount
= attrList
.count();
123 SpeakElem e
= m_speakStack
.top();
124 for ( int ndx
=0; ndx
< attrCount
; ++ndx
)
126 QDomAttr a
= attrList
.item( ndx
).toAttr();
127 if ( a
.name() == "lang" ) e
.lang
= a
.value();
129 m_speakStack
.push( e
);
132 VoiceElem e
= m_voiceStack
.top();
133 // TODO: Since Festival chokes on <voice> tags, don't output them at all.
134 // This means we can't support voice changes, and probably more irritatingly,
135 // gender changes either.
136 m_voiceStack
.push( e
);
139 ProsodyElem e
= m_prosodyStack
.top();
140 for ( int ndx
=0; ndx
< attrCount
; ++ndx
)
142 QDomAttr a
= attrList
.item( ndx
).toAttr();
143 if ( a
.name() == "pitch" ) e
.pitch
= a
.value();
144 if ( a
.name() == "contour" ) e
.contour
= a
.value();
145 if ( a
.name() == "range" ) e
.range
= a
.value();
146 if ( a
.name() == "rate" ) e
.rate
= a
.value();
147 if ( a
.name() == "duration" ) e
.duration
= a
.value();
148 if ( a
.name() == "volume" ) e
.volume
= a
.value();
150 m_prosodyStack
.push( e
);
153 EmphasisElem e
= m_emphasisStack
.top();
154 for ( int ndx
=0; ndx
< attrCount
; ++ndx
)
156 QDomAttr a
= attrList
.item( ndx
).toAttr();
157 if ( a
.name() == "level" ) e
.level
= a
.value();
159 m_emphasisStack
.push( e
);
162 PSElem e
= m_psStack
.top();
163 for ( int ndx
=0; ndx
< attrCount
; ++ndx
)
165 QDomAttr a
= attrList
.item( ndx
).toAttr();
166 if ( a
.name() == "lang" ) e
.lang
= a
.value();
174 // Given an attribute name and value, constructs an XML representation of the attribute,
175 // i.e., name="value".
176 QString
SbdThread::makeAttr( const QString
& name
, const QString
& value
)
178 if ( value
.isEmpty() ) return QString();
179 return " " + name
+ "=\"" + value
+ "\"";
182 // Returns an XML representation of an SSML tag from the top of the context stack.
183 QString
SbdThread::makeSsmlElem( SsmlElemType et
)
189 // Must always output speak tag, otherwise kttsd won't think each sentence is SSML.
190 // For all other tags, only output the tag if it contains at least one attribute.
192 SpeakElem e
= m_speakStack
.top();
194 if ( !e
.lang
.isEmpty() ) s
+= makeAttr( "lang", e
.lang
);
198 VoiceElem e
= m_voiceStack
.top();
199 a
+= makeAttr( "lang", e
.lang
);
200 a
+= makeAttr( "gender", e
.gender
);
201 a
+= makeAttr( "age", QString::number(e
.age
) );
202 a
+= makeAttr( "name", e
.name
);
203 a
+= makeAttr( "variant", e
.variant
);
204 if ( !a
.isEmpty() ) s
= "<voice" + a
+ '>';
207 ProsodyElem e
= m_prosodyStack
.top();
208 a
+= makeAttr( "pitch", e
.pitch
);
209 a
+= makeAttr( "contour", e
.contour
);
210 a
+= makeAttr( "range", e
.range
);
211 a
+= makeAttr( "rate", e
.rate
);
212 a
+= makeAttr( "duration", e
.duration
);
213 a
+= makeAttr( "volume", e
.volume
);
214 if ( !a
.isEmpty() ) s
= "<prosody" + a
+ '>';
217 EmphasisElem e
= m_emphasisStack
.top();
218 a
+= makeAttr( "level", e
.level
);
219 if ( !a
.isEmpty() ) s
= "<emphasis" + a
+ '>';
228 // Pops element from the indicated context stack.
229 void SbdThread::popSsmlElem( SsmlElemType et
)
233 case etSpeak
: m_speakStack
.pop(); break;
234 case etVoice
: m_voiceStack
.pop(); break;
235 case etProsody
: m_prosodyStack
.pop(); break;
236 case etEmphasis
: m_emphasisStack
.pop(); break;
237 case etPS
: m_psStack
.pop(); break;
242 // Returns an XML representation of a break element.
243 QString
SbdThread::makeBreakElem( const QDomElement
& e
)
245 QString s
= "<break";
246 QDomNamedNodeMap attrList
= e
.attributes();
247 int attrCount
= attrList
.count();
248 for ( int ndx
=0; ndx
< attrCount
; ++ndx
)
250 QDomAttr a
= attrList
.item( ndx
).toAttr();
251 s
+= makeAttr( a
.name(), a
.value() );
257 // Converts a text fragment into a CDATA section.
258 QString
SbdThread::makeCDATA( const QString
& text
)
260 QString s
= "<![CDATA[";
266 // Returns an XML representation of an utterance node consisting of voice,
267 // prosody, and emphasis elements.
268 QString
SbdThread::makeSentence( const QString
& text
)
271 QString v
= makeSsmlElem( etVoice
);
272 QString p
= makeSsmlElem( etProsody
);
273 QString e
= makeSsmlElem( etEmphasis
);
274 // TODO: Lang settings from psStack.
275 if ( !v
.isEmpty() ) s
+= v
;
276 if ( !p
.isEmpty() ) s
+= p
;
277 if ( !e
.isEmpty() ) s
+= e
;
278 // Escape ampersands and less thans.
279 QString newText
= text
;
280 newText
.replace(QRegExp("&(?!amp;)"), "&");
281 newText
.replace(QRegExp("<(?!lt;)"), "<");
283 if ( !e
.isEmpty() ) s
+= "</emphasis>";
284 if ( !p
.isEmpty() ) s
+= "</prosody>";
285 if ( !v
.isEmpty() ) s
+= "</voice>";
289 // Starts a sentence by returning a speak tag.
290 QString
SbdThread::startSentence()
292 if ( m_sentenceStarted
) return QString();
294 s
+= makeSsmlElem( etSpeak
);
295 m_sentenceStarted
= true;
299 // Ends a sentence and appends a Tab.
300 QString
SbdThread::endSentence()
302 if ( !m_sentenceStarted
) return QString();
303 QString s
= "</speak>";
305 m_sentenceStarted
= false;
309 // Parses a node of the SSML tree and recursively parses its children.
310 // Returns the filtered text with each sentence a complete ssml tree.
311 QString
SbdThread::parseSsmlNode( QDomNode
& n
, const QString
& re
)
314 switch ( n
.nodeType() )
316 case QDomNode::ElementNode
: { // = 1
317 QDomElement e
= n
.toElement();
318 QString tagName
= e
.tagName();
319 SsmlElemType et
= tagToSsmlElemType( tagName
);
328 pushSsmlElem( et
, e
);
329 QDomNode t
= n
.firstChild();
330 while ( !t
.isNull() )
332 result
+= parseSsmlNode( t
, re
);
337 result
+= endSentence();
342 // Break elements are empty.
343 result
+= makeBreakElem( e
);
345 // Ignore any elements we don't recognize.
349 case QDomNode::AttributeNode
: { // = 2
351 case QDomNode::TextNode
: { // = 3
352 QString s
= parsePlainText( n
.toText().data(), re
);
354 // d.replace("\t", "\\t");
355 // kDebug() << "SbdThread::parseSsmlNode: parsedPlainText = [" << d << "]";
356 QStringList sentenceList
= s
.split( '\t', QString::SkipEmptyParts
);
357 int lastNdx
= sentenceList
.count() - 1;
358 for ( int ndx
=0; ndx
< lastNdx
; ++ndx
)
360 result
+= startSentence();
361 result
+= makeSentence( sentenceList
[ndx
] );
362 result
+= endSentence();
364 // Only output sentence boundary if last text fragment ended a sentence.
367 result
+= startSentence();
368 result
+= makeSentence( sentenceList
[lastNdx
] );
369 if ( s
.endsWith( '\t' ) ) result
+= endSentence();
372 case QDomNode::CDATASectionNode
: { // = 4
373 QString s
= parsePlainText( n
.toCDATASection().data(), re
);
374 QStringList sentenceList
= s
.split( '\t', QString::SkipEmptyParts
);
375 int lastNdx
= sentenceList
.count() - 1;
376 for ( int ndx
=0; ndx
< lastNdx
; ++ndx
)
378 result
+= startSentence();
379 result
+= makeSentence( makeCDATA( sentenceList
[ndx
] ) );
380 result
+= endSentence();
382 // Only output sentence boundary if last text fragment ended a sentence.
385 result
+= startSentence();
386 result
+= makeSentence( makeCDATA( sentenceList
[lastNdx
] ) );
387 if ( s
.endsWith( '\t' ) ) result
+= endSentence();
390 case QDomNode::EntityReferenceNode
: { // = 5
392 case QDomNode::EntityNode
: { // = 6
394 case QDomNode::ProcessingInstructionNode
: { // = 7
396 case QDomNode::CommentNode
: { // = 8
398 case QDomNode::DocumentNode
: { // = 9
400 case QDomNode::DocumentTypeNode
: { // = 10
402 case QDomNode::DocumentFragmentNode
: { // = 11
404 case QDomNode::NotationNode
: { // = 12
406 case QDomNode::BaseNode
: { // = 21
408 case QDomNode::CharacterDataNode
: { // = 22
415 QString
SbdThread::parseSsml( const QString
& inputText
, const QString
& re
)
417 QRegExp sentenceDelimiter
= QRegExp( re
);
419 // Read the text into xml dom tree.
420 QDomDocument
doc( "" );
421 // If an error occurs parsing the SSML, return "invalid S S M L".
422 if ( !doc
.setContent( inputText
) ) return i18n("Invalid S S M L.");
424 // Set up context stacks and set defaults for all element attributes.
425 m_speakStack
.clear();
426 m_voiceStack
.clear();
427 m_prosodyStack
.clear();
428 m_emphasisStack
.clear();
430 SpeakElem se
= { "" };
431 m_speakStack
.push ( se
);
432 VoiceElem ve
= {"", "neutral", 40, "", ""};
433 m_voiceStack
.push( ve
);
434 ProsodyElem pe
= { "medium", "", "medium", "medium", "", "medium" };
435 m_prosodyStack
.push( pe
);
436 EmphasisElem em
= { "" };
437 m_emphasisStack
.push( em
);
439 m_psStack
.push ( pse
);
441 // This flag is used to close out a previous sentence.
442 m_sentenceStarted
= false;
444 // Get the root element (speak) and recursively process its children.
445 QDomElement docElem
= doc
.documentElement();
446 QDomNode n
= docElem
.firstChild();
447 QString ssml
= parseSsmlNode( docElem
, re
);
449 // Close out last sentence.
450 if ( m_sentenceStarted
) ssml
+= "</speak>";
455 // Parses code. Each newline is converted into a tab character (\t).
456 QString
SbdThread::parseCode( const QString
& inputText
)
458 QString temp
= inputText
;
459 // Replace newlines with tabs.
460 temp
.replace("\n","\t");
461 // Remove leading spaces.
462 temp
.replace(QRegExp("\\t +"), "\t");
463 // Remove trailing spaces.
464 temp
.replace(QRegExp(" +\\t"), "\t");
465 // Remove blank lines.
466 temp
.replace(QRegExp("\t\t+"),"\t");
470 // Parses plain text.
471 QString
SbdThread::parsePlainText( const QString
& inputText
, const QString
& re
)
473 // kDebug() << "SbdThread::parsePlainText: parsing " << inputText << " with re " << re;
474 QRegExp sentenceDelimiter
= QRegExp( re
);
475 QString temp
= inputText
;
476 // Replace sentence delimiters with tab.
477 temp
.replace(sentenceDelimiter
, m_configuredSentenceBoundary
);
478 // Replace remaining newlines with spaces.
479 temp
.replace("\n"," ");
480 temp
.replace("\r"," ");
481 // Remove leading spaces.
482 temp
.replace(QRegExp("\\t +"), "\t");
483 // Remove trailing spaces.
484 temp
.replace(QRegExp(" +\\t"), "\t");
485 // Remove blank lines.
486 temp
.replace(QRegExp("\t\t+"),"\t");
487 // kDebug() << "SbdThread::parsePlainText: returning " << temp;
491 // This is where the real work takes place.
492 /*virtual*/ void SbdThread::run()
494 // kDebug() << "SbdThread::run: processing text = " << m_text;
496 // TODO: Determine if we should do anything or not.
497 m_wasModified
= true;
499 // Determine what kind of input text we are dealing with.
501 if ( KttsUtils::hasRootElement( m_text
, "speak" ) )
505 // Examine just the first 500 chars to see if it is code.
506 QString p
= m_text
.left( 500 );
507 if ( p
.contains( QRegExp( "(/\\*)|(if\\b\\()|(^#include\\b)" ) ) )
513 // If application specified a sentence delimiter regular expression, use that,
514 // otherwise use configured default.
516 if ( re
.isEmpty() ) re
= m_configuredRe
;
518 // Replace spaces, tabs, and formfeeds with a single space.
519 m_text
.replace(QRegExp("[ \\t\\f]+"), " ");
521 // kDebug() << "SbdThread::run: textType = " << textType;
523 // Perform the filtering based on type of text.
527 m_text
= parseSsml( m_text
, re
);
531 m_text
= parseCode( m_text
);
535 m_text
= parsePlainText( m_text
, re
);
539 // Clear app-specified sentence delimiter. App must call setSbRegExp for each conversion.
542 // kDebug() << "SbdThread::run: filtered text = " << m_text;
544 // Result is in m_text;
546 // Post an event. We need to emit filterFinished signal, but not from the
548 QEvent
* ev
= new QEvent(QEvent::Type(QEvent::User
+ 301));
549 QApplication::postEvent(this, ev
);
552 bool SbdThread::event ( QEvent
* e
)
554 if ( e
->type() == (QEvent::User
+ 301) )
556 // kDebug() << "SbdThread::event: emitting filteringFinished signal.";
557 emit
filteringFinished();
563 // ----------------------------------------------------------------------------
568 SbdProc::SbdProc( QObject
*parent
, const QStringList
& /*args*/) :
569 KttsFilterProc(parent
)
571 // kDebug() << "SbdProc::SbdProc: Running";
572 m_sbdThread
= new SbdThread(this);
573 connect( m_sbdThread
, SIGNAL(filteringFinished()), this, SLOT(slotSbdThreadFilteringFinished()) );
581 // kDebug() << "SbdProc::~SbdProc: Running";
584 if ( m_sbdThread
->isRunning() )
586 m_sbdThread
->terminate();
594 * Initialize the filter.
595 * @param config Settings object.
596 * @param configGroup Settings Group.
597 * @return False if filter is not ready to filter.
599 * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain
600 * separate configuration files of their own.
602 bool SbdProc::init(KConfig
* c
, const QString
& configGroup
){
603 // kDebug() << "SbdProc::init: Running";
604 KConfigGroup
config(c
, configGroup
);
605 // m_configuredRe = config.readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])\\s|(\\n *\\n)" );
606 m_configuredRe
= config
.readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))" );
607 m_sbdThread
->setConfiguredSbRegExp( m_configuredRe
);
608 QString sb
= config
.readEntry( "SentenceBoundary", "\\1\t" );
609 sb
.replace( "\\t", "\t" );
610 m_sbdThread
->setConfiguredSentenceBoundary( sb
);
611 m_appIdList
= config
.readEntry( "AppID", QStringList(), ',' );
612 m_languageCodeList
= config
.readEntry( "LanguageCodes", QStringList(), ',' );
617 * Returns True if this filter is a Sentence Boundary Detector.
618 * If so, the filter should implement @ref setSbRegExp() .
619 * @return True if this filter is a SBD.
621 /*virtual*/ bool SbdProc::isSBD() { return true; }
624 * Returns True if the plugin supports asynchronous processing,
625 * i.e., supports asyncConvert method.
626 * @return True if this plugin supports asynchronous processing.
628 * If the plugin returns True, it must also implement @ref getState .
629 * It must also emit @ref filteringFinished when filtering is completed.
630 * If the plugin returns True, it must also implement @ref stopFiltering .
631 * It must also emit @ref filteringStopped when filtering has been stopped.
633 /*virtual*/ bool SbdProc::supportsAsync() { return true; }
636 * Convert input, returning output. Runs synchronously.
637 * @param inputText Input text.
638 * @param talkerCode TalkerCode structure for the talker that KTTSD intends to
639 * use for synthing the text. Useful for extracting hints about
640 * how to filter the text. For example, languageCode.
641 * @param appId The DCOP appId of the application that queued the text.
642 * Also useful for hints about how to do the filtering.
644 /*virtual*/ QString
SbdProc::convert(const QString
& inputText
, TalkerCode
* talkerCode
,
645 const QString
& appId
)
647 if ( asyncConvert( inputText
, talkerCode
, appId
) )
650 // kDebug() << "SbdProc::convert: returning " << getOutput();
652 } else return inputText
;
656 * Convert input. Runs asynchronously.
657 * @param inputText Input text.
658 * @param talkerCode TalkerCode structure for the talker that KTTSD intends to
659 * use for synthing the text. Useful for extracting hints about
660 * how to filter the text. For example, languageCode.
661 * @param appId The DCOP appId of the application that queued the text.
662 * Also useful for hints about how to do the filtering.
663 * @return False if the filter cannot perform the conversion.
665 * When conversion is completed, emits signal @ref filteringFinished. Calling
666 * program may then call @ref getOutput to retrieve converted text. Calling
667 * program must call @ref ackFinished to acknowledge the conversion.
669 /*virtual*/ bool SbdProc::asyncConvert(const QString
& inputText
, TalkerCode
* talkerCode
,
670 const QString
& appId
)
672 // kDebug() << "SbdProc::asyncConvert: running";
673 m_sbdThread
->setWasModified( false );
674 // If language doesn't match, return input unmolested.
675 if ( !m_languageCodeList
.isEmpty() )
677 QString languageCode
= talkerCode
->languageCode();
678 // kDebug() << "StringReplacerProc::convert: converting " << inputText <<
679 // " if language code " << languageCode << " matches " << m_languageCodeList << endl;
680 if ( !m_languageCodeList
.contains( languageCode
) )
682 if ( !talkerCode
->countryCode().isEmpty() )
684 languageCode
+= '_' + talkerCode
->countryCode();
685 // kDebug() << "StringReplacerProc::convert: converting " << inputText <<
686 // " if language code " << languageCode << " matches " << m_languageCodeList << endl;
687 if ( !m_languageCodeList
.contains( languageCode
) ) return false;
691 // If appId doesn't match, return input unmolested.
692 if ( !m_appIdList
.isEmpty() )
694 // kDebug() << "SbdProc::convert: converting " << inputText << " if appId "
695 // << appId << " matches " << m_appIdList << endl;
697 QString appIdStr
= appId
;
698 for ( int ndx
=0; ndx
< m_appIdList
.count(); ++ndx
)
700 if ( appIdStr
.contains(m_appIdList
[ndx
]) )
706 if ( !found
) return false;
708 m_sbdThread
->setText( inputText
);
709 m_sbdThread
->setTalkerCode( talkerCode
);
710 m_state
= fsFiltering
;
711 m_sbdThread
->start();
716 * Waits for a previous call to asyncConvert to finish.
718 /*virtual*/ void SbdProc::waitForFinished()
720 if ( m_sbdThread
->isRunning() )
722 // kDebug() << "SbdProc::waitForFinished: waiting";
724 // kDebug() << "SbdProc::waitForFinished: finished waiting";
725 m_state
= fsFinished
;
730 * Returns the state of the Filter.
732 /*virtual*/ int SbdProc::getState() { return m_state
; }
735 * Returns the filtered output.
737 /*virtual*/ QString
SbdProc::getOutput() { return m_sbdThread
->text(); }
740 * Acknowledges the finished filtering.
742 /*virtual*/ void SbdProc::ackFinished()
745 m_sbdThread
->setText( QString() );
749 * Stops filtering. The filteringStopped signal will emit when filtering
750 * has in fact stopped and state returns to fsIdle;
752 /*virtual*/ void SbdProc::stopFiltering()
754 if ( m_sbdThread
->isRunning() )
756 m_sbdThread
->terminate();
759 m_sbdThread
= new SbdThread(this);
760 m_sbdThread
->setConfiguredSbRegExp( m_configuredRe
);
761 connect( m_sbdThread
, SIGNAL(filteringFinished()), this, SLOT(slotSbdThreadFilteringFinished()) );
763 emit
filteringStopped();
768 * Did this filter do anything? If the filter returns the input as output
769 * unmolested, it should return False when this method is called.
771 /*virtual*/ bool SbdProc::wasModified() { return m_sbdThread
->wasModified(); }
774 * Set Sentence Boundary Regular Expression.
775 * This method will only be called if the application overrode the default.
777 * @param re The sentence delimiter regular expression.
779 /*virtual*/ void SbdProc::setSbRegExp(const QString
& re
) { m_sbdThread
->setSbRegExp( re
); }
781 // Received when SBD Thread finishes.
782 void SbdProc::slotSbdThreadFilteringFinished()
784 m_state
= fsFinished
;
785 // kDebug() << "SbdProc::slotSbdThreadFilteringFinished: emitting filterFinished signal.";
786 emit
filteringFinished();
789 #include "sbdproc.moc"