util/tools/assistant/lib/qhelpindexwidget.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Assistant of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    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(setupStarted()), 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     filter(QString());
       
   248 }
       
   249 
       
   250 /*!
       
   251     Creates a new index by querying the help system for
       
   252     keywords for the specified \a customFilterName.
       
   253 */
       
   254 void QHelpIndexModel::createIndex(const QString &customFilterName)
       
   255 {
       
   256     d->currentFilter = customFilterName;
       
   257     d->indexProvider->collectIndices(customFilterName);
       
   258     emit indexCreationStarted();
       
   259 }
       
   260 
       
   261 void QHelpIndexModel::insertIndices()
       
   262 {
       
   263     d->indices = d->indexProvider->indices();
       
   264     d->activeReaders = d->indexProvider->activeReaders();
       
   265     QStringList attributes = d->helpEngine->q->filterAttributes(d->currentFilter);
       
   266     if (attributes.count() > 1) {
       
   267         foreach (QHelpDBReader *r, d->activeReaders)
       
   268             r->createAttributesCache(attributes, d->indexProvider->indexIds(r));
       
   269     }
       
   270     filter(QString());
       
   271     emit indexCreated();
       
   272 }
       
   273 
       
   274 /*!
       
   275     Returns true if the index is currently built up, otherwise
       
   276     false.
       
   277 */
       
   278 bool QHelpIndexModel::isCreatingIndex() const
       
   279 {
       
   280     return d->indexProvider->isRunning();
       
   281 }
       
   282 
       
   283 /*!
       
   284     Returns all hits found for the \a keyword. A hit consists of
       
   285     the URL and the document title.
       
   286 */
       
   287 QMap<QString, QUrl> QHelpIndexModel::linksForKeyword(const QString &keyword) const
       
   288 {
       
   289     QMap<QString, QUrl> linkMap;
       
   290     QStringList filterAttributes = d->helpEngine->q->filterAttributes(d->currentFilter);
       
   291     foreach (QHelpDBReader *reader, d->activeReaders)
       
   292         reader->linksForKeyword(keyword, filterAttributes, linkMap);
       
   293     return linkMap;
       
   294 }
       
   295 
       
   296 /*!
       
   297     Filters the indices and returns the model index of the best
       
   298     matching keyword. In a first step, only the keywords containing
       
   299     \a filter are kept in the model's index list. Analogously, if
       
   300     \a wildcard is not empty, only the keywords matched are left
       
   301     in the index list. In a second step, the best match is
       
   302     determined and its index model returned. When specifying a
       
   303     wildcard expression, the \a filter string is used to
       
   304     search for the best match.
       
   305 */
       
   306 QModelIndex QHelpIndexModel::filter(const QString &filter, const QString &wildcard)
       
   307 {
       
   308     if (filter.isEmpty()) {
       
   309         setStringList(d->indices);
       
   310         return index(-1, 0, QModelIndex());
       
   311     }
       
   312 
       
   313     QStringList lst;
       
   314     int goodMatch = -1;
       
   315     int perfectMatch = -1;
       
   316 
       
   317     if (!wildcard.isEmpty()) {
       
   318         QRegExp regExp(wildcard, Qt::CaseInsensitive);
       
   319         regExp.setPatternSyntax(QRegExp::Wildcard);
       
   320         foreach (const QString &index, d->indices) {
       
   321             if (index.contains(regExp)) {
       
   322                 lst.append(index);
       
   323                 if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
       
   324                     if (goodMatch == -1)
       
   325                         goodMatch = lst.count()-1;
       
   326                     if (filter.length() == index.length()){
       
   327                         perfectMatch = lst.count()-1;
       
   328                     }
       
   329                 } else if (perfectMatch > -1 && index == filter) {
       
   330                     perfectMatch = lst.count()-1;
       
   331                 }
       
   332             }
       
   333         }
       
   334     } else {
       
   335         foreach (const QString &index, d->indices) {
       
   336             if (index.contains(filter, Qt::CaseInsensitive)) {
       
   337                 lst.append(index);
       
   338                 if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
       
   339                     if (goodMatch == -1)
       
   340                         goodMatch = lst.count()-1;
       
   341                     if (filter.length() == index.length()){
       
   342                         perfectMatch = lst.count()-1;
       
   343                     }
       
   344                 } else if (perfectMatch > -1 && index == filter) {
       
   345                     perfectMatch = lst.count()-1;
       
   346                 }
       
   347             }
       
   348         }
       
   349 
       
   350     }
       
   351 
       
   352     if (perfectMatch == -1)
       
   353         perfectMatch = qMax(0, goodMatch);
       
   354 
       
   355     setStringList(lst);
       
   356     return index(perfectMatch, 0, QModelIndex());
       
   357 }
       
   358 
       
   359 
       
   360 
       
   361 /*!
       
   362     \class QHelpIndexWidget
       
   363     \inmodule QtHelp
       
   364     \since 4.4
       
   365     \brief The QHelpIndexWidget class provides a list view
       
   366     displaying the QHelpIndexModel.
       
   367 */
       
   368 
       
   369 /*!
       
   370     \fn void QHelpIndexWidget::linkActivated(const QUrl &link,
       
   371         const QString &keyword)
       
   372 
       
   373     This signal is emitted when an item is activated and its
       
   374     associated \a link should be shown. To know where the link
       
   375     belongs to, the \a keyword is given as a second paremeter.
       
   376 */
       
   377 
       
   378 /*!
       
   379     \fn void QHelpIndexWidget::linksActivated(const QMap<QString, QUrl> &links,
       
   380         const QString &keyword)
       
   381 
       
   382     This signal is emitted when the item representing the \a keyword
       
   383     is activated and the item has more than one link associated.
       
   384     The \a links consist of the document title and their URL.
       
   385 */
       
   386 
       
   387 QHelpIndexWidget::QHelpIndexWidget()
       
   388     : QListView(0)
       
   389 {
       
   390     setEditTriggers(QAbstractItemView::NoEditTriggers);
       
   391     setUniformItemSizes(true);
       
   392     connect(this, SIGNAL(activated(QModelIndex)),
       
   393         this, SLOT(showLink(QModelIndex)));
       
   394 }
       
   395 
       
   396 void QHelpIndexWidget::showLink(const QModelIndex &index)
       
   397 {
       
   398     if (!index.isValid())
       
   399         return;
       
   400 
       
   401     QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(model());
       
   402     if (!indexModel)
       
   403         return;
       
   404     QVariant v = indexModel->data(index, Qt::DisplayRole);
       
   405     QString name;
       
   406     if (v.isValid())
       
   407         name = v.toString();
       
   408 
       
   409     QMap<QString, QUrl> links = indexModel->linksForKeyword(name);
       
   410     if (links.count() == 1) {
       
   411         emit linkActivated(links.constBegin().value(), name);
       
   412     } else if (links.count() > 1) {
       
   413         emit linksActivated(links, name);
       
   414     }
       
   415 }
       
   416 
       
   417 /*!
       
   418     Activates the current item which will result eventually in
       
   419     the emitting of a linkActivated() or linksActivated()
       
   420     signal.
       
   421 */
       
   422 void QHelpIndexWidget::activateCurrentItem()
       
   423 {
       
   424     showLink(currentIndex());
       
   425 }
       
   426 
       
   427 /*!
       
   428     Filters the indices according to \a filter or \a wildcard.
       
   429     The item with the best match is set as current item.
       
   430 
       
   431     \sa QHelpIndexModel::filter()
       
   432 */
       
   433 void QHelpIndexWidget::filterIndices(const QString &filter, const QString &wildcard)
       
   434 {
       
   435     QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(model());
       
   436     if (!indexModel)
       
   437         return;
       
   438     QModelIndex idx = indexModel->filter(filter, wildcard);
       
   439     if (idx.isValid())
       
   440         setCurrentIndex(idx);
       
   441 }
       
   442 
       
   443 QT_END_NAMESPACE