Fix crash when accessing the content model.
[qt:qt.git] / tools / assistant / lib / qhelpindexwidget.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 "qhelpindexwidget.h"
43 #include "qhelpenginecore.h"
44 #include "qhelpengine_p.h"
45 #include "qhelpdbreader_p.h"
46
47 #include <QtCore/QThread>
48 #include <QtCore/QMutex>
49 #include <QtGui/QListView>
50 #include <QtGui/QHeaderView>
51
52 QT_BEGIN_NAMESPACE
53
54 class QHelpIndexProvider : public QThread
55 {
56 public:
57     QHelpIndexProvider(QHelpEnginePrivate *helpEngine);
58     ~QHelpIndexProvider();
59     void collectIndices(const QString &customFilterName);
60     void stopCollecting();
61     QStringList indices() const;
62     QList<QHelpDBReader*> activeReaders() const;
63     QSet<int> indexIds(QHelpDBReader *reader) const;
64
65 private:
66     void run();
67
68     QHelpEnginePrivate *m_helpEngine;
69     QStringList m_indices;
70     QList<QHelpDBReader*> m_activeReaders;
71     QMap<QHelpDBReader*, QSet<int> > m_indexIds;
72     QStringList m_filterAttributes;
73     mutable QMutex m_mutex;
74     bool m_abort;
75 };
76
77 class QHelpIndexModelPrivate
78 {
79 public:
80     QHelpIndexModelPrivate(QHelpEnginePrivate *hE)
81     {
82         helpEngine = hE;
83         indexProvider = new QHelpIndexProvider(helpEngine);
84         insertedRows = 0;
85     }
86
87     QHelpEnginePrivate *helpEngine;
88     QHelpIndexProvider *indexProvider;
89     QStringList indices;
90     int insertedRows;
91     QString currentFilter;
92     QList<QHelpDBReader*> activeReaders;
93 };
94
95 static bool caseInsensitiveLessThan(const QString &as, const QString &bs)
96 {
97     return QString::compare(as, bs, Qt::CaseInsensitive) < 0;
98 }
99
100 QHelpIndexProvider::QHelpIndexProvider(QHelpEnginePrivate *helpEngine)
101     : QThread(helpEngine)
102 {
103     m_helpEngine = helpEngine;
104     m_abort = false;
105 }
106
107 QHelpIndexProvider::~QHelpIndexProvider()
108 {
109     stopCollecting();
110 }
111
112 void QHelpIndexProvider::collectIndices(const QString &customFilterName)
113 {
114     m_mutex.lock();
115     m_filterAttributes = m_helpEngine->q->filterAttributes(customFilterName);
116     m_mutex.unlock();
117     if (!isRunning()) {
118         start(LowPriority);
119     } else {
120         stopCollecting();
121         start(LowPriority);
122     }
123 }
124
125 void QHelpIndexProvider::stopCollecting()
126 {
127     if (!isRunning())
128         return;
129     m_mutex.lock();
130     m_abort = true;
131     m_mutex.unlock();
132     wait();
133     m_abort = false;
134 }
135
136 QStringList QHelpIndexProvider::indices() const
137 {
138     QMutexLocker lck(&m_mutex);
139     return m_indices;
140 }
141
142 QList<QHelpDBReader*> QHelpIndexProvider::activeReaders() const
143 {
144     QMutexLocker lck(&m_mutex);
145     return m_activeReaders;
146 }
147
148 QSet<int> QHelpIndexProvider::indexIds(QHelpDBReader *reader) const
149 {
150     QMutexLocker lck(&m_mutex);
151     if (m_indexIds.contains(reader))
152         return m_indexIds.value(reader);
153     return QSet<int>();
154 }
155
156 void QHelpIndexProvider::run()
157 {
158     m_mutex.lock();
159     QStringList atts = m_filterAttributes;
160     m_indices.clear();
161     m_activeReaders.clear();
162     QSet<QString> indicesSet;
163     m_mutex.unlock();
164
165     foreach (const QString &dbFileName, m_helpEngine->fileNameReaderMap.keys()) {
166         m_mutex.lock();
167         if (m_abort) {
168             m_mutex.unlock();
169             return;
170         }
171         m_mutex.unlock();
172         QHelpDBReader reader(dbFileName,
173             QHelpGlobal::uniquifyConnectionName(dbFileName +
174             QLatin1String("FromIndexProvider"),
175             QThread::currentThread()), 0);
176         if (!reader.init())
177             continue;
178         QStringList lst = reader.indicesForFilter(atts);
179         if (!lst.isEmpty()) {
180             m_mutex.lock();
181             foreach (const QString &s, lst)
182                 indicesSet.insert(s);
183             if (m_abort) {
184                 m_mutex.unlock();
185                 return;
186             }
187             QHelpDBReader *orgReader = m_helpEngine->fileNameReaderMap.value(dbFileName);
188             m_indexIds.insert(orgReader, reader.indexIds(atts));
189             m_activeReaders.append(orgReader);
190             m_mutex.unlock();
191         }
192     }
193     m_mutex.lock();
194     m_indices = indicesSet.values();
195     qSort(m_indices.begin(), m_indices.end(), caseInsensitiveLessThan);
196     m_mutex.unlock();
197 }
198
199
200
201 /*!
202     \class QHelpIndexModel
203     \since 4.4
204     \inmodule QtHelp
205     \brief The QHelpIndexModel class provides a model that
206     supplies index keywords to views.
207
208
209 */
210
211 /*!
212     \fn void QHelpIndexModel::indexCreationStarted()
213
214     This signal is emitted when the creation of a new index
215     has started. The current index is invalid from this
216     point on until the signal indexCreated() is emitted.
217
218     \sa isCreatingIndex()
219 */
220
221 /*!
222     \fn void QHelpIndexModel::indexCreated()
223
224     This signal is emitted when the index has been created.
225 */
226
227 QHelpIndexModel::QHelpIndexModel(QHelpEnginePrivate *helpEngine)
228     : QStringListModel(helpEngine)
229 {
230     d = new QHelpIndexModelPrivate(helpEngine);
231
232     connect(d->indexProvider, SIGNAL(finished()), this, SLOT(insertIndices()));
233     connect(helpEngine->q, SIGNAL(readersAboutToBeInvalidated()), this, SLOT(invalidateIndex()));
234 }
235
236 QHelpIndexModel::~QHelpIndexModel()
237 {
238     delete d;
239 }
240
241 void QHelpIndexModel::invalidateIndex(bool onShutDown)
242 {
243     if (onShutDown)
244         disconnect(this, SLOT(insertIndices()));
245     d->indexProvider->stopCollecting();
246     d->indices.clear();
247     if (!onShutDown)
248         filter(QString());
249 }
250
251 /*!
252     Creates a new index by querying the help system for
253     keywords for the specified \a customFilterName.
254 */
255 void QHelpIndexModel::createIndex(const QString &customFilterName)
256 {
257     d->currentFilter = customFilterName;
258     d->indexProvider->collectIndices(customFilterName);
259     emit indexCreationStarted();
260 }
261
262 void QHelpIndexModel::insertIndices()
263 {
264     d->indices = d->indexProvider->indices();
265     d->activeReaders = d->indexProvider->activeReaders();
266     QStringList attributes = d->helpEngine->q->filterAttributes(d->currentFilter);
267     if (attributes.count() > 1) {
268         foreach (QHelpDBReader *r, d->activeReaders)
269             r->createAttributesCache(attributes, d->indexProvider->indexIds(r));
270     }
271     filter(QString());
272     emit indexCreated();
273 }
274
275 /*!
276     Returns true if the index is currently built up, otherwise
277     false.
278 */
279 bool QHelpIndexModel::isCreatingIndex() const
280 {
281     return d->indexProvider->isRunning();
282 }
283
284 /*!
285     Returns all hits found for the \a keyword. A hit consists of
286     the URL and the document title.
287 */
288 QMap<QString, QUrl> QHelpIndexModel::linksForKeyword(const QString &keyword) const
289 {
290     QMap<QString, QUrl> linkMap;
291     QStringList filterAttributes = d->helpEngine->q->filterAttributes(d->currentFilter);
292     foreach (QHelpDBReader *reader, d->activeReaders)
293         reader->linksForKeyword(keyword, filterAttributes, linkMap);
294     return linkMap;
295 }
296
297 /*!
298     Filters the indices and returns the model index of the best
299     matching keyword. In a first step, only the keywords containing
300     \a filter are kept in the model's index list. Analogously, if
301     \a wildcard is not empty, only the keywords matched are left
302     in the index list. In a second step, the best match is
303     determined and its index model returned. When specifying a
304     wildcard expression, the \a filter string is used to
305     search for the best match.
306 */
307 QModelIndex QHelpIndexModel::filter(const QString &filter, const QString &wildcard)
308 {
309     if (filter.isEmpty()) {
310         setStringList(d->indices);
311         return index(-1, 0, QModelIndex());
312     }
313
314     QStringList lst;
315     int goodMatch = -1;
316     int perfectMatch = -1;
317
318     if (!wildcard.isEmpty()) {
319         QRegExp regExp(wildcard, Qt::CaseInsensitive);
320         regExp.setPatternSyntax(QRegExp::Wildcard);
321         foreach (const QString &index, d->indices) {
322             if (index.contains(regExp)) {
323                 lst.append(index);
324                 if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
325                     if (goodMatch == -1)
326                         goodMatch = lst.count()-1;
327                     if (filter.length() == index.length()){
328                         perfectMatch = lst.count()-1;
329                     }
330                 } else if (perfectMatch > -1 && index == filter) {
331                     perfectMatch = lst.count()-1;
332                 }
333             }
334         }
335     } else {
336         foreach (const QString &index, d->indices) {
337             if (index.contains(filter, Qt::CaseInsensitive)) {
338                 lst.append(index);
339                 if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
340                     if (goodMatch == -1)
341                         goodMatch = lst.count()-1;
342                     if (filter.length() == index.length()){
343                         perfectMatch = lst.count()-1;
344                     }
345                 } else if (perfectMatch > -1 && index == filter) {
346                     perfectMatch = lst.count()-1;
347                 }
348             }
349         }
350
351     }
352
353     if (perfectMatch == -1)
354         perfectMatch = qMax(0, goodMatch);
355
356     setStringList(lst);
357     return index(perfectMatch, 0, QModelIndex());
358 }
359
360
361
362 /*!
363     \class QHelpIndexWidget
364     \inmodule QtHelp
365     \since 4.4
366     \brief The QHelpIndexWidget class provides a list view
367     displaying the QHelpIndexModel.
368 */
369
370 /*!
371     \fn void QHelpIndexWidget::linkActivated(const QUrl &link,
372         const QString &keyword)
373
374     This signal is emitted when an item is activated and its
375     associated \a link should be shown. To know where the link
376     belongs to, the \a keyword is given as a second paremeter.
377 */
378
379 /*!
380     \fn void QHelpIndexWidget::linksActivated(const QMap<QString, QUrl> &links,
381         const QString &keyword)
382
383     This signal is emitted when the item representing the \a keyword
384     is activated and the item has more than one link associated.
385     The \a links consist of the document title and their URL.
386 */
387
388 QHelpIndexWidget::QHelpIndexWidget()
389     : QListView(0)
390 {
391     setEditTriggers(QAbstractItemView::NoEditTriggers);
392     setUniformItemSizes(true);
393     connect(this, SIGNAL(activated(QModelIndex)),
394         this, SLOT(showLink(QModelIndex)));
395 }
396
397 void QHelpIndexWidget::showLink(const QModelIndex &index)
398 {
399     if (!index.isValid())
400         return;
401
402     QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(model());
403     if (!indexModel)
404         return;
405     QVariant v = indexModel->data(index, Qt::DisplayRole);
406     QString name;
407     if (v.isValid())
408         name = v.toString();
409
410     QMap<QString, QUrl> links = indexModel->linksForKeyword(name);
411     if (links.count() == 1) {
412         emit linkActivated(links.constBegin().value(), name);
413     } else if (links.count() > 1) {
414         emit linksActivated(links, name);
415     }
416 }
417
418 /*!
419     Activates the current item which will result eventually in
420     the emitting of a linkActivated() or linksActivated()
421     signal.
422 */
423 void QHelpIndexWidget::activateCurrentItem()
424 {
425     showLink(currentIndex());
426 }
427
428 /*!
429     Filters the indices according to \a filter or \a wildcard.
430     The item with the best match is set as current item.
431
432     \sa QHelpIndexModel::filter()
433 */
434 void QHelpIndexWidget::filterIndices(const QString &filter, const QString &wildcard)
435 {
436     QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(model());
437     if (!indexModel)
438         return;
439     QModelIndex idx = indexModel->filter(filter, wildcard);
440     if (idx.isValid())
441         setCurrentIndex(idx);
442 }
443
444 QT_END_NAMESPACE