1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Assistant of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qhelpindexwidget.h"
41#include "qhelpenginecore.h"
42#include "qhelpengine_p.h"
43#include "qhelpdbreader_p.h"
44#include "qhelpcollectionhandler_p.h"
45
46#include <QtCore/QThread>
47#include <QtCore/QMutex>
48#include <QtHelp/QHelpLink>
49#include <QtWidgets/QListView>
50#include <QtWidgets/QHeaderView>
51
52#include <algorithm>
53
54QT_BEGIN_NAMESPACE
55
56class QHelpIndexProvider : public QThread
57{
58public:
59 QHelpIndexProvider(QHelpEnginePrivate *helpEngine);
60 ~QHelpIndexProvider() override;
61 void collectIndices(const QString &customFilterName);
62 void stopCollecting();
63 QStringList indices() const;
64
65private:
66 void run() override;
67
68 QHelpEnginePrivate *m_helpEngine;
69 QString m_currentFilter;
70 QStringList m_filterAttributes;
71 QStringList m_indices;
72 mutable QMutex m_mutex;
73};
74
75class QHelpIndexModelPrivate
76{
77public:
78 QHelpIndexModelPrivate(QHelpEnginePrivate *hE)
79 : helpEngine(hE),
80 indexProvider(new QHelpIndexProvider(helpEngine))
81 {
82 }
83
84 QHelpEnginePrivate *helpEngine;
85 QHelpIndexProvider *indexProvider;
86 QStringList indices;
87};
88
89QHelpIndexProvider::QHelpIndexProvider(QHelpEnginePrivate *helpEngine)
90 : QThread(helpEngine),
91 m_helpEngine(helpEngine)
92{
93}
94
95QHelpIndexProvider::~QHelpIndexProvider()
96{
97 stopCollecting();
98}
99
100void QHelpIndexProvider::collectIndices(const QString &customFilterName)
101{
102 m_mutex.lock();
103 m_currentFilter = customFilterName;
104 m_filterAttributes = m_helpEngine->q->filterAttributes(filterName: customFilterName);
105 m_mutex.unlock();
106
107 if (isRunning())
108 stopCollecting();
109 start(LowPriority);
110}
111
112void QHelpIndexProvider::stopCollecting()
113{
114 if (!isRunning())
115 return;
116 wait();
117}
118
119QStringList QHelpIndexProvider::indices() const
120{
121 QMutexLocker lck(&m_mutex);
122 return m_indices;
123}
124
125void QHelpIndexProvider::run()
126{
127 m_mutex.lock();
128 const QString currentFilter = m_currentFilter;
129 const QStringList attributes = m_filterAttributes;
130 const QString collectionFile = m_helpEngine->collectionHandler->collectionFile();
131 m_indices = QStringList();
132 m_mutex.unlock();
133
134 if (collectionFile.isEmpty())
135 return;
136
137 QHelpCollectionHandler collectionHandler(collectionFile);
138 collectionHandler.setReadOnly(true);
139 if (!collectionHandler.openCollectionFile())
140 return;
141
142 const QStringList result = m_helpEngine->usesFilterEngine
143 ? collectionHandler.indicesForFilter(filterName: currentFilter)
144 : collectionHandler.indicesForFilter(filterAttributes: attributes);
145
146 m_mutex.lock();
147 m_indices = result;
148 m_mutex.unlock();
149}
150
151/*!
152 \class QHelpIndexModel
153 \since 4.4
154 \inmodule QtHelp
155 \brief The QHelpIndexModel class provides a model that
156 supplies index keywords to views.
157
158
159*/
160
161/*!
162 \fn void QHelpIndexModel::indexCreationStarted()
163
164 This signal is emitted when the creation of a new index
165 has started. The current index is invalid from this
166 point on until the signal indexCreated() is emitted.
167
168 \sa isCreatingIndex()
169*/
170
171/*!
172 \fn void QHelpIndexModel::indexCreated()
173
174 This signal is emitted when the index has been created.
175*/
176
177QHelpIndexModel::QHelpIndexModel(QHelpEnginePrivate *helpEngine)
178 : QStringListModel(helpEngine)
179{
180 d = new QHelpIndexModelPrivate(helpEngine);
181
182 connect(sender: d->indexProvider, signal: &QThread::finished,
183 receiver: this, slot: &QHelpIndexModel::insertIndices);
184}
185
186QHelpIndexModel::~QHelpIndexModel()
187{
188 delete d;
189}
190
191/*!
192 Creates a new index by querying the help system for
193 keywords for the specified \a customFilterName.
194*/
195void QHelpIndexModel::createIndex(const QString &customFilterName)
196{
197 const bool running = d->indexProvider->isRunning();
198 d->indexProvider->collectIndices(customFilterName);
199 if (running)
200 return;
201
202 d->indices = QStringList();
203 filter(filter: QString());
204 emit indexCreationStarted();
205}
206
207void QHelpIndexModel::insertIndices()
208{
209 if (d->indexProvider->isRunning())
210 return;
211
212 d->indices = d->indexProvider->indices();
213 filter(filter: QString());
214 emit indexCreated();
215}
216
217/*!
218 Returns true if the index is currently built up, otherwise
219 false.
220*/
221bool QHelpIndexModel::isCreatingIndex() const
222{
223 return d->indexProvider->isRunning();
224}
225
226/*!
227 \since 5.15
228
229 Returns the associated help engine that manages this model.
230*/
231QHelpEngineCore *QHelpIndexModel::helpEngine() const
232{
233 return d->helpEngine->q;
234}
235
236#if QT_DEPRECATED_SINCE(5, 15)
237/*!
238 \obsolete
239 Use QHelpEngineCore::documentsForKeyword() instead.
240*/
241QT_WARNING_PUSH
242QT_WARNING_DISABLE_DEPRECATED
243QMap<QString, QUrl> QHelpIndexModel::linksForKeyword(const QString &keyword) const
244{
245 return d->helpEngine->q->linksForKeyword(keyword);
246}
247QT_WARNING_POP
248#endif
249
250/*!
251 Filters the indices and returns the model index of the best
252 matching keyword. In a first step, only the keywords containing
253 \a filter are kept in the model's index list. Analogously, if
254 \a wildcard is not empty, only the keywords matched are left
255 in the index list. In a second step, the best match is
256 determined and its index model returned. When specifying a
257 wildcard expression, the \a filter string is used to
258 search for the best match.
259*/
260QModelIndex QHelpIndexModel::filter(const QString &filter, const QString &wildcard)
261{
262 if (filter.isEmpty()) {
263 setStringList(d->indices);
264 return index(row: -1, column: 0, parent: QModelIndex());
265 }
266
267 QStringList lst;
268 int goodMatch = -1;
269 int perfectMatch = -1;
270
271 if (!wildcard.isEmpty()) {
272 const QRegExp regExp(wildcard, Qt::CaseInsensitive, QRegExp::Wildcard);
273 for (const QString &index : qAsConst(t&: d->indices)) {
274 if (index.contains(rx: regExp)) {
275 lst.append(t: index);
276 if (perfectMatch == -1 && index.startsWith(s: filter, cs: Qt::CaseInsensitive)) {
277 if (goodMatch == -1)
278 goodMatch = lst.count() - 1;
279 if (filter.length() == index.length()){
280 perfectMatch = lst.count() - 1;
281 }
282 } else if (perfectMatch > -1 && index == filter) {
283 perfectMatch = lst.count() - 1;
284 }
285 }
286 }
287 } else {
288 for (const QString &index : qAsConst(t&: d->indices)) {
289 if (index.contains(s: filter, cs: Qt::CaseInsensitive)) {
290 lst.append(t: index);
291 if (perfectMatch == -1 && index.startsWith(s: filter, cs: Qt::CaseInsensitive)) {
292 if (goodMatch == -1)
293 goodMatch = lst.count() - 1;
294 if (filter.length() == index.length()){
295 perfectMatch = lst.count() - 1;
296 }
297 } else if (perfectMatch > -1 && index == filter) {
298 perfectMatch = lst.count() - 1;
299 }
300 }
301 }
302
303 }
304
305 if (perfectMatch == -1)
306 perfectMatch = qMax(a: 0, b: goodMatch);
307
308 setStringList(lst);
309 return index(row: perfectMatch, column: 0, parent: QModelIndex());
310}
311
312
313
314/*!
315 \class QHelpIndexWidget
316 \inmodule QtHelp
317 \since 4.4
318 \brief The QHelpIndexWidget class provides a list view
319 displaying the QHelpIndexModel.
320*/
321
322/*!
323 \fn void QHelpIndexWidget::linkActivated(const QUrl &link,
324 const QString &keyword)
325
326 \obsolete
327
328 Use documentActivated() instead.
329
330 This signal is emitted when an item is activated and its
331 associated \a link should be shown. To know where the link
332 belongs to, the \a keyword is given as a second parameter.
333*/
334
335/*!
336 \fn void QHelpIndexWidget::linksActivated(const QMap<QString, QUrl> &links,
337 const QString &keyword)
338
339 \obsolete
340
341 Use documentsActivated() instead.
342
343 This signal is emitted when the item representing the \a keyword
344 is activated and the item has more than one link associated.
345 The \a links consist of the document titles and their URLs.
346*/
347
348/*!
349 \fn void QHelpIndexWidget::documentActivated(const QHelpLink &document,
350 const QString &keyword)
351
352 \since 5.15
353
354 This signal is emitted when an item is activated and its
355 associated \a document should be shown. To know where the link
356 belongs to, the \a keyword is given as a second parameter.
357*/
358
359/*!
360 \fn void QHelpIndexWidget::documentsActivated(const QList<QHelpLink> &documents,
361 const QString &keyword)
362
363 \since 5.15
364
365 This signal is emitted when the item representing the \a keyword
366 is activated and the item has more than one document associated.
367 The \a documents consist of the document titles and their URLs.
368*/
369
370QHelpIndexWidget::QHelpIndexWidget()
371 : QListView(nullptr)
372{
373 setEditTriggers(QAbstractItemView::NoEditTriggers);
374 setUniformItemSizes(true);
375 connect(sender: this, signal: &QAbstractItemView::activated,
376 receiver: this, slot: &QHelpIndexWidget::showLink);
377}
378
379void QHelpIndexWidget::showLink(const QModelIndex &index)
380{
381 if (!index.isValid())
382 return;
383
384 QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(object: model());
385 if (!indexModel)
386 return;
387
388 const QVariant &v = indexModel->data(index, role: Qt::DisplayRole);
389 const QString name = v.isValid() ? v.toString() : QString();
390
391 const QList<QHelpLink> &docs = indexModel->helpEngine()->documentsForKeyword(keyword: name);
392 if (docs.count() > 1) {
393 emit documentsActivated(documents: docs, keyword: name);
394 QT_WARNING_PUSH
395 QT_WARNING_DISABLE_DEPRECATED
396 QMap<QString, QUrl> links;
397 for (const auto &doc : docs)
398 static_cast<QMultiMap<QString, QUrl> &>(links).insert(akey: doc.title, avalue: doc.url);
399 emit linksActivated(links, keyword: name);
400 QT_WARNING_POP
401 } else if (!docs.isEmpty()) {
402 emit documentActivated(document: docs.first(), keyword: name);
403 QT_WARNING_PUSH
404 QT_WARNING_DISABLE_DEPRECATED
405 emit linkActivated(link: docs.first().url, keyword: name);
406 QT_WARNING_POP
407 }
408}
409
410/*!
411 Activates the current item which will result eventually in
412 the emitting of a linkActivated() or linksActivated()
413 signal.
414*/
415void QHelpIndexWidget::activateCurrentItem()
416{
417 showLink(index: currentIndex());
418}
419
420/*!
421 Filters the indices according to \a filter or \a wildcard.
422 The item with the best match is set as current item.
423
424 \sa QHelpIndexModel::filter()
425*/
426void QHelpIndexWidget::filterIndices(const QString &filter, const QString &wildcard)
427{
428 QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(object: model());
429 if (!indexModel)
430 return;
431 const QModelIndex &idx = indexModel->filter(filter, wildcard);
432 if (idx.isValid())
433 setCurrentIndex(idx);
434}
435
436QT_END_NAMESPACE
437

source code of qttools/src/assistant/help/qhelpindexwidget.cpp