1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qhelpcontentwidget.h"
43 #include "qhelpenginecore.h"
44 #include "qhelpengine_p.h"
45 #include "qhelpdbreader_p.h"
46 
47 #include <QtCore/QStack>
48 #include <QtCore/QThread>
49 #include <QtCore/QMutex>
50 #include <QtGui/QHeaderView>
51 
52 QT_BEGIN_NAMESPACE
53 
54 class QHelpContentItemPrivate
55 {
56 public:
QHelpContentItemPrivate(const QString & t,const QString & l,QHelpDBReader * r,QHelpContentItem * p)57     QHelpContentItemPrivate(const QString &t, const QString &l,
58                             QHelpDBReader *r, QHelpContentItem *p)
59     {
60         parent = p;
61         title = t;
62         link = l;
63         helpDBReader = r;
64     }
65 
66     QList<QHelpContentItem*> childItems;
67     QHelpContentItem *parent;
68     QString title;
69     QString link;
70     QHelpDBReader *helpDBReader;
71 };
72 
73 class QHelpContentProvider : public QThread
74 {
75     Q_OBJECT
76 public:
77     QHelpContentProvider(QHelpEnginePrivate *helpEngine);
78     ~QHelpContentProvider();
79     void collectContents(const QString &customFilterName);
80     void stopCollecting();
81     QHelpContentItem *rootItem();
82     int nextChildCount() const;
83 
84 signals:
85     void finishedSuccessFully();
86 
87 private:
88     void run();
89 
90     QHelpEnginePrivate *m_helpEngine;
91     QStringList m_filterAttributes;
92     QQueue<QHelpContentItem*> m_rootItems;
93     QMutex m_mutex;
94     bool m_abort;
95 };
96 
97 class QHelpContentModelPrivate
98 {
99 public:
100     QHelpContentItem *rootItem;
101     QHelpContentProvider *qhelpContentProvider;
102 };
103 
104 
105 
106 /*!
107     \class QHelpContentItem
108     \inmodule QtHelp
109     \brief The QHelpContentItem class provides an item for use with QHelpContentModel.
110     \since 4.4
111 */
112 
QHelpContentItem(const QString & name,const QString & link,QHelpDBReader * reader,QHelpContentItem * parent)113 QHelpContentItem::QHelpContentItem(const QString &name, const QString &link,
114                                    QHelpDBReader *reader, QHelpContentItem *parent)
115 {
116     d = new QHelpContentItemPrivate(name, link, reader, parent);
117 }
118 
119 /*!
120     Destroys the help content item.
121 */
~QHelpContentItem()122 QHelpContentItem::~QHelpContentItem()
123 {
124     qDeleteAll(d->childItems);
125     delete d;
126 }
127 
appendChild(QHelpContentItem * item)128 void QHelpContentItem::appendChild(QHelpContentItem *item)
129 {
130     d->childItems.append(item);
131 }
132 
133 /*!
134     Returns the child of the content item in the give \a row.
135 
136     \sa parent()
137 */
child(int row) const138 QHelpContentItem *QHelpContentItem::child(int row) const
139 {
140     if (row >= childCount())
141         return 0;
142     return d->childItems.value(row);
143 }
144 
145 /*!
146     Returns the number of child items.
147 */
childCount() const148 int QHelpContentItem::childCount() const
149 {
150     return d->childItems.count();
151 }
152 
153 /*!
154     Returns the row of this item from its parents view.
155 */
row() const156 int QHelpContentItem::row() const
157 {
158     if (d->parent)
159         return d->parent->d->childItems.indexOf(const_cast<QHelpContentItem*>(this));
160     return 0;
161 }
162 
163 /*!
164     Returns the title of the content item.
165 */
title() const166 QString QHelpContentItem::title() const
167 {
168     return d->title;
169 }
170 
171 /*!
172     Returns the URL of this content item.
173 */
url() const174 QUrl QHelpContentItem::url() const
175 {
176     return d->helpDBReader->urlOfPath(d->link);
177 }
178 
179 /*!
180     Returns the parent content item.
181 */
parent() const182 QHelpContentItem *QHelpContentItem::parent() const
183 {
184     return d->parent;
185 }
186 
187 /*!
188     Returns the position of a given \a child.
189 */
childPosition(QHelpContentItem * child) const190 int QHelpContentItem::childPosition(QHelpContentItem *child) const
191 {
192     return d->childItems.indexOf(child);
193 }
194 
195 
196 
QHelpContentProvider(QHelpEnginePrivate * helpEngine)197 QHelpContentProvider::QHelpContentProvider(QHelpEnginePrivate *helpEngine)
198     : QThread(helpEngine)
199 {
200     m_helpEngine = helpEngine;
201     m_abort = false;
202 }
203 
~QHelpContentProvider()204 QHelpContentProvider::~QHelpContentProvider()
205 {
206     stopCollecting();
207 }
208 
collectContents(const QString & customFilterName)209 void QHelpContentProvider::collectContents(const QString &customFilterName)
210 {
211     m_mutex.lock();
212     m_filterAttributes = m_helpEngine->q->filterAttributes(customFilterName);
213     m_mutex.unlock();
214     if (!isRunning()) {
215         start(LowPriority);
216     } else {
217         stopCollecting();
218         start(LowPriority);
219     }
220 }
221 
stopCollecting()222 void QHelpContentProvider::stopCollecting()
223 {
224     if (isRunning()) {
225         m_mutex.lock();
226         m_abort = true;
227         m_mutex.unlock();
228         wait();
229     }
230     qDeleteAll(m_rootItems);
231     m_rootItems.clear();
232 }
233 
rootItem()234 QHelpContentItem *QHelpContentProvider::rootItem()
235 {
236     QMutexLocker locker(&m_mutex);
237     if (m_rootItems.isEmpty())
238         return 0;
239     return m_rootItems.dequeue();
240 }
241 
nextChildCount() const242 int QHelpContentProvider::nextChildCount() const
243 {
244     if (m_rootItems.isEmpty())
245         return 0;
246     return m_rootItems.head()->childCount();
247 }
248 
run()249 void QHelpContentProvider::run()
250 {
251     QString title;
252     QString link;
253     int depth = 0;
254     QHelpContentItem *item = 0;
255 
256     m_mutex.lock();
257     QHelpContentItem * const rootItem = new QHelpContentItem(QString(), QString(), 0);
258     QStringList atts = m_filterAttributes;
259     const QStringList fileNames = m_helpEngine->orderedFileNameList;
260     m_mutex.unlock();
261 
262     foreach (const QString &dbFileName, fileNames) {
263         m_mutex.lock();
264         if (m_abort) {
265             delete rootItem;
266             m_abort = false;
267             m_mutex.unlock();
268             return;
269         }
270         m_mutex.unlock();
271         QHelpDBReader reader(dbFileName,
272             QHelpGlobal::uniquifyConnectionName(dbFileName +
273             QLatin1String("FromQHelpContentProvider"),
274             QThread::currentThread()), 0);
275         if (!reader.init())
276             continue;
277         foreach (const QByteArray& ba, reader.contentsForFilter(atts)) {
278             if (ba.size() < 1)
279                 continue;
280 
281             int _depth = 0;
282             bool _root = false;
283             QStack<QHelpContentItem*> stack;
284 
285             QDataStream s(ba);
286             for (;;) {
287                 s >> depth;
288                 s >> link;
289                 s >> title;
290                 if (title.isEmpty())
291                     break;
292 CHECK_DEPTH:
293                 if (depth == 0) {
294                     m_mutex.lock();
295                     item = new QHelpContentItem(title, link,
296                         m_helpEngine->fileNameReaderMap.value(dbFileName), rootItem);
297                     rootItem->appendChild(item);
298                     m_mutex.unlock();
299                     stack.push(item);
300                     _depth = 1;
301                     _root = true;
302                 } else {
303                     if (depth > _depth && _root) {
304                         _depth = depth;
305                         stack.push(item);
306                     }
307                     if (depth == _depth) {
308                         item = new QHelpContentItem(title, link,
309                             m_helpEngine->fileNameReaderMap.value(dbFileName), stack.top());
310                         stack.top()->appendChild(item);
311                     } else if (depth < _depth) {
312                         stack.pop();
313                         --_depth;
314                         goto CHECK_DEPTH;
315                     }
316                 }
317             }
318         }
319     }
320     m_mutex.lock();
321     m_rootItems.enqueue(rootItem);
322     m_abort = false;
323     m_mutex.unlock();
324     emit finishedSuccessFully();
325 }
326 
327 
328 
329 /*!
330     \class QHelpContentModel
331     \inmodule QtHelp
332     \brief The QHelpContentModel class provides a model that supplies content to views.
333     \since 4.4
334 */
335 
336 /*!
337     \fn void QHelpContentModel::contentsCreationStarted()
338 
339     This signal is emitted when the creation of the contents has
340     started. The current contents are invalid from this point on
341     until the signal contentsCreated() is emitted.
342 
343     \sa isCreatingContents()
344 */
345 
346 /*!
347     \fn void QHelpContentModel::contentsCreated()
348 
349     This signal is emitted when the contents have been created.
350 */
351 
QHelpContentModel(QHelpEnginePrivate * helpEngine)352 QHelpContentModel::QHelpContentModel(QHelpEnginePrivate *helpEngine)
353     : QAbstractItemModel(helpEngine)
354 {
355     d = new QHelpContentModelPrivate();
356     d->rootItem = 0;
357     d->qhelpContentProvider = new QHelpContentProvider(helpEngine);
358 
359     connect(d->qhelpContentProvider, SIGNAL(finishedSuccessFully()),
360         this, SLOT(insertContents()), Qt::QueuedConnection);
361     connect(helpEngine->q, SIGNAL(readersAboutToBeInvalidated()), this, SLOT(invalidateContents()));
362 }
363 
364 /*!
365     Destroys the help content model.
366 */
~QHelpContentModel()367 QHelpContentModel::~QHelpContentModel()
368 {
369     delete d->rootItem;
370     delete d;
371 }
372 
invalidateContents(bool onShutDown)373 void QHelpContentModel::invalidateContents(bool onShutDown)
374 {
375     if (onShutDown)
376         disconnect(this, SLOT(insertContents()));
377     d->qhelpContentProvider->stopCollecting();
378     if (d->rootItem) {
379         delete d->rootItem;
380         d->rootItem = 0;
381     }
382     if (!onShutDown)
383         reset();
384 }
385 
386 /*!
387     Creates new contents by querying the help system
388     for contents specified for the \a customFilterName.
389 */
createContents(const QString & customFilterName)390 void QHelpContentModel::createContents(const QString &customFilterName)
391 {
392     d->qhelpContentProvider->collectContents(customFilterName);
393     emit contentsCreationStarted();
394 }
395 
insertContents()396 void QHelpContentModel::insertContents()
397 {
398     QHelpContentItem * const newRootItem = d->qhelpContentProvider->rootItem();
399     if (!newRootItem)
400         return;
401     int count;
402     if (d->rootItem) {
403         count = d->rootItem->childCount() - 1;
404         beginRemoveRows(QModelIndex(), 0, count > 0 ? count : 0);
405         delete d->rootItem;
406         d->rootItem = 0;
407         endRemoveRows();
408     }
409 
410     count = d->qhelpContentProvider->nextChildCount() - 1;
411     beginInsertRows(QModelIndex(), 0, count > 0 ? count : 0);
412     d->rootItem = newRootItem;
413     endInsertRows();
414     reset();
415     emit contentsCreated();
416 }
417 
418 /*!
419     Returns true if the contents are currently rebuilt, otherwise
420     false.
421 */
isCreatingContents() const422 bool QHelpContentModel::isCreatingContents() const
423 {
424     return d->qhelpContentProvider->isRunning();
425 }
426 
427 /*!
428     Returns the help content item at the model index position
429     \a index.
430 */
contentItemAt(const QModelIndex & index) const431 QHelpContentItem *QHelpContentModel::contentItemAt(const QModelIndex &index) const
432 {
433     if (index.isValid())
434         return static_cast<QHelpContentItem*>(index.internalPointer());
435     else
436         return d->rootItem;
437 }
438 
439 /*!
440     Returns the index of the item in the model specified by
441     the given \a row, \a column and \a parent index.
442 */
index(int row,int column,const QModelIndex & parent) const443 QModelIndex QHelpContentModel::index(int row, int column, const QModelIndex &parent) const
444 {
445     if (!d->rootItem)
446         return QModelIndex();
447 
448     QHelpContentItem *parentItem = contentItemAt(parent);
449     QHelpContentItem *item = parentItem->child(row);
450     if (!item)
451         return QModelIndex();
452     return createIndex(row, column, item);
453 }
454 
455 /*!
456     Returns the parent of the model item with the given
457     \a index, or QModelIndex() if it has no parent.
458 */
parent(const QModelIndex & index) const459 QModelIndex QHelpContentModel::parent(const QModelIndex &index) const
460 {
461     QHelpContentItem *item = contentItemAt(index);
462     if (!item)
463         return QModelIndex();
464 
465     QHelpContentItem *parentItem = static_cast<QHelpContentItem*>(item->parent());
466     if (!parentItem)
467         return QModelIndex();
468 
469     QHelpContentItem *grandparentItem = static_cast<QHelpContentItem*>(parentItem->parent());
470     if (!grandparentItem)
471         return QModelIndex();
472 
473     int row = grandparentItem->childPosition(parentItem);
474     return createIndex(row, index.column(), parentItem);
475 }
476 
477 /*!
478     Returns the number of rows under the given \a parent.
479 */
rowCount(const QModelIndex & parent) const480 int QHelpContentModel::rowCount(const QModelIndex &parent) const
481 {
482     QHelpContentItem *parentItem = contentItemAt(parent);
483     if (!parentItem)
484         return 0;
485     return parentItem->childCount();
486 }
487 
488 /*!
489     Returns the number of columns under the given \a parent. Currently returns always 1.
490 */
columnCount(const QModelIndex & parent) const491 int QHelpContentModel::columnCount(const QModelIndex &parent) const
492 {
493     Q_UNUSED(parent)
494 
495     return 1;
496 }
497 
498 /*!
499     Returns the data stored under the given \a role for
500     the item referred to by the \a index.
501 */
data(const QModelIndex & index,int role) const502 QVariant QHelpContentModel::data(const QModelIndex &index, int role) const
503 {
504     if (role != Qt::DisplayRole)
505         return QVariant();
506 
507     QHelpContentItem *item = contentItemAt(index);
508     if (!item)
509         return QVariant();
510     return item->title();
511 }
512 
513 
514 
515 /*!
516     \class QHelpContentWidget
517     \inmodule QtHelp
518     \brief The QHelpContentWidget class provides a tree view for displaying help content model items.
519     \since 4.4
520 */
521 
522 /*!
523     \fn void QHelpContentWidget::linkActivated(const QUrl &link)
524 
525     This signal is emitted when a content item is activated and
526     its associated \a link should be shown.
527 */
528 
QHelpContentWidget()529 QHelpContentWidget::QHelpContentWidget()
530     : QTreeView(0)
531 {
532     header()->hide();
533     setUniformRowHeights(true);
534     connect(this, SIGNAL(activated(QModelIndex)),
535         this, SLOT(showLink(QModelIndex)));
536 }
537 
538 /*!
539     Returns the index of the content item with the \a link.
540     An invalid index is returned if no such an item exists.
541 */
indexOf(const QUrl & link)542 QModelIndex QHelpContentWidget::indexOf(const QUrl &link)
543 {
544     QHelpContentModel *contentModel =
545         qobject_cast<QHelpContentModel*>(model());
546     if (!contentModel || link.scheme() != QLatin1String("qthelp"))
547         return QModelIndex();
548 
549     m_syncIndex = QModelIndex();
550     for (int i=0; i<contentModel->rowCount(); ++i) {
551         QHelpContentItem *itm =
552             contentModel->contentItemAt(contentModel->index(i, 0));
553         if (itm && itm->url().host() == link.host()) {
554             QString path = link.path();
555             if (path.startsWith(QLatin1Char('/')))
556                 path = path.mid(1);
557             if (searchContentItem(contentModel, contentModel->index(i, 0), path)) {
558                 return m_syncIndex;
559             }
560         }
561     }
562     return QModelIndex();
563 }
564 
searchContentItem(QHelpContentModel * model,const QModelIndex & parent,const QString & path)565 bool QHelpContentWidget::searchContentItem(QHelpContentModel *model,
566                                            const QModelIndex &parent, const QString &path)
567 {
568     QHelpContentItem *parentItem = model->contentItemAt(parent);
569     if (!parentItem)
570         return false;
571 
572     if (parentItem->url().path() == path) {
573         m_syncIndex = parent;
574         return true;
575     }
576 
577     for (int i=0; i<parentItem->childCount(); ++i) {
578         if (searchContentItem(model, model->index(i, 0, parent), path))
579             return true;
580     }
581     return false;
582 }
583 
showLink(const QModelIndex & index)584 void QHelpContentWidget::showLink(const QModelIndex &index)
585 {
586     QHelpContentModel *contentModel = qobject_cast<QHelpContentModel*>(model());
587     if (!contentModel)
588         return;
589 
590     QHelpContentItem *item = contentModel->contentItemAt(index);
591     if (!item)
592         return;
593     QUrl url = item->url();
594     if (url.isValid())
595         emit linkActivated(url);
596 }
597 
598 QT_END_NAMESPACE
599 
600 #include "qhelpcontentwidget.moc"
601