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>
32 #include <Qt3Support/Q3Header>
33 #include <QtXml/QtXml>
34 #include <QTextStream>
37 #include <QPushButton>
41 #include <QHBoxLayout>
43 #include <QVBoxLayout>
46 #include <kapplication.h>
48 #include <kstandarddirs.h>
52 #include <k3listview.h>
54 #include <kmessagebox.h>
55 #include <kiconloader.h>
56 #include <kcharsets.h>
58 #include <kdesktopfile.h>
59 #include <kprotocolinfo.h>
60 #include <kservicegroup.h>
61 #include <kservicetypetrader.h>
62 #include <kcmoduleinfo.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"
75 #include "mainwindow.h"
76 #include "plugintraverser.h"
77 #include "scrollkeepertreebuilder.h"
78 #include "kcmhelpcenter.h"
79 #include "formatter.h"
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
);
129 if ( !mSearchEngine
->initSearchHandlers() ) {
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
);
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
) {
209 kDebug( 1400 ) << "</docmetainfo>";
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
);
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
;
238 if ( type
== QString("kcontrol") ) {
239 list
= KServiceTypeTrader::self()->query( "KCModule", "[X-KDE-ParentApp] == 'kcontrol'" );
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();
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
);
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" ) {
316 // help:/foo&anchor=bar gets redirected to help:/foo#bar
317 // Make sure that we match both the original URL as well as
319 KUrl alternativeURL
= url
;
322 alternativeURL
.setQuery("anchor="+url
.ref());
323 alternativeURL
.setRef(QString());
326 // If the navigator already has the given URL selected, do nothing.
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.";
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
);
361 if ( !it
.current() ) {
368 void Navigator::clearSelection()
370 mContentsTree
->clearSelection();
374 void Navigator::slotItemSelected( Q3ListViewItem
*currentItem
)
376 if ( !currentItem
) return;
380 NavigatorItem
*item
= static_cast<NavigatorItem
*>( currentItem
);
382 kDebug(1400) << "Navigator::slotItemSelected(): " << item
->entry()->name()
385 if ( item
->childCount() > 0 || item
->isExpandable() )
386 item
->setOpen( !item
->isOpen() );
388 KUrl
url ( item
->entry()->url() );
390 if ( url
.protocol() == "khelpcenter" ) {
392 History::self().updateCurrentEntry( mView
);
393 History::self().createEntry();
394 showOverview( item
, url
);
396 if ( url
.protocol() == "help" ) {
397 kDebug( 1400 ) << "slotItemSelected(): Got help URL " << url
.url()
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
407 if ( !doc
.isNull() ) {
408 int pos
= doc
.indexOf( ".html" );
410 doc
.replace( pos
, 5, ".docbook" );
412 kDebug( 1400 ) << "slotItemSelected(): doc = " << doc
;
414 tocTree
->build( doc
);
418 emit
itemSelected( url
.url() );
424 void Navigator::openInternalUrl( const KUrl
&url
)
426 if ( url
.url() == "khelpcenter:home" ) {
428 showOverview( 0, 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() )
449 QFile
file( fileName
);
451 if ( !file
.open( QIODevice::ReadOnly
) )
454 QTextStream
stream( &file
);
455 QString res
= stream
.readAll();
457 QString title
,name
,content
;
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();
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();
482 content
+= createChildrenList( child
);
485 content
+= QLatin1String("<p></p>");
487 res
= res
.arg(title
).arg(name
).arg(content
);
494 QString
Navigator::createChildrenList( Q3ListViewItem
*child
)
500 t
+= QLatin1String("<ul>\n");
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>");
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");
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
)
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
) {
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") );
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
) );
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
);
663 Prefs::setCurrentTab( Prefs::Content
);
667 void Navigator::clearSearch()
669 mSearchEdit
->setText( QString() );
672 #include "navigator.moc"