tools/assistant/compat/helpdialog.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 "helpdialog.h"
       
    43 #include "helpwindow.h"
       
    44 #include "topicchooser.h"
       
    45 #include "docuparser.h"
       
    46 #include "mainwindow.h"
       
    47 #include "config.h"
       
    48 #include "tabbedbrowser.h"
       
    49 
       
    50 #include <QtGui>
       
    51 #include <QtDebug>
       
    52 #include <QtCore/QVarLengthArray>
       
    53 
       
    54 #include <stdlib.h>
       
    55 #include <limits.h>
       
    56 
       
    57 QT_BEGIN_NAMESPACE
       
    58 
       
    59 enum
       
    60 {
       
    61     LinkRole = Qt::UserRole + 1000
       
    62 };
       
    63 
       
    64 static bool verifyDirectory(const QString &str)
       
    65 {
       
    66     QFileInfo dirInfo(str);
       
    67     if (!dirInfo.exists())
       
    68         return QDir().mkdir(str);
       
    69     if (!dirInfo.isDir()) {
       
    70         qWarning("'%s' exists but is not a directory", str.toLatin1().constData());
       
    71         return false;
       
    72     }
       
    73     return true;
       
    74 }
       
    75 
       
    76 struct IndexKeyword {
       
    77     IndexKeyword(const QString &kw, const QString &l)
       
    78         : keyword(kw), link(l) {}
       
    79     IndexKeyword() : keyword(QString()), link(QString()) {}
       
    80     bool operator<(const IndexKeyword &ik) const {
       
    81         return keyword.toLower() < ik.keyword.toLower();
       
    82     }
       
    83     bool operator<=(const IndexKeyword &ik) const {
       
    84         return keyword.toLower() <= ik.keyword.toLower();
       
    85     }
       
    86     bool operator>(const IndexKeyword &ik) const {
       
    87         return keyword.toLower() > ik.keyword.toLower();
       
    88     }
       
    89     Q_DUMMY_COMPARISON_OPERATOR(IndexKeyword)
       
    90     QString keyword;
       
    91     QString link;
       
    92 };
       
    93 
       
    94 QDataStream &operator>>(QDataStream &s, IndexKeyword &ik)
       
    95 {
       
    96     s >> ik.keyword;
       
    97     s >> ik.link;
       
    98     return s;
       
    99 }
       
   100 
       
   101 QDataStream &operator<<(QDataStream &s, const IndexKeyword &ik)
       
   102 {
       
   103     s << ik.keyword;
       
   104     s << ik.link;
       
   105     return s;
       
   106 }
       
   107 
       
   108 QValidator::State SearchValidator::validate(QString &str, int &) const
       
   109 {
       
   110     for (int i = 0; i < (int) str.length(); ++i) {
       
   111         QChar c = str[i];
       
   112         if (!c.isLetterOrNumber() && c != QLatin1Char('\'') && c != QLatin1Char('`')
       
   113             && c != QLatin1Char('\"') && c != QLatin1Char(' ') && c != QLatin1Char('-') && c != QLatin1Char('_')
       
   114             && c!= QLatin1Char('*'))
       
   115             return QValidator::Invalid;
       
   116     }
       
   117     return QValidator::Acceptable;
       
   118 }
       
   119 
       
   120 class IndexListModel: public QStringListModel
       
   121 {
       
   122 public:
       
   123     IndexListModel(QObject *parent = 0)
       
   124         : QStringListModel(parent) {}
       
   125 
       
   126     void clear() { contents.clear(); setStringList(QStringList()); }
       
   127 
       
   128     QString description(int index) const { return stringList().at(index); }
       
   129     QStringList links(int index) const { return contents.values(stringList().at(index)); }
       
   130     void addLink(const QString &description, const QString &link) { contents.insert(description, link); }
       
   131 
       
   132     void publish() { filter(QString(), QString()); }
       
   133 
       
   134     QModelIndex filter(const QString &s, const QString &real);
       
   135 
       
   136     virtual Qt::ItemFlags flags(const QModelIndex &index) const
       
   137     { return QStringListModel::flags(index) & ~Qt::ItemIsEditable; }
       
   138 
       
   139 private:
       
   140     QMultiMap<QString, QString> contents;
       
   141 };
       
   142 
       
   143 bool caseInsensitiveLessThan(const QString &as, const QString &bs)
       
   144 {
       
   145     const QChar *a = as.unicode();
       
   146     const QChar *b = bs.unicode();
       
   147     if (a == 0)
       
   148         return true;
       
   149     if (b == 0)
       
   150         return false;
       
   151     if (a == b)
       
   152         return false;
       
   153     int l=qMin(as.length(),bs.length());
       
   154     while (l-- && QChar::toLower(a->unicode()) == QChar::toLower(b->unicode()))
       
   155         a++,b++;
       
   156     if (l==-1)
       
   157         return (as.length() < bs.length());
       
   158     return QChar::toLower(a->unicode()) < QChar::toLower(b->unicode());
       
   159 }
       
   160 
       
   161 /**
       
   162  * \a real is kinda a hack for the smart search, need a way to match a regexp to an item
       
   163  * How would you say the best match for Q.*Wiget is QWidget?
       
   164  */
       
   165 QModelIndex IndexListModel::filter(const QString &s, const QString &real)
       
   166 {
       
   167     QStringList list;
       
   168 
       
   169     int goodMatch = -1;
       
   170     int perfectMatch = -1;
       
   171     if (s.isEmpty())
       
   172         perfectMatch = 0;
       
   173 
       
   174     const QRegExp regExp(s, Qt::CaseInsensitive);
       
   175     QMultiMap<QString, QString>::iterator it = contents.begin();
       
   176     QString lastKey;
       
   177     for (; it != contents.end(); ++it) {
       
   178         if (it.key() == lastKey)
       
   179             continue;
       
   180         lastKey = it.key();
       
   181         const QString key = it.key();
       
   182         if (key.contains(regExp) || key.contains(s, Qt::CaseInsensitive)) {
       
   183             list.append(key);
       
   184             if (perfectMatch == -1 && (key.startsWith(real, Qt::CaseInsensitive))) {
       
   185                 if (goodMatch == -1)
       
   186                     goodMatch = list.count() - 1;
       
   187                 if (real.length() == key.length()){
       
   188                     perfectMatch = list.count() - 1;
       
   189                 }
       
   190             }  else if (perfectMatch > -1 && s == key) {
       
   191                 perfectMatch = list.count() - 1;
       
   192             }
       
   193         }
       
   194     }
       
   195 
       
   196     int bestMatch = perfectMatch;
       
   197     if (bestMatch == -1)
       
   198         bestMatch = goodMatch;
       
   199     bestMatch = qMax(0, bestMatch);
       
   200 
       
   201     // sort the new list
       
   202     QString match;
       
   203     if (bestMatch >= 0 && list.count() > bestMatch)
       
   204         match = list[bestMatch];
       
   205     qSort(list.begin(), list.end(), caseInsensitiveLessThan);
       
   206     setStringList(list);
       
   207     for (int i = 0; i < list.size(); ++i) {
       
   208         if (list.at(i) == match){
       
   209             bestMatch = i;
       
   210             break;
       
   211         }
       
   212     }
       
   213     return index(bestMatch, 0, QModelIndex());
       
   214 }
       
   215 
       
   216 HelpNavigationListItem::HelpNavigationListItem(QListWidget *ls, const QString &txt)
       
   217     : QListWidgetItem(txt, ls)
       
   218 {
       
   219 }
       
   220 
       
   221 void HelpNavigationListItem::addLink(const QString &link)
       
   222 {
       
   223     QString lnk = HelpDialog::removeAnchorFromLink(link);
       
   224     if (linkList.filter(lnk, Qt::CaseInsensitive).count() > 0)
       
   225         return;
       
   226     linkList << link;
       
   227 }
       
   228 
       
   229 HelpDialog::HelpDialog(QWidget *parent, MainWindow *h)
       
   230     : QWidget(parent), lwClosed(false), help(h)
       
   231 {
       
   232     ui.setupUi(this);
       
   233     ui.listContents->setUniformRowHeights(true);
       
   234     ui.listContents->header()->setStretchLastSection(false);
       
   235     ui.listContents->header()->setResizeMode(QHeaderView::ResizeToContents);
       
   236     ui.listBookmarks->setUniformRowHeights(true);
       
   237     ui.listBookmarks->header()->setStretchLastSection(false);
       
   238     ui.listBookmarks->header()->setResizeMode(QHeaderView::ResizeToContents);
       
   239 
       
   240     indexModel = new IndexListModel(this);
       
   241     ui.listIndex->setModel(indexModel);
       
   242     ui.listIndex->setLayoutMode(QListView::Batched);
       
   243     ui.listBookmarks->setItemHidden(ui.listBookmarks->headerItem(), true);
       
   244     ui.listContents->setItemHidden(ui.listContents->headerItem(), true);
       
   245     ui.searchButton->setShortcut(QKeySequence(Qt::ALT|Qt::SHIFT|Qt::Key_S));
       
   246 }
       
   247 
       
   248 void HelpDialog::initialize()
       
   249 {
       
   250     connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
       
   251 
       
   252     connect(ui.listContents, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(showTopic(QTreeWidgetItem*)));
       
   253     connect(ui.listContents, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showTreeItemMenu(QPoint)));
       
   254     ui.listContents->viewport()->installEventFilter(this);
       
   255 
       
   256     connect(ui.editIndex, SIGNAL(returnPressed()), this, SLOT(showTopic()));
       
   257     connect(ui.editIndex, SIGNAL(textEdited(QString)), this, SLOT(searchInIndex(QString)));
       
   258 
       
   259     connect(ui.listIndex, SIGNAL(activated(QModelIndex)), this, SLOT(showTopic()));
       
   260     connect(ui.listIndex, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showIndexItemMenu(QPoint)));
       
   261 
       
   262     connect(ui.listBookmarks, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(showTopic(QTreeWidgetItem*)));
       
   263     connect(ui.listBookmarks, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showTreeItemMenu(QPoint)));
       
   264 
       
   265     connect(ui.termsEdit, SIGNAL(textChanged(const QString&)), this, SLOT(updateSearchButton(const QString&)));
       
   266 
       
   267     connect(ui.resultBox, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showListItemMenu(QPoint)));
       
   268 
       
   269     cacheFilesPath = QDir::homePath() + QLatin1String("/.assistant"); //### Find a better location for the dbs
       
   270 
       
   271     ui.editIndex->installEventFilter(this);
       
   272 
       
   273     ui.framePrepare->hide();
       
   274     connect(qApp, SIGNAL(lastWindowClosed()), SLOT(lastWinClosed()));
       
   275 
       
   276     ui.termsEdit->setValidator(new SearchValidator(ui.termsEdit));
       
   277 
       
   278     actionOpenCurrentTab = new QAction(this);
       
   279     actionOpenCurrentTab->setText(tr("Open Link in Current Tab"));
       
   280 
       
   281     actionOpenLinkInNewWindow = new QAction(this);
       
   282     actionOpenLinkInNewWindow->setText(tr("Open Link in New Window"));
       
   283 
       
   284     actionOpenLinkInNewTab = new QAction(this);
       
   285     actionOpenLinkInNewTab->setText(tr("Open Link in New Tab"));
       
   286 
       
   287     itemPopup = new QMenu(this);
       
   288     itemPopup->addAction(actionOpenCurrentTab);
       
   289     itemPopup->addAction(actionOpenLinkInNewWindow);
       
   290     itemPopup->addAction(actionOpenLinkInNewTab);
       
   291 
       
   292     ui.tabWidget->setElideMode(Qt::ElideNone);
       
   293 
       
   294     contentList.clear();
       
   295 
       
   296     initDoneMsgShown = false;
       
   297     fullTextIndex = 0;
       
   298     indexDone = false;
       
   299     titleMapDone = false;
       
   300     contentsInserted = false;
       
   301     bookmarksInserted = false;
       
   302     setupTitleMap();
       
   303 
       
   304 }
       
   305 
       
   306 void HelpDialog::processEvents()
       
   307 {
       
   308     qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
       
   309 }
       
   310 
       
   311 
       
   312 void HelpDialog::lastWinClosed()
       
   313 {
       
   314     lwClosed = true;
       
   315 }
       
   316 
       
   317 void HelpDialog::removeOldCacheFiles(bool onlyFulltextSearchIndex)
       
   318 {
       
   319     if (!verifyDirectory(cacheFilesPath)) {
       
   320         qWarning("Failed to created assistant directory");
       
   321         return;
       
   322     }
       
   323     QString pname = QLatin1String(".") + Config::configuration()->profileName();
       
   324 
       
   325     QStringList fileList;
       
   326     fileList << QLatin1String("indexdb40.dict")
       
   327         << QLatin1String("indexdb40.doc");
       
   328 
       
   329     if (!onlyFulltextSearchIndex)
       
   330         fileList << QLatin1String("indexdb40") << QLatin1String("contentdb40");
       
   331 
       
   332     QStringList::iterator it = fileList.begin();
       
   333     for (; it != fileList.end(); ++it) {
       
   334       if (QFile::exists(cacheFilesPath + QDir::separator() + *it + pname)) {
       
   335             QFile f(cacheFilesPath + QDir::separator() + *it + pname);
       
   336             f.remove();
       
   337         }
       
   338     }
       
   339 }
       
   340 
       
   341 void HelpDialog::timerEvent(QTimerEvent *e)
       
   342 {
       
   343     Q_UNUSED(e);
       
   344     static int opacity = 255;
       
   345     help->setWindowOpacity((opacity-=4)/255.0);
       
   346     if (opacity<=0)
       
   347         qApp->quit();
       
   348 }
       
   349 
       
   350 
       
   351 void HelpDialog::loadIndexFile()
       
   352 {
       
   353     if (indexDone)
       
   354         return;
       
   355 
       
   356     setCursor(Qt::WaitCursor);
       
   357     indexDone = true;
       
   358     ui.labelPrepare->setText(tr("Prepare..."));
       
   359     ui.framePrepare->show();
       
   360     processEvents();
       
   361 
       
   362     QProgressBar *bar = ui.progressPrepare;
       
   363     bar->setMaximum(100);
       
   364     bar->setValue(0);
       
   365 
       
   366     keywordDocuments.clear();
       
   367     QList<IndexKeyword> lst;
       
   368     QFile indexFile(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.") +
       
   369                      Config::configuration()->profileName());
       
   370     if (!indexFile.open(QFile::ReadOnly)) {
       
   371         buildKeywordDB();
       
   372         processEvents();
       
   373         if (lwClosed)
       
   374             return;
       
   375         if (!indexFile.open(QFile::ReadOnly)) {
       
   376             QMessageBox::warning(help, tr("Qt Assistant"), tr("Failed to load keyword index file\n"
       
   377                                                               "Assistant will not work!"));
       
   378 #if defined Q_WS_WIN || defined Q_WS_MACX
       
   379             startTimer(50);
       
   380 #endif
       
   381             return;
       
   382         }
       
   383     }
       
   384 
       
   385     QDataStream ds(&indexFile);
       
   386     quint32 fileAges;
       
   387     ds >> fileAges;
       
   388     if (fileAges != getFileAges()) {
       
   389         indexFile.close();
       
   390         buildKeywordDB();
       
   391         if (!indexFile.open(QFile::ReadOnly)) {
       
   392             QMessageBox::warning(help, tr("Qt Assistant"),
       
   393                 tr("Cannot open the index file %1").arg(QFileInfo(indexFile).absoluteFilePath()));
       
   394             return;
       
   395         }
       
   396         ds.setDevice(&indexFile);
       
   397         ds >> fileAges;
       
   398     }
       
   399     ds >> lst;
       
   400     indexFile.close();
       
   401 
       
   402     bar->setValue(bar->maximum());
       
   403     processEvents();
       
   404 
       
   405     for (int i=0; i<lst.count(); ++i) {
       
   406         const IndexKeyword &idx = lst.at(i);
       
   407         indexModel->addLink(idx.keyword, idx.link);
       
   408 
       
   409         keywordDocuments << HelpDialog::removeAnchorFromLink(idx.link);
       
   410     }
       
   411 
       
   412     indexModel->publish();
       
   413 
       
   414     ui.framePrepare->hide();
       
   415     showInitDoneMessage();
       
   416     setCursor(Qt::ArrowCursor);
       
   417 }
       
   418 
       
   419 quint32 HelpDialog::getFileAges()
       
   420 {
       
   421     QStringList addDocuFiles = Config::configuration()->docFiles();
       
   422     QStringList::const_iterator i = addDocuFiles.constBegin();
       
   423 
       
   424     quint32 fileAges = 0;
       
   425     for (; i != addDocuFiles.constEnd(); ++i) {
       
   426         QFileInfo fi(*i);
       
   427         if (fi.exists())
       
   428             fileAges += fi.lastModified().toTime_t();
       
   429     }
       
   430 
       
   431     return fileAges;
       
   432 }
       
   433 
       
   434 void HelpDialog::buildKeywordDB()
       
   435 {
       
   436     QStringList addDocuFiles = Config::configuration()->docFiles();
       
   437     QStringList::iterator i = addDocuFiles.begin();
       
   438 
       
   439     // Set up an indeterminate progress bar.
       
   440     ui.labelPrepare->setText(tr("Prepare..."));
       
   441     ui.progressPrepare->setMaximum(0);
       
   442     ui.progressPrepare->setMinimum(0);
       
   443     ui.progressPrepare->setValue(0);
       
   444     processEvents();
       
   445 
       
   446     QList<IndexKeyword> lst;
       
   447     quint32 fileAges = 0;
       
   448     for (i = addDocuFiles.begin(); i != addDocuFiles.end(); ++i) {
       
   449         QFile file(*i);
       
   450         if (!file.exists()) {
       
   451             QMessageBox::warning(this, tr("Warning"),
       
   452                 tr("Documentation file %1 does not exist!\n"
       
   453                     "Skipping file.").arg(QFileInfo(file).absoluteFilePath()));
       
   454             continue;
       
   455         }
       
   456         fileAges += QFileInfo(file).lastModified().toTime_t();
       
   457         DocuParser *handler = DocuParser::createParser(*i);
       
   458         bool ok = handler->parse(&file);
       
   459         file.close();
       
   460         if (!ok){
       
   461             QString msg = QString::fromLatin1("In file %1:\n%2")
       
   462                           .arg(QFileInfo(file).absoluteFilePath())
       
   463                           .arg(handler->errorProtocol());
       
   464             QMessageBox::critical(this, tr("Parse Error"), tr(msg.toUtf8()));
       
   465             delete handler;
       
   466             continue;
       
   467         }
       
   468 
       
   469         QList<IndexItem*> indLst = handler->getIndexItems();
       
   470         int counter = 0;
       
   471         foreach (IndexItem *indItem, indLst) {
       
   472             QFileInfo fi(indItem->reference);
       
   473             lst.append(IndexKeyword(indItem->keyword, indItem->reference));
       
   474 
       
   475             if (++counter%100 == 0) {
       
   476                 if (ui.progressPrepare)
       
   477                     ui.progressPrepare->setValue(counter);
       
   478                 processEvents();
       
   479                 if (lwClosed) {
       
   480                     return;
       
   481                 }
       
   482             }
       
   483         }
       
   484         delete handler;
       
   485     }
       
   486     if (!lst.isEmpty())
       
   487         qSort(lst);
       
   488 
       
   489     QFile indexout(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.")
       
   490       + Config::configuration()->profileName());
       
   491     if (verifyDirectory(cacheFilesPath) && indexout.open(QFile::WriteOnly)) {
       
   492         QDataStream s(&indexout);
       
   493         s << fileAges;
       
   494         s << lst;
       
   495         indexout.close();
       
   496     }
       
   497 }
       
   498 
       
   499 void HelpDialog::setupTitleMap()
       
   500 {
       
   501     if (titleMapDone)
       
   502         return;
       
   503 
       
   504     bool needRebuild = false;
       
   505     if (Config::configuration()->profileName() == QLatin1String("default")) {
       
   506         const QStringList docuFiles = Config::configuration()->docFiles();
       
   507         for (QStringList::ConstIterator it = docuFiles.begin(); it != docuFiles.end(); ++it) {
       
   508             if (!QFile::exists(*it)) {
       
   509                 Config::configuration()->saveProfile(Profile::createDefaultProfile());
       
   510                 Config::configuration()->loadDefaultProfile();
       
   511                 needRebuild = true;
       
   512                 break;
       
   513             }
       
   514         }
       
   515     }
       
   516 
       
   517     if (Config::configuration()->docRebuild() || needRebuild) {
       
   518         removeOldCacheFiles();
       
   519         Config::configuration()->setDocRebuild(false);
       
   520         Config::configuration()->saveProfile(Config::configuration()->profile());
       
   521     }
       
   522     if (contentList.isEmpty())
       
   523         getAllContents();
       
   524 
       
   525     titleMapDone = true;
       
   526     titleMap.clear();
       
   527     for (QList<QPair<QString, ContentList> >::Iterator it = contentList.begin(); it != contentList.end(); ++it) {
       
   528         ContentList lst = (*it).second;
       
   529         foreach (ContentItem item, lst) {
       
   530             titleMap[item.reference] = item.title.trimmed();
       
   531         }
       
   532     }
       
   533     processEvents();
       
   534 }
       
   535 
       
   536 void HelpDialog::getAllContents()
       
   537 {
       
   538     QFile contentFile(cacheFilesPath + QDir::separator() + QLatin1String("contentdb40.")
       
   539       + Config::configuration()->profileName());
       
   540     contentList.clear();
       
   541     if (!contentFile.open(QFile::ReadOnly)) {
       
   542         buildContentDict();
       
   543         return;
       
   544     }
       
   545 
       
   546     QDataStream ds(&contentFile);
       
   547     quint32 fileAges;
       
   548     ds >> fileAges;
       
   549     if (fileAges != getFileAges()) {
       
   550         contentFile.close();
       
   551         removeOldCacheFiles(true);
       
   552         buildContentDict();
       
   553         return;
       
   554     }
       
   555     QString key;
       
   556     QList<ContentItem> lst;
       
   557     while (!ds.atEnd()) {
       
   558         ds >> key;
       
   559         ds >> lst;
       
   560         contentList += qMakePair(key, QList<ContentItem>(lst));
       
   561     }
       
   562     contentFile.close();
       
   563     processEvents();
       
   564 
       
   565 }
       
   566 
       
   567 void HelpDialog::buildContentDict()
       
   568 {
       
   569     QStringList docuFiles = Config::configuration()->docFiles();
       
   570 
       
   571     quint32 fileAges = 0;
       
   572     for (QStringList::iterator it = docuFiles.begin(); it != docuFiles.end(); ++it) {
       
   573         QFile file(*it);
       
   574         if (!file.exists()) {
       
   575             QMessageBox::warning(this, tr("Warning"),
       
   576             tr("Documentation file %1 does not exist!\n"
       
   577                 "Skipping file.").arg(QFileInfo(file).absoluteFilePath()));
       
   578             continue;
       
   579         }
       
   580         fileAges += QFileInfo(file).lastModified().toTime_t();
       
   581         DocuParser *handler = DocuParser::createParser(*it);
       
   582         if (!handler) {
       
   583             QMessageBox::warning(this, tr("Warning"),
       
   584             tr("Documentation file %1 is not compatible!\n"
       
   585                 "Skipping file.").arg(QFileInfo(file).absoluteFilePath()));
       
   586             continue;
       
   587         }
       
   588         bool ok = handler->parse(&file);
       
   589         file.close();
       
   590         if (ok) {
       
   591             contentList += qMakePair(*it, QList<ContentItem>(handler->getContentItems()));
       
   592             delete handler;
       
   593         } else {
       
   594             QString msg = QString::fromLatin1("In file %1:\n%2")
       
   595                           .arg(QFileInfo(file).absoluteFilePath())
       
   596                           .arg(handler->errorProtocol());
       
   597             QMessageBox::critical(this, tr("Parse Error"), tr(msg.toUtf8()));
       
   598             continue;
       
   599         }
       
   600     }
       
   601 
       
   602     QFile contentOut(cacheFilesPath + QDir::separator() + QLatin1String("contentdb40.")
       
   603       + Config::configuration()->profileName());
       
   604     if (contentOut.open(QFile::WriteOnly)) {
       
   605         QDataStream s(&contentOut);
       
   606         s << fileAges;
       
   607         for (QList<QPair<QString, ContentList> >::Iterator it = contentList.begin(); it != contentList.end(); ++it) {
       
   608             s << *it;
       
   609         }
       
   610         contentOut.close();
       
   611     }
       
   612 }
       
   613 
       
   614 void HelpDialog::currentTabChanged(int index)
       
   615 {
       
   616     QString s = ui.tabWidget->widget(index)->objectName();
       
   617     if (s == QLatin1String("indexPage"))
       
   618         QTimer::singleShot(0, this, SLOT(loadIndexFile()));
       
   619     else if (s == QLatin1String("bookmarkPage"))
       
   620         insertBookmarks();
       
   621     else if (s == QLatin1String("contentPage"))
       
   622         QTimer::singleShot(0, this, SLOT(insertContents()));
       
   623     else if (s == QLatin1String("searchPage"))
       
   624         QTimer::singleShot(0, this, SLOT(setupFullTextIndex()));
       
   625 }
       
   626 
       
   627 void HelpDialog::showInitDoneMessage()
       
   628 {
       
   629     if (initDoneMsgShown)
       
   630         return;
       
   631     initDoneMsgShown = true;
       
   632     help->statusBar()->showMessage(tr("Done"), 3000);
       
   633 }
       
   634 
       
   635 void HelpDialog::showTopic(QTreeWidgetItem *item)
       
   636 {
       
   637     if (item)
       
   638         showTopic();
       
   639 }
       
   640 
       
   641 void HelpDialog::showTopic()
       
   642 {
       
   643     QString tabName = ui.tabWidget->currentWidget()->objectName();
       
   644 
       
   645     if (tabName == QLatin1String("indexPage"))
       
   646         showIndexTopic();
       
   647     else if (tabName == QLatin1String("bookmarkPage"))
       
   648         showBookmarkTopic();
       
   649     else if (tabName == QLatin1String("contentPage"))
       
   650         showContentsTopic();
       
   651 }
       
   652 
       
   653 void HelpDialog::showIndexTopic()
       
   654 {
       
   655     int row = ui.listIndex->currentIndex().row();
       
   656     if (row == -1 || row >= indexModel->rowCount())
       
   657         return;
       
   658 
       
   659     QString description = indexModel->description(row);
       
   660     QStringList links = indexModel->links(row);
       
   661 
       
   662     bool blocked = ui.editIndex->blockSignals(true);
       
   663     ui.editIndex->setText(description);
       
   664     ui.editIndex->blockSignals(blocked);
       
   665 
       
   666     if (links.count() == 1) {
       
   667         emit showLink(links.first());
       
   668     } else {
       
   669         qSort(links);
       
   670         QStringList::Iterator it = links.begin();
       
   671         QStringList linkList;
       
   672         QStringList linkNames;
       
   673         for (; it != links.end(); ++it) {
       
   674             linkList << *it;
       
   675             linkNames << titleOfLink(*it);
       
   676         }
       
   677         QString link = TopicChooser::getLink(this, linkNames, linkList, description);
       
   678         if (!link.isEmpty())
       
   679             emit showLink(link);
       
   680     }
       
   681 
       
   682     ui.listIndex->setCurrentIndex(indexModel->index(indexModel->stringList().indexOf(description)));
       
   683     ui.listIndex->scrollTo(ui.listIndex->currentIndex(), QAbstractItemView::PositionAtTop);
       
   684 }
       
   685 
       
   686 void HelpDialog::searchInIndex(const QString &searchString)
       
   687 {
       
   688     QRegExp atoz(QLatin1String("[A-Z]"));
       
   689     int matches = searchString.count(atoz);
       
   690     if (matches > 0 && !searchString.contains(QLatin1String(".*")))
       
   691     {
       
   692         int start = 0;
       
   693         QString newSearch;
       
   694         for (; matches > 0; --matches) {
       
   695             int match = searchString.indexOf(atoz, start+1);
       
   696             if (match <= start)
       
   697                 continue;
       
   698             newSearch += searchString.mid(start, match-start);
       
   699             newSearch += QLatin1String(".*");
       
   700             start = match;
       
   701         }
       
   702         newSearch += searchString.mid(start);
       
   703         ui.listIndex->setCurrentIndex(indexModel->filter(newSearch, searchString));
       
   704     }
       
   705     else
       
   706         ui.listIndex->setCurrentIndex(indexModel->filter(searchString, searchString));
       
   707 }
       
   708 
       
   709 QString HelpDialog::titleOfLink(const QString &link)
       
   710 {
       
   711     QString s = HelpDialog::removeAnchorFromLink(link);
       
   712     s = titleMap[s];
       
   713     if (s.isEmpty())
       
   714         return link;
       
   715     return s;
       
   716 }
       
   717 
       
   718 bool HelpDialog::eventFilter(QObject * o, QEvent * e)
       
   719 {
       
   720     if (o == ui.editIndex && e->type() == QEvent::KeyPress) {
       
   721         switch (static_cast<QKeyEvent*>(e)->key()) {
       
   722             case Qt::Key_Up:
       
   723             case Qt::Key_Down:
       
   724             case Qt::Key_PageDown:
       
   725             case Qt::Key_PageUp:
       
   726                 QApplication::sendEvent(ui.listIndex, e);
       
   727                 break;
       
   728 
       
   729             default:
       
   730                 break;
       
   731         }
       
   732     } else if (o == ui.listContents->viewport()) {
       
   733         if (e->type() == QEvent::MouseButtonRelease) {
       
   734             QMouseEvent *me = static_cast<QMouseEvent*>(e);
       
   735             if (me->button() == Qt::LeftButton) {
       
   736                 QTreeWidgetItem *item = ui.listContents->itemAt(me->pos());
       
   737                 QRect vRect = ui.listContents->visualItemRect(item);
       
   738 
       
   739                 // only show topic if we clicked an item
       
   740                 if (item && vRect.contains(me->pos()))
       
   741                     showTopic(item);
       
   742             }
       
   743         }
       
   744     }
       
   745 
       
   746     return QWidget::eventFilter(o, e);
       
   747 }
       
   748 
       
   749 void HelpDialog::addBookmark()
       
   750 {
       
   751     if (!bookmarksInserted)
       
   752         insertBookmarks();
       
   753     QString link = help->browsers()->currentBrowser()->source().toString();
       
   754     QString title = help->browsers()->currentBrowser()->documentTitle();
       
   755     if (title.isEmpty())
       
   756         title = titleOfLink(link);
       
   757 
       
   758     QTreeWidgetItem *i = new QTreeWidgetItem(ui.listBookmarks, 0);
       
   759     i->setText(0, title);
       
   760     i->setData(0, LinkRole, link);
       
   761     ui.buttonRemove->setEnabled(true);
       
   762     saveBookmarks();
       
   763     help->updateBookmarkMenu();
       
   764 }
       
   765 
       
   766 void HelpDialog::on_buttonAdd_clicked()
       
   767 {
       
   768     addBookmark();
       
   769 }
       
   770 
       
   771 void HelpDialog::on_buttonRemove_clicked()
       
   772 {
       
   773     if (!ui.listBookmarks->currentItem())
       
   774         return;
       
   775 
       
   776     delete ui.listBookmarks->currentItem();
       
   777     saveBookmarks();
       
   778     if (ui.listBookmarks->topLevelItemCount() != 0) {
       
   779         ui.listBookmarks->setCurrentItem(ui.listBookmarks->topLevelItem(0));
       
   780     }
       
   781     ui.buttonRemove->setEnabled(ui.listBookmarks->topLevelItemCount() > 0);
       
   782     help->updateBookmarkMenu();
       
   783 }
       
   784 
       
   785 void HelpDialog::insertBookmarks()
       
   786 {
       
   787     if (bookmarksInserted)
       
   788         return;
       
   789     bookmarksInserted = true;
       
   790     ui.listBookmarks->clear();
       
   791     QFile f(cacheFilesPath + QDir::separator() + QLatin1String("bookmarks.")
       
   792       + Config::configuration()->profileName());
       
   793     if (!f.open(QFile::ReadOnly))
       
   794         return;
       
   795     QTextStream ts(&f);
       
   796     while (!ts.atEnd()) {
       
   797         QTreeWidgetItem *i = new QTreeWidgetItem(ui.listBookmarks, 0);
       
   798         i->setText(0, ts.readLine());
       
   799         i->setData(0, LinkRole, ts.readLine());
       
   800     }
       
   801     ui.buttonRemove->setEnabled(ui.listBookmarks->topLevelItemCount() > 0);
       
   802     help->updateBookmarkMenu();
       
   803     showInitDoneMessage();
       
   804 }
       
   805 
       
   806 void HelpDialog::showBookmarkTopic()
       
   807 {
       
   808     if (!ui.listBookmarks->currentItem())
       
   809         return;
       
   810 
       
   811     QTreeWidgetItem *i = (QTreeWidgetItem*)ui.listBookmarks->currentItem();
       
   812     emit showLink(i->data(0, LinkRole).toString());
       
   813 }
       
   814 
       
   815 static void store(QTreeWidgetItem *i, QTextStream &ts)
       
   816 {
       
   817     ts << i->text(0) << endl;
       
   818     ts << i->data(0, LinkRole).toString() << endl;
       
   819 
       
   820     for (int index = 0; index < i->childCount(); ++index)
       
   821         store(i->child(index), ts);
       
   822 }
       
   823 
       
   824 static void store(QTreeWidget *tw, QTextStream &ts)
       
   825 {
       
   826     for (int index = 0; index < tw->topLevelItemCount(); ++index)
       
   827         store(tw->topLevelItem(index), ts);
       
   828 }
       
   829 
       
   830 void HelpDialog::saveBookmarks()
       
   831 {
       
   832     QFile f(cacheFilesPath + QDir::separator() + QLatin1String("bookmarks.")
       
   833       + Config::configuration()->profileName());
       
   834     if (!f.open(QFile::WriteOnly))
       
   835         return;
       
   836 
       
   837     QTextStream ts(&f);
       
   838     store(ui.listBookmarks, ts);
       
   839     f.close();
       
   840 }
       
   841 
       
   842 void HelpDialog::insertContents()
       
   843 {
       
   844 #ifdef Q_WS_MAC
       
   845     static const QLatin1String IconPath(":/trolltech/assistant/images/mac/book.png");
       
   846 #else
       
   847     static const QLatin1String IconPath(":/trolltech/assistant/images/win/book.png");
       
   848 #endif
       
   849     if (contentsInserted)
       
   850         return;
       
   851 
       
   852     if (contentList.isEmpty())
       
   853         getAllContents();
       
   854 
       
   855     contentsInserted = true;
       
   856     ui.listContents->clear();
       
   857     setCursor(Qt::WaitCursor);
       
   858     if (!titleMapDone)
       
   859         setupTitleMap();
       
   860 
       
   861 #if 0 // ### port me
       
   862     ui.listContents->setSorting(-1);
       
   863 #endif
       
   864 
       
   865     for (QList<QPair<QString, ContentList> >::Iterator it = contentList.begin(); it != contentList.end(); ++it) {
       
   866         QTreeWidgetItem *newEntry = 0;
       
   867 
       
   868         QTreeWidgetItem *contentEntry = 0;
       
   869         QStack<QTreeWidgetItem*> stack;
       
   870         stack.clear();
       
   871         int depth = 0;
       
   872         bool root = false;
       
   873 
       
   874         const int depthSize = 32;
       
   875         QVarLengthArray<QTreeWidgetItem*, depthSize> lastItem(depthSize);
       
   876 
       
   877         ContentList lst = (*it).second;
       
   878         for (ContentList::ConstIterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
       
   879             ContentItem item = *it;
       
   880             if (item.depth == 0) {
       
   881                 lastItem[0] = 0;
       
   882                 newEntry = new QTreeWidgetItem(ui.listContents, 0);
       
   883                 newEntry->setIcon(0, QIcon(IconPath));
       
   884                 newEntry->setText(0, item.title);
       
   885                 newEntry->setData(0, LinkRole, item.reference);
       
   886                 stack.push(newEntry);
       
   887                 depth = 1;
       
   888                 root = true;
       
   889             }
       
   890             else{
       
   891                 if ((item.depth > depth) && root) {
       
   892                     depth = item.depth;
       
   893                     stack.push(contentEntry);
       
   894                 }
       
   895                 if (item.depth == depth) {
       
   896                     if (lastItem.capacity() == depth)
       
   897                         lastItem.resize(depth + depthSize);
       
   898                     contentEntry = new QTreeWidgetItem(stack.top(), lastItem[ depth ]);
       
   899                     lastItem[ depth ] = contentEntry;
       
   900                     contentEntry->setText(0, item.title);
       
   901                     contentEntry->setData(0, LinkRole, item.reference);
       
   902                 }
       
   903                 else if (item.depth < depth) {
       
   904                     stack.pop();
       
   905                     depth--;
       
   906                     item = *(--it);
       
   907                 }
       
   908             }
       
   909         }
       
   910         processEvents();
       
   911     }
       
   912     setCursor(Qt::ArrowCursor);
       
   913     showInitDoneMessage();
       
   914 }
       
   915 
       
   916 void HelpDialog::showContentsTopic()
       
   917 {
       
   918     QTreeWidgetItem *i = (QTreeWidgetItem*)ui.listContents->currentItem();
       
   919     if (!i)
       
   920         return;
       
   921     emit showLink(i->data(0, LinkRole).toString());
       
   922 }
       
   923 
       
   924 QTreeWidgetItem * HelpDialog::locateLink(QTreeWidgetItem *item, const QString &link)
       
   925 {
       
   926     QTreeWidgetItem *child = 0;
       
   927 #ifdef Q_OS_WIN
       
   928     Qt::CaseSensitivity checkCase = Qt::CaseInsensitive;
       
   929 #else
       
   930     Qt::CaseSensitivity checkCase = Qt::CaseSensitive;
       
   931 #endif
       
   932     for (int i = 0, childCount = item->childCount(); i<childCount; i++) {
       
   933         child = item->child(i);
       
   934         ///check whether it is this item
       
   935         if (link.startsWith(child->data(0, LinkRole).toString(), checkCase))
       
   936             break;
       
   937         //check if the link is a child of this item
       
   938         else if (child->childCount()) {
       
   939             child = locateLink(child, link);
       
   940             if (child)
       
   941                 break;
       
   942         }
       
   943         child = 0;
       
   944     }
       
   945     return child;
       
   946 }
       
   947 
       
   948 void HelpDialog::locateContents(const QString &link)
       
   949 {
       
   950     //ensure the TOC is filled
       
   951     if (!contentsInserted)
       
   952         insertContents();
       
   953 #ifdef Q_OS_WIN
       
   954     Qt::CaseSensitivity checkCase = Qt::CaseInsensitive;
       
   955 #else
       
   956     Qt::CaseSensitivity checkCase = Qt::CaseSensitive;
       
   957 #endif
       
   958     QString findLink(link);
       
   959     //Installations on a windows local drive will give the 'link' as <file:///C:/xxx>
       
   960     //and the contents in the TOC will be <file:C:/xxx>.
       
   961     //But on others the 'link' of format <file:///root/xxx>
       
   962     //and the contents in the TOC will be <file:/root/xxx>.
       
   963     if (findLink.contains(QLatin1String("file:///"))) {
       
   964         if (findLink[9] == QLatin1Char(':')) //on windows drives
       
   965             findLink.replace(0, 8, QLatin1String("file:"));
       
   966         else
       
   967             findLink.replace(0, 8, QLatin1String("file:/"));
       
   968     }
       
   969 
       
   970     bool topLevel = false;
       
   971     QTreeWidgetItem *item = 0;
       
   972     int totalItems = ui.listContents->topLevelItemCount();
       
   973 
       
   974     for (int i = 0; i < totalItems; i++ ) {
       
   975         // first see if we are one of the top level items
       
   976         item = (QTreeWidgetItem*)ui.listContents->topLevelItem(i);
       
   977         if (findLink.startsWith(item->data(0, LinkRole).toString(), checkCase)) {
       
   978             topLevel = true;
       
   979             break;
       
   980         }
       
   981     }
       
   982 
       
   983     if (!topLevel) {
       
   984         // now try to find it in the sublevel items
       
   985         for (int n = 0; n < totalItems; ++n) {
       
   986             item = (QTreeWidgetItem*)ui.listContents->topLevelItem(n);
       
   987             item = locateLink(item, findLink);
       
   988             if (item)
       
   989                 break;
       
   990         }
       
   991     }
       
   992 
       
   993     //remove the old selection
       
   994     QList<QTreeWidgetItem *> selected = ui.listContents->selectedItems();
       
   995     foreach(QTreeWidgetItem *sel, selected)
       
   996         ui.listContents->setItemSelected(sel, false);
       
   997 
       
   998     //set the TOC item and show
       
   999     ui.listContents->setCurrentItem(item);
       
  1000     ui.listContents->setItemSelected(item, true);
       
  1001     ui.listContents->scrollToItem(item);
       
  1002 }
       
  1003 
       
  1004 void HelpDialog::toggleContents()
       
  1005 {
       
  1006     if (!isVisible() || ui.tabWidget->currentIndex() != 0) {
       
  1007         ui.tabWidget->setCurrentIndex(0);
       
  1008         parentWidget()->show();
       
  1009     }
       
  1010     else
       
  1011         parentWidget()->hide();
       
  1012 }
       
  1013 
       
  1014 void HelpDialog::toggleIndex()
       
  1015 {
       
  1016     if (!isVisible() || ui.tabWidget->currentIndex() != 1 || !ui.editIndex->hasFocus()) {
       
  1017         ui.tabWidget->setCurrentIndex(1);
       
  1018         parentWidget()->show();
       
  1019         ui.editIndex->setFocus();
       
  1020     }
       
  1021     else
       
  1022         parentWidget()->hide();
       
  1023 }
       
  1024 
       
  1025 void HelpDialog::toggleBookmarks()
       
  1026 {
       
  1027     if (!isVisible() || ui.tabWidget->currentIndex() != 2) {
       
  1028         ui.tabWidget->setCurrentIndex(2);
       
  1029         parentWidget()->show();
       
  1030     }
       
  1031     else
       
  1032         parentWidget()->hide();
       
  1033 }
       
  1034 
       
  1035 void HelpDialog::toggleSearch()
       
  1036 {
       
  1037     if (!isVisible() || ui.tabWidget->currentIndex() != 3) {
       
  1038         ui.tabWidget->setCurrentIndex(3);
       
  1039         parentWidget()->show();
       
  1040     }
       
  1041     else
       
  1042         parentWidget()->hide();
       
  1043 }
       
  1044 
       
  1045 void HelpDialog::setupFullTextIndex()
       
  1046 {
       
  1047     if (fullTextIndex)
       
  1048         return;
       
  1049 
       
  1050     QString pname = Config::configuration()->profileName();
       
  1051     fullTextIndex = new Index(QStringList(), QDir::homePath()); // ### Is this correct ?
       
  1052     if (!verifyDirectory(cacheFilesPath)) {
       
  1053         QMessageBox::warning(help, tr("Qt Assistant"),
       
  1054                              tr("Failed to save fulltext search index\n"
       
  1055                                 "Assistant will not work!"));
       
  1056         return;
       
  1057     }
       
  1058     fullTextIndex->setDictionaryFile(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.dict.") + pname);
       
  1059     fullTextIndex->setDocListFile(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.doc.") + pname);
       
  1060     processEvents();
       
  1061 
       
  1062     connect(fullTextIndex, SIGNAL(indexingProgress(int)),
       
  1063              this, SLOT(setIndexingProgress(int)));
       
  1064     QFile f(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.dict.") + pname);
       
  1065     if (!f.exists()) {
       
  1066         QString doc;
       
  1067         QSet<QString> documentSet;
       
  1068         QMap<QString, QString>::ConstIterator it = titleMap.constBegin();
       
  1069         for (; it != titleMap.constEnd(); ++it) {
       
  1070             doc = HelpDialog::removeAnchorFromLink(it.key());
       
  1071             if (!doc.isEmpty())
       
  1072                 documentSet.insert(doc);
       
  1073         }
       
  1074         loadIndexFile();
       
  1075         for ( QStringList::Iterator it = keywordDocuments.begin(); it != keywordDocuments.end(); ++it ) {
       
  1076             if (!(*it).isEmpty())
       
  1077                 documentSet.insert(*it);
       
  1078         }
       
  1079         fullTextIndex->setDocList( documentSet.toList() );
       
  1080 
       
  1081         help->statusBar()->clearMessage();
       
  1082         setCursor(Qt::WaitCursor);
       
  1083         ui.labelPrepare->setText(tr("Indexing files..."));
       
  1084         ui.progressPrepare->setMaximum(100);
       
  1085         ui.progressPrepare->reset();
       
  1086         ui.progressPrepare->show();
       
  1087         ui.framePrepare->show();
       
  1088         processEvents();
       
  1089         if (fullTextIndex->makeIndex() == -1)
       
  1090             return;
       
  1091         fullTextIndex->writeDict();
       
  1092         ui.progressPrepare->setValue(100);
       
  1093         ui.framePrepare->hide();
       
  1094         setCursor(Qt::ArrowCursor);
       
  1095         showInitDoneMessage();
       
  1096     } else {
       
  1097         setCursor(Qt::WaitCursor);
       
  1098         help->statusBar()->showMessage(tr("Reading dictionary..."));
       
  1099         processEvents();
       
  1100         fullTextIndex->readDict();
       
  1101         help->statusBar()->showMessage(tr("Done"), 3000);
       
  1102         setCursor(Qt::ArrowCursor);
       
  1103     }
       
  1104     keywordDocuments.clear();
       
  1105 }
       
  1106 
       
  1107 void HelpDialog::setIndexingProgress(int prog)
       
  1108 {
       
  1109     ui.progressPrepare->setValue(prog);
       
  1110     processEvents();
       
  1111 }
       
  1112 
       
  1113 void HelpDialog::startSearch()
       
  1114 {
       
  1115     QString str = ui.termsEdit->text();
       
  1116     str = str.simplified();
       
  1117     str = str.replace(QLatin1String("\'"), QLatin1String("\""));
       
  1118     str = str.replace(QLatin1String("`"), QLatin1String("\""));
       
  1119     QString buf = str;
       
  1120     str = str.replace(QLatin1String("-"), QLatin1String(" "));
       
  1121     str = str.replace(QRegExp(QLatin1String("\\s[\\S]?\\s")), QLatin1String(" "));
       
  1122     terms = str.split(QLatin1Char(' '));
       
  1123     QStringList termSeq;
       
  1124     QStringList seqWords;
       
  1125     QStringList::iterator it = terms.begin();
       
  1126     for (; it != terms.end(); ++it) {
       
  1127         (*it) = (*it).simplified();
       
  1128         (*it) = (*it).toLower();
       
  1129         (*it) = (*it).replace(QLatin1String("\""), QLatin1String(""));
       
  1130     }
       
  1131     if (str.contains(QLatin1Char('\"'))) {
       
  1132         if ((str.count(QLatin1Char('\"')))%2 == 0) {
       
  1133             int beg = 0;
       
  1134             int end = 0;
       
  1135             QString s;
       
  1136             beg = str.indexOf(QLatin1Char('\"'), beg);
       
  1137             while (beg != -1) {
       
  1138                 beg++;
       
  1139                 end = str.indexOf(QLatin1Char('\"'), beg);
       
  1140                 s = str.mid(beg, end - beg);
       
  1141                 s = s.toLower();
       
  1142                 s = s.simplified();
       
  1143                 if (s.contains(QLatin1Char('*'))) {
       
  1144                     QMessageBox::warning(this, tr("Full Text Search"),
       
  1145                         tr("Using a wildcard within phrases is not allowed."));
       
  1146                     return;
       
  1147                 }
       
  1148                 seqWords += s.split(QLatin1Char(' '));
       
  1149                 termSeq << s;
       
  1150                 beg = str.indexOf(QLatin1Char('\"'), end + 1);
       
  1151             }
       
  1152         } else {
       
  1153             QMessageBox::warning(this, tr("Full Text Search"),
       
  1154                 tr("The closing quotation mark is missing."));
       
  1155             return;
       
  1156         }
       
  1157     }
       
  1158     setCursor(Qt::WaitCursor);
       
  1159     foundDocs.clear();
       
  1160     foundDocs = fullTextIndex->query(terms, termSeq, seqWords);
       
  1161     QString msg = tr("%n document(s) found.", "", foundDocs.count());
       
  1162     help->statusBar()->showMessage(tr(msg.toUtf8()), 3000);
       
  1163     ui.resultBox->clear();
       
  1164     for (it = foundDocs.begin(); it != foundDocs.end(); ++it)
       
  1165         ui.resultBox->addItem(fullTextIndex->getDocumentTitle(*it));
       
  1166 
       
  1167     terms.clear();
       
  1168     bool isPhrase = false;
       
  1169     QString s;
       
  1170     for (int i = 0; i < (int)buf.length(); ++i) {
       
  1171         if (buf[i] == QLatin1Char('\"')) {
       
  1172             isPhrase = !isPhrase;
       
  1173             s = s.simplified();
       
  1174             if (!s.isEmpty())
       
  1175                 terms << s;
       
  1176             s = QLatin1String("");
       
  1177         } else if (buf[i] == QLatin1Char(' ') && !isPhrase) {
       
  1178             s = s.simplified();
       
  1179             if (!s.isEmpty())
       
  1180                 terms << s;
       
  1181             s = QLatin1String("");
       
  1182         } else
       
  1183             s += buf[i];
       
  1184     }
       
  1185     if (!s.isEmpty())
       
  1186         terms << s;
       
  1187 
       
  1188     setCursor(Qt::ArrowCursor);
       
  1189 }
       
  1190 
       
  1191 void HelpDialog::on_helpButton_clicked()
       
  1192 {
       
  1193     emit showLink(MainWindow::urlifyFileName(
       
  1194                   Config::configuration()->assistantDocPath() +
       
  1195                   QLatin1String("/assistant-manual.html#full-text-searching")));
       
  1196 }
       
  1197 
       
  1198 void HelpDialog::on_resultBox_itemActivated(QListWidgetItem *item)
       
  1199 {
       
  1200     showResultPage(item);
       
  1201 }
       
  1202 
       
  1203 void HelpDialog::showResultPage(QListWidgetItem *item)
       
  1204 {
       
  1205     if (item)
       
  1206         emit showSearchLink(foundDocs[ui.resultBox->row(item)], terms);
       
  1207 }
       
  1208 
       
  1209 void HelpDialog::showIndexItemMenu(const QPoint &pos)
       
  1210 {
       
  1211     QListView *listView = qobject_cast<QListView*>(sender());
       
  1212     if (!listView)
       
  1213         return;
       
  1214 
       
  1215     QModelIndex idx = listView->indexAt(pos);
       
  1216     if (!idx.isValid())
       
  1217         return;
       
  1218 
       
  1219     QAction *action = itemPopup->exec(listView->viewport()->mapToGlobal(pos));
       
  1220     if (action == actionOpenCurrentTab) {
       
  1221         showTopic();
       
  1222     } else if (action) {
       
  1223         HelpWindow *hw = help->browsers()->currentBrowser();
       
  1224         QString itemName = idx.data().toString();
       
  1225         ui.editIndex->setText(itemName);
       
  1226         QStringList links = indexModel->links(idx.row());
       
  1227         if (links.count() == 1) {
       
  1228             if (action == actionOpenLinkInNewWindow)
       
  1229                 hw->openLinkInNewWindow(links.first());
       
  1230             else
       
  1231                 hw->openLinkInNewPage(links.first());
       
  1232         } else {
       
  1233             QStringList::Iterator it = links.begin();
       
  1234             QStringList linkList;
       
  1235             QStringList linkNames;
       
  1236             for (; it != links.end(); ++it) {
       
  1237                 linkList << *it;
       
  1238                 linkNames << titleOfLink(*it);
       
  1239             }
       
  1240             QString link = TopicChooser::getLink(this, linkNames, linkList, itemName);
       
  1241             if (!link.isEmpty()) {
       
  1242                 if (action == actionOpenLinkInNewWindow)
       
  1243                     hw->openLinkInNewWindow(link);
       
  1244                 else
       
  1245                     hw->openLinkInNewPage(link);
       
  1246             }
       
  1247         }
       
  1248     }
       
  1249 }
       
  1250 
       
  1251 void HelpDialog::showListItemMenu(const QPoint &pos)
       
  1252 {
       
  1253     QListWidget *listWidget = qobject_cast<QListWidget*>(sender());
       
  1254     if (!listWidget)
       
  1255         return;
       
  1256     QListWidgetItem *item = listWidget->itemAt(pos);
       
  1257     if (!item)
       
  1258         return;
       
  1259 
       
  1260     QAction *action = itemPopup->exec(listWidget->viewport()->mapToGlobal(pos));
       
  1261     if (action == actionOpenCurrentTab) {
       
  1262         showResultPage(item);
       
  1263     } else if (action) {
       
  1264         HelpWindow *hw = help->browsers()->currentBrowser();
       
  1265         QString link = foundDocs[ui.resultBox->row(item)];
       
  1266         if (action == actionOpenLinkInNewWindow)
       
  1267             hw->openLinkInNewWindow(link);
       
  1268         else
       
  1269             hw->openLinkInNewPage(link);
       
  1270     }
       
  1271 }
       
  1272 
       
  1273 void HelpDialog::showTreeItemMenu(const QPoint &pos)
       
  1274 {
       
  1275     QTreeWidget *treeWidget = qobject_cast<QTreeWidget*>(sender());
       
  1276 
       
  1277     if (!treeWidget)
       
  1278         return;
       
  1279 
       
  1280     QTreeWidgetItem *item = treeWidget->itemAt(pos);
       
  1281 
       
  1282     if (!item)
       
  1283         return;
       
  1284 
       
  1285     QAction *action = itemPopup->exec(treeWidget->viewport()->mapToGlobal(pos));
       
  1286     if (action == actionOpenCurrentTab) {
       
  1287         if (ui.tabWidget->currentWidget()->objectName() == QLatin1String("contentPage"))
       
  1288             showContentsTopic();
       
  1289         else
       
  1290             showBookmarkTopic();
       
  1291     } else if (action) {
       
  1292         QTreeWidgetItem *i = (QTreeWidgetItem*)item;
       
  1293         if (action == actionOpenLinkInNewWindow)
       
  1294             help->browsers()->currentBrowser()->openLinkInNewWindow(i->data(0, LinkRole).toString());
       
  1295         else
       
  1296             help->browsers()->currentBrowser()->openLinkInNewPage(i->data(0, LinkRole).toString());
       
  1297     }
       
  1298 }
       
  1299 
       
  1300 void HelpDialog::on_termsEdit_returnPressed()
       
  1301 {
       
  1302     startSearch();
       
  1303 }
       
  1304 
       
  1305 void HelpDialog::updateSearchButton(const QString &txt)
       
  1306 {
       
  1307     ui.searchButton->setDisabled(txt.isEmpty());
       
  1308 }
       
  1309 
       
  1310 void HelpDialog::on_searchButton_clicked()
       
  1311 {
       
  1312     startSearch();
       
  1313 }
       
  1314 
       
  1315 QString HelpDialog::removeAnchorFromLink(const QString &link)
       
  1316 {
       
  1317     int i = link.length();
       
  1318    int j = link.lastIndexOf(QLatin1Char('/'));
       
  1319     int l = link.lastIndexOf(QDir::separator());
       
  1320     if (l > j)
       
  1321         j = l;
       
  1322    if (j > -1) {
       
  1323       QString fileName = link.mid(j+1);
       
  1324       int k = fileName.lastIndexOf(QLatin1Char('#'));
       
  1325       if (k > -1)
       
  1326          i = j + k + 1;
       
  1327    }
       
  1328    return link.left(i);
       
  1329 }
       
  1330 
       
  1331 QT_END_NAMESPACE