summaryrefslogtreecommitdiffstats
path: root/tools/assistant/compat/helpdialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/assistant/compat/helpdialog.cpp')
-rw-r--r--tools/assistant/compat/helpdialog.cpp1331
1 files changed, 1331 insertions, 0 deletions
diff --git a/tools/assistant/compat/helpdialog.cpp b/tools/assistant/compat/helpdialog.cpp
new file mode 100644
index 0000000..4ea97d0
--- /dev/null
+++ b/tools/assistant/compat/helpdialog.cpp
@@ -0,0 +1,1331 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the Qt Assistant of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "helpdialog.h"
+#include "helpwindow.h"
+#include "topicchooser.h"
+#include "docuparser.h"
+#include "mainwindow.h"
+#include "config.h"
+#include "tabbedbrowser.h"
+
+#include <QtGui>
+#include <QtDebug>
+#include <QtCore/QVarLengthArray>
+
+#include <stdlib.h>
+#include <limits.h>
+
+QT_BEGIN_NAMESPACE
+
+enum
+{
+ LinkRole = Qt::UserRole + 1000
+};
+
+static bool verifyDirectory(const QString &str)
+{
+ QFileInfo dirInfo(str);
+ if (!dirInfo.exists())
+ return QDir().mkdir(str);
+ if (!dirInfo.isDir()) {
+ qWarning("'%s' exists but is not a directory", str.toLatin1().constData());
+ return false;
+ }
+ return true;
+}
+
+struct IndexKeyword {
+ IndexKeyword(const QString &kw, const QString &l)
+ : keyword(kw), link(l) {}
+ IndexKeyword() : keyword(QString()), link(QString()) {}
+ bool operator<(const IndexKeyword &ik) const {
+ return keyword.toLower() < ik.keyword.toLower();
+ }
+ bool operator<=(const IndexKeyword &ik) const {
+ return keyword.toLower() <= ik.keyword.toLower();
+ }
+ bool operator>(const IndexKeyword &ik) const {
+ return keyword.toLower() > ik.keyword.toLower();
+ }
+ Q_DUMMY_COMPARISON_OPERATOR(IndexKeyword)
+ QString keyword;
+ QString link;
+};
+
+QDataStream &operator>>(QDataStream &s, IndexKeyword &ik)
+{
+ s >> ik.keyword;
+ s >> ik.link;
+ return s;
+}
+
+QDataStream &operator<<(QDataStream &s, const IndexKeyword &ik)
+{
+ s << ik.keyword;
+ s << ik.link;
+ return s;
+}
+
+QValidator::State SearchValidator::validate(QString &str, int &) const
+{
+ for (int i = 0; i < (int) str.length(); ++i) {
+ QChar c = str[i];
+ if (!c.isLetterOrNumber() && c != QLatin1Char('\'') && c != QLatin1Char('`')
+ && c != QLatin1Char('\"') && c != QLatin1Char(' ') && c != QLatin1Char('-') && c != QLatin1Char('_')
+ && c!= QLatin1Char('*'))
+ return QValidator::Invalid;
+ }
+ return QValidator::Acceptable;
+}
+
+class IndexListModel: public QStringListModel
+{
+public:
+ IndexListModel(QObject *parent = 0)
+ : QStringListModel(parent) {}
+
+ void clear() { contents.clear(); setStringList(QStringList()); }
+
+ QString description(int index) const { return stringList().at(index); }
+ QStringList links(int index) const { return contents.values(stringList().at(index)); }
+ void addLink(const QString &description, const QString &link) { contents.insert(description, link); }
+
+ void publish() { filter(QString(), QString()); }
+
+ QModelIndex filter(const QString &s, const QString &real);
+
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const
+ { return QStringListModel::flags(index) & ~Qt::ItemIsEditable; }
+
+private:
+ QMultiMap<QString, QString> contents;
+};
+
+bool caseInsensitiveLessThan(const QString &as, const QString &bs)
+{
+ const QChar *a = as.unicode();
+ const QChar *b = bs.unicode();
+ if (a == 0)
+ return true;
+ if (b == 0)
+ return false;
+ if (a == b)
+ return false;
+ int l=qMin(as.length(),bs.length());
+ while (l-- && QChar::toLower(a->unicode()) == QChar::toLower(b->unicode()))
+ a++,b++;
+ if (l==-1)
+ return (as.length() < bs.length());
+ return QChar::toLower(a->unicode()) < QChar::toLower(b->unicode());
+}
+
+/**
+ * \a real is kinda a hack for the smart search, need a way to match a regexp to an item
+ * How would you say the best match for Q.*Wiget is QWidget?
+ */
+QModelIndex IndexListModel::filter(const QString &s, const QString &real)
+{
+ QStringList list;
+
+ int goodMatch = -1;
+ int perfectMatch = -1;
+ if (s.isEmpty())
+ perfectMatch = 0;
+
+ const QRegExp regExp(s, Qt::CaseInsensitive);
+ QMultiMap<QString, QString>::iterator it = contents.begin();
+ QString lastKey;
+ for (; it != contents.end(); ++it) {
+ if (it.key() == lastKey)
+ continue;
+ lastKey = it.key();
+ const QString key = it.key();
+ if (key.contains(regExp) || key.contains(s, Qt::CaseInsensitive)) {
+ list.append(key);
+ if (perfectMatch == -1 && (key.startsWith(real, Qt::CaseInsensitive))) {
+ if (goodMatch == -1)
+ goodMatch = list.count() - 1;
+ if (real.length() == key.length()){
+ perfectMatch = list.count() - 1;
+ }
+ } else if (perfectMatch > -1 && s == key) {
+ perfectMatch = list.count() - 1;
+ }
+ }
+ }
+
+ int bestMatch = perfectMatch;
+ if (bestMatch == -1)
+ bestMatch = goodMatch;
+ bestMatch = qMax(0, bestMatch);
+
+ // sort the new list
+ QString match;
+ if (bestMatch >= 0 && list.count() > bestMatch)
+ match = list[bestMatch];
+ qSort(list.begin(), list.end(), caseInsensitiveLessThan);
+ setStringList(list);
+ for (int i = 0; i < list.size(); ++i) {
+ if (list.at(i) == match){
+ bestMatch = i;
+ break;
+ }
+ }
+ return index(bestMatch, 0, QModelIndex());
+}
+
+HelpNavigationListItem::HelpNavigationListItem(QListWidget *ls, const QString &txt)
+ : QListWidgetItem(txt, ls)
+{
+}
+
+void HelpNavigationListItem::addLink(const QString &link)
+{
+ QString lnk = HelpDialog::removeAnchorFromLink(link);
+ if (linkList.filter(lnk, Qt::CaseInsensitive).count() > 0)
+ return;
+ linkList << link;
+}
+
+HelpDialog::HelpDialog(QWidget *parent, MainWindow *h)
+ : QWidget(parent), lwClosed(false), help(h)
+{
+ ui.setupUi(this);
+ ui.listContents->setUniformRowHeights(true);
+ ui.listContents->header()->setStretchLastSection(false);
+ ui.listContents->header()->setResizeMode(QHeaderView::ResizeToContents);
+ ui.listBookmarks->setUniformRowHeights(true);
+ ui.listBookmarks->header()->setStretchLastSection(false);
+ ui.listBookmarks->header()->setResizeMode(QHeaderView::ResizeToContents);
+
+ indexModel = new IndexListModel(this);
+ ui.listIndex->setModel(indexModel);
+ ui.listIndex->setLayoutMode(QListView::Batched);
+ ui.listBookmarks->setItemHidden(ui.listBookmarks->headerItem(), true);
+ ui.listContents->setItemHidden(ui.listContents->headerItem(), true);
+ ui.searchButton->setShortcut(QKeySequence(Qt::ALT|Qt::SHIFT|Qt::Key_S));
+}
+
+void HelpDialog::initialize()
+{
+ connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
+
+ connect(ui.listContents, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(showTopic(QTreeWidgetItem*)));
+ connect(ui.listContents, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showTreeItemMenu(QPoint)));
+ ui.listContents->viewport()->installEventFilter(this);
+
+ connect(ui.editIndex, SIGNAL(returnPressed()), this, SLOT(showTopic()));
+ connect(ui.editIndex, SIGNAL(textEdited(QString)), this, SLOT(searchInIndex(QString)));
+
+ connect(ui.listIndex, SIGNAL(activated(QModelIndex)), this, SLOT(showTopic()));
+ connect(ui.listIndex, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showIndexItemMenu(QPoint)));
+
+ connect(ui.listBookmarks, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(showTopic(QTreeWidgetItem*)));
+ connect(ui.listBookmarks, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showTreeItemMenu(QPoint)));
+
+ connect(ui.termsEdit, SIGNAL(textChanged(const QString&)), this, SLOT(updateSearchButton(const QString&)));
+
+ connect(ui.resultBox, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showListItemMenu(QPoint)));
+
+ cacheFilesPath = QDir::homePath() + QLatin1String("/.assistant"); //### Find a better location for the dbs
+
+ ui.editIndex->installEventFilter(this);
+
+ ui.framePrepare->hide();
+ connect(qApp, SIGNAL(lastWindowClosed()), SLOT(lastWinClosed()));
+
+ ui.termsEdit->setValidator(new SearchValidator(ui.termsEdit));
+
+ actionOpenCurrentTab = new QAction(this);
+ actionOpenCurrentTab->setText(tr("Open Link in Current Tab"));
+
+ actionOpenLinkInNewWindow = new QAction(this);
+ actionOpenLinkInNewWindow->setText(tr("Open Link in New Window"));
+
+ actionOpenLinkInNewTab = new QAction(this);
+ actionOpenLinkInNewTab->setText(tr("Open Link in New Tab"));
+
+ itemPopup = new QMenu(this);
+ itemPopup->addAction(actionOpenCurrentTab);
+ itemPopup->addAction(actionOpenLinkInNewWindow);
+ itemPopup->addAction(actionOpenLinkInNewTab);
+
+ ui.tabWidget->setElideMode(Qt::ElideNone);
+
+ contentList.clear();
+
+ initDoneMsgShown = false;
+ fullTextIndex = 0;
+ indexDone = false;
+ titleMapDone = false;
+ contentsInserted = false;
+ bookmarksInserted = false;
+ setupTitleMap();
+
+}
+
+void HelpDialog::processEvents()
+{
+ qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
+}
+
+
+void HelpDialog::lastWinClosed()
+{
+ lwClosed = true;
+}
+
+void HelpDialog::removeOldCacheFiles(bool onlyFulltextSearchIndex)
+{
+ if (!verifyDirectory(cacheFilesPath)) {
+ qWarning("Failed to created assistant directory");
+ return;
+ }
+ QString pname = QLatin1String(".") + Config::configuration()->profileName();
+
+ QStringList fileList;
+ fileList << QLatin1String("indexdb40.dict")
+ << QLatin1String("indexdb40.doc");
+
+ if (!onlyFulltextSearchIndex)
+ fileList << QLatin1String("indexdb40") << QLatin1String("contentdb40");
+
+ QStringList::iterator it = fileList.begin();
+ for (; it != fileList.end(); ++it) {
+ if (QFile::exists(cacheFilesPath + QDir::separator() + *it + pname)) {
+ QFile f(cacheFilesPath + QDir::separator() + *it + pname);
+ f.remove();
+ }
+ }
+}
+
+void HelpDialog::timerEvent(QTimerEvent *e)
+{
+ Q_UNUSED(e);
+ static int opacity = 255;
+ help->setWindowOpacity((opacity-=4)/255.0);
+ if (opacity<=0)
+ qApp->quit();
+}
+
+
+void HelpDialog::loadIndexFile()
+{
+ if (indexDone)
+ return;
+
+ setCursor(Qt::WaitCursor);
+ indexDone = true;
+ ui.labelPrepare->setText(tr("Prepare..."));
+ ui.framePrepare->show();
+ processEvents();
+
+ QProgressBar *bar = ui.progressPrepare;
+ bar->setMaximum(100);
+ bar->setValue(0);
+
+ keywordDocuments.clear();
+ QList<IndexKeyword> lst;
+ QFile indexFile(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.") +
+ Config::configuration()->profileName());
+ if (!indexFile.open(QFile::ReadOnly)) {
+ buildKeywordDB();
+ processEvents();
+ if (lwClosed)
+ return;
+ if (!indexFile.open(QFile::ReadOnly)) {
+ QMessageBox::warning(help, tr("Qt Assistant"), tr("Failed to load keyword index file\n"
+ "Assistant will not work!"));
+#if defined Q_WS_WIN || defined Q_WS_MACX
+ startTimer(50);
+#endif
+ return;
+ }
+ }
+
+ QDataStream ds(&indexFile);
+ quint32 fileAges;
+ ds >> fileAges;
+ if (fileAges != getFileAges()) {
+ indexFile.close();
+ buildKeywordDB();
+ if (!indexFile.open(QFile::ReadOnly)) {
+ QMessageBox::warning(help, tr("Qt Assistant"),
+ tr("Cannot open the index file %1").arg(QFileInfo(indexFile).absoluteFilePath()));
+ return;
+ }
+ ds.setDevice(&indexFile);
+ ds >> fileAges;
+ }
+ ds >> lst;
+ indexFile.close();
+
+ bar->setValue(bar->maximum());
+ processEvents();
+
+ for (int i=0; i<lst.count(); ++i) {
+ const IndexKeyword &idx = lst.at(i);
+ indexModel->addLink(idx.keyword, idx.link);
+
+ keywordDocuments << HelpDialog::removeAnchorFromLink(idx.link);
+ }
+
+ indexModel->publish();
+
+ ui.framePrepare->hide();
+ showInitDoneMessage();
+ setCursor(Qt::ArrowCursor);
+}
+
+quint32 HelpDialog::getFileAges()
+{
+ QStringList addDocuFiles = Config::configuration()->docFiles();
+ QStringList::const_iterator i = addDocuFiles.constBegin();
+
+ quint32 fileAges = 0;
+ for (; i != addDocuFiles.constEnd(); ++i) {
+ QFileInfo fi(*i);
+ if (fi.exists())
+ fileAges += fi.lastModified().toTime_t();
+ }
+
+ return fileAges;
+}
+
+void HelpDialog::buildKeywordDB()
+{
+ QStringList addDocuFiles = Config::configuration()->docFiles();
+ QStringList::iterator i = addDocuFiles.begin();
+
+ // Set up an indeterminate progress bar.
+ ui.labelPrepare->setText(tr("Prepare..."));
+ ui.progressPrepare->setMaximum(0);
+ ui.progressPrepare->setMinimum(0);
+ ui.progressPrepare->setValue(0);
+ processEvents();
+
+ QList<IndexKeyword> lst;
+ quint32 fileAges = 0;
+ for (i = addDocuFiles.begin(); i != addDocuFiles.end(); ++i) {
+ QFile file(*i);
+ if (!file.exists()) {
+ QMessageBox::warning(this, tr("Warning"),
+ tr("Documentation file %1 does not exist!\n"
+ "Skipping file.").arg(QFileInfo(file).absoluteFilePath()));
+ continue;
+ }
+ fileAges += QFileInfo(file).lastModified().toTime_t();
+ DocuParser *handler = DocuParser::createParser(*i);
+ bool ok = handler->parse(&file);
+ file.close();
+ if (!ok){
+ QString msg = QString::fromLatin1("In file %1:\n%2")
+ .arg(QFileInfo(file).absoluteFilePath())
+ .arg(handler->errorProtocol());
+ QMessageBox::critical(this, tr("Parse Error"), tr(msg.toUtf8()));
+ delete handler;
+ continue;
+ }
+
+ QList<IndexItem*> indLst = handler->getIndexItems();
+ int counter = 0;
+ foreach (IndexItem *indItem, indLst) {
+ QFileInfo fi(indItem->reference);
+ lst.append(IndexKeyword(indItem->keyword, indItem->reference));
+
+ if (++counter%100 == 0) {
+ if (ui.progressPrepare)
+ ui.progressPrepare->setValue(counter);
+ processEvents();
+ if (lwClosed) {
+ return;
+ }
+ }
+ }
+ delete handler;
+ }
+ if (!lst.isEmpty())
+ qSort(lst);
+
+ QFile indexout(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.")
+ + Config::configuration()->profileName());
+ if (verifyDirectory(cacheFilesPath) && indexout.open(QFile::WriteOnly)) {
+ QDataStream s(&indexout);
+ s << fileAges;
+ s << lst;
+ indexout.close();
+ }
+}
+
+void HelpDialog::setupTitleMap()
+{
+ if (titleMapDone)
+ return;
+
+ bool needRebuild = false;
+ if (Config::configuration()->profileName() == QLatin1String("default")) {
+ const QStringList docuFiles = Config::configuration()->docFiles();
+ for (QStringList::ConstIterator it = docuFiles.begin(); it != docuFiles.end(); ++it) {
+ if (!QFile::exists(*it)) {
+ Config::configuration()->saveProfile(Profile::createDefaultProfile());
+ Config::configuration()->loadDefaultProfile();
+ needRebuild = true;
+ break;
+ }
+ }
+ }
+
+ if (Config::configuration()->docRebuild() || needRebuild) {
+ removeOldCacheFiles();
+ Config::configuration()->setDocRebuild(false);
+ Config::configuration()->saveProfile(Config::configuration()->profile());
+ }
+ if (contentList.isEmpty())
+ getAllContents();
+
+ titleMapDone = true;
+ titleMap.clear();
+ for (QList<QPair<QString, ContentList> >::Iterator it = contentList.begin(); it != contentList.end(); ++it) {
+ ContentList lst = (*it).second;
+ foreach (ContentItem item, lst) {
+ titleMap[item.reference] = item.title.trimmed();
+ }
+ }
+ processEvents();
+}
+
+void HelpDialog::getAllContents()
+{
+ QFile contentFile(cacheFilesPath + QDir::separator() + QLatin1String("contentdb40.")
+ + Config::configuration()->profileName());
+ contentList.clear();
+ if (!contentFile.open(QFile::ReadOnly)) {
+ buildContentDict();
+ return;
+ }
+
+ QDataStream ds(&contentFile);
+ quint32 fileAges;
+ ds >> fileAges;
+ if (fileAges != getFileAges()) {
+ contentFile.close();
+ removeOldCacheFiles(true);
+ buildContentDict();
+ return;
+ }
+ QString key;
+ QList<ContentItem> lst;
+ while (!ds.atEnd()) {
+ ds >> key;
+ ds >> lst;
+ contentList += qMakePair(key, QList<ContentItem>(lst));
+ }
+ contentFile.close();
+ processEvents();
+
+}
+
+void HelpDialog::buildContentDict()
+{
+ QStringList docuFiles = Config::configuration()->docFiles();
+
+ quint32 fileAges = 0;
+ for (QStringList::iterator it = docuFiles.begin(); it != docuFiles.end(); ++it) {
+ QFile file(*it);
+ if (!file.exists()) {
+ QMessageBox::warning(this, tr("Warning"),
+ tr("Documentation file %1 does not exist!\n"
+ "Skipping file.").arg(QFileInfo(file).absoluteFilePath()));
+ continue;
+ }
+ fileAges += QFileInfo(file).lastModified().toTime_t();
+ DocuParser *handler = DocuParser::createParser(*it);
+ if (!handler) {
+ QMessageBox::warning(this, tr("Warning"),
+ tr("Documentation file %1 is not compatible!\n"
+ "Skipping file.").arg(QFileInfo(file).absoluteFilePath()));
+ continue;
+ }
+ bool ok = handler->parse(&file);
+ file.close();
+ if (ok) {
+ contentList += qMakePair(*it, QList<ContentItem>(handler->getContentItems()));
+ delete handler;
+ } else {
+ QString msg = QString::fromLatin1("In file %1:\n%2")
+ .arg(QFileInfo(file).absoluteFilePath())
+ .arg(handler->errorProtocol());
+ QMessageBox::critical(this, tr("Parse Error"), tr(msg.toUtf8()));
+ continue;
+ }
+ }
+
+ QFile contentOut(cacheFilesPath + QDir::separator() + QLatin1String("contentdb40.")
+ + Config::configuration()->profileName());
+ if (contentOut.open(QFile::WriteOnly)) {
+ QDataStream s(&contentOut);
+ s << fileAges;
+ for (QList<QPair<QString, ContentList> >::Iterator it = contentList.begin(); it != contentList.end(); ++it) {
+ s << *it;
+ }
+ contentOut.close();
+ }
+}
+
+void HelpDialog::currentTabChanged(int index)
+{
+ QString s = ui.tabWidget->widget(index)->objectName();
+ if (s == QLatin1String("indexPage"))
+ QTimer::singleShot(0, this, SLOT(loadIndexFile()));
+ else if (s == QLatin1String("bookmarkPage"))
+ insertBookmarks();
+ else if (s == QLatin1String("contentPage"))
+ QTimer::singleShot(0, this, SLOT(insertContents()));
+ else if (s == QLatin1String("searchPage"))
+ QTimer::singleShot(0, this, SLOT(setupFullTextIndex()));
+}
+
+void HelpDialog::showInitDoneMessage()
+{
+ if (initDoneMsgShown)
+ return;
+ initDoneMsgShown = true;
+ help->statusBar()->showMessage(tr("Done"), 3000);
+}
+
+void HelpDialog::showTopic(QTreeWidgetItem *item)
+{
+ if (item)
+ showTopic();
+}
+
+void HelpDialog::showTopic()
+{
+ QString tabName = ui.tabWidget->currentWidget()->objectName();
+
+ if (tabName == QLatin1String("indexPage"))
+ showIndexTopic();
+ else if (tabName == QLatin1String("bookmarkPage"))
+ showBookmarkTopic();
+ else if (tabName == QLatin1String("contentPage"))
+ showContentsTopic();
+}
+
+void HelpDialog::showIndexTopic()
+{
+ int row = ui.listIndex->currentIndex().row();
+ if (row == -1 || row >= indexModel->rowCount())
+ return;
+
+ QString description = indexModel->description(row);
+ QStringList links = indexModel->links(row);
+
+ bool blocked = ui.editIndex->blockSignals(true);
+ ui.editIndex->setText(description);
+ ui.editIndex->blockSignals(blocked);
+
+ if (links.count() == 1) {
+ emit showLink(links.first());
+ } else {
+ qSort(links);
+ QStringList::Iterator it = links.begin();
+ QStringList linkList;
+ QStringList linkNames;
+ for (; it != links.end(); ++it) {
+ linkList << *it;
+ linkNames << titleOfLink(*it);
+ }
+ QString link = TopicChooser::getLink(this, linkNames, linkList, description);
+ if (!link.isEmpty())
+ emit showLink(link);
+ }
+
+ ui.listIndex->setCurrentIndex(indexModel->index(indexModel->stringList().indexOf(description)));
+ ui.listIndex->scrollTo(ui.listIndex->currentIndex(), QAbstractItemView::PositionAtTop);
+}
+
+void HelpDialog::searchInIndex(const QString &searchString)
+{
+ QRegExp atoz(QLatin1String("[A-Z]"));
+ int matches = searchString.count(atoz);
+ if (matches > 0 && !searchString.contains(QLatin1String(".*")))
+ {
+ int start = 0;
+ QString newSearch;
+ for (; matches > 0; --matches) {
+ int match = searchString.indexOf(atoz, start+1);
+ if (match <= start)
+ continue;
+ newSearch += searchString.mid(start, match-start);
+ newSearch += QLatin1String(".*");
+ start = match;
+ }
+ newSearch += searchString.mid(start);
+ ui.listIndex->setCurrentIndex(indexModel->filter(newSearch, searchString));
+ }
+ else
+ ui.listIndex->setCurrentIndex(indexModel->filter(searchString, searchString));
+}
+
+QString HelpDialog::titleOfLink(const QString &link)
+{
+ QString s = HelpDialog::removeAnchorFromLink(link);
+ s = titleMap[s];
+ if (s.isEmpty())
+ return link;
+ return s;
+}
+
+bool HelpDialog::eventFilter(QObject * o, QEvent * e)
+{
+ if (o == ui.editIndex && e->type() == QEvent::KeyPress) {
+ switch (static_cast<QKeyEvent*>(e)->key()) {
+ case Qt::Key_Up:
+ case Qt::Key_Down:
+ case Qt::Key_PageDown:
+ case Qt::Key_PageUp:
+ QApplication::sendEvent(ui.listIndex, e);
+ break;
+
+ default:
+ break;
+ }
+ } else if (o == ui.listContents->viewport()) {
+ if (e->type() == QEvent::MouseButtonRelease) {
+ QMouseEvent *me = static_cast<QMouseEvent*>(e);
+ if (me->button() == Qt::LeftButton) {
+ QTreeWidgetItem *item = ui.listContents->itemAt(me->pos());
+ QRect vRect = ui.listContents->visualItemRect(item);
+
+ // only show topic if we clicked an item
+ if (item && vRect.contains(me->pos()))
+ showTopic(item);
+ }
+ }
+ }
+
+ return QWidget::eventFilter(o, e);
+}
+
+void HelpDialog::addBookmark()
+{
+ if (!bookmarksInserted)
+ insertBookmarks();
+ QString link = help->browsers()->currentBrowser()->source().toString();
+ QString title = help->browsers()->currentBrowser()->documentTitle();
+ if (title.isEmpty())
+ title = titleOfLink(link);
+
+ QTreeWidgetItem *i = new QTreeWidgetItem(ui.listBookmarks, 0);
+ i->setText(0, title);
+ i->setData(0, LinkRole, link);
+ ui.buttonRemove->setEnabled(true);
+ saveBookmarks();
+ help->updateBookmarkMenu();
+}
+
+void HelpDialog::on_buttonAdd_clicked()
+{
+ addBookmark();
+}
+
+void HelpDialog::on_buttonRemove_clicked()
+{
+ if (!ui.listBookmarks->currentItem())
+ return;
+
+ delete ui.listBookmarks->currentItem();
+ saveBookmarks();
+ if (ui.listBookmarks->topLevelItemCount() != 0) {
+ ui.listBookmarks->setCurrentItem(ui.listBookmarks->topLevelItem(0));
+ }
+ ui.buttonRemove->setEnabled(ui.listBookmarks->topLevelItemCount() > 0);
+ help->updateBookmarkMenu();
+}
+
+void HelpDialog::insertBookmarks()
+{
+ if (bookmarksInserted)
+ return;
+ bookmarksInserted = true;
+ ui.listBookmarks->clear();
+ QFile f(cacheFilesPath + QDir::separator() + QLatin1String("bookmarks.")
+ + Config::configuration()->profileName());
+ if (!f.open(QFile::ReadOnly))
+ return;
+ QTextStream ts(&f);
+ while (!ts.atEnd()) {
+ QTreeWidgetItem *i = new QTreeWidgetItem(ui.listBookmarks, 0);
+ i->setText(0, ts.readLine());
+ i->setData(0, LinkRole, ts.readLine());
+ }
+ ui.buttonRemove->setEnabled(ui.listBookmarks->topLevelItemCount() > 0);
+ help->updateBookmarkMenu();
+ showInitDoneMessage();
+}
+
+void HelpDialog::showBookmarkTopic()
+{
+ if (!ui.listBookmarks->currentItem())
+ return;
+
+ QTreeWidgetItem *i = (QTreeWidgetItem*)ui.listBookmarks->currentItem();
+ emit showLink(i->data(0, LinkRole).toString());
+}
+
+static void store(QTreeWidgetItem *i, QTextStream &ts)
+{
+ ts << i->text(0) << endl;
+ ts << i->data(0, LinkRole).toString() << endl;
+
+ for (int index = 0; index < i->childCount(); ++index)
+ store(i->child(index), ts);
+}
+
+static void store(QTreeWidget *tw, QTextStream &ts)
+{
+ for (int index = 0; index < tw->topLevelItemCount(); ++index)
+ store(tw->topLevelItem(index), ts);
+}
+
+void HelpDialog::saveBookmarks()
+{
+ QFile f(cacheFilesPath + QDir::separator() + QLatin1String("bookmarks.")
+ + Config::configuration()->profileName());
+ if (!f.open(QFile::WriteOnly))
+ return;
+
+ QTextStream ts(&f);
+ store(ui.listBookmarks, ts);
+ f.close();
+}
+
+void HelpDialog::insertContents()
+{
+#ifdef Q_WS_MAC
+ static const QLatin1String IconPath(":/trolltech/assistant/images/mac/book.png");
+#else
+ static const QLatin1String IconPath(":/trolltech/assistant/images/win/book.png");
+#endif
+ if (contentsInserted)
+ return;
+
+ if (contentList.isEmpty())
+ getAllContents();
+
+ contentsInserted = true;
+ ui.listContents->clear();
+ setCursor(Qt::WaitCursor);
+ if (!titleMapDone)
+ setupTitleMap();
+
+#if 0 // ### port me
+ ui.listContents->setSorting(-1);
+#endif
+
+ for (QList<QPair<QString, ContentList> >::Iterator it = contentList.begin(); it != contentList.end(); ++it) {
+ QTreeWidgetItem *newEntry = 0;
+
+ QTreeWidgetItem *contentEntry = 0;
+ QStack<QTreeWidgetItem*> stack;
+ stack.clear();
+ int depth = 0;
+ bool root = false;
+
+ const int depthSize = 32;
+ QVarLengthArray<QTreeWidgetItem*, depthSize> lastItem(depthSize);
+
+ ContentList lst = (*it).second;
+ for (ContentList::ConstIterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
+ ContentItem item = *it;
+ if (item.depth == 0) {
+ lastItem[0] = 0;
+ newEntry = new QTreeWidgetItem(ui.listContents, 0);
+ newEntry->setIcon(0, QIcon(IconPath));
+ newEntry->setText(0, item.title);
+ newEntry->setData(0, LinkRole, item.reference);
+ stack.push(newEntry);
+ depth = 1;
+ root = true;
+ }
+ else{
+ if ((item.depth > depth) && root) {
+ depth = item.depth;
+ stack.push(contentEntry);
+ }
+ if (item.depth == depth) {
+ if (lastItem.capacity() == depth)
+ lastItem.resize(depth + depthSize);
+ contentEntry = new QTreeWidgetItem(stack.top(), lastItem[ depth ]);
+ lastItem[ depth ] = contentEntry;
+ contentEntry->setText(0, item.title);
+ contentEntry->setData(0, LinkRole, item.reference);
+ }
+ else if (item.depth < depth) {
+ stack.pop();
+ depth--;
+ item = *(--it);
+ }
+ }
+ }
+ processEvents();
+ }
+ setCursor(Qt::ArrowCursor);
+ showInitDoneMessage();
+}
+
+void HelpDialog::showContentsTopic()
+{
+ QTreeWidgetItem *i = (QTreeWidgetItem*)ui.listContents->currentItem();
+ if (!i)
+ return;
+ emit showLink(i->data(0, LinkRole).toString());
+}
+
+QTreeWidgetItem * HelpDialog::locateLink(QTreeWidgetItem *item, const QString &link)
+{
+ QTreeWidgetItem *child = 0;
+#ifdef Q_OS_WIN
+ Qt::CaseSensitivity checkCase = Qt::CaseInsensitive;
+#else
+ Qt::CaseSensitivity checkCase = Qt::CaseSensitive;
+#endif
+ for (int i = 0, childCount = item->childCount(); i<childCount; i++) {
+ child = item->child(i);
+ ///check whether it is this item
+ if (link.startsWith(child->data(0, LinkRole).toString(), checkCase))
+ break;
+ //check if the link is a child of this item
+ else if (child->childCount()) {
+ child = locateLink(child, link);
+ if (child)
+ break;
+ }
+ child = 0;
+ }
+ return child;
+}
+
+void HelpDialog::locateContents(const QString &link)
+{
+ //ensure the TOC is filled
+ if (!contentsInserted)
+ insertContents();
+#ifdef Q_OS_WIN
+ Qt::CaseSensitivity checkCase = Qt::CaseInsensitive;
+#else
+ Qt::CaseSensitivity checkCase = Qt::CaseSensitive;
+#endif
+ QString findLink(link);
+ //Installations on a windows local drive will give the 'link' as <file:///C:/xxx>
+ //and the contents in the TOC will be <file:C:/xxx>.
+ //But on others the 'link' of format <file:///root/xxx>
+ //and the contents in the TOC will be <file:/root/xxx>.
+ if (findLink.contains(QLatin1String("file:///"))) {
+ if (findLink[9] == QLatin1Char(':')) //on windows drives
+ findLink.replace(0, 8, QLatin1String("file:"));
+ else
+ findLink.replace(0, 8, QLatin1String("file:/"));
+ }
+
+ bool topLevel = false;
+ QTreeWidgetItem *item = 0;
+ int totalItems = ui.listContents->topLevelItemCount();
+
+ for (int i = 0; i < totalItems; i++ ) {
+ // first see if we are one of the top level items
+ item = (QTreeWidgetItem*)ui.listContents->topLevelItem(i);
+ if (findLink.startsWith(item->data(0, LinkRole).toString(), checkCase)) {
+ topLevel = true;
+ break;
+ }
+ }
+
+ if (!topLevel) {
+ // now try to find it in the sublevel items
+ for (int n = 0; n < totalItems; ++n) {
+ item = (QTreeWidgetItem*)ui.listContents->topLevelItem(n);
+ item = locateLink(item, findLink);
+ if (item)
+ break;
+ }
+ }
+
+ //remove the old selection
+ QList<QTreeWidgetItem *> selected = ui.listContents->selectedItems();
+ foreach(QTreeWidgetItem *sel, selected)
+ ui.listContents->setItemSelected(sel, false);
+
+ //set the TOC item and show
+ ui.listContents->setCurrentItem(item);
+ ui.listContents->setItemSelected(item, true);
+ ui.listContents->scrollToItem(item);
+}
+
+void HelpDialog::toggleContents()
+{
+ if (!isVisible() || ui.tabWidget->currentIndex() != 0) {
+ ui.tabWidget->setCurrentIndex(0);
+ parentWidget()->show();
+ }
+ else
+ parentWidget()->hide();
+}
+
+void HelpDialog::toggleIndex()
+{
+ if (!isVisible() || ui.tabWidget->currentIndex() != 1 || !ui.editIndex->hasFocus()) {
+ ui.tabWidget->setCurrentIndex(1);
+ parentWidget()->show();
+ ui.editIndex->setFocus();
+ }
+ else
+ parentWidget()->hide();
+}
+
+void HelpDialog::toggleBookmarks()
+{
+ if (!isVisible() || ui.tabWidget->currentIndex() != 2) {
+ ui.tabWidget->setCurrentIndex(2);
+ parentWidget()->show();
+ }
+ else
+ parentWidget()->hide();
+}
+
+void HelpDialog::toggleSearch()
+{
+ if (!isVisible() || ui.tabWidget->currentIndex() != 3) {
+ ui.tabWidget->setCurrentIndex(3);
+ parentWidget()->show();
+ }
+ else
+ parentWidget()->hide();
+}
+
+void HelpDialog::setupFullTextIndex()
+{
+ if (fullTextIndex)
+ return;
+
+ QString pname = Config::configuration()->profileName();
+ fullTextIndex = new Index(QStringList(), QDir::homePath()); // ### Is this correct ?
+ if (!verifyDirectory(cacheFilesPath)) {
+ QMessageBox::warning(help, tr("Qt Assistant"),
+ tr("Failed to save fulltext search index\n"
+ "Assistant will not work!"));
+ return;
+ }
+ fullTextIndex->setDictionaryFile(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.dict.") + pname);
+ fullTextIndex->setDocListFile(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.doc.") + pname);
+ processEvents();
+
+ connect(fullTextIndex, SIGNAL(indexingProgress(int)),
+ this, SLOT(setIndexingProgress(int)));
+ QFile f(cacheFilesPath + QDir::separator() + QLatin1String("indexdb40.dict.") + pname);
+ if (!f.exists()) {
+ QString doc;
+ QSet<QString> documentSet;
+ QMap<QString, QString>::ConstIterator it = titleMap.constBegin();
+ for (; it != titleMap.constEnd(); ++it) {
+ doc = HelpDialog::removeAnchorFromLink(it.key());
+ if (!doc.isEmpty())
+ documentSet.insert(doc);
+ }
+ loadIndexFile();
+ for ( QStringList::Iterator it = keywordDocuments.begin(); it != keywordDocuments.end(); ++it ) {
+ if (!(*it).isEmpty())
+ documentSet.insert(*it);
+ }
+ fullTextIndex->setDocList( documentSet.toList() );
+
+ help->statusBar()->clearMessage();
+ setCursor(Qt::WaitCursor);
+ ui.labelPrepare->setText(tr("Indexing files..."));
+ ui.progressPrepare->setMaximum(100);
+ ui.progressPrepare->reset();
+ ui.progressPrepare->show();
+ ui.framePrepare->show();
+ processEvents();
+ if (fullTextIndex->makeIndex() == -1)
+ return;
+ fullTextIndex->writeDict();
+ ui.progressPrepare->setValue(100);
+ ui.framePrepare->hide();
+ setCursor(Qt::ArrowCursor);
+ showInitDoneMessage();
+ } else {
+ setCursor(Qt::WaitCursor);
+ help->statusBar()->showMessage(tr("Reading dictionary..."));
+ processEvents();
+ fullTextIndex->readDict();
+ help->statusBar()->showMessage(tr("Done"), 3000);
+ setCursor(Qt::ArrowCursor);
+ }
+ keywordDocuments.clear();
+}
+
+void HelpDialog::setIndexingProgress(int prog)
+{
+ ui.progressPrepare->setValue(prog);
+ processEvents();
+}
+
+void HelpDialog::startSearch()
+{
+ QString str = ui.termsEdit->text();
+ str = str.simplified();
+ str = str.replace(QLatin1String("\'"), QLatin1String("\""));
+ str = str.replace(QLatin1String("`"), QLatin1String("\""));
+ QString buf = str;
+ str = str.replace(QLatin1String("-"), QLatin1String(" "));
+ str = str.replace(QRegExp(QLatin1String("\\s[\\S]?\\s")), QLatin1String(" "));
+ terms = str.split(QLatin1Char(' '));
+ QStringList termSeq;
+ QStringList seqWords;
+ QStringList::iterator it = terms.begin();
+ for (; it != terms.end(); ++it) {
+ (*it) = (*it).simplified();
+ (*it) = (*it).toLower();
+ (*it) = (*it).replace(QLatin1String("\""), QLatin1String(""));
+ }
+ if (str.contains(QLatin1Char('\"'))) {
+ if ((str.count(QLatin1Char('\"')))%2 == 0) {
+ int beg = 0;
+ int end = 0;
+ QString s;
+ beg = str.indexOf(QLatin1Char('\"'), beg);
+ while (beg != -1) {
+ beg++;
+ end = str.indexOf(QLatin1Char('\"'), beg);
+ s = str.mid(beg, end - beg);
+ s = s.toLower();
+ s = s.simplified();
+ if (s.contains(QLatin1Char('*'))) {
+ QMessageBox::warning(this, tr("Full Text Search"),
+ tr("Using a wildcard within phrases is not allowed."));
+ return;
+ }
+ seqWords += s.split(QLatin1Char(' '));
+ termSeq << s;
+ beg = str.indexOf(QLatin1Char('\"'), end + 1);
+ }
+ } else {
+ QMessageBox::warning(this, tr("Full Text Search"),
+ tr("The closing quotation mark is missing."));
+ return;
+ }
+ }
+ setCursor(Qt::WaitCursor);
+ foundDocs.clear();
+ foundDocs = fullTextIndex->query(terms, termSeq, seqWords);
+ QString msg = tr("%n document(s) found.", "", foundDocs.count());
+ help->statusBar()->showMessage(tr(msg.toUtf8()), 3000);
+ ui.resultBox->clear();
+ for (it = foundDocs.begin(); it != foundDocs.end(); ++it)
+ ui.resultBox->addItem(fullTextIndex->getDocumentTitle(*it));
+
+ terms.clear();
+ bool isPhrase = false;
+ QString s;
+ for (int i = 0; i < (int)buf.length(); ++i) {
+ if (buf[i] == QLatin1Char('\"')) {
+ isPhrase = !isPhrase;
+ s = s.simplified();
+ if (!s.isEmpty())
+ terms << s;
+ s = QLatin1String("");
+ } else if (buf[i] == QLatin1Char(' ') && !isPhrase) {
+ s = s.simplified();
+ if (!s.isEmpty())
+ terms << s;
+ s = QLatin1String("");
+ } else
+ s += buf[i];
+ }
+ if (!s.isEmpty())
+ terms << s;
+
+ setCursor(Qt::ArrowCursor);
+}
+
+void HelpDialog::on_helpButton_clicked()
+{
+ emit showLink(MainWindow::urlifyFileName(
+ Config::configuration()->assistantDocPath() +
+ QLatin1String("/assistant-manual.html#full-text-searching")));
+}
+
+void HelpDialog::on_resultBox_itemActivated(QListWidgetItem *item)
+{
+ showResultPage(item);
+}
+
+void HelpDialog::showResultPage(QListWidgetItem *item)
+{
+ if (item)
+ emit showSearchLink(foundDocs[ui.resultBox->row(item)], terms);
+}
+
+void HelpDialog::showIndexItemMenu(const QPoint &pos)
+{
+ QListView *listView = qobject_cast<QListView*>(sender());
+ if (!listView)
+ return;
+
+ QModelIndex idx = listView->indexAt(pos);
+ if (!idx.isValid())
+ return;
+
+ QAction *action = itemPopup->exec(listView->viewport()->mapToGlobal(pos));
+ if (action == actionOpenCurrentTab) {
+ showTopic();
+ } else if (action) {
+ HelpWindow *hw = help->browsers()->currentBrowser();
+ QString itemName = idx.data().toString();
+ ui.editIndex->setText(itemName);
+ QStringList links = indexModel->links(idx.row());
+ if (links.count() == 1) {
+ if (action == actionOpenLinkInNewWindow)
+ hw->openLinkInNewWindow(links.first());
+ else
+ hw->openLinkInNewPage(links.first());
+ } else {
+ QStringList::Iterator it = links.begin();
+ QStringList linkList;
+ QStringList linkNames;
+ for (; it != links.end(); ++it) {
+ linkList << *it;
+ linkNames << titleOfLink(*it);
+ }
+ QString link = TopicChooser::getLink(this, linkNames, linkList, itemName);
+ if (!link.isEmpty()) {
+ if (action == actionOpenLinkInNewWindow)
+ hw->openLinkInNewWindow(link);
+ else
+ hw->openLinkInNewPage(link);
+ }
+ }
+ }
+}
+
+void HelpDialog::showListItemMenu(const QPoint &pos)
+{
+ QListWidget *listWidget = qobject_cast<QListWidget*>(sender());
+ if (!listWidget)
+ return;
+ QListWidgetItem *item = listWidget->itemAt(pos);
+ if (!item)
+ return;
+
+ QAction *action = itemPopup->exec(listWidget->viewport()->mapToGlobal(pos));
+ if (action == actionOpenCurrentTab) {
+ showResultPage(item);
+ } else if (action) {
+ HelpWindow *hw = help->browsers()->currentBrowser();
+ QString link = foundDocs[ui.resultBox->row(item)];
+ if (action == actionOpenLinkInNewWindow)
+ hw->openLinkInNewWindow(link);
+ else
+ hw->openLinkInNewPage(link);
+ }
+}
+
+void HelpDialog::showTreeItemMenu(const QPoint &pos)
+{
+ QTreeWidget *treeWidget = qobject_cast<QTreeWidget*>(sender());
+
+ if (!treeWidget)
+ return;
+
+ QTreeWidgetItem *item = treeWidget->itemAt(pos);
+
+ if (!item)
+ return;
+
+ QAction *action = itemPopup->exec(treeWidget->viewport()->mapToGlobal(pos));
+ if (action == actionOpenCurrentTab) {
+ if (ui.tabWidget->currentWidget()->objectName() == QLatin1String("contentPage"))
+ showContentsTopic();
+ else
+ showBookmarkTopic();
+ } else if (action) {
+ QTreeWidgetItem *i = (QTreeWidgetItem*)item;
+ if (action == actionOpenLinkInNewWindow)
+ help->browsers()->currentBrowser()->openLinkInNewWindow(i->data(0, LinkRole).toString());
+ else
+ help->browsers()->currentBrowser()->openLinkInNewPage(i->data(0, LinkRole).toString());
+ }
+}
+
+void HelpDialog::on_termsEdit_returnPressed()
+{
+ startSearch();
+}
+
+void HelpDialog::updateSearchButton(const QString &txt)
+{
+ ui.searchButton->setDisabled(txt.isEmpty());
+}
+
+void HelpDialog::on_searchButton_clicked()
+{
+ startSearch();
+}
+
+QString HelpDialog::removeAnchorFromLink(const QString &link)
+{
+ int i = link.length();
+ int j = link.lastIndexOf(QLatin1Char('/'));
+ int l = link.lastIndexOf(QDir::separator());
+ if (l > j)
+ j = l;
+ if (j > -1) {
+ QString fileName = link.mid(j+1);
+ int k = fileName.lastIndexOf(QLatin1Char('#'));
+ if (k > -1)
+ i = j + k + 1;
+ }
+ return link.left(i);
+}
+
+QT_END_NAMESPACE