1
2/*
3 * This file is part of the KDE Help Center
4 *
5 * Copyright (C) 2002 Frerich Raabe (raabe@kde.org)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22#include "glossary.h"
23#include "view.h"
24
25#include <KApplication>
26#include <KConfig>
27#include <KDebug>
28#include <KIconLoader>
29#include <KLocale>
30#include <KXmlGuiWindow>
31#include <KProcess>
32#include <KStandardDirs>
33#include <KStatusBar>
34#include <KGlobal>
35
36#include <QTreeWidgetItemIterator>
37#include <QTreeWidgetItem>
38
39#include <QFrame>
40#include <QListView>
41#include <QTextStream>
42
43#include <sys/stat.h>
44
45using namespace KHC;
46
47class SectionItem : public QTreeWidgetItem
48{
49 public:
50 SectionItem( QTreeWidgetItem *parent, const QString &text )
51 : QTreeWidgetItem( parent )
52 {
53 setText(0,text);
54 setIcon(0,SmallIcon( "help-contents" ));
55 }
56};
57
58class EntryItem : public QTreeWidgetItem
59{
60 public:
61 EntryItem( SectionItem *parent, const QString &term, const QString &id )
62 : QTreeWidgetItem( parent ), m_id( id )
63 {
64 setText(0,term);
65 }
66
67 QString id() const { return m_id; }
68
69 private:
70 QString m_id;
71};
72
73bool Glossary::m_alreadyWarned = false;
74
75Glossary::Glossary( QWidget *parent ) : QTreeWidget( parent )
76{
77 m_initialized = false;
78 setFrameStyle( QFrame::NoFrame );
79
80 connect( this, SIGNAL( itemActivated(QTreeWidgetItem *, int) ),
81 this, SLOT( treeItemSelected( QTreeWidgetItem * ) ) );
82
83 setHeaderHidden(true);
84 setAllColumnsShowFocus( true );
85 setRootIsDecorated( true );
86
87 m_byTopicItem = new QTreeWidgetItem( this );
88 m_byTopicItem->setText( 0, i18n( "By Topic" ) );
89 m_byTopicItem->setIcon( 0, SmallIcon( "help-contents" ) );
90
91 m_alphabItem = new QTreeWidgetItem( this );
92 m_alphabItem->setText( 0, i18n( "Alphabetically" ) );
93 m_alphabItem->setIcon( 0, SmallIcon( "character-set" ) );
94
95 m_cacheFile = KStandardDirs::locateLocal( "cache", "help/glossary.xml" );
96
97 m_sourceFile = View::langLookup( QLatin1String( "khelpcenter/glossary/index.docbook" ) );
98 m_config = KGlobal::config();
99
100}
101
102void Glossary::showEvent(QShowEvent *event)
103{
104 if ( !m_initialized )
105 {
106 if ( cacheStatus() == NeedRebuild )
107 rebuildGlossaryCache();
108 else
109 buildGlossaryTree();
110 m_initialized = true;
111 }
112 QTreeWidget::showEvent(event);
113}
114
115Glossary::~Glossary()
116{
117 qDeleteAll( m_glossEntries );
118}
119
120const GlossaryEntry &Glossary::entry( const QString &id ) const
121{
122 return *m_glossEntries[ id ];
123}
124
125Glossary::CacheStatus Glossary::cacheStatus() const
126{
127 if ( !QFile::exists( m_cacheFile ) ||
128 m_config->group("Glossary").readPathEntry( "CachedGlossary", QString() ) != m_sourceFile ||
129 m_config->group("Glossary").readEntry( "CachedGlossaryTimestamp" ).toInt() != glossaryCTime() )
130 return NeedRebuild;
131
132 return CacheOk;
133}
134
135int Glossary::glossaryCTime() const
136{
137 struct stat stat_buf;
138 stat( QFile::encodeName( m_sourceFile ).data(), &stat_buf );
139
140 return stat_buf.st_ctime;
141}
142
143void Glossary::rebuildGlossaryCache()
144{
145 KXmlGuiWindow *mainWindow = dynamic_cast<KXmlGuiWindow *>( kapp->activeWindow() );
146 if (mainWindow)
147 mainWindow->statusBar()->showMessage( i18n( "Rebuilding glossary cache..." ) );
148
149 KProcess *meinproc = new KProcess;
150 connect( meinproc, SIGNAL( finished(int,QProcess::ExitStatus) ),
151 this, SLOT( meinprocFinished(int,QProcess::ExitStatus) ) );
152
153 *meinproc << KStandardDirs::locate( "exe", QLatin1String( "meinproc4" ) );
154 *meinproc << QLatin1String( "--output" ) << m_cacheFile;
155 *meinproc << QLatin1String( "--stylesheet" )
156 << KStandardDirs::locate( "data", QLatin1String( "khelpcenter/glossary.xslt" ) );
157 *meinproc << m_sourceFile;
158
159 meinproc->setOutputChannelMode(KProcess::OnlyStderrChannel);
160 meinproc->start();
161 if (!meinproc->waitForStarted())
162 {
163 kError() << "could not start process" << meinproc->program();
164 if (mainWindow && !m_alreadyWarned)
165 {
166 ; // add warning message box with don't display again option
167 // http://api.kde.org/4.0-api/kdelibs-apidocs/kdeui/html/classKDialog.html
168 m_alreadyWarned = true;
169 }
170 delete meinproc;
171 }
172}
173
174void Glossary::meinprocFinished( int exitCode, QProcess::ExitStatus exitStatus )
175{
176 KProcess *meinproc = static_cast<KProcess *>(sender());
177 KXmlGuiWindow *mainWindow = dynamic_cast<KXmlGuiWindow *>( kapp->activeWindow() );
178
179 if (exitStatus != QProcess::NormalExit || exitCode != 0)
180 {
181 kError() << "running" << meinproc->program() << "failed with exitCode" << exitCode;
182 kError() << "stderr output:" << meinproc->readAllStandardError();
183 if (mainWindow && !m_alreadyWarned)
184 {
185 ; // add warning message box with don't display again option
186 // http://api.kde.org/4.0-api/kdelibs-apidocs/kdeui/html/classKDialog.html
187 m_alreadyWarned = true;
188 }
189 delete meinproc;
190 return;
191 }
192 delete meinproc;
193
194 if ( !QFile::exists( m_cacheFile ) )
195 return;
196
197 m_config->group("Glossary").writePathEntry( "CachedGlossary", m_sourceFile );
198 m_config->group("Glossary").writeEntry( "CachedGlossaryTimestamp", glossaryCTime() );
199 m_config->sync();
200
201 m_status = CacheOk;
202
203 if (mainWindow)
204 mainWindow->statusBar()->showMessage( i18n( "Rebuilding cache... done." ), 2000 );
205
206 buildGlossaryTree();
207}
208
209void Glossary::buildGlossaryTree()
210{
211 QFile cacheFile(m_cacheFile);
212 if ( !cacheFile.open( QIODevice::ReadOnly ) )
213 return;
214
215 QDomDocument doc;
216 if ( !doc.setContent( &cacheFile ) )
217 return;
218
219 QDomNodeList sectionNodes = doc.documentElement().elementsByTagName( QLatin1String( "section" ) );
220 for ( int i = 0; i < sectionNodes.count(); i++ )
221 {
222 QDomElement sectionElement = sectionNodes.item( i ).toElement();
223 QString title = sectionElement.attribute( QLatin1String( "title" ) );
224 SectionItem *topicSection = new SectionItem( m_byTopicItem, title );
225
226 QDomNodeList entryNodes = sectionElement.elementsByTagName( QLatin1String( "entry" ) );
227 for ( int j = 0; j < entryNodes.count(); j++ )
228 {
229 QDomElement entryElement = entryNodes.item( j ).toElement();
230
231 QString entryId = entryElement.attribute( QLatin1String( "id" ) );
232 if ( entryId.isNull() )
233 continue;
234
235 QDomElement termElement = childElement( entryElement, QLatin1String( "term" ) );
236 QString term = termElement.text().simplified();
237
238 EntryItem *entry = new EntryItem(topicSection, term, entryId );
239 m_idDict.insert( entryId, entry );
240
241 SectionItem *alphabSection = 0L;
242
243 QTreeWidgetItemIterator it(m_alphabItem);
244 while(*it)
245 {
246 if ( (*it)->text( 0 ) == QString( term[ 0 ].toUpper() ) )
247 {
248 alphabSection = static_cast<SectionItem *>( (*it) );
249 break;
250 }
251 ++it;
252 }
253
254 if ( !alphabSection )
255 alphabSection = new SectionItem( m_alphabItem, QString( term[ 0 ].toUpper() ) );
256
257 new EntryItem( alphabSection, term, entryId );
258
259 QDomElement definitionElement = childElement( entryElement, QLatin1String( "definition" ) );
260 QString definition = definitionElement.text().simplified();
261
262 GlossaryEntryXRef::List seeAlso;
263
264 QDomElement referencesElement = childElement( entryElement, QLatin1String( "references" ) );
265 QDomNodeList referenceNodes = referencesElement.elementsByTagName( QLatin1String( "reference" ) );
266 if ( referenceNodes.count() > 0 )
267 for ( int k = 0; k < referenceNodes.count(); k++ )
268 {
269 QDomElement referenceElement = referenceNodes.item( k ).toElement();
270
271 QString term = referenceElement.attribute( QLatin1String( "term" ) );
272 QString id = referenceElement.attribute( QLatin1String( "id" ) );
273
274 seeAlso += GlossaryEntryXRef( term, id );
275 }
276
277 m_glossEntries.insert( entryId, new GlossaryEntry( term, definition, seeAlso ) );
278 }
279 }
280 sortItems(0, Qt::AscendingOrder);
281}
282
283void Glossary::treeItemSelected( QTreeWidgetItem *item )
284{
285 if ( !item )
286 return;
287
288 if ( EntryItem *i = dynamic_cast<EntryItem *>( item ) )
289 emit entrySelected( entry( i->id() ) );
290
291 item->setExpanded( !item->isExpanded() );
292}
293
294QDomElement Glossary::childElement( const QDomElement &element, const QString &name )
295{
296 QDomElement e;
297 for ( e = element.firstChild().toElement(); !e.isNull(); e = e.nextSibling().toElement() )
298 if ( e.tagName() == name )
299 break;
300 return e;
301}
302
303QString Glossary::entryToHtml( const GlossaryEntry &entry )
304{
305 QFile htmlFile( KStandardDirs::locate("data", "khelpcenter/glossary.html.in" ) );
306 if (!htmlFile.open(QIODevice::ReadOnly))
307 return QString( "<html><head></head><body><h3>%1</h3>%2</body></html>" )
308 .arg( i18n( "Error" ) )
309 .arg( i18n( "Unable to show selected glossary entry: unable to open "
310 "file 'glossary.html.in'!" ) );
311
312 QString seeAlso;
313 if (!entry.seeAlso().isEmpty())
314 {
315 seeAlso = i18n("See also: ");
316 GlossaryEntryXRef::List seeAlsos = entry.seeAlso();
317 GlossaryEntryXRef::List::ConstIterator it = seeAlsos.constBegin();
318 GlossaryEntryXRef::List::ConstIterator end = seeAlsos.constEnd();
319 for (; it != end; ++it)
320 {
321 seeAlso += QLatin1String("<a href=\"glossentry:");
322 seeAlso += (*it).id();
323 seeAlso += QLatin1String("\">") + (*it).term();
324 seeAlso += QLatin1String("</a>, ");
325 }
326 seeAlso = seeAlso.left(seeAlso.length() - 2);
327 }
328
329 QTextStream htmlStream(&htmlFile);
330 return htmlStream.readAll()
331 .arg( i18n( "KDE Glossary" ) )
332 .arg( entry.term() )
333 .arg( entry.definition() )
334 .arg( seeAlso );
335}
336
337void Glossary::slotSelectGlossEntry( const QString &id )
338{
339 if ( !m_idDict.contains( id ) )
340 return;
341
342 EntryItem *newItem = m_idDict.value( id );
343 EntryItem *curItem = dynamic_cast<EntryItem *>( currentItem() );
344 if ( curItem != 0 )
345 {
346 if ( curItem->id() == id )
347 return;
348 curItem->parent()->setExpanded( false );
349 }
350
351 setCurrentItem( newItem );
352}
353
354#include "glossary.moc"
355// vim:ts=4:sw=4:et
356