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

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