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