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 "qhelpcontentwidget.h"
5#include "qhelpenginecore.h"
6#include "qhelpengine_p.h"
7#include "qhelpcollectionhandler_p.h"
8
9#include <QDir>
10#include <QtCore/QStack>
11#include <QtCore/QThread>
12#include <QtCore/QMutex>
13#include <QtWidgets/QHeaderView>
14
15QT_BEGIN_NAMESPACE
16
17class QHelpContentItemPrivate
18{
19public:
20 QHelpContentItemPrivate(const QString &t, const QUrl &l, QHelpContentItem *p)
21 : parent(p),
22 title(t),
23 link(l)
24 {
25 }
26
27 void appendChild(QHelpContentItem *item) { childItems.append(t: item); }
28
29 QList<QHelpContentItem*> childItems;
30 QHelpContentItem *parent;
31 QString title;
32 QUrl link;
33};
34
35class QHelpContentProvider : public QThread
36{
37 Q_OBJECT
38public:
39 QHelpContentProvider(QHelpEnginePrivate *helpEngine);
40 ~QHelpContentProvider() override;
41 void collectContents(const QString &customFilterName);
42 void stopCollecting();
43 QHelpContentItem *takeContentItem();
44
45private:
46 void run() override;
47
48 QHelpEnginePrivate *m_helpEngine;
49 QString m_currentFilter;
50 QStringList m_filterAttributes;
51 QString m_collectionFile;
52 QHelpContentItem *m_rootItem = nullptr;
53 QMutex m_mutex;
54 bool m_usesFilterEngine = false;
55 bool m_abort = false;
56};
57
58class QHelpContentModelPrivate
59{
60public:
61 QHelpContentItem *rootItem = nullptr;
62 QHelpContentProvider *qhelpContentProvider;
63};
64
65
66
67/*!
68 \class QHelpContentItem
69 \inmodule QtHelp
70 \brief The QHelpContentItem class provides an item for use with QHelpContentModel.
71 \since 4.4
72*/
73
74QHelpContentItem::QHelpContentItem(const QString &name, const QUrl &link, QHelpContentItem *parent)
75{
76 d = new QHelpContentItemPrivate(name, link, parent);
77}
78
79/*!
80 Destroys the help content item.
81*/
82QHelpContentItem::~QHelpContentItem()
83{
84 qDeleteAll(c: d->childItems);
85 delete d;
86}
87
88/*!
89 Returns the child of the content item in the give \a row.
90
91 \sa parent()
92*/
93QHelpContentItem *QHelpContentItem::child(int row) const
94{
95 return d->childItems.value(i: row);
96}
97
98/*!
99 Returns the number of child items.
100*/
101int QHelpContentItem::childCount() const
102{
103 return d->childItems.size();
104}
105
106/*!
107 Returns the row of this item from its parents view.
108*/
109int QHelpContentItem::row() const
110{
111 if (d->parent)
112 return d->parent->d->childItems.indexOf(t: const_cast<QHelpContentItem*>(this));
113 return 0;
114}
115
116/*!
117 Returns the title of the content item.
118*/
119QString QHelpContentItem::title() const
120{
121 return d->title;
122}
123
124/*!
125 Returns the URL of this content item.
126*/
127QUrl QHelpContentItem::url() const
128{
129 return d->link;
130}
131
132/*!
133 Returns the parent content item.
134*/
135QHelpContentItem *QHelpContentItem::parent() const
136{
137 return d->parent;
138}
139
140/*!
141 Returns the position of a given \a child.
142*/
143int QHelpContentItem::childPosition(QHelpContentItem *child) const
144{
145 return d->childItems.indexOf(t: child);
146}
147
148
149
150QHelpContentProvider::QHelpContentProvider(QHelpEnginePrivate *helpEngine)
151 : QThread(helpEngine)
152{
153 m_helpEngine = helpEngine;
154}
155
156QHelpContentProvider::~QHelpContentProvider()
157{
158 stopCollecting();
159}
160
161void QHelpContentProvider::collectContents(const QString &customFilterName)
162{
163 m_mutex.lock();
164 m_currentFilter = customFilterName;
165 m_filterAttributes = m_helpEngine->q->filterAttributes(filterName: customFilterName);
166 m_collectionFile = m_helpEngine->collectionHandler->collectionFile();
167 m_usesFilterEngine = m_helpEngine->usesFilterEngine;
168 m_mutex.unlock();
169
170 if (isRunning())
171 stopCollecting();
172 start(LowPriority);
173}
174
175void QHelpContentProvider::stopCollecting()
176{
177 if (isRunning()) {
178 m_mutex.lock();
179 m_abort = true;
180 m_mutex.unlock();
181 wait();
182 // we need to force-set m_abort to false, because the thread might either have
183 // finished between the isRunning() check and the "m_abort = true" above, or the
184 // isRunning() check might already happen after the "m_abort = false" in the run() method,
185 // either way never resetting m_abort to false from within the run() method
186 m_abort = false;
187 }
188 delete m_rootItem;
189 m_rootItem = nullptr;
190}
191
192QHelpContentItem *QHelpContentProvider::takeContentItem()
193{
194 QMutexLocker locker(&m_mutex);
195 QHelpContentItem *content = m_rootItem;
196 m_rootItem = nullptr;
197 return content;
198}
199
200
201static QUrl constructUrl(const QString &namespaceName,
202 const QString &folderName,
203 const QString &relativePath)
204{
205 const int idx = relativePath.indexOf(c: QLatin1Char('#'));
206 const QString &rp = idx < 0 ? relativePath : relativePath.left(n: idx);
207 const QString anchor = idx < 0 ? QString() : relativePath.mid(position: idx + 1);
208 return QHelpCollectionHandler::buildQUrl(ns: namespaceName, folder: folderName, relFileName: rp, anchor);
209}
210
211void QHelpContentProvider::run()
212{
213 m_mutex.lock();
214 const QString currentFilter = m_currentFilter;
215 const QStringList attributes = m_filterAttributes;
216 const QString collectionFile = m_collectionFile;
217 const bool usesFilterEngine = m_usesFilterEngine;
218 delete m_rootItem;
219 m_rootItem = nullptr;
220 m_mutex.unlock();
221
222 if (collectionFile.isEmpty())
223 return;
224
225 QHelpCollectionHandler collectionHandler(collectionFile);
226 if (!collectionHandler.openCollectionFile())
227 return;
228
229 QString title;
230 QString link;
231 int depth = 0;
232 QHelpContentItem *item = nullptr;
233 QHelpContentItem * const rootItem = new QHelpContentItem(QString(), QString(), nullptr);
234
235 const QList<QHelpCollectionHandler::ContentsData> result = usesFilterEngine
236 ? collectionHandler.contentsForFilter(filterName: currentFilter)
237 : collectionHandler.contentsForFilter(filterAttributes: attributes);
238
239 for (const auto &contentsData : result) {
240 m_mutex.lock();
241 if (m_abort) {
242 delete rootItem;
243 m_abort = false;
244 m_mutex.unlock();
245 return;
246 }
247 m_mutex.unlock();
248
249 const QString namespaceName = contentsData.namespaceName;
250 const QString folderName = contentsData.folderName;
251 for (const QByteArray &contents : contentsData.contentsList) {
252 if (contents.size() < 1)
253 continue;
254
255 int _depth = 0;
256 bool _root = false;
257 QStack<QHelpContentItem*> stack;
258
259 QDataStream s(contents);
260 for (;;) {
261 s >> depth;
262 s >> link;
263 s >> title;
264 if (title.isEmpty())
265 break;
266 const QUrl url = constructUrl(namespaceName, folderName, relativePath: link);
267CHECK_DEPTH:
268 if (depth == 0) {
269 m_mutex.lock();
270 item = new QHelpContentItem(title, url, rootItem);
271 rootItem->d->appendChild(item);
272 m_mutex.unlock();
273 stack.push(t: item);
274 _depth = 1;
275 _root = true;
276 } else {
277 if (depth > _depth && _root) {
278 _depth = depth;
279 stack.push(t: item);
280 }
281 if (depth == _depth) {
282 item = new QHelpContentItem(title, url, stack.top());
283 stack.top()->d->appendChild(item);
284 } else if (depth < _depth) {
285 stack.pop();
286 --_depth;
287 goto CHECK_DEPTH;
288 }
289 }
290 }
291 }
292 }
293
294 m_mutex.lock();
295 m_rootItem = rootItem;
296 m_abort = false;
297 m_mutex.unlock();
298}
299
300/*!
301 \class QHelpContentModel
302 \inmodule QtHelp
303 \brief The QHelpContentModel class provides a model that supplies content to views.
304 \since 4.4
305*/
306
307/*!
308 \fn void QHelpContentModel::contentsCreationStarted()
309
310 This signal is emitted when the creation of the contents has
311 started. The current contents are invalid from this point on
312 until the signal contentsCreated() is emitted.
313
314 \sa isCreatingContents()
315*/
316
317/*!
318 \fn void QHelpContentModel::contentsCreated()
319
320 This signal is emitted when the contents have been created.
321*/
322
323QHelpContentModel::QHelpContentModel(QHelpEnginePrivate *helpEngine)
324 : QAbstractItemModel(helpEngine)
325{
326 d = new QHelpContentModelPrivate();
327 d->qhelpContentProvider = new QHelpContentProvider(helpEngine);
328
329 connect(sender: d->qhelpContentProvider, signal: &QThread::finished,
330 context: this, slot: &QHelpContentModel::insertContents);
331}
332
333/*!
334 Destroys the help content model.
335*/
336QHelpContentModel::~QHelpContentModel()
337{
338 delete d->rootItem;
339 delete d;
340}
341
342/*!
343 Creates new contents by querying the help system
344 for contents specified for the \a customFilterName.
345*/
346void QHelpContentModel::createContents(const QString &customFilterName)
347{
348 const bool running = d->qhelpContentProvider->isRunning();
349 d->qhelpContentProvider->collectContents(customFilterName);
350 if (running)
351 return;
352
353 if (d->rootItem) {
354 beginResetModel();
355 delete d->rootItem;
356 d->rootItem = nullptr;
357 endResetModel();
358 }
359 emit contentsCreationStarted();
360}
361
362void QHelpContentModel::insertContents()
363{
364 if (d->qhelpContentProvider->isRunning())
365 return;
366
367 QHelpContentItem * const newRootItem = d->qhelpContentProvider->takeContentItem();
368 if (!newRootItem)
369 return;
370 beginResetModel();
371 delete d->rootItem;
372 d->rootItem = newRootItem;
373 endResetModel();
374 emit contentsCreated();
375}
376
377/*!
378 Returns true if the contents are currently rebuilt, otherwise
379 false.
380*/
381bool QHelpContentModel::isCreatingContents() const
382{
383 return d->qhelpContentProvider->isRunning();
384}
385
386/*!
387 Returns the help content item at the model index position
388 \a index.
389*/
390QHelpContentItem *QHelpContentModel::contentItemAt(const QModelIndex &index) const
391{
392 if (index.isValid())
393 return static_cast<QHelpContentItem*>(index.internalPointer());
394 else
395 return d->rootItem;
396}
397
398/*!
399 Returns the index of the item in the model specified by
400 the given \a row, \a column and \a parent index.
401*/
402QModelIndex QHelpContentModel::index(int row, int column, const QModelIndex &parent) const
403{
404 if (!d->rootItem)
405 return QModelIndex();
406
407 QHelpContentItem *parentItem = contentItemAt(index: parent);
408 QHelpContentItem *item = parentItem->child(row);
409 if (!item)
410 return QModelIndex();
411 return createIndex(arow: row, acolumn: column, adata: item);
412}
413
414/*!
415 Returns the parent of the model item with the given
416 \a index, or QModelIndex() if it has no parent.
417*/
418QModelIndex QHelpContentModel::parent(const QModelIndex &index) const
419{
420 QHelpContentItem *item = contentItemAt(index);
421 if (!item)
422 return QModelIndex();
423
424 QHelpContentItem *parentItem = static_cast<QHelpContentItem*>(item->parent());
425 if (!parentItem)
426 return QModelIndex();
427
428 QHelpContentItem *grandparentItem = static_cast<QHelpContentItem*>(parentItem->parent());
429 if (!grandparentItem)
430 return QModelIndex();
431
432 int row = grandparentItem->childPosition(child: parentItem);
433 return createIndex(arow: row, acolumn: index.column(), adata: parentItem);
434}
435
436/*!
437 Returns the number of rows under the given \a parent.
438*/
439int QHelpContentModel::rowCount(const QModelIndex &parent) const
440{
441 QHelpContentItem *parentItem = contentItemAt(index: parent);
442 if (!parentItem)
443 return 0;
444 return parentItem->childCount();
445}
446
447/*!
448 Returns the number of columns under the given \a parent. Currently returns always 1.
449*/
450int QHelpContentModel::columnCount(const QModelIndex &parent) const
451{
452 Q_UNUSED(parent);
453
454 return 1;
455}
456
457/*!
458 Returns the data stored under the given \a role for
459 the item referred to by the \a index.
460*/
461QVariant QHelpContentModel::data(const QModelIndex &index, int role) const
462{
463 if (role != Qt::DisplayRole)
464 return QVariant();
465
466 QHelpContentItem *item = contentItemAt(index);
467 if (!item)
468 return QVariant();
469 return item->title();
470}
471
472
473
474/*!
475 \class QHelpContentWidget
476 \inmodule QtHelp
477 \brief The QHelpContentWidget class provides a tree view for displaying help content model items.
478 \since 4.4
479*/
480
481/*!
482 \fn void QHelpContentWidget::linkActivated(const QUrl &link)
483
484 This signal is emitted when a content item is activated and
485 its associated \a link should be shown.
486*/
487
488QHelpContentWidget::QHelpContentWidget()
489 : QTreeView(nullptr)
490{
491 header()->hide();
492 setUniformRowHeights(true);
493 connect(sender: this, signal: &QAbstractItemView::activated,
494 context: this, slot: &QHelpContentWidget::showLink);
495}
496
497/*!
498 Returns the index of the content item with the \a link.
499 An invalid index is returned if no such an item exists.
500*/
501QModelIndex QHelpContentWidget::indexOf(const QUrl &link)
502{
503 QHelpContentModel *contentModel = qobject_cast<QHelpContentModel*>(object: model());
504 if (!contentModel || link.scheme() != QLatin1String("qthelp"))
505 return QModelIndex();
506
507 m_syncIndex = QModelIndex();
508 for (int i = 0; i < contentModel->rowCount(); ++i) {
509 QHelpContentItem *itm = contentModel->contentItemAt(index: contentModel->index(row: i, column: 0));
510 if (itm && itm->url().host() == link.host()) {
511 if (searchContentItem(model: contentModel, parent: contentModel->index(row: i, column: 0), path: QDir::cleanPath(path: link.path())))
512 return m_syncIndex;
513 }
514 }
515 return QModelIndex();
516}
517
518bool QHelpContentWidget::searchContentItem(QHelpContentModel *model, const QModelIndex &parent,
519 const QString &cleanPath)
520{
521 QHelpContentItem *parentItem = model->contentItemAt(index: parent);
522 if (!parentItem)
523 return false;
524
525 if (QDir::cleanPath(path: parentItem->url().path()) == cleanPath) {
526 m_syncIndex = parent;
527 return true;
528 }
529
530 for (int i = 0; i < parentItem->childCount(); ++i) {
531 if (searchContentItem(model, parent: model->index(row: i, column: 0, parent), cleanPath))
532 return true;
533 }
534 return false;
535}
536
537void QHelpContentWidget::showLink(const QModelIndex &index)
538{
539 QHelpContentModel *contentModel = qobject_cast<QHelpContentModel*>(object: model());
540 if (!contentModel)
541 return;
542
543 QHelpContentItem *item = contentModel->contentItemAt(index);
544 if (!item)
545 return;
546 QUrl url = item->url();
547 if (url.isValid())
548 emit linkActivated(link: url);
549}
550
551QT_END_NAMESPACE
552
553#include "qhelpcontentwidget.moc"
554

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