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 | |
45 | using namespace KHC; |
46 | |
47 | class 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 | |
58 | class 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 | |
73 | bool Glossary::m_alreadyWarned = false; |
74 | |
75 | Glossary::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 | |
102 | void 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 | |
115 | Glossary::~Glossary() |
116 | { |
117 | qDeleteAll( m_glossEntries ); |
118 | } |
119 | |
120 | const GlossaryEntry &Glossary::entry( const QString &id ) const |
121 | { |
122 | return *m_glossEntries[ id ]; |
123 | } |
124 | |
125 | Glossary::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 | |
135 | int 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 | |
143 | void 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 | |
174 | void 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 | |
209 | void 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 | |
283 | void 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 | |
294 | QDomElement 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 | |
303 | QString 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 | |
337 | void 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 | |