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

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