tools/assistant/lib/qhelpindexwidget.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 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 }
       
   134 
       
   135 QStringList QHelpIndexProvider::indices() const
       
   136 {
       
   137     QMutexLocker lck(&m_mutex);
       
   138     return m_indices;
       
   139 }
       
   140 
       
   141 QList<QHelpDBReader*> QHelpIndexProvider::activeReaders() const
       
   142 {
       
   143     QMutexLocker lck(&m_mutex);
       
   144     return m_activeReaders;
       
   145 }
       
   146 
       
   147 QSet<int> QHelpIndexProvider::indexIds(QHelpDBReader *reader) const
       
   148 {
       
   149     QMutexLocker lck(&m_mutex);
       
   150     if (m_indexIds.contains(reader))
       
   151         return m_indexIds.value(reader);
       
   152     return QSet<int>();
       
   153 }
       
   154 
       
   155 void QHelpIndexProvider::run()
       
   156 {
       
   157     m_mutex.lock();
       
   158     QStringList atts = m_filterAttributes;
       
   159     m_indices.clear();
       
   160     m_activeReaders.clear();
       
   161     QSet<QString> indicesSet;
       
   162     m_mutex.unlock();
       
   163 
       
   164     foreach (QString dbFileName, m_helpEngine->fileNameReaderMap.keys()) {
       
   165         m_mutex.lock();
       
   166         if (m_abort) {
       
   167             m_abort = false;
       
   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 (QString s, lst)
       
   182                 indicesSet.insert(s);
       
   183             if (m_abort) {
       
   184                 m_abort = false;
       
   185                 m_mutex.unlock();
       
   186                 return;
       
   187             }
       
   188             QHelpDBReader *orgReader = m_helpEngine->fileNameReaderMap.value(dbFileName);
       
   189             m_indexIds.insert(orgReader, reader.indexIds(atts));
       
   190             m_activeReaders.append(orgReader);
       
   191             m_mutex.unlock();
       
   192         }
       
   193     }
       
   194     m_mutex.lock();
       
   195     m_indices = indicesSet.values();
       
   196     qSort(m_indices.begin(), m_indices.end(), caseInsensitiveLessThan);
       
   197     m_abort = false;
       
   198     m_mutex.unlock();
       
   199 }
       
   200 
       
   201 
       
   202 
       
   203 /*!
       
   204     \class QHelpIndexModel
       
   205     \since 4.4
       
   206     \inmodule QtHelp
       
   207     \brief The QHelpIndexModel class provides a model that
       
   208     supplies index keywords to views.
       
   209 
       
   210 
       
   211 */
       
   212 
       
   213 /*!
       
   214     \fn void QHelpIndexModel::indexCreationStarted()
       
   215 
       
   216     This signal is emitted when the creation of a new index
       
   217     has started. The current index is invalid from this
       
   218     point on until the signal indexCreated() is emitted.
       
   219 
       
   220     \sa isCreatingIndex()
       
   221 */
       
   222 
       
   223 /*!
       
   224     \fn void QHelpIndexModel::indexCreated()
       
   225 
       
   226     This signal is emitted when the index has been created.
       
   227 */
       
   228 
       
   229 QHelpIndexModel::QHelpIndexModel(QHelpEnginePrivate *helpEngine)
       
   230     : QStringListModel(helpEngine)
       
   231 {
       
   232     d = new QHelpIndexModelPrivate(helpEngine);
       
   233 
       
   234     connect(d->indexProvider, SIGNAL(finished()), this, SLOT(insertIndices()));
       
   235     connect(helpEngine->q, SIGNAL(setupStarted()), this, SLOT(invalidateIndex()));
       
   236 }
       
   237 
       
   238 QHelpIndexModel::~QHelpIndexModel()
       
   239 {
       
   240     delete d;
       
   241 }
       
   242 
       
   243 void QHelpIndexModel::invalidateIndex(bool onShutDown)
       
   244 {
       
   245     if (onShutDown)
       
   246         disconnect(this, SLOT(insertIndices()));
       
   247     d->indexProvider->stopCollecting();
       
   248     d->indices.clear();
       
   249     filter(QString());
       
   250 }
       
   251 
       
   252 /*!
       
   253     Creates a new index by querying the help system for
       
   254     keywords for the specified \a customFilterName.
       
   255 */
       
   256 void QHelpIndexModel::createIndex(const QString &customFilterName)
       
   257 {
       
   258     d->currentFilter = customFilterName;
       
   259     d->indexProvider->collectIndices(customFilterName);
       
   260     emit indexCreationStarted();
       
   261 }
       
   262 
       
   263 void QHelpIndexModel::insertIndices()
       
   264 {
       
   265     d->indices = d->indexProvider->indices();
       
   266     d->activeReaders = d->indexProvider->activeReaders();
       
   267     QStringList attributes = d->helpEngine->q->filterAttributes(d->currentFilter);
       
   268     if (attributes.count() > 1) {
       
   269         foreach (QHelpDBReader *r, d->activeReaders)
       
   270             r->createAttributesCache(attributes, d->indexProvider->indexIds(r));
       
   271     }
       
   272     filter(QString());
       
   273     emit indexCreated();
       
   274 }
       
   275 
       
   276 /*!
       
   277     Returns true if the index is currently built up, otherwise
       
   278     false.
       
   279 */
       
   280 bool QHelpIndexModel::isCreatingIndex() const
       
   281 {
       
   282     return d->indexProvider->isRunning();
       
   283 }
       
   284 
       
   285 /*!
       
   286     Returns all hits found for the \a keyword. A hit consists of
       
   287     the URL and the document title.
       
   288 */
       
   289 QMap<QString, QUrl> QHelpIndexModel::linksForKeyword(const QString &keyword) const
       
   290 {
       
   291     QMap<QString, QUrl> linkMap;
       
   292     QStringList filterAttributes = d->helpEngine->q->filterAttributes(d->currentFilter);
       
   293     foreach (QHelpDBReader *reader, d->activeReaders)
       
   294         reader->linksForKeyword(keyword, filterAttributes, linkMap);
       
   295     return linkMap;
       
   296 }
       
   297 
       
   298 /*!
       
   299     Filters the indices and returns the model index of the best
       
   300     matching keyword. In a first step, only the keywords containing
       
   301     \a filter are kept in the model's index list. Analogously, if
       
   302     \a wildcard is not empty, only the keywords matched are left
       
   303     in the index list. In a second step, the best match is
       
   304     determined and its index model returned. When specifying a
       
   305     wildcard expression, the \a filter string is used to
       
   306     search for the best match.
       
   307 */
       
   308 QModelIndex QHelpIndexModel::filter(const QString &filter, const QString &wildcard)
       
   309 {
       
   310     if (filter.isEmpty()) {
       
   311         setStringList(d->indices);
       
   312         return index(-1, 0, QModelIndex());
       
   313     }
       
   314 
       
   315     QStringList lst;
       
   316     int goodMatch = -1;
       
   317     int perfectMatch = -1;
       
   318 
       
   319     if (!wildcard.isEmpty()) {
       
   320         QRegExp regExp(wildcard, Qt::CaseInsensitive);
       
   321         regExp.setPatternSyntax(QRegExp::Wildcard);
       
   322         foreach (QString index, d->indices) {
       
   323             if (index.contains(regExp)) {
       
   324                 lst.append(index);
       
   325                 if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
       
   326                     if (goodMatch == -1)
       
   327                         goodMatch = lst.count()-1;
       
   328                     if (filter.length() == index.length()){
       
   329                         perfectMatch = lst.count()-1;
       
   330                     }
       
   331                 } else if (perfectMatch > -1 && index == filter) {
       
   332                     perfectMatch = lst.count()-1;
       
   333                 }
       
   334             }
       
   335         }
       
   336     } else {
       
   337         foreach (QString index, d->indices) {
       
   338             if (index.contains(filter, Qt::CaseInsensitive)) {
       
   339                 lst.append(index);
       
   340                 if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
       
   341                     if (goodMatch == -1)
       
   342                         goodMatch = lst.count()-1;
       
   343                     if (filter.length() == index.length()){
       
   344                         perfectMatch = lst.count()-1;
       
   345                     }
       
   346                 } else if (perfectMatch > -1 && index == filter) {
       
   347                     perfectMatch = lst.count()-1;
       
   348                 }
       
   349             }
       
   350         }
       
   351 
       
   352     }
       
   353 
       
   354     if (perfectMatch == -1)
       
   355         perfectMatch = qMax(0, goodMatch);
       
   356 
       
   357     setStringList(lst);
       
   358     return index(perfectMatch, 0, QModelIndex());
       
   359 }
       
   360 
       
   361 
       
   362 
       
   363 /*!
       
   364     \class QHelpIndexWidget
       
   365     \inmodule QtHelp
       
   366     \since 4.4
       
   367     \brief The QHelpIndexWidget class provides a list view
       
   368     displaying the QHelpIndexModel.
       
   369 */
       
   370 
       
   371 /*!
       
   372     \fn void QHelpIndexWidget::linkActivated(const QUrl &link,
       
   373         const QString &keyword)
       
   374 
       
   375     This signal is emitted when an item is activated and its
       
   376     associated \a link should be shown. To know where the link
       
   377     belongs to, the \a keyword is given as a second paremeter.
       
   378 */
       
   379 
       
   380 /*!
       
   381     \fn void QHelpIndexWidget::linksActivated(const QMap<QString, QUrl> &links,
       
   382         const QString &keyword)
       
   383 
       
   384     This signal is emitted when the item representing the \a keyword
       
   385     is activated and the item has more than one link associated.
       
   386     The \a links consist of the document title and their URL.
       
   387 */
       
   388 
       
   389 QHelpIndexWidget::QHelpIndexWidget()
       
   390     : QListView(0)
       
   391 {
       
   392     setEditTriggers(QAbstractItemView::NoEditTriggers);
       
   393     setUniformItemSizes(true);
       
   394     connect(this, SIGNAL(activated(const QModelIndex&)),
       
   395         this, SLOT(showLink(const QModelIndex&)));
       
   396 }
       
   397 
       
   398 void QHelpIndexWidget::showLink(const QModelIndex &index)
       
   399 {
       
   400     if (!index.isValid())
       
   401         return;
       
   402 
       
   403     QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(model());
       
   404     if (!indexModel)
       
   405         return;
       
   406     QVariant v = indexModel->data(index, Qt::DisplayRole);
       
   407     QString name;
       
   408     if (v.isValid())
       
   409         name = v.toString();
       
   410 
       
   411     QMap<QString, QUrl> links = indexModel->linksForKeyword(name);
       
   412     if (links.count() == 1) {
       
   413         emit linkActivated(links.constBegin().value(), name);
       
   414     } else if (links.count() > 1) {
       
   415         emit linksActivated(links, name);
       
   416     }
       
   417 }
       
   418 
       
   419 /*!
       
   420     Activates the current item which will result eventually in
       
   421     the emitting of a linkActivated() or linksActivated()
       
   422     signal.
       
   423 */
       
   424 void QHelpIndexWidget::activateCurrentItem()
       
   425 {
       
   426     showLink(currentIndex());
       
   427 }
       
   428 
       
   429 /*!
       
   430     Filters the indices according to \a filter or \a wildcard.
       
   431     The item with the best match is set as current item.
       
   432 
       
   433     \sa QHelpIndexModel::filter()
       
   434 */
       
   435 void QHelpIndexWidget::filterIndices(const QString &filter, const QString &wildcard)
       
   436 {
       
   437     QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel*>(model());
       
   438     if (!indexModel)
       
   439         return;
       
   440     QModelIndex idx = indexModel->filter(filter, wildcard);
       
   441     if (idx.isValid())
       
   442         setCurrentIndex(idx);
       
   443 }
       
   444 
       
   445 QT_END_NAMESPACE