delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / khelpcenter / navigator.cpp
blob1eb2c6916ef115561d0ca8289fca2234dfea2a70
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>
61 #include <kservicetypetrader.h>
62 #include <kcmoduleinfo.h>
63 #include <kcmodule.h>
65 #include "navigatoritem.h"
66 #include "navigatorappitem.h"
67 #include "searchwidget.h"
68 #include "searchengine.h"
69 #include "docmetainfo.h"
70 #include "docentrytraverser.h"
71 #include "glossary.h"
72 #include "toc.h"
73 #include "view.h"
74 #include "infotree.h"
75 #include "mainwindow.h"
76 #include "plugintraverser.h"
77 #include "scrollkeepertreebuilder.h"
78 #include "kcmhelpcenter.h"
79 #include "formatter.h"
80 #include "history.h"
81 #include "prefs.h"
83 using namespace KHC;
85 Navigator::Navigator( View *view, QWidget *parent, const char *name )
86 : QWidget( parent ), mIndexDialog( 0 ),
87 mView( view ), mSelected( false )
89 setObjectName( name );
91 KConfigGroup config(KGlobal::config(), "General");
92 mShowMissingDocs = config.readEntry("ShowMissingDocs", false);
94 mSearchEngine = new SearchEngine( view );
95 connect( mSearchEngine, SIGNAL( searchFinished() ),
96 SLOT( slotSearchFinished() ) );
98 DocMetaInfo::self()->scanMetaInfo();
100 QBoxLayout *topLayout = new QVBoxLayout( this );
102 mSearchFrame = new QFrame( this );
103 topLayout->addWidget( mSearchFrame );
105 QBoxLayout *searchLayout = new QHBoxLayout( mSearchFrame );
106 searchLayout->setSpacing( KDialog::spacingHint() );
107 searchLayout->setMargin( 6 );
109 mSearchEdit = new KLineEdit( mSearchFrame );
110 mSearchEdit->setClearButtonShown(true);
111 searchLayout->addWidget( mSearchEdit );
112 connect( mSearchEdit, SIGNAL( returnPressed() ), SLOT( slotSearch() ) );
113 connect( mSearchEdit, SIGNAL( textChanged( const QString & ) ),
114 SLOT( checkSearchButton() ) );
116 mSearchButton = new QPushButton( i18n("&Search"), mSearchFrame );
117 searchLayout->addWidget( mSearchButton );
118 connect( mSearchButton, SIGNAL( clicked() ), SLOT( slotSearch() ) );
120 mTabWidget = new QTabWidget( this );
121 topLayout->addWidget( mTabWidget );
123 setupContentsTab();
124 setupGlossaryTab();
125 setupSearchTab();
127 insertPlugins();
129 if ( !mSearchEngine->initSearchHandlers() ) {
130 hideSearch();
131 } else {
132 mSearchWidget->updateScopeList();
133 mSearchWidget->readConfig( KGlobal::config().data() );
135 connect( mTabWidget, SIGNAL( currentChanged( QWidget * ) ),
136 SLOT( slotTabChanged( QWidget * ) ) );
139 Navigator::~Navigator()
141 delete mSearchEngine;
144 SearchEngine *Navigator::searchEngine() const
146 return mSearchEngine;
149 Formatter *Navigator::formatter() const
151 return mView->formatter();
154 bool Navigator::showMissingDocs() const
156 return mShowMissingDocs;
159 void Navigator::setupContentsTab()
161 mContentsTree = new K3ListView( mTabWidget );
162 mContentsTree->setFrameStyle( QFrame::NoFrame );
163 mContentsTree->addColumn(QString());
164 mContentsTree->setAllColumnsShowFocus(true);
165 mContentsTree->header()->hide();
166 mContentsTree->setRootIsDecorated(false);
167 mContentsTree->setSorting(-1, false);
169 connect(mContentsTree, SIGNAL(clicked(Q3ListViewItem*)),
170 SLOT(slotItemSelected(Q3ListViewItem*)));
171 connect(mContentsTree, SIGNAL(returnPressed(Q3ListViewItem*)),
172 SLOT(slotItemSelected(Q3ListViewItem*)));
173 mTabWidget->addTab(mContentsTree, i18n("&Contents"));
176 void Navigator::setupSearchTab()
178 mSearchWidget = new SearchWidget( mSearchEngine, mTabWidget );
179 connect( mSearchWidget, SIGNAL( searchResult( const QString & ) ),
180 SLOT( slotShowSearchResult( const QString & ) ) );
181 connect( mSearchWidget, SIGNAL( scopeCountChanged( int ) ),
182 SLOT( checkSearchButton() ) );
183 connect( mSearchWidget, SIGNAL( showIndexDialog() ),
184 SLOT( showIndexDialog() ) );
186 mTabWidget->addTab( mSearchWidget, i18n("Search Options"));
189 void Navigator::setupGlossaryTab()
191 mGlossaryTree = new Glossary( mTabWidget );
192 connect( mGlossaryTree, SIGNAL( entrySelected( const GlossaryEntry & ) ),
193 this, SIGNAL( glossSelected( const GlossaryEntry & ) ) );
194 mTabWidget->addTab( mGlossaryTree, i18n( "G&lossary" ) );
197 void Navigator::insertPlugins()
199 PluginTraverser t( this, mContentsTree );
200 DocMetaInfo::self()->traverseEntries( &t );
202 #if 0
203 kDebug( 1400 ) << "<docmetainfo>";
204 DocEntry::List entries = DocMetaInfo::self()->docEntries();
205 DocEntry::List::ConstIterator it;
206 for( it = entries.begin(); it != entries.end(); ++it ) {
207 (*it)->dump();
209 kDebug( 1400 ) << "</docmetainfo>";
210 #endif
213 void Navigator::insertParentAppDocs( const QString &name, NavigatorItem *topItem )
215 kDebug(1400) << "Requested plugin documents for ID " << name;
217 KServiceGroup::Ptr grp = KServiceGroup::childGroup( name );
218 if ( !grp )
219 return;
221 KServiceGroup::List entries = grp->entries();
222 KServiceGroup::List::ConstIterator it = entries.constBegin();
223 KServiceGroup::List::ConstIterator end = entries.constEnd();
224 for ( ; it != end; ++it ) {
225 QString desktopFile = ( *it )->entryPath();
226 if ( QDir::isRelativePath( desktopFile ) )
227 desktopFile = KStandardDirs::locate( "apps", desktopFile );
228 createItemFromDesktopFile( topItem, desktopFile );
232 void Navigator::insertKCMDocs( const QString &name, NavigatorItem *topItem, const QString &type )
234 kDebug(1400) << "Requested IOSlave documents for ID " << name;
236 KService::List list;
238 if ( type == QString("kcontrol") ) {
239 list = KServiceTypeTrader::self()->query( "KCModule", "[X-KDE-ParentApp] == 'kcontrol'" );
240 } else {
241 list = KServiceTypeTrader::self()->query( "KCModule", "[X-KDE-ParentApp] == 'kinfocenter'" );
244 for ( KService::List::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
246 KService::Ptr s = (*it);
247 KCModuleInfo m = KCModuleInfo(s);
248 QString desktopFile = KStandardDirs::locate( "services", m.fileName() );
249 createItemFromDesktopFile( topItem, desktopFile );
253 void Navigator::insertIOSlaveDocs( const QString &name, NavigatorItem *topItem )
255 kDebug(1400) << "Requested IOSlave documents for ID " << name;
257 QStringList list = KProtocolInfo::protocols();
258 list.sort();
260 NavigatorItem *prevItem = 0;
261 for ( QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it )
263 QString docPath = KProtocolInfo::docPath(*it);
264 if ( !docPath.isNull() )
266 // First parameter is ignored if second is an absolute path
267 KUrl url(KUrl("help:/"), docPath);
268 QString icon = KProtocolInfo::icon(*it);
269 if ( icon.isEmpty() ) icon = "text-plain";
270 DocEntry *entry = new DocEntry( *it, url.url(), icon );
271 NavigatorItem *item = new NavigatorItem( entry, topItem, prevItem );
272 prevItem = item;
273 item->setAutoDeleteDocEntry( true );
278 void Navigator::createItemFromDesktopFile( NavigatorItem *topItem,
279 const QString &file )
281 KDesktopFile desktopFile( file );
282 QString docPath = desktopFile.readDocPath();
283 if ( !docPath.isNull() ) {
284 // First parameter is ignored if second is an absolute path
285 KUrl url(KUrl("help:/"), docPath);
286 QString icon = desktopFile.readIcon();
287 if ( icon.isEmpty() ) icon = "text-plain";
288 DocEntry *entry = new DocEntry( desktopFile.readName(), url.url(), icon );
289 NavigatorItem *item = new NavigatorItem( entry, topItem );
290 item->setAutoDeleteDocEntry( true );
294 void Navigator::insertInfoDocs( NavigatorItem *topItem )
296 InfoTree *infoTree = new InfoTree( this );
297 infoTree->build( topItem );
300 NavigatorItem *Navigator::insertScrollKeeperDocs( NavigatorItem *topItem,
301 NavigatorItem *after )
303 ScrollKeeperTreeBuilder *builder = new ScrollKeeperTreeBuilder( this );
304 return builder->build( topItem, after );
307 void Navigator::selectItem( const KUrl &url )
309 kDebug() << "Navigator::selectItem(): " << url.url();
311 if ( url.url() == "khelpcenter:home" ) {
312 clearSelection();
313 return;
316 // help:/foo&anchor=bar gets redirected to help:/foo#bar
317 // Make sure that we match both the original URL as well as
318 // its counterpart.
319 KUrl alternativeURL = url;
320 if (url.hasRef())
322 alternativeURL.setQuery("anchor="+url.ref());
323 alternativeURL.setRef(QString());
326 // If the navigator already has the given URL selected, do nothing.
327 NavigatorItem *item;
328 item = static_cast<NavigatorItem *>( mContentsTree->selectedItem() );
329 if ( item && mSelected ) {
330 KUrl currentURL ( item->entry()->url() );
331 if ( (currentURL == url) || (currentURL == alternativeURL) ) {
332 kDebug() << "URL already shown.";
333 return;
337 // First, populate the NavigatorAppItems if we don't want the home page
338 if ( url != homeURL() ) {
339 for ( Q3ListViewItem *item = mContentsTree->firstChild(); item;
340 item = item->nextSibling() ) {
341 NavigatorAppItem *appItem = dynamic_cast<NavigatorAppItem *>( item );
342 if ( appItem ) appItem->populate( true /* recursive */ );
346 Q3ListViewItemIterator it( mContentsTree );
347 while ( it.current() ) {
348 NavigatorItem *item = static_cast<NavigatorItem *>( it.current() );
349 KUrl itemUrl( item->entry()->url() );
350 if ( (itemUrl == url) || (itemUrl == alternativeURL) ) {
351 mContentsTree->setCurrentItem( item );
352 // If the current item was not selected and remained unchanged it
353 // needs to be explicitly selected
354 mContentsTree->setSelected(item, true);
355 item->setOpen( true );
356 mContentsTree->ensureItemVisible( item );
357 break;
359 ++it;
361 if ( !it.current() ) {
362 clearSelection();
363 } else {
364 mSelected = true;
368 void Navigator::clearSelection()
370 mContentsTree->clearSelection();
371 mSelected = false;
374 void Navigator::slotItemSelected( Q3ListViewItem *currentItem )
376 if ( !currentItem ) return;
378 mSelected = true;
380 NavigatorItem *item = static_cast<NavigatorItem *>( currentItem );
382 kDebug(1400) << "Navigator::slotItemSelected(): " << item->entry()->name()
383 << endl;
385 if ( item->childCount() > 0 || item->isExpandable() )
386 item->setOpen( !item->isOpen() );
388 KUrl url ( item->entry()->url() );
390 if ( url.protocol() == "khelpcenter" ) {
391 mView->closeUrl();
392 History::self().updateCurrentEntry( mView );
393 History::self().createEntry();
394 showOverview( item, url );
395 } else {
396 if ( url.protocol() == "help" ) {
397 kDebug( 1400 ) << "slotItemSelected(): Got help URL " << url.url()
398 << endl;
399 if ( !item->toc() ) {
400 TOC *tocTree = item->createTOC();
401 kDebug( 1400 ) << "slotItemSelected(): Trying to build TOC for "
402 << item->entry()->name() << endl;
403 tocTree->setApplication( url.directory() );
404 QString doc = View::langLookup( url.path() );
405 // Enforce the original .docbook version, in case langLookup returns a
406 // cached version
407 if ( !doc.isNull() ) {
408 int pos = doc.indexOf( ".html" );
409 if ( pos >= 0 ) {
410 doc.replace( pos, 5, ".docbook" );
412 kDebug( 1400 ) << "slotItemSelected(): doc = " << doc;
414 tocTree->build( doc );
418 emit itemSelected( url.url() );
421 mLastUrl = url;
424 void Navigator::openInternalUrl( const KUrl &url )
426 if ( url.url() == "khelpcenter:home" ) {
427 clearSelection();
428 showOverview( 0, url );
429 return;
432 selectItem( url );
433 if ( !mSelected ) return;
435 NavigatorItem *item =
436 static_cast<NavigatorItem *>( mContentsTree->currentItem() );
438 if ( item ) showOverview( item, url );
441 void Navigator::showOverview( NavigatorItem *item, const KUrl &url )
443 mView->beginInternal( url );
445 QString fileName = KStandardDirs::locate( "data", "khelpcenter/index.html.in" );
446 if ( fileName.isEmpty() )
447 return;
449 QFile file( fileName );
451 if ( !file.open( QIODevice::ReadOnly ) )
452 return;
454 QTextStream stream( &file );
455 QString res = stream.readAll();
457 QString title,name,content;
458 uint childCount;
460 if ( item ) {
461 title = item->entry()->name();
462 name = item->entry()->name();
464 QString info = item->entry()->info();
465 if ( !info.isEmpty() ) content = QLatin1String("<p>") + info + QLatin1String("</p>\n");
467 childCount = item->childCount();
468 } else {
469 title = i18n("Start Page");
470 name = i18n("KDE Help Center");
472 childCount = mContentsTree->childCount();
475 if ( childCount > 0 ) {
476 Q3ListViewItem *child;
477 if ( item ) child = item->firstChild();
478 else child = mContentsTree->firstChild();
480 mDirLevel = 0;
482 content += createChildrenList( child );
484 else
485 content += QLatin1String("<p></p>");
487 res = res.arg(title).arg(name).arg(content);
489 mView->write( res );
491 mView->end();
494 QString Navigator::createChildrenList( Q3ListViewItem *child )
496 ++mDirLevel;
498 QString t;
500 t += QLatin1String("<ul>\n");
502 while ( child ) {
503 NavigatorItem *childItem = static_cast<NavigatorItem *>( child );
505 DocEntry *e = childItem->entry();
507 t += QLatin1String("<li><a href=\"") + e->url() + QLatin1String("\">");
508 if ( e->isDirectory() ) t += QLatin1String("<b>");
509 t += e->name();
510 if ( e->isDirectory() ) t += QLatin1String("</b>");
511 t += QLatin1String("</a>");
513 if ( !e->info().isEmpty() ) {
514 t += QLatin1String("<br>") + e->info();
517 t += QLatin1String("</li>\n");
519 if ( childItem->childCount() > 0 && mDirLevel < 2 ) {
520 t += createChildrenList( childItem->firstChild() );
523 child = child->nextSibling();
526 t += QLatin1String("</ul>\n");
528 --mDirLevel;
530 return t;
533 void Navigator::slotSearch()
535 kDebug(1400) << "Navigator::slotSearch()";
537 if ( !checkSearchIndex() ) return;
539 if ( mSearchEngine->isRunning() ) return;
541 QString words = mSearchEdit->text();
542 QString method = mSearchWidget->method();
543 int pages = mSearchWidget->pages();
544 QString scope = mSearchWidget->scope();
546 kDebug(1400) << "Navigator::slotSearch() words: " << words;
547 kDebug(1400) << "Navigator::slotSearch() scope: " << scope;
549 if ( words.isEmpty() || scope.isEmpty() ) return;
551 // disable search Button during searches
552 mSearchButton->setEnabled(false);
553 QApplication::setOverrideCursor(Qt::WaitCursor);
555 if ( !mSearchEngine->search( words, method, pages, scope ) ) {
556 slotSearchFinished();
557 KMessageBox::sorry( this, i18n("Unable to run search program.") );
561 void Navigator::slotShowSearchResult( const QString &url )
563 QString u = url;
564 u.replace( "%k", mSearchEdit->text() );
566 emit itemSelected( u );
569 void Navigator::slotSearchFinished()
571 mSearchButton->setEnabled(true);
572 QApplication::restoreOverrideCursor();
574 kDebug( 1400 ) << "Search finished.";
577 void Navigator::checkSearchButton()
579 mSearchButton->setEnabled( !mSearchEdit->text().isEmpty() &&
580 mSearchWidget->scopeCount() > 0 );
581 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mSearchWidget ) );
584 void Navigator::hideSearch()
586 mSearchFrame->hide();
587 mTabWidget->removeTab( mTabWidget->indexOf( mSearchWidget ) );
590 bool Navigator::checkSearchIndex()
592 KConfigGroup cfg(KGlobal::config(), "Search" );
593 if ( cfg.readEntry( "IndexExists", false) ) return true;
595 if ( mIndexDialog && !mIndexDialog->isHidden() ) return true;
597 QString text = i18n( "A search index does not yet exist. Do you want "
598 "to create the index now?" );
600 int result = KMessageBox::questionYesNo( this, text, QString(),
601 KGuiItem(i18n("Create")),
602 KGuiItem(i18n("Do Not Create")),
603 QLatin1String("indexcreation") );
604 if ( result == KMessageBox::Yes ) {
605 showIndexDialog();
606 return false;
609 return true;
612 void Navigator::slotTabChanged( QWidget *wid )
614 if ( wid == mSearchWidget ) checkSearchIndex();
617 void Navigator::slotSelectGlossEntry( const QString &id )
619 mGlossaryTree->slotSelectGlossEntry( id );
622 KUrl Navigator::homeURL()
624 if ( !mHomeUrl.isEmpty() ) return mHomeUrl;
626 KSharedConfig::Ptr cfg = KGlobal::config();
627 // We have to reparse the configuration here in order to get a
628 // language-specific StartUrl, e.g. "StartUrl[de]".
629 cfg->reparseConfiguration();
630 mHomeUrl = cfg->group("General").readPathEntry( "StartUrl", QLatin1String("khelpcenter:home") );
631 return mHomeUrl;
634 void Navigator::showIndexDialog()
636 if ( !mIndexDialog ) {
637 mIndexDialog = new KCMHelpCenter( mSearchEngine, this );
638 connect( mIndexDialog, SIGNAL( searchIndexUpdated() ), mSearchWidget,
639 SLOT( updateScopeList() ) );
641 mIndexDialog->show();
642 mIndexDialog->raise();
645 void Navigator::readConfig()
647 if ( Prefs::currentTab() == Prefs::Search ) {
648 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mSearchWidget ) );
649 } else if ( Prefs::currentTab() == Prefs::Glossary ) {
650 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mGlossaryTree ) );
651 } else {
652 mTabWidget->setCurrentIndex( mTabWidget->indexOf( mContentsTree ) );
656 void Navigator::writeConfig()
658 if ( mTabWidget->currentWidget() == mSearchWidget ) {
659 Prefs::setCurrentTab( Prefs::Search );
660 } else if ( mTabWidget->currentWidget() == mGlossaryTree ) {
661 Prefs::setCurrentTab( Prefs::Glossary );
662 } else {
663 Prefs::setCurrentTab( Prefs::Content );
667 void Navigator::clearSearch()
669 mSearchEdit->setText( QString() );
672 #include "navigator.moc"
674 // vim:ts=2:sw=2:et