delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / khelpcenter / searchengine.cpp
blob89600a700721b31b37a30188bfa2b0a10f639ad3
1 #include "searchengine.h"
3 #include "stdlib.h"
5 #include <QTextDocument>
6 #include <KApplication>
7 #include <KConfig>
8 #include <KDebug>
9 #include <KStandardDirs>
10 #include <KProcess>
11 #include <KShell>
12 #include <KLocale>
13 #include <KMessageBox>
15 #include "docmetainfo.h"
16 #include "formatter.h"
17 #include "view.h"
18 #include "searchhandler.h"
19 #include "prefs.h"
21 namespace KHC
24 SearchTraverser::SearchTraverser( SearchEngine *engine, int level ) :
25 mMaxLevel( 999 ), mEngine( engine), mLevel( level )
27 #if 0
28 kDebug() << "SearchTraverser(): " << mLevel
29 << " 0x" << QString::number( int( this ), 16 ) << endl;
30 #endif
33 SearchTraverser::~SearchTraverser()
35 #if 0
36 kDebug() << "~SearchTraverser(): " << mLevel
37 << " 0x" << QString::number( int( this ), 16 ) << endl;
38 #endif
40 QString section;
41 if ( parentEntry() ) {
42 section = parentEntry()->name();
43 } else {
44 section = ("Unknown Section");
47 if ( !mResult.isEmpty() ) {
48 mEngine->view()->writeSearchResult(
49 mEngine->formatter()->sectionHeader( section ) );
50 mEngine->view()->writeSearchResult( mResult );
54 void SearchTraverser::process( DocEntry * )
56 kDebug() << "SearchTraverser::process()";
59 void SearchTraverser::startProcess( DocEntry *entry )
61 // kDebug() << "SearchTraverser::startProcess(): " << entry->name() << " "
62 // << "SEARCH: '" << entry->search() << "'" << endl;
64 if ( !mEngine->canSearch( entry ) || !entry->searchEnabled() ) {
65 mNotifyee->endProcess( entry, this );
66 return;
69 // kDebug() << "SearchTraverser::startProcess(): " << entry->identifier()
70 // << endl;
72 SearchHandler *handler = mEngine->handler( entry->documentType() );
74 if ( !handler ) {
75 QString txt;
76 if ( entry->documentType().isEmpty() ) {
77 txt = i18n("Error: No document type specified.");
78 } else {
79 txt = i18n("Error: No search handler for document type '%1'.",
80 entry->documentType() );
82 showSearchError( handler, entry, txt );
83 return;
86 connectHandler( handler );
88 handler->search( entry, mEngine->words(), mEngine->maxResults(),
89 mEngine->operation() );
91 // kDebug() << "SearchTraverser::startProcess() done: " << entry->name();
94 void SearchTraverser::connectHandler( SearchHandler *handler )
96 QMap<SearchHandler *,int>::Iterator it;
97 it = mConnectCount.find( handler );
98 int count = 0;
99 if ( it != mConnectCount.end() ) count = *it;
100 if ( count == 0 ) {
101 connect( handler, SIGNAL( searchError( SearchHandler *, DocEntry *, const QString & ) ),
102 SLOT( showSearchError( SearchHandler *, DocEntry *, const QString & ) ) );
103 connect( handler, SIGNAL( searchFinished( SearchHandler *, DocEntry *, const QString & ) ),
104 SLOT( showSearchResult( SearchHandler *, DocEntry *, const QString & ) ) );
106 mConnectCount[ handler ] = ++count;
109 void SearchTraverser::disconnectHandler( SearchHandler *handler )
111 QMap<SearchHandler *,int>::Iterator it;
112 it = mConnectCount.find( handler );
113 if ( it == mConnectCount.end() ) {
114 kError() << "SearchTraverser::disconnectHandler() handler not connected."
115 << endl;
116 } else {
117 int count = *it;
118 --count;
119 if ( count == 0 ) {
120 disconnect( handler, SIGNAL( searchError( SearchHandler *, DocEntry *, const QString & ) ),
121 this, SLOT( showSearchError( SearchHandler *, DocEntry *, const QString & ) ) );
122 disconnect( handler, SIGNAL( searchFinished( SearchHandler *, DocEntry *, const QString & ) ),
123 this, SLOT( showSearchResult( SearchHandler *, DocEntry *, const QString & ) ) );
125 mConnectCount[ handler ] = count;
129 DocEntryTraverser *SearchTraverser::createChild( DocEntry *parentEntry )
131 // kDebug() << "SearchTraverser::createChild() level " << mLevel;
133 if ( mLevel >= mMaxLevel ) {
134 ++mLevel;
135 return this;
136 } else {
137 DocEntryTraverser *t = new SearchTraverser( mEngine, mLevel + 1 );
138 t->setParentEntry( parentEntry );
139 return t;
143 DocEntryTraverser *SearchTraverser::parentTraverser()
145 // kDebug() << "SearchTraverser::parentTraverser(): level: " << mLevel;
147 if ( mLevel > mMaxLevel ) {
148 return this;
149 } else {
150 return mParent;
154 void SearchTraverser::deleteTraverser()
156 // kDebug() << "SearchTraverser::deleteTraverser()";
158 if ( mLevel > mMaxLevel ) {
159 --mLevel;
160 } else {
161 delete this;
165 void SearchTraverser::showSearchError( SearchHandler *handler, DocEntry *entry, const QString &error )
167 // kDebug() << "SearchTraverser::showSearchError(): " << entry->name()
168 // << endl;
170 mResult += mEngine->formatter()->docTitle( entry->name() );
171 mResult += mEngine->formatter()->paragraph( error );
173 mEngine->logError( entry, error );
175 disconnectHandler( handler );
177 mNotifyee->endProcess( entry, this );
180 void SearchTraverser::showSearchResult( SearchHandler *handler, DocEntry *entry, const QString &result )
182 // kDebug() << "SearchTraverser::showSearchResult(): " << entry->name()
183 // << endl;
185 mResult += mEngine->formatter()->docTitle( entry->name() );
186 mResult += mEngine->formatter()->processResult( result );
188 disconnectHandler( handler );
190 mNotifyee->endProcess( entry, this );
193 void SearchTraverser::finishTraversal()
195 // kDebug() << "SearchTraverser::finishTraversal()";
197 mEngine->view()->writeSearchResult( mEngine->formatter()->footer() );
198 mEngine->view()->endSearchResult();
200 mEngine->finishSearch();
204 SearchEngine::SearchEngine( View *destination )
205 : QObject(),
206 mProc( 0 ), mSearchRunning( false ), mView( destination ),
207 mRootTraverser( 0 )
209 mLang = KGlobal::locale()->language().left( 2 );
212 SearchEngine::~SearchEngine()
214 delete mRootTraverser;
217 bool SearchEngine::initSearchHandlers()
219 const QStringList resources = KGlobal::dirs()->findAllResources(
220 "appdata", "searchhandlers/*.desktop" );
221 QStringList::ConstIterator it;
222 for( it = resources.constBegin(); it != resources.constEnd(); ++it ) {
223 QString filename = *it;
224 kDebug() << "SearchEngine::initSearchHandlers(): " << filename;
225 SearchHandler *handler = SearchHandler::initFromFile( filename );
226 if ( !handler || !handler->checkPaths() ) {
227 QString txt = i18n("Unable to initialize SearchHandler from file '%1'.",
228 filename );
229 kWarning() << txt ;
230 // KMessageBox::sorry( mView->widget(), txt );
231 } else {
232 QStringList documentTypes = handler->documentTypes();
233 QStringList::ConstIterator it;
234 for( it = documentTypes.constBegin(); it != documentTypes.constEnd(); ++it ) {
235 mHandlers.insert( *it, handler );
240 if ( mHandlers.isEmpty() ) {
241 QString txt = i18n("No valid search handler found.");
242 kWarning() << txt ;
243 // KMessageBox::sorry( mView->widget(), txt );
244 return false;
247 return true;
250 void SearchEngine::searchExited(int exitCode, QProcess::ExitStatus exitStatus)
252 kDebug() << "Search terminated";
253 mSearchRunning = false;
256 bool SearchEngine::search( const QString & words, const QString & method, int matches,
257 const QString & scope )
259 if ( mSearchRunning ) return false;
261 // These should be removed
262 mWords = words;
263 mMethod = method;
264 mMatches = matches;
265 mScope = scope;
267 // Saner variables to store search parameters:
268 mWordList = words.split( " ");
269 mMaxResults = matches;
270 if ( method == "or" ) mOperation = Or;
271 else mOperation = And;
273 KConfigGroup cfg(KGlobal::config(), "Search");
274 QString commonSearchProgram = cfg.readPathEntry( "CommonProgram", QString() );
275 bool useCommon = cfg.readEntry( "UseCommonProgram", false);
277 if ( commonSearchProgram.isEmpty() || !useCommon ) {
278 if ( !mView ) {
279 return false;
282 QString txt = i18n("Search Results for '%1':", Qt::escape(words) );
284 mStderr = "<b>" + txt + "</b>\n";
286 mView->beginSearchResult();
287 mView->writeSearchResult( formatter()->header( i18n("Search Results") ) );
288 mView->writeSearchResult( formatter()->title( txt ) );
290 if ( mRootTraverser ) {
291 kDebug() << "SearchEngine::search(): mRootTraverser not null.";
292 return false;
294 mRootTraverser = new SearchTraverser( this, 0 );
295 DocMetaInfo::self()->startTraverseEntries( mRootTraverser );
297 return true;
298 } else {
299 QString lang = KGlobal::locale()->language().left(2);
301 if ( lang.toLower() == "c" || lang.toLower() == "posix" )
302 lang = "en";
304 // if the string contains '&' replace with a '+' and set search method to and
305 if (mWords.indexOf("&") != -1) {
306 mWords.replace("&", " ");
307 mMethod = "and";
310 // replace whitespace with a '+'
311 mWords = mWords.trimmed();
312 mWords = mWords.simplified();
313 mWords.replace(QRegExp("\\s"), "+");
315 commonSearchProgram = substituteSearchQuery( commonSearchProgram );
317 kDebug() << "Common Search: " << commonSearchProgram;
319 mProc = new KProcess();
320 *mProc << KShell::splitArgs(commonSearchProgram);
322 connect( mProc, SIGNAL( finished(int, QProcess::ExitStatus) ),
323 this, SLOT( searchExited(int, QProcess::ExitStatus) ) );
325 mSearchRunning = true;
326 mSearchResult = "";
327 mStderr = "<b>" + commonSearchProgram + "</b>\n\n";
329 mProc->start();
330 if (!mProc->waitForStarted()) {
331 kError() << "could not start search program '" << commonSearchProgram
332 << "'" << endl;
333 delete mProc;
334 return false;
337 while (mSearchRunning && mProc->state() == QProcess::Running)
338 kapp->processEvents();
340 // no need to use signals/slots
341 mStderr += mProc->readAllStandardError();
342 mSearchResult += mProc->readAllStandardOutput();
344 if ( mProc->exitStatus() == KProcess::CrashExit || mProc->exitCode() != 0 ) {
345 kError() << "Unable to run search program '" << commonSearchProgram
346 << "'" << endl;
347 delete mProc;
349 return false;
352 delete mProc;
354 // modify the search result
355 mSearchResult = mSearchResult.replace("http://localhost/", "file:/");
356 mSearchResult = mSearchResult.mid( mSearchResult.indexOf( '<' ) );
358 mView->beginSearchResult();
359 mView->writeSearchResult( mSearchResult );
360 mView->endSearchResult();
362 emit searchFinished();
365 return true;
368 QString SearchEngine::substituteSearchQuery( const QString &query )
370 QString result = query;
371 result.replace( QLatin1String("%k"), mWords );
372 result.replace( QLatin1String("%n"), QString::number( mMatches ) );
373 result.replace( QLatin1String("%m"), mMethod );
374 result.replace( QLatin1String("%l"), mLang );
375 result.replace( QLatin1String("%s"), mScope );
377 return result;
380 QString SearchEngine::substituteSearchQuery( const QString &query,
381 const QString &identifier, const QStringList &words, int maxResults,
382 Operation operation, const QString &lang )
384 QString result = query;
385 result.replace( QLatin1String("%i"), identifier );
386 result.replace( QLatin1String("%w"), words.join( "+" ) );
387 result.replace( QLatin1String("%m"), QString::number( maxResults ) );
388 QString o = QLatin1String(operation == Or ? "or" : "and");
389 result.replace( QLatin1String("%o"), o );
390 result.replace( QLatin1String("%d"), Prefs::indexDirectory() );
391 result.replace( QLatin1String("%l"), lang );
393 return result;
396 Formatter *SearchEngine::formatter() const
398 return mView->formatter();
401 View *SearchEngine::view() const
403 return mView;
406 void SearchEngine::finishSearch()
408 delete mRootTraverser;
409 mRootTraverser = 0;
411 emit searchFinished();
414 QString SearchEngine::errorLog() const
416 return mStderr;
419 void SearchEngine::logError( DocEntry *entry, const QString &msg )
421 mStderr += entry->identifier() + QLatin1String(": ") + msg;
424 bool SearchEngine::isRunning() const
426 return mSearchRunning;
429 SearchHandler *SearchEngine::handler( const QString &documentType ) const
431 return mHandlers.value( documentType, 0 );
434 QStringList SearchEngine::words() const
436 return mWordList;
439 int SearchEngine::maxResults() const
441 return mMaxResults;
444 SearchEngine::Operation SearchEngine::operation() const
446 return mOperation;
449 bool SearchEngine::canSearch( DocEntry *entry )
451 return entry->docExists() && !entry->documentType().isEmpty() &&
452 handler( entry->documentType() );
455 bool SearchEngine::needsIndex( DocEntry *entry )
457 if ( !canSearch( entry ) ) return false;
459 SearchHandler *h = handler( entry->documentType() );
460 if ( !h || h->indexCommand( entry->identifier() ).isEmpty() ) return false;
462 return true;
467 #include "searchengine.moc"
469 // vim:ts=2:sw=2:et