Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / runtime / khelpcenter / navigator.cpp
blobb3d90f4a3a6d400d7c70119976fc5d394ecfdc1b
1 /*
2 * This file is part of the KDE Help Center
4 * Copyright (C) 1999 Matthias Elter (me@kde.org)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "navigator.h"
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
27 #include <QDir>
28 #include <QFile>
29 #include <QPixmap>
31 #include <QLabel>
32 #include <Qt3Support/Q3Header>
33 #include <QtXml/QtXml>
34 #include <QTextStream>
35 #include <QRegExp>
36 #include <QLayout>
37 #include <QPushButton>
39 //Added by qt3to4:
40 #include <QFrame>
41 #include <QHBoxLayout>
42 #include <QBoxLayout>
43 #include <QVBoxLayout>
45 #include <kaction.h>
46 #include <kapplication.h>
47 #include <kconfig.h>
48 #include <kstandarddirs.h>
49 #include <kglobal.h>
50 #include <klocale.h>
51 #include <kdebug.h>
52 #include <k3listview.h>
53 #include <KLineEdit>
54 #include <kmessagebox.h>
55 #include <kiconloader.h>
56 #include <kcharsets.h>
57 #include <kdialog.h>
58 #include <kdesktopfile.h>
59 #include <kprotocolinfo.h>
60 #include <kservicegroup.h>
62 #include "navigatoritem.h"
63 #include "navigatorappitem.h"
64 #include "searchwidget.h"
65 #include "searchengine.h"
66 #include "docmetainfo.h"
67 #include "docentrytraverser.h"
68 #include "glossary.h"
69 #include "toc.h"
70 #include "view.h"
71 #include "infotree.h"
72 #include "mainwindow.h"
73 #include "plugintraverser.h"
74 #include "scrollkeepertreebuilder.h"
75 #include "kcmhelpcenter.h"
76 #include "formatter.h"
77 #include "history.h"
78 #include "prefs.h"
80 using namespace KHC;
82 Navigator::Navigator( View *view, QWidget *parent, const char *name )
83 : QWidget( parent ), mIndexDialog( 0 ),
84 mView( view ), mSelected( false )
86 setObjectName( name );
88 KConfigGroup config(KGlobal::config(), "General");
89 mShowMissingDocs = config.readEntry("ShowMissingDocs", false);
91 mSearchEngine = new SearchEngine( view );
92 connect( mSearchEngine, SIGNAL( searchFinished() ),
93 SLOT( slotSearchFinished() ) );
95 DocMetaInfo::self()->scanMetaInfo();
97 QBoxLayout *topLayout = new QVBoxLayout( this );
99 mSearchFrame = new QFrame( this );
100 topLayout->addWidget( mSearchFrame );
102 QBoxLayout *searchLayout = new QHBoxLayout( mSearchFrame );
103 searchLayout->setSpacing( KDialog::spacingHint() );
104 searchLayout->setMargin( 6 );
106 mSearchEdit = new KLineEdit( mSearchFrame );
107 mSearchEdit->setClearButtonShown(true);
108 searchLayout->addWidget( mSearchEdit );
109 connect( mSearchEdit, SIGNAL( returnPressed() ), SLOT( slotSearch() ) );
110 connect( mSearchEdit, SIGNAL( textChanged( const QString & ) ),
111 SLOT( checkSearchButton() ) );
113 mSearchButton = new QPushButton( i18n("&Search"), mSearchFrame );
114 searchLayout->addWidget( mSearchButton );
115 connect( mSearchButton, SIGNAL( clicked() ), SLOT( slotSearch() ) );
117 mTabWidget = new QTabWidget( this );
118 topLayout->addWidget( mTabWidget );
120 setupContentsTab();
121 setupGlossaryTab();
122 setupSearchTab();
124 insertPlugins();
126 if ( !mSearchEngine->initSearchHandlers() ) {
127 hideSearch();
128 } else {
129 mSearchWidget->updateScopeList();
130 mSearchWidget->readConfig( KGlobal::config().data() );
132 connect( mTabWidget, SIGNAL( currentChanged( QWidget * ) ),
133 SLOT( slotTabChanged( QWidget * ) ) );
136 Navigator::~Navigator()
138 delete mSearchEngine;
141 SearchEngine *Navigator::searchEngine() const
143 return mSearchEngine;
146 Formatter *Navigator::formatter() const
148 return mView->formatter();
151 bool Navigator::showMissingDocs() const
153 return mShowMissingDocs;
156 void Navigator::setupContentsTab()
158 mContentsTree = new K3ListView( mTabWidget );
159 mContentsTree->setFrameStyle( QFrame::NoFrame );
160 mContentsTree->addColumn(QString());
161 mContentsTree->setAllColumnsShowFocus(true);
162 mContentsTree->header()->hide();
163 mContentsTree->setRootIsDecorated(false);
164 mContentsTree->setSorting(-1, false);
166 connect(mContentsTree, SIGNAL(clicked(Q3ListViewItem*)),
167 SLOT(slotItemSelected(Q3ListViewItem*)));
168 connect(mContentsTree, SIGNAL(returnPressed(Q3ListViewItem*)),
169 SLOT(slotItemSelected(Q3ListViewItem*)));
170 mTabWidget->addTab(mContentsTree, i18n("&Contents"));
173 void Navigator::setupSearchTab()
175 mSearchWidget = new SearchWidget( mSearchEngine, mTabWidget );
176 connect( mSearchWidget, SIGNAL( searchResult( const QString & ) ),
177 SLOT( slotShowSearchResult( const QString & ) ) );
178 connect( mSearchWidget, SIGNAL( scopeCountChanged( int ) ),
179 SLOT( checkSearchButton() ) );
180 connect( mSearchWidget, SIGNAL( showIndexDialog() ),
181 SLOT( showIndexDialog() ) );
183 mTabWidget->addTab( mSearchWidget, i18n("Search Options"));
186 void Navigator::setupGlossaryTab()
188 mGlossaryTree = new Glossary( mTabWidget );
189 connect( mGlossaryTree, SIGNAL( entrySelected( const GlossaryEntry & ) ),
190 this, SIGNAL( glossSelected( const GlossaryEntry & ) ) );
191 mTabWidget->addTab( mGlossaryTree, i18n( "G&lossary" ) );
194 void Navigator::insertPlugins()
196 PluginTraverser t( this, mContentsTree );
197 DocMetaInfo::self()->traverseEntries( &t );
199 #if 0
200 kDebug( 1400 ) << "<docmetainfo>";
201 DocEntry::List entries = DocMetaInfo::self()->docEntries();
202 DocEntry::List::ConstIterator it;
203 for( it = entries.begin(); it != entries.end(); ++it ) {
204 (*it)->dump();
206 kDebug( 1400 ) << "</docmetainfo>";
207 #endif
210 void Navigator::insertParentAppDocs( const QString &name, NavigatorItem *topItem )
212 kDebug(1400) << "Requested plugin documents for ID " << name;
214 KServiceGroup::Ptr grp = KServiceGroup::childGroup( name );
215 if ( !grp )
216 return;
218 KServiceGroup::List entries = grp->entries();
219 KServiceGroup::List::ConstIterator it = entries.begin();
220 KServiceGroup::List::ConstIterator end = entries.end();
221 for ( ; it != end; ++it ) {
222 QString desktopFile = ( *it )->entryPath();
223 if ( QDir::isRelativePath( desktopFile ) )
224 desktopFile = KStandardDirs::locate( "apps", desktopFile );
225 createItemFromDesktopFile( topItem, desktopFile );
229 void Navigator::insertIOSlaveDocs( const QString &name, NavigatorItem *topItem )
231 kDebug(1400) << "Requested IOSlave documents for ID " << name;
233 QStringList list = KProtocolInfo::protocols();
234 list.sort();
236 NavigatorItem *prevItem = 0;
237 for ( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
239 QString docPath = KProtocolInfo::docPath(*it);
240 if ( !docPath.isNull() )
242 // First parameter is ignored if second is an absolute path
243 KUrl url(KUrl("help:/"), docPath);
244 QString icon = KProtocolInfo::icon(*it);
245 if ( icon.isEmpty() ) icon = "document2";
246 DocEntry *entry = new DocEntry( *it, url.url(), icon );
247 NavigatorItem *item = new NavigatorItem( entry, topItem, prevItem );
248 prevItem = item;
249 item->setAutoDeleteDocEntry( true );
254 void Navigator::insertAppletDocs( NavigatorItem *topItem )
256 QDir appletDir( KStandardDirs::locate( "data", QLatin1String( "kicker/applets/" ) ) );
257 appletDir.setNameFilters( QStringList( "*.desktop" ) );
259 QStringList files = appletDir.entryList( QDir::Files | QDir::Readable );
260 QStringList::ConstIterator it = files.begin();
261 QStringList::ConstIterator end = files.end();
262 for ( ; it != end; ++it )
263 createItemFromDesktopFile( topItem, appletDir.absolutePath() + '/' + *it );
266 void Navigator::createItemFromDesktopFile( NavigatorItem *topItem,
267 const QString &file )
269 KDesktopFile desktopFile( file );
270 QString docPath = desktopFile.readDocPath();
271 if ( !docPath.isNull() ) {
272 // First parameter is ignored if second is an absolute path
273 KUrl url(KUrl("help:/"), docPath);
274 QString icon = desktopFile.readIcon();
275 if ( icon.isEmpty() ) icon = "document2";
276 DocEntry *entry = new DocEntry( desktopFile.readName(), url.url(), icon );
277 NavigatorItem *item = new NavigatorItem( entry, topItem );
278 item->setAutoDeleteDocEntry( true );
282 void Navigator::insertInfoDocs( NavigatorItem *topItem )
284 InfoTree *infoTree = new InfoTree( this );
285 infoTree->build( topItem );
288 NavigatorItem *Navigator::insertScrollKeeperDocs( NavigatorItem *topItem,
289 NavigatorItem *after )
291 ScrollKeeperTreeBuilder *builder = new ScrollKeeperTreeBuilder( this );
292 return builder->build( topItem, after );
295 void Navigator::selectItem( const KUrl &url )
297 kDebug() << "Navigator::selectItem(): " << url.url();
299 if ( url.url() == "khelpcenter:home" ) {
300 clearSelection();
301 return;
304 // help:/foo&anchor=bar gets redirected to help:/foo#bar
305 // Make sure that we match both the original URL as well as
306 // its counterpart.
307 KUrl alternativeURL = url;
308 if (url.hasRef())
310 alternativeURL.setQuery("anchor="+url.ref());
311 alternativeURL.setRef(QString());
314 // If the navigator already has the given URL selected, do nothing.
315 NavigatorItem *item;
316 item = static_cast<NavigatorItem *>( mContentsTree->selectedItem() );
317 if ( item && mSelected ) {
318 KUrl currentURL ( item->entry()->url() );
319 if ( (currentURL == url) || (currentURL == alternativeURL) ) {
320 kDebug() << "URL already shown.";
321 return;
325 // First, populate the NavigatorAppItems if we don't want the home page
326 if ( url != homeURL() ) {
327 for ( Q3ListViewItem *item = mContentsTree->firstChild(); item;
328 item = item->nextSibling() ) {
329 NavigatorAppItem *appItem = dynamic_cast<NavigatorAppItem *>( item );
330 if ( appItem ) appItem->populate( true /* recursive */ );
334 Q3ListViewItemIterator it( mContentsTree );
335 while ( it.current() ) {
336 NavigatorItem *item = static_cast<NavigatorItem *>( it.current() );
337 KUrl itemUrl( item->entry()->url() );
338 if ( (itemUrl == url) || (itemUrl == alternativeURL) ) {
339 mContentsTree->setCurrentItem( item );
340 // If the current item was not selected and remained unchanged it
341 // needs to be explicitly selected
342 mContentsTree->setSelected(item, true);
343 item->setOpen( true );
344 mContentsTree->ensureItemVisible( item );
345 break;
347 ++it;
349 if ( !it.current() ) {
350 clearSelection();
351 } else {
352 mSelected = true;
356 void Navigator::clearSelection()
358 mContentsTree->clearSelection();
359 mSelected = false;
362 void Navigator::slotItemSelected( Q3ListViewItem *currentItem )
364 if ( !currentItem ) return;
366 mSelected = true;
368 NavigatorItem *item = static_cast<NavigatorItem *>( currentItem );
370 kDebug(1400) << "Navigator::slotItemSelected(): " << item->entry()->name()
371 << endl;
373 if ( item->childCount() > 0 || item->isExpandable() )
374 item->setOpen( !item->isOpen() );
376 KUrl url ( item->entry()->url() );
378 if ( url.protocol() == "khelpcenter" ) {
379 mView->closeUrl();
380 History::self().updateCurrentEntry( mView );
381 History::self().createEntry();
382 showOverview( item, url );
383 } else {
384 if ( url.protocol() == "help" ) {
385 kDebug( 1400 ) << "slotItemSelected(): Got help URL " << url.url()
386 << endl;
387 if ( !item->toc() ) {
388 TOC *tocTree = item->createTOC();
389 kDebug( 1400 ) << "slotItemSelected(): Trying to build TOC for "
390 << item->entry()->name() << endl;
391 tocTree->setApplication( url.directory() );
392 QString doc = View::langLookup( url.path() );
393 // Enforce the original .docbook version, in case langLookup returns a
394 // cached version
395 if ( !doc.isNull() ) {
396 int pos = doc.indexOf( ".html" );
397 if ( pos >= 0 ) {
398 doc.replace( pos, 5, ".docbook" );
400 kDebug( 1400 ) << "slotItemSelected(): doc = " << doc;
402 tocTree->build( doc );
406 emit itemSelected( url.url() );
409 mLastUrl = url;
412 void Navigator::openInternalUrl( const KUrl &url )
414 if ( url.url() == "khelpcenter:home" ) {
415 clearSelection();
416 showOverview( 0, url );
417 return;
420 selectItem( url );
421 if ( !mSelected ) return;
423 NavigatorItem *item =
424 static_cast<NavigatorItem *>( mContentsTree->currentItem() );
426 if ( item ) showOverview( item, url );
429 void Navigator::showOverview( NavigatorItem *item, const KUrl &url )
431 mView->beginInternal( url );
433 QString fileName = KStandardDirs::locate( "data", "khelpcenter/index.html.in" );
434 if ( fileName.isEmpty() )
435 return;
437 QFile file( fileName );
439 if ( !file.open( QIODevice::ReadOnly ) )
440 return;
442 QTextStream stream( &file );
443 QString res = stream.readAll();
445 QString title,name,content;
446 uint childCount;
448 if ( item ) {
449 title = item->entry()->name();
450 name = item->entry()->name();
452 QString info = item->entry()->info();
453 if ( !info.isEmpty() ) content = QLatin1String("<p>") + info + QLatin1String("</p>\n");
455 childCount = item->childCount();
456 } else {
457 title = i18n("Start Page");
458 name = i18n("KDE Help Center");
460 childCount = mContentsTree->childCount();
463 if ( childCount > 0 ) {
464 Q3ListViewItem *child;
465 if ( item ) child = item->firstChild();
466 else child = mContentsTree->firstChild();
468 mDirLevel = 0;
470 content += createChildrenList( child );
472 else
473 content += QLatin1String("<p></p>");
475 res = res.arg(title).arg(name).arg(content);
477 mView->write( res );
479 mView->end();
482 QString Navigator::createChildrenList( Q3ListViewItem *child )
484 ++mDirLevel;
486 QString t;
488 t += QLatin1String("<ul>\n");
490 while ( child ) {
491 NavigatorItem *childItem = static_cast<NavigatorItem *>( child );
493 DocEntry *e = childItem->entry();
495 t += QLatin1String("<li><a href=\"") + e->url() + QLatin1String("\">");
496 if ( e->isDirectory() ) t += QLatin1String("<b>");
497 t += e->name();
498 if ( e->isDirectory() ) t += QLatin1String("</b>");
499 t += QLatin1String("</a>");
501 if ( !e->info().isEmpty() ) {
502 t += QLatin1String("<br>") + e->info();
505 t += QLatin1String("</li>\n");
507 if ( childItem->childCount() > 0 && mDirLevel < 2 ) {
508 t += createChildrenList( childItem->firstChild() );
511 child = child->nextSibling();
514 t += QLatin1String("</ul>\n");
516 --mDirLevel;
518 return t;
521 void Navigator::slotSearch()
523 kDebug(1400) << "Navigator::slotSearch()";
525 if ( !checkSearchIndex() ) return;
527 if ( mSearchEngine->isRunning() ) return;
529 QString words = mSearchEdit->text();
530 QString method = mSearchWidget->method();
531 int pages = mSearchWidget->pages();
532 QString scope = mSearchWidget->scope();
534 kDebug(1400) << "Navigator::slotSearch() words: " << words;
535 kDebug(1400) << "Navigator::slotSearch() scope: " << scope;
537 if ( words.isEmpty() || scope.isEmpty() ) return;
539 // disable search Button during searches
540 mSearchButton->setEnabled(false);
541 QApplication::setOverrideCursor(Qt::WaitCursor);
543 if ( !mSearchEngine->search( words, method, pages, scope ) ) {
544 slotSearchFinished();
545 KMessageBox::sorry( this, i18n("Unable to run search program.") );
549 void Navigator::slotShowSearchResult( const QString &url )
551 QString u = url;
552 u.replace( "%k", mSearchEdit->text() );
554 emit itemSelected( u );
557 void Navigator::slotSearchFinished()
559 mSearchButton->setEnabled(true);
560 QApplication::restoreOverrideCursor();
562 kDebug( 1400 ) << "Search finished.";
565 void Navigator::checkSearchButton()
567 mSearchButton->setEnabled( !mSearchEdit->text().isEmpty() &&
568 mSearchWidget->scopeCount() > 0 );
569 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mSearchWidget ) );
572 void Navigator::hideSearch()
574 mSearchFrame->hide();
575 mTabWidget->removeTab( mTabWidget->indexOf( mSearchWidget ) );
578 bool Navigator::checkSearchIndex()
580 KConfigGroup cfg(KGlobal::config(), "Search" );
581 if ( cfg.readEntry( "IndexExists", false) ) return true;
583 if ( mIndexDialog && !mIndexDialog->isHidden() ) return true;
585 QString text = i18n( "A search index does not yet exist. Do you want "
586 "to create the index now?" );
588 int result = KMessageBox::questionYesNo( this, text, QString(),
589 KGuiItem(i18n("Create")),
590 KGuiItem(i18n("Do Not Create")),
591 QLatin1String("indexcreation") );
592 if ( result == KMessageBox::Yes ) {
593 showIndexDialog();
594 return false;
597 return true;
600 void Navigator::slotTabChanged( QWidget *wid )
602 if ( wid == mSearchWidget ) checkSearchIndex();
605 void Navigator::slotSelectGlossEntry( const QString &id )
607 mGlossaryTree->slotSelectGlossEntry( id );
610 KUrl Navigator::homeURL()
612 if ( !mHomeUrl.isEmpty() ) return mHomeUrl;
614 KSharedConfig::Ptr cfg = KGlobal::config();
615 // We have to reparse the configuration here in order to get a
616 // language-specific StartUrl, e.g. "StartUrl[de]".
617 cfg->reparseConfiguration();
618 mHomeUrl = cfg->group("General").readPathEntry( "StartUrl", QLatin1String("khelpcenter:home") );
619 return mHomeUrl;
622 void Navigator::showIndexDialog()
624 if ( !mIndexDialog ) {
625 mIndexDialog = new KCMHelpCenter( mSearchEngine, this );
626 connect( mIndexDialog, SIGNAL( searchIndexUpdated() ), mSearchWidget,
627 SLOT( updateScopeList() ) );
629 mIndexDialog->show();
630 mIndexDialog->raise();
633 void Navigator::readConfig()
635 if ( Prefs::currentTab() == Prefs::Search ) {
636 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mSearchWidget ) );
637 } else if ( Prefs::currentTab() == Prefs::Glossary ) {
638 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mGlossaryTree ) );
639 } else {
640 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mContentsTree ) );
644 void Navigator::writeConfig()
646 if ( mTabWidget->currentWidget() == mSearchWidget ) {
647 Prefs::setCurrentTab( Prefs::Search );
648 } else if ( mTabWidget->currentWidget() == mGlossaryTree ) {
649 Prefs::setCurrentTab( Prefs::Glossary );
650 } else {
651 Prefs::setCurrentTab( Prefs::Content );
655 void Navigator::clearSearch()
657 mSearchEdit->setText( QString() );
660 #include "navigator.moc"
662 // vim:ts=2:sw=2:et