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>
62 #include "navigatoritem.h"
63 #include "navigatorappitem.h"
64 #include "searchwidget.h"
65 #include "searchengine.h"
66 #include "docmetainfo.h"
67 #include "docentrytraverser.h"
72 #include "mainwindow.h"
73 #include "plugintraverser.h"
74 #include "scrollkeepertreebuilder.h"
75 #include "kcmhelpcenter.h"
76 #include "formatter.h"
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
);
126 if ( !mSearchEngine
->initSearchHandlers() ) {
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
);
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
) {
206 kDebug( 1400 ) << "</docmetainfo>";
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
);
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();
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
);
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" ) {
304 // help:/foo&anchor=bar gets redirected to help:/foo#bar
305 // Make sure that we match both the original URL as well as
307 KUrl alternativeURL
= url
;
310 alternativeURL
.setQuery("anchor="+url
.ref());
311 alternativeURL
.setRef(QString());
314 // If the navigator already has the given URL selected, do nothing.
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.";
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
);
349 if ( !it
.current() ) {
356 void Navigator::clearSelection()
358 mContentsTree
->clearSelection();
362 void Navigator::slotItemSelected( Q3ListViewItem
*currentItem
)
364 if ( !currentItem
) return;
368 NavigatorItem
*item
= static_cast<NavigatorItem
*>( currentItem
);
370 kDebug(1400) << "Navigator::slotItemSelected(): " << item
->entry()->name()
373 if ( item
->childCount() > 0 || item
->isExpandable() )
374 item
->setOpen( !item
->isOpen() );
376 KUrl
url ( item
->entry()->url() );
378 if ( url
.protocol() == "khelpcenter" ) {
380 History::self().updateCurrentEntry( mView
);
381 History::self().createEntry();
382 showOverview( item
, url
);
384 if ( url
.protocol() == "help" ) {
385 kDebug( 1400 ) << "slotItemSelected(): Got help URL " << url
.url()
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
395 if ( !doc
.isNull() ) {
396 int pos
= doc
.indexOf( ".html" );
398 doc
.replace( pos
, 5, ".docbook" );
400 kDebug( 1400 ) << "slotItemSelected(): doc = " << doc
;
402 tocTree
->build( doc
);
406 emit
itemSelected( url
.url() );
412 void Navigator::openInternalUrl( const KUrl
&url
)
414 if ( url
.url() == "khelpcenter:home" ) {
416 showOverview( 0, 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() )
437 QFile
file( fileName
);
439 if ( !file
.open( QIODevice::ReadOnly
) )
442 QTextStream
stream( &file
);
443 QString res
= stream
.readAll();
445 QString title
,name
,content
;
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();
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();
470 content
+= createChildrenList( child
);
473 content
+= QLatin1String("<p></p>");
475 res
= res
.arg(title
).arg(name
).arg(content
);
482 QString
Navigator::createChildrenList( Q3ListViewItem
*child
)
488 t
+= QLatin1String("<ul>\n");
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>");
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");
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
)
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
) {
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") );
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
) );
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
);
651 Prefs::setCurrentTab( Prefs::Content
);
655 void Navigator::clearSearch()
657 mSearchEdit
->setText( QString() );
660 #include "navigator.moc"