diff options
21 files changed, 786 insertions, 598 deletions
diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index 67d2e33495..e2fa72999b 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -69,10 +69,13 @@ using namespace Core; using namespace Core::Internal; using namespace Utils; +static CorePlugin *m_instance = nullptr; + CorePlugin::CorePlugin() { qRegisterMetaType<Id>(); qRegisterMetaType<Core::Search::TextPosition>(); + m_instance = this; } CorePlugin::~CorePlugin() @@ -89,6 +92,11 @@ CorePlugin::~CorePlugin() setCreatorTheme(nullptr); } +CorePlugin *CorePlugin::instance() +{ + return m_instance; +} + struct CoreArguments { QColor overrideColor; Id themeId; @@ -226,7 +234,6 @@ void CorePlugin::extensionsInitialized() bool CorePlugin::delayedInitialize() { - HelpManager::setupHelpManager(); m_locator->delayedInitialize(); IWizardFactory::allWizardFactories(); // scan for all wizard factories return true; @@ -290,6 +297,5 @@ ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() { Find::aboutToShutdown(); m_mainWindow->aboutToShutdown(); - HelpManager::aboutToShutdown(); return SynchronousShutdown; } diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index b427bdd4d1..cc0d8c75d2 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -53,6 +53,8 @@ public: CorePlugin(); ~CorePlugin() override; + static CorePlugin *instance(); + bool initialize(const QStringList &arguments, QString *errorMessage = nullptr) override; void extensionsInitialized() override; bool delayedInitialize() override; diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro index 53b3943476..b722987beb 100644 --- a/src/plugins/coreplugin/coreplugin.pro +++ b/src/plugins/coreplugin/coreplugin.pro @@ -5,8 +5,6 @@ QT += \ qml \ sql -qtHaveModule(help): QT += help - # embedding build time information prevents repeatedly binary exact versions from same source code isEmpty(QTC_SHOW_BUILD_DATE): QTC_SHOW_BUILD_DATE = $$(QTC_SHOW_BUILD_DATE) !isEmpty(QTC_SHOW_BUILD_DATE): DEFINES += QTC_SHOW_BUILD_DATE @@ -195,6 +193,7 @@ HEADERS += corejsextensions.h \ designmode.h \ editortoolbar.h \ helpmanager.h \ + helpmanager_implementation.h \ outputpanemanager.h \ navigationsubwidget.h \ sidebarwidget.h \ diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index c19b5d6845..885aeba1e3 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -9,7 +9,7 @@ Project { QtcPlugin { Depends { name: "Qt" - submodules: ["widgets", "xml", "network", "qml", "sql", "help", "printsupport"] + submodules: ["widgets", "xml", "network", "qml", "sql", "printsupport"] } Depends { @@ -86,6 +86,7 @@ Project { "generatedfile.h", "helpmanager.cpp", "helpmanager.h", + "helpmanager_implementation.h", "icontext.cpp", "icontext.h", "icore.cpp", diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp index 51a2c426d7..a27b1499b3 100644 --- a/src/plugins/coreplugin/helpmanager.cpp +++ b/src/plugins/coreplugin/helpmanager.cpp @@ -23,526 +23,82 @@ ** ****************************************************************************/ -#include "helpmanager.h" +#include "helpmanager_implementation.h" -#include <coreplugin/icore.h> -#include <coreplugin/progressmanager/progressmanager.h> -#include <utils/algorithm.h> -#include <utils/filesystemwatcher.h> +#include "coreplugin.h" + +#include <extensionsystem/pluginspec.h> #include <utils/qtcassert.h> -#include <utils/runextensions.h> -#include <QDateTime> -#include <QDebug> -#include <QDir> -#include <QFileInfo> -#include <QFutureWatcher> -#include <QStringList> #include <QUrl> -#ifdef QT_HELP_LIB - -#include <QHelpEngineCore> - -#include <QMutexLocker> -#include <QSqlDatabase> -#include <QSqlDriver> -#include <QSqlError> -#include <QSqlQuery> - -static const char kUserDocumentationKey[] = "Help/UserDocumentation"; -static const char kUpdateDocumentationTask[] = "UpdateDocumentationTask"; - namespace Core { +namespace HelpManager { -struct HelpManagerPrivate -{ - HelpManagerPrivate() = default; - ~HelpManagerPrivate(); - - const QStringList documentationFromInstaller(); - void readSettings(); - void writeSettings(); - void cleanUpDocumentation(); - - bool m_needsSetup = true; - QHelpEngineCore *m_helpEngine = nullptr; - Utils::FileSystemWatcher *m_collectionWatcher = nullptr; - - // data for delayed initialization - QSet<QString> m_filesToRegister; - QSet<QString> m_nameSpacesToUnregister; - QHash<QString, QVariant> m_customValues; +// makes sure that plugins can connect to HelpManager signals even if the Help plugin is not loaded +Q_GLOBAL_STATIC(Signals, m_signals) - QSet<QString> m_userRegisteredFiles; +static Implementation *m_instance = nullptr; - QMutex m_helpengineMutex; - QFuture<bool> m_registerFuture; -}; - -static HelpManager *m_instance = nullptr; -static HelpManagerPrivate *d = nullptr; - -static const char linksForKeyQuery[] = "SELECT d.Title, f.Name, e.Name, " - "d.Name, a.Anchor FROM IndexTable a, FileNameTable d, FolderTable e, " - "NamespaceTable f WHERE a.FileId=d.FileId AND d.FolderId=e.Id AND " - "a.NamespaceId=f.Id AND a.Name='%1'"; - -// -- DbCleaner - -struct DbCleaner +static bool checkInstance() { - DbCleaner(const QString &dbName) : name(dbName) {} - ~DbCleaner() { QSqlDatabase::removeDatabase(name); } - QString name; -}; - -// -- HelpManager - -HelpManager::HelpManager(QObject *parent) : - QObject(parent) -{ - QTC_CHECK(!m_instance); - m_instance = this; - d = new HelpManagerPrivate; -} - -HelpManager::~HelpManager() -{ - delete d; - m_instance = nullptr; -} - -HelpManager *HelpManager::instance() -{ - Q_ASSERT(m_instance); - return m_instance; + auto plugin = Internal::CorePlugin::instance(); + // HelpManager API can only be used after the actual implementation has been created by the + // Help plugin, so check that the plugins have all been created. That is the case + // when the Core plugin is initialized. + QTC_CHECK(plugin && plugin->pluginSpec() + && plugin->pluginSpec()->state() >= ExtensionSystem::PluginSpec::Initialized); + return m_instance != nullptr; } -QString HelpManager::collectionFilePath() +Signals *Signals::instance() { - return QDir::cleanPath(ICore::userResourcePath() - + QLatin1String("/helpcollection.qhc")); + return m_signals; } -void HelpManager::registerDocumentation(const QStringList &files) +Implementation::Implementation() { - if (d->m_needsSetup) { - for (const QString &filePath : files) - d->m_filesToRegister.insert(filePath); - return; - } - - QFuture<bool> future = Utils::runAsync(&HelpManager::registerDocumentationNow, files); - Utils::onResultReady(future, m_instance, [](bool docsChanged){ - if (docsChanged) { - d->m_helpEngine->setupData(); - emit m_instance->documentationChanged(); - } - }); - ProgressManager::addTask(future, tr("Update Documentation"), - kUpdateDocumentationTask); -} - -void HelpManager::registerDocumentationNow(QFutureInterface<bool> &futureInterface, - const QStringList &files) -{ - QMutexLocker locker(&d->m_helpengineMutex); - - futureInterface.setProgressRange(0, files.count()); - futureInterface.setProgressValue(0); - - QHelpEngineCore helpEngine(collectionFilePath()); - helpEngine.setupData(); - bool docsChanged = false; - QStringList nameSpaces = helpEngine.registeredDocumentations(); - for (const QString &file : files) { - if (futureInterface.isCanceled()) - break; - futureInterface.setProgressValue(futureInterface.progressValue() + 1); - const QString &nameSpace = helpEngine.namespaceName(file); - if (nameSpace.isEmpty()) - continue; - if (!nameSpaces.contains(nameSpace)) { - if (helpEngine.registerDocumentation(file)) { - nameSpaces.append(nameSpace); - docsChanged = true; - } else { - qWarning() << "Error registering namespace '" << nameSpace - << "' from file '" << file << "':" << helpEngine.error(); - } - } else { - const QLatin1String key("CreationDate"); - const QString &newDate = helpEngine.metaData(file, key).toString(); - const QString &oldDate = helpEngine.metaData( - helpEngine.documentationFileName(nameSpace), key).toString(); - if (QDateTime::fromString(newDate, Qt::ISODate) - > QDateTime::fromString(oldDate, Qt::ISODate)) { - if (helpEngine.unregisterDocumentation(nameSpace)) { - docsChanged = true; - helpEngine.registerDocumentation(file); - } - } - } - } - futureInterface.reportResult(docsChanged); -} - -void HelpManager::unregisterDocumentation(const QStringList &nameSpaces) -{ - if (d->m_needsSetup) { - for (const QString &name : nameSpaces) - d->m_nameSpacesToUnregister.insert(name); - return; - } - - QMutexLocker locker(&d->m_helpengineMutex); - bool docsChanged = false; - for (const QString &nameSpace : nameSpaces) { - const QString filePath = d->m_helpEngine->documentationFileName(nameSpace); - if (d->m_helpEngine->unregisterDocumentation(nameSpace)) { - docsChanged = true; - d->m_userRegisteredFiles.remove(filePath); - } else { - qWarning() << "Error unregistering namespace '" << nameSpace - << "' from file '" << filePath - << "': " << d->m_helpEngine->error(); - } - } - locker.unlock(); - if (docsChanged) - emit m_instance->documentationChanged(); -} - -void HelpManager::registerUserDocumentation(const QStringList &filePaths) -{ - for (const QString &filePath : filePaths) - d->m_userRegisteredFiles.insert(filePath); - registerDocumentation(filePaths); -} - -QSet<QString> HelpManager::userDocumentationPaths() -{ - return d->m_userRegisteredFiles; + QTC_CHECK(!m_instance); + m_instance = this; } -static QUrl buildQUrl(const QString &ns, const QString &folder, - const QString &relFileName, const QString &anchor) +Implementation::~Implementation() { - QUrl url; - url.setScheme(QLatin1String("qthelp")); - url.setAuthority(ns); - url.setPath(QLatin1Char('/') + folder + QLatin1Char('/') + relFileName); - url.setFragment(anchor); - return url; + m_instance = nullptr; } -// This should go into Qt 4.8 once we start using it for Qt Creator -QMap<QString, QUrl> HelpManager::linksForKeyword(const QString &key) +void registerDocumentation(const QStringList &files) { - QMap<QString, QUrl> links; - QTC_ASSERT(!d->m_needsSetup, return links); - - const QLatin1String sqlite("QSQLITE"); - const QLatin1String name("HelpManager::linksForKeyword"); - - DbCleaner cleaner(name); - QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name); - if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) { - const QStringList ®isteredDocs = d->m_helpEngine->registeredDocumentations(); - for (const QString &nameSpace : registeredDocs) { - db.setDatabaseName(d->m_helpEngine->documentationFileName(nameSpace)); - if (db.open()) { - QSqlQuery query = QSqlQuery(db); - query.setForwardOnly(true); - query.exec(QString::fromLatin1(linksForKeyQuery).arg(key)); - while (query.next()) { - QString title = query.value(0).toString(); - if (title.isEmpty()) // generate a title + corresponding path - title = key + QLatin1String(" : ") + query.value(3).toString(); - links.insertMulti(title, buildQUrl(query.value(1).toString(), - query.value(2).toString(), query.value(3).toString(), - query.value(4).toString())); - } - } - } - } - return links; + if (checkInstance()) + m_instance->registerDocumentation(files); } -QMap<QString, QUrl> HelpManager::linksForIdentifier(const QString &id) +void unregisterDocumentation(const QStringList &nameSpaces) { - QMap<QString, QUrl> empty; - QTC_ASSERT(!d->m_needsSetup, return empty); - return d->m_helpEngine->linksForIdentifier(id); + if (checkInstance()) + m_instance->unregisterDocumentation(nameSpaces); } -QUrl HelpManager::findFile(const QUrl &url) +QMap<QString, QUrl> linksForIdentifier(const QString &id) { - QTC_ASSERT(!d->m_needsSetup, return QUrl()); - return d->m_helpEngine->findFile(url); + return checkInstance() ? m_instance->linksForIdentifier(id) : QMap<QString, QUrl>(); } -QByteArray HelpManager::fileData(const QUrl &url) +QByteArray fileData(const QUrl &url) { - QTC_ASSERT(!d->m_needsSetup, return QByteArray()); - return d->m_helpEngine->fileData(url); + return checkInstance() ? m_instance->fileData(url) : QByteArray(); } -void HelpManager::handleHelpRequest(const QUrl &url, HelpManager::HelpViewerLocation location) +void handleHelpRequest(const QUrl &url, HelpManager::HelpViewerLocation location) { - emit m_instance->helpRequested(url, location); + if (checkInstance()) + m_instance->handleHelpRequest(url, location); } -void HelpManager::handleHelpRequest(const QString &url, HelpViewerLocation location) +void handleHelpRequest(const QString &url, HelpViewerLocation location) { handleHelpRequest(QUrl(url), location); } -QStringList HelpManager::registeredNamespaces() -{ - QTC_ASSERT(!d->m_needsSetup, return QStringList()); - return d->m_helpEngine->registeredDocumentations(); -} - -QString HelpManager::namespaceFromFile(const QString &file) -{ - QTC_ASSERT(!d->m_needsSetup, return QString()); - return d->m_helpEngine->namespaceName(file); -} - -QString HelpManager::fileFromNamespace(const QString &nameSpace) -{ - QTC_ASSERT(!d->m_needsSetup, return QString()); - return d->m_helpEngine->documentationFileName(nameSpace); -} - -void HelpManager::setCustomValue(const QString &key, const QVariant &value) -{ - if (d->m_needsSetup) { - d->m_customValues.insert(key, value); - return; - } - if (d->m_helpEngine->setCustomValue(key, value)) - emit m_instance->collectionFileChanged(); -} - -QVariant HelpManager::customValue(const QString &key, const QVariant &value) -{ - QTC_ASSERT(!d->m_needsSetup, return QVariant()); - return d->m_helpEngine->customValue(key, value); -} - -HelpManager::Filters HelpManager::filters() -{ - QTC_ASSERT(!d->m_needsSetup, return Filters()); - - Filters filters; - const QStringList &customFilters = d->m_helpEngine->customFilters(); - for (const QString &filter : customFilters) - filters.insert(filter, d->m_helpEngine->filterAttributes(filter)); - return filters; -} - -HelpManager::Filters HelpManager::fixedFilters() -{ - Filters fixedFilters; - QTC_ASSERT(!d->m_needsSetup, return fixedFilters); - - const QLatin1String sqlite("QSQLITE"); - const QLatin1String name("HelpManager::fixedCustomFilters"); - - DbCleaner cleaner(name); - QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name); - if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) { - const QStringList ®isteredDocs = d->m_helpEngine->registeredDocumentations(); - for (const QString &nameSpace : registeredDocs) { - db.setDatabaseName(d->m_helpEngine->documentationFileName(nameSpace)); - if (db.open()) { - QSqlQuery query = QSqlQuery(db); - query.setForwardOnly(true); - query.exec(QLatin1String("SELECT Name FROM FilterNameTable")); - while (query.next()) { - const QString &filter = query.value(0).toString(); - fixedFilters.insert(filter, d->m_helpEngine->filterAttributes(filter)); - } - } - } - } - return fixedFilters; -} - -HelpManager::Filters HelpManager::userDefinedFilters() -{ - QTC_ASSERT(!d->m_needsSetup, return Filters()); - - Filters all = filters(); - const Filters &fixed = fixedFilters(); - for (Filters::const_iterator it = fixed.constBegin(); it != fixed.constEnd(); ++it) - all.remove(it.key()); - return all; -} - -void HelpManager::removeUserDefinedFilter(const QString &filter) -{ - QTC_ASSERT(!d->m_needsSetup, return); - - if (d->m_helpEngine->removeCustomFilter(filter)) - emit m_instance->collectionFileChanged(); -} - -void HelpManager::addUserDefinedFilter(const QString &filter, const QStringList &attr) -{ - QTC_ASSERT(!d->m_needsSetup, return); - - if (d->m_helpEngine->addCustomFilter(filter, attr)) - emit m_instance->collectionFileChanged(); -} - -void HelpManager::aboutToShutdown() -{ - if (d && d->m_registerFuture.isRunning()) { - d->m_registerFuture.cancel(); - d->m_registerFuture.waitForFinished(); - } -} - -// -- private - -void HelpManager::setupHelpManager() -{ - if (!d->m_needsSetup) - return; - d->m_needsSetup = false; - - d->readSettings(); - - // create the help engine - d->m_helpEngine = new QHelpEngineCore(collectionFilePath(), m_instance); - d->m_helpEngine->setupData(); - - for (const QString &filePath : d->documentationFromInstaller()) - d->m_filesToRegister.insert(filePath); - - d->cleanUpDocumentation(); - - if (!d->m_nameSpacesToUnregister.isEmpty()) { - unregisterDocumentation(d->m_nameSpacesToUnregister.toList()); - d->m_nameSpacesToUnregister.clear(); - } - - if (!d->m_filesToRegister.isEmpty()) { - registerDocumentation(d->m_filesToRegister.toList()); - d->m_filesToRegister.clear(); - } - - QHash<QString, QVariant>::const_iterator it; - for (it = d->m_customValues.constBegin(); it != d->m_customValues.constEnd(); ++it) - setCustomValue(it.key(), it.value()); - - emit m_instance->setupFinished(); -} - -void HelpManagerPrivate::cleanUpDocumentation() -{ - // mark documentation for removal for which there is no documentation file anymore - // mark documentation for removal that is neither user registered, nor marked for registration - const QStringList ®isteredDocs = m_helpEngine->registeredDocumentations(); - for (const QString &nameSpace : registeredDocs) { - const QString filePath = m_helpEngine->documentationFileName(nameSpace); - if (!QFileInfo::exists(filePath) - || (!m_filesToRegister.contains(filePath) - && !m_userRegisteredFiles.contains(filePath))) { - m_nameSpacesToUnregister.insert(nameSpace); - } - } -} - -HelpManagerPrivate::~HelpManagerPrivate() -{ - writeSettings(); - delete m_helpEngine; - m_helpEngine = nullptr; -} - -const QStringList HelpManagerPrivate::documentationFromInstaller() -{ - QSettings *installSettings = ICore::settings(); - const QStringList documentationPaths = installSettings->value(QLatin1String("Help/InstalledDocumentation")) - .toStringList(); - QStringList documentationFiles; - for (const QString &path : documentationPaths) { - QFileInfo pathInfo(path); - if (pathInfo.isFile() && pathInfo.isReadable()) { - documentationFiles << pathInfo.absoluteFilePath(); - } else if (pathInfo.isDir()) { - const QFileInfoList files(QDir(path).entryInfoList(QStringList(QLatin1String("*.qch")), - QDir::Files | QDir::Readable)); - for (const QFileInfo &fileInfo : files) - documentationFiles << fileInfo.absoluteFilePath(); - } - } - return documentationFiles; -} - -void HelpManagerPrivate::readSettings() -{ - m_userRegisteredFiles = ICore::settings()->value(QLatin1String(kUserDocumentationKey)) - .toStringList().toSet(); -} - -void HelpManagerPrivate::writeSettings() -{ - const QStringList list = m_userRegisteredFiles.toList(); - ICore::settings()->setValue(QLatin1String(kUserDocumentationKey), list); -} - -} // Core - -#else // QT_HELP_LIB - -namespace Core { - -HelpManager *HelpManager::instance() { return nullptr; } - -QString HelpManager::collectionFilePath() { return QString(); } - -void HelpManager::registerDocumentation(const QStringList &) {} -void HelpManager::registerDocumentationNow(QFutureInterface<bool> &, const QStringList &) {} -void HelpManager::unregisterDocumentation(const QStringList &) {} - -void HelpManager::registerUserDocumentation(const QStringList &) {} -QSet<QString> HelpManager::userDocumentationPaths() { return {}; } - -QMap<QString, QUrl> HelpManager::linksForKeyword(const QString &) { return {}; } -QMap<QString, QUrl> HelpManager::linksForIdentifier(const QString &) { return {}; } - -QUrl HelpManager::findFile(const QUrl &) { return QUrl();} -QByteArray HelpManager::fileData(const QUrl &) { return QByteArray();} - -QStringList HelpManager::registeredNamespaces() { return {}; } -QString HelpManager::namespaceFromFile(const QString &) { return QString(); } -QString HelpManager::fileFromNamespace(const QString &) { return QString(); } - -void HelpManager::setCustomValue(const QString &, const QVariant &) {} -QVariant HelpManager::customValue(const QString &, const QVariant &) { return QVariant(); } - -HelpManager::Filters filters() { return {}; } -HelpManager::Filters fixedFilters() { return {}; } - -HelpManager::Filters userDefinedFilters() { return {}; } - -void HelpManager::removeUserDefinedFilter(const QString &) {} -void HelpManager::addUserDefinedFilter(const QString &, const QStringList &) {} - -void HelpManager::handleHelpRequest(const QUrl &, HelpManager::HelpViewerLocation) {} -void HelpManager::handleHelpRequest(const QString &, HelpViewerLocation) {} - -HelpManager::HelpManager(QObject *) {} -HelpManager::~HelpManager() = default; -void HelpManager::aboutToShutdown() {} -void HelpManager::setupHelpManager() {} - -} // namespace Core - -#endif // QT_HELP_LIB +} // HelpManager +} // Core diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h index f3e2db5ea3..60bb412752 100644 --- a/src/plugins/coreplugin/helpmanager.h +++ b/src/plugins/coreplugin/helpmanager.h @@ -28,85 +28,44 @@ #include "core_global.h" #include <QObject> -#include <QStringList> -#include <QVariant> #include <QMap> -#include <QHash> -#include <QFutureInterface> -QT_FORWARD_DECLARE_CLASS(QUrl) +QT_BEGIN_NAMESPACE +class QStringList; +class QUrl; +QT_END_NAMESPACE namespace Core { -struct HelpManagerPrivate; -namespace Internal { -class CorePlugin; -class MainWindow; -} +namespace HelpManager { -class CORE_EXPORT HelpManager : public QObject +class CORE_EXPORT Signals : public QObject { Q_OBJECT public: - enum HelpViewerLocation { - SideBySideIfPossible = 0, - SideBySideAlways = 1, - HelpModeAlways = 2, - ExternalHelpAlways = 3 - }; - - using Filters = QHash<QString, QStringList>; - - static HelpManager *instance(); - static QString collectionFilePath(); - - static void registerDocumentation(const QStringList &fileNames); - static void unregisterDocumentation(const QStringList &nameSpaces); - - static void registerUserDocumentation(const QStringList &filePaths); - static QSet<QString> userDocumentationPaths(); - - static QUrl findFile(const QUrl &url); - static QByteArray fileData(const QUrl &url); - - - static QMap<QString, QUrl> linksForKeyword(const QString &key); - static QMap<QString, QUrl> linksForIdentifier(const QString &id); - static QStringList registeredNamespaces(); - static QString namespaceFromFile(const QString &file); - static QString fileFromNamespace(const QString &nameSpace); - static void setCustomValue(const QString &key, const QVariant &value); - static QVariant customValue(const QString &key, const QVariant &value = QVariant()); - static Filters filters(); - static Filters fixedFilters(); - static Filters userDefinedFilters(); - static void removeUserDefinedFilter(const QString &filter); - static void addUserDefinedFilter(const QString &filter, const QStringList &attr); - - static void aboutToShutdown(); - -public slots: - static void handleHelpRequest(const QUrl &url, - Core::HelpManager::HelpViewerLocation location = HelpModeAlways); - static void handleHelpRequest(const QString &url, - Core::HelpManager::HelpViewerLocation location = HelpModeAlways); + static Signals *instance(); signals: void setupFinished(); void documentationChanged(); - void collectionFileChanged(); - void helpRequested(const QUrl &url, Core::HelpManager::HelpViewerLocation location); - -private: - explicit HelpManager(QObject *parent = nullptr); - ~HelpManager() override; +}; - static void setupHelpManager(); - static void registerDocumentationNow(QFutureInterface<bool> &futureInterface, - const QStringList &fileNames); - friend class Core::Internal::CorePlugin; // setupHelpManager - friend class Core::Internal::MainWindow; // constructor/destructor +enum HelpViewerLocation { + SideBySideIfPossible = 0, + SideBySideAlways = 1, + HelpModeAlways = 2, + ExternalHelpAlways = 3 }; -} // Core +CORE_EXPORT void registerDocumentation(const QStringList &fileNames); +CORE_EXPORT void unregisterDocumentation(const QStringList &nameSpaces); + +CORE_EXPORT QMap<QString, QUrl> linksForIdentifier(const QString &id); +CORE_EXPORT QByteArray fileData(const QUrl &url); + +CORE_EXPORT void handleHelpRequest(const QUrl &url, HelpViewerLocation location = HelpModeAlways); +CORE_EXPORT void handleHelpRequest(const QString &url, HelpViewerLocation location = HelpModeAlways); + +} // HelpManager +} // Core diff --git a/src/plugins/coreplugin/helpmanager_implementation.h b/src/plugins/coreplugin/helpmanager_implementation.h new file mode 100644 index 0000000000..76e7a1a807 --- /dev/null +++ b/src/plugins/coreplugin/helpmanager_implementation.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "helpmanager.h" + +namespace Core { + +namespace HelpManager { + +class CORE_EXPORT Implementation +{ +protected: + Implementation(); + virtual ~Implementation(); + +public: + virtual void registerDocumentation(const QStringList &fileNames) = 0; + virtual void unregisterDocumentation(const QStringList &nameSpaces) = 0; + virtual QMap<QString, QUrl> linksForIdentifier(const QString &id) = 0; + virtual QByteArray fileData(const QUrl &url) = 0; + virtual void handleHelpRequest(const QUrl &url, HelpViewerLocation location = HelpModeAlways) = 0; +}; + +} // HelpManager +} // Core diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index 68af2e6f89..6b86a9ca22 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -31,7 +31,6 @@ #include "fancytabwidget.h" #include "documentmanager.h" #include "generalsettings.h" -#include "helpmanager.h" #include "idocumentfactory.h" #include "messagemanager.h" #include "modemanager.h" @@ -110,7 +109,6 @@ MainWindow::MainWindow() : m_progressManager(new ProgressManagerPrivate), m_jsExpander(new JsExpander), m_vcsManager(new VcsManager), - m_helpManager(new HelpManager), m_modeStack(new FancyTabWidget(this)), m_generalSettings(new GeneralSettings), m_systemSettings(new SystemSettings), @@ -281,8 +279,6 @@ MainWindow::~MainWindow() delete m_modeManager; m_modeManager = nullptr; - delete m_helpManager; - m_helpManager = nullptr; delete m_jsExpander; m_jsExpander = nullptr; } diff --git a/src/plugins/coreplugin/mainwindow.h b/src/plugins/coreplugin/mainwindow.h index 7f49a772a4..8f9939ee8f 100644 --- a/src/plugins/coreplugin/mainwindow.h +++ b/src/plugins/coreplugin/mainwindow.h @@ -45,7 +45,6 @@ namespace Core { class EditorManager; class ExternalToolManager; -class HelpManager; class IDocument; class JsExpander; class MessageManager; @@ -154,7 +153,6 @@ private: JsExpander *m_jsExpander = nullptr; VcsManager *m_vcsManager = nullptr; ModeManager *m_modeManager = nullptr; - HelpManager *m_helpManager = nullptr; FancyTabWidget *m_modeStack = nullptr; NavigationWidget *m_leftNavigationWidget = nullptr; NavigationWidget *m_rightNavigationWidget = nullptr; diff --git a/src/plugins/designer/formeditorw.cpp b/src/plugins/designer/formeditorw.cpp index b3e64ec2b0..4d091d6d27 100644 --- a/src/plugins/designer/formeditorw.cpp +++ b/src/plugins/designer/formeditorw.cpp @@ -365,8 +365,8 @@ void FormEditorData::fullInit() m_formeditor->setIntegration(m_integration); // Connect Qt Designer help request to HelpManager. QObject::connect(m_integration, &QtCreatorIntegration::creatorHelpRequested, - HelpManager::instance(), - [](const QUrl &url) { HelpManager::instance()->handleHelpRequest(url, HelpManager::HelpModeAlways); }); + HelpManager::Signals::instance(), + [](const QUrl &url) { HelpManager::handleHelpRequest(url, HelpManager::HelpModeAlways); }); /** * This will initialize our TabOrder, Signals and slots and Buddy editors. diff --git a/src/plugins/help/docsettingspage.cpp b/src/plugins/help/docsettingspage.cpp index 93fbdb2621..6ca884ceb2 100644 --- a/src/plugins/help/docsettingspage.cpp +++ b/src/plugins/help/docsettingspage.cpp @@ -25,6 +25,7 @@ #include "docsettingspage.h" #include "helpconstants.h" +#include "helpmanager.h" #include <coreplugin/helpmanager.h> #include <utils/algorithm.h> @@ -42,8 +43,6 @@ #include <algorithm> -using namespace Core; - namespace Help { namespace Internal { @@ -245,7 +244,7 @@ void DocSettingsPage::addDocumentation() void DocSettingsPage::apply() { - HelpManager::unregisterDocumentation(m_filesToUnregister.keys()); + Core::HelpManager::unregisterDocumentation(m_filesToUnregister.keys()); QStringList files; auto it = m_filesToRegisterUserManaged.constBegin(); while (it != m_filesToRegisterUserManaged.constEnd()) { diff --git a/src/plugins/help/filtersettingspage.cpp b/src/plugins/help/filtersettingspage.cpp index adbab36c6e..83d0d365f7 100644 --- a/src/plugins/help/filtersettingspage.cpp +++ b/src/plugins/help/filtersettingspage.cpp @@ -26,6 +26,7 @@ #include "filtersettingspage.h" #include "helpconstants.h" +#include "helpmanager.h" #include <filternamedialog.h> @@ -36,7 +37,6 @@ #include <QMessageBox> using namespace Help::Internal; -using namespace Core; FilterSettingsPage::FilterSettingsPage() { @@ -61,8 +61,10 @@ QWidget *FilterSettingsPage::widget() this, &FilterSettingsPage::addFilter); connect(m_ui.filterRemoveButton, &QPushButton::clicked, this, &FilterSettingsPage::removeFilter); - connect(HelpManager::instance(), &HelpManager::documentationChanged, - this, &FilterSettingsPage::updateFilterPage); + connect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::documentationChanged, + this, + &FilterSettingsPage::updateFilterPage); } return m_widget; } @@ -219,8 +221,10 @@ void FilterSettingsPage::apply() void FilterSettingsPage::finish() { - disconnect(HelpManager::instance(), &HelpManager::documentationChanged, - this, &FilterSettingsPage::updateFilterPage); + disconnect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::documentationChanged, + this, + &FilterSettingsPage::updateFilterPage); delete m_widget; } diff --git a/src/plugins/help/help.pro b/src/plugins/help/help.pro index ffe71b6f18..a967663078 100644 --- a/src/plugins/help/help.pro +++ b/src/plugins/help/help.pro @@ -17,6 +17,7 @@ HEADERS += \ helpfindsupport.h \ helpindexfilter.h \ localhelpmanager.h \ + helpmanager.h \ helpmode.h \ helpplugin.h \ helpviewer.h \ @@ -39,6 +40,7 @@ SOURCES += \ helpfindsupport.cpp \ helpindexfilter.cpp \ localhelpmanager.cpp \ + helpmanager.cpp \ helpmode.cpp \ helpplugin.cpp \ helpviewer.cpp \ diff --git a/src/plugins/help/help.qbs b/src/plugins/help/help.qbs index 56f5c4fbd0..38eda44aa6 100644 --- a/src/plugins/help/help.qbs +++ b/src/plugins/help/help.qbs @@ -36,6 +36,7 @@ QtcPlugin { "helpconstants.h", "helpfindsupport.cpp", "helpfindsupport.h", "helpindexfilter.cpp", "helpindexfilter.h", + "helpmanager.cpp", "helpmanager.h", "helpmode.cpp", "helpmode.h", "helpplugin.cpp", "helpplugin.h", "helpviewer.cpp", "helpviewer.h", diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp index b662c9be01..568b32a03f 100644 --- a/src/plugins/help/helpindexfilter.cpp +++ b/src/plugins/help/helpindexfilter.cpp @@ -27,6 +27,7 @@ #include "centralwidget.h" #include "helpicons.h" +#include "helpmanager.h" #include "topicchooser.h" #include <coreplugin/icore.h> @@ -54,10 +55,12 @@ HelpIndexFilter::HelpIndexFilter() setShortcutString("?"); m_icon = Utils::Icons::BOOKMARK.icon(); - connect(HelpManager::instance(), &HelpManager::setupFinished, - this, &HelpIndexFilter::invalidateCache); - connect(HelpManager::instance(), &HelpManager::documentationChanged, + connect(Core::HelpManager::Signals::instance(), &Core::HelpManager::Signals::setupFinished, this, &HelpIndexFilter::invalidateCache); + connect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::documentationChanged, + this, + &HelpIndexFilter::invalidateCache); connect(HelpManager::instance(), &HelpManager::collectionFileChanged, this, &HelpIndexFilter::invalidateCache); } diff --git a/src/plugins/help/helpmanager.cpp b/src/plugins/help/helpmanager.cpp new file mode 100644 index 0000000000..34be168bcb --- /dev/null +++ b/src/plugins/help/helpmanager.cpp @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "helpmanager.h" + +#include <coreplugin/icore.h> +#include <coreplugin/progressmanager/progressmanager.h> +#include <utils/algorithm.h> +#include <utils/filesystemwatcher.h> +#include <utils/qtcassert.h> +#include <utils/runextensions.h> + +#include <QDateTime> +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QFutureWatcher> +#include <QStringList> +#include <QUrl> + +#include <QHelpEngineCore> + +#include <QMutexLocker> +#include <QSqlDatabase> +#include <QSqlDriver> +#include <QSqlError> +#include <QSqlQuery> + +using namespace Core; + +static const char kUserDocumentationKey[] = "Help/UserDocumentation"; +static const char kUpdateDocumentationTask[] = "UpdateDocumentationTask"; + +namespace Help { +namespace Internal { + +struct HelpManagerPrivate +{ + HelpManagerPrivate() = default; + ~HelpManagerPrivate(); + + const QStringList documentationFromInstaller(); + void readSettings(); + void writeSettings(); + void cleanUpDocumentation(); + + bool m_needsSetup = true; + QHelpEngineCore *m_helpEngine = nullptr; + Utils::FileSystemWatcher *m_collectionWatcher = nullptr; + + // data for delayed initialization + QSet<QString> m_filesToRegister; + QSet<QString> m_nameSpacesToUnregister; + QHash<QString, QVariant> m_customValues; + + QSet<QString> m_userRegisteredFiles; + + QMutex m_helpengineMutex; + QFuture<bool> m_registerFuture; +}; + +static HelpManager *m_instance = nullptr; +static HelpManagerPrivate *d = nullptr; + +static const char linksForKeyQuery[] = "SELECT d.Title, f.Name, e.Name, " + "d.Name, a.Anchor FROM IndexTable a, FileNameTable d, FolderTable e, " + "NamespaceTable f WHERE a.FileId=d.FileId AND d.FolderId=e.Id AND " + "a.NamespaceId=f.Id AND a.Name='%1'"; + +// -- DbCleaner + +struct DbCleaner +{ + DbCleaner(const QString &dbName) : name(dbName) {} + ~DbCleaner() { QSqlDatabase::removeDatabase(name); } + QString name; +}; + +// -- HelpManager + +HelpManager::HelpManager(QObject *parent) : + QObject(parent) +{ + QTC_CHECK(!m_instance); + m_instance = this; + d = new HelpManagerPrivate; +} + +HelpManager::~HelpManager() +{ + delete d; + m_instance = nullptr; +} + +HelpManager *HelpManager::instance() +{ + Q_ASSERT(m_instance); + return m_instance; +} + +QString HelpManager::collectionFilePath() +{ + return QDir::cleanPath(ICore::userResourcePath() + + QLatin1String("/helpcollection.qhc")); +} + +void HelpManager::registerDocumentation(const QStringList &files) +{ + if (d->m_needsSetup) { + for (const QString &filePath : files) + d->m_filesToRegister.insert(filePath); + return; + } + + QFuture<bool> future = Utils::runAsync(&HelpManager::registerDocumentationNow, files); + Utils::onResultReady(future, this, [](bool docsChanged){ + if (docsChanged) { + d->m_helpEngine->setupData(); + emit Core::HelpManager::Signals::instance()->documentationChanged(); + } + }); + ProgressManager::addTask(future, tr("Update Documentation"), + kUpdateDocumentationTask); +} + +void HelpManager::registerDocumentationNow(QFutureInterface<bool> &futureInterface, + const QStringList &files) +{ + QMutexLocker locker(&d->m_helpengineMutex); + + futureInterface.setProgressRange(0, files.count()); + futureInterface.setProgressValue(0); + + QHelpEngineCore helpEngine(collectionFilePath()); + helpEngine.setupData(); + bool docsChanged = false; + QStringList nameSpaces = helpEngine.registeredDocumentations(); + for (const QString &file : files) { + if (futureInterface.isCanceled()) + break; + futureInterface.setProgressValue(futureInterface.progressValue() + 1); + const QString &nameSpace = helpEngine.namespaceName(file); + if (nameSpace.isEmpty()) + continue; + if (!nameSpaces.contains(nameSpace)) { + if (helpEngine.registerDocumentation(file)) { + nameSpaces.append(nameSpace); + docsChanged = true; + } else { + qWarning() << "Error registering namespace '" << nameSpace + << "' from file '" << file << "':" << helpEngine.error(); + } + } else { + const QLatin1String key("CreationDate"); + const QString &newDate = helpEngine.metaData(file, key).toString(); + const QString &oldDate = helpEngine.metaData( + helpEngine.documentationFileName(nameSpace), key).toString(); + if (QDateTime::fromString(newDate, Qt::ISODate) + > QDateTime::fromString(oldDate, Qt::ISODate)) { + if (helpEngine.unregisterDocumentation(nameSpace)) { + docsChanged = true; + helpEngine.registerDocumentation(file); + } + } + } + } + futureInterface.reportResult(docsChanged); +} + +void HelpManager::unregisterDocumentation(const QStringList &nameSpaces) +{ + if (d->m_needsSetup) { + for (const QString &name : nameSpaces) + d->m_nameSpacesToUnregister.insert(name); + return; + } + + QMutexLocker locker(&d->m_helpengineMutex); + bool docsChanged = false; + for (const QString &nameSpace : nameSpaces) { + const QString filePath = d->m_helpEngine->documentationFileName(nameSpace); + if (d->m_helpEngine->unregisterDocumentation(nameSpace)) { + docsChanged = true; + d->m_userRegisteredFiles.remove(filePath); + } else { + qWarning() << "Error unregistering namespace '" << nameSpace + << "' from file '" << filePath + << "': " << d->m_helpEngine->error(); + } + } + locker.unlock(); + if (docsChanged) + emit Core::HelpManager::Signals::instance()->documentationChanged(); +} + +void HelpManager::registerUserDocumentation(const QStringList &filePaths) +{ + for (const QString &filePath : filePaths) + d->m_userRegisteredFiles.insert(filePath); + m_instance->registerDocumentation(filePaths); +} + +QSet<QString> HelpManager::userDocumentationPaths() +{ + return d->m_userRegisteredFiles; +} + +static QUrl buildQUrl(const QString &ns, const QString &folder, + const QString &relFileName, const QString &anchor) +{ + QUrl url; + url.setScheme(QLatin1String("qthelp")); + url.setAuthority(ns); + url.setPath(QLatin1Char('/') + folder + QLatin1Char('/') + relFileName); + url.setFragment(anchor); + return url; +} + +// This should go into Qt 4.8 once we start using it for Qt Creator +QMap<QString, QUrl> HelpManager::linksForKeyword(const QString &key) +{ + QMap<QString, QUrl> links; + QTC_ASSERT(!d->m_needsSetup, return links); + + const QLatin1String sqlite("QSQLITE"); + const QLatin1String name("HelpManager::linksForKeyword"); + + DbCleaner cleaner(name); + QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name); + if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) { + const QStringList ®isteredDocs = d->m_helpEngine->registeredDocumentations(); + for (const QString &nameSpace : registeredDocs) { + db.setDatabaseName(d->m_helpEngine->documentationFileName(nameSpace)); + if (db.open()) { + QSqlQuery query = QSqlQuery(db); + query.setForwardOnly(true); + query.exec(QString::fromLatin1(linksForKeyQuery).arg(key)); + while (query.next()) { + QString title = query.value(0).toString(); + if (title.isEmpty()) // generate a title + corresponding path + title = key + QLatin1String(" : ") + query.value(3).toString(); + links.insertMulti(title, buildQUrl(query.value(1).toString(), + query.value(2).toString(), query.value(3).toString(), + query.value(4).toString())); + } + } + } + } + return links; +} + +QMap<QString, QUrl> HelpManager::linksForIdentifier(const QString &id) +{ + QMap<QString, QUrl> empty; + QTC_ASSERT(!d->m_needsSetup, return empty); + return d->m_helpEngine->linksForIdentifier(id); +} + +QUrl HelpManager::findFile(const QUrl &url) +{ + QTC_ASSERT(!d->m_needsSetup, return QUrl()); + return d->m_helpEngine->findFile(url); +} + +QByteArray HelpManager::fileData(const QUrl &url) +{ + QTC_ASSERT(!d->m_needsSetup, return QByteArray()); + return d->m_helpEngine->fileData(url); +} + +void HelpManager::handleHelpRequest(const QUrl &url, Core::HelpManager::HelpViewerLocation location) +{ + emit m_instance->helpRequested(url, location); +} + +QStringList HelpManager::registeredNamespaces() +{ + QTC_ASSERT(!d->m_needsSetup, return QStringList()); + return d->m_helpEngine->registeredDocumentations(); +} + +QString HelpManager::namespaceFromFile(const QString &file) +{ + QTC_ASSERT(!d->m_needsSetup, return QString()); + return d->m_helpEngine->namespaceName(file); +} + +QString HelpManager::fileFromNamespace(const QString &nameSpace) +{ + QTC_ASSERT(!d->m_needsSetup, return QString()); + return d->m_helpEngine->documentationFileName(nameSpace); +} + +void HelpManager::setCustomValue(const QString &key, const QVariant &value) +{ + if (d->m_needsSetup) { + d->m_customValues.insert(key, value); + return; + } + if (d->m_helpEngine->setCustomValue(key, value)) + emit m_instance->collectionFileChanged(); +} + +QVariant HelpManager::customValue(const QString &key, const QVariant &value) +{ + QTC_ASSERT(!d->m_needsSetup, return QVariant()); + return d->m_helpEngine->customValue(key, value); +} + +HelpManager::Filters HelpManager::filters() +{ + QTC_ASSERT(!d->m_needsSetup, return Filters()); + + Filters filters; + const QStringList &customFilters = d->m_helpEngine->customFilters(); + for (const QString &filter : customFilters) + filters.insert(filter, d->m_helpEngine->filterAttributes(filter)); + return filters; +} + +HelpManager::Filters HelpManager::fixedFilters() +{ + Filters fixedFilters; + QTC_ASSERT(!d->m_needsSetup, return fixedFilters); + + const QLatin1String sqlite("QSQLITE"); + const QLatin1String name("HelpManager::fixedCustomFilters"); + + DbCleaner cleaner(name); + QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name); + if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) { + const QStringList ®isteredDocs = d->m_helpEngine->registeredDocumentations(); + for (const QString &nameSpace : registeredDocs) { + db.setDatabaseName(d->m_helpEngine->documentationFileName(nameSpace)); + if (db.open()) { + QSqlQuery query = QSqlQuery(db); + query.setForwardOnly(true); + query.exec(QLatin1String("SELECT Name FROM FilterNameTable")); + while (query.next()) { + const QString &filter = query.value(0).toString(); + fixedFilters.insert(filter, d->m_helpEngine->filterAttributes(filter)); + } + } + } + } + return fixedFilters; +} + +HelpManager::Filters HelpManager::userDefinedFilters() +{ + QTC_ASSERT(!d->m_needsSetup, return Filters()); + + Filters all = filters(); + const Filters &fixed = fixedFilters(); + for (Filters::const_iterator it = fixed.constBegin(); it != fixed.constEnd(); ++it) + all.remove(it.key()); + return all; +} + +void HelpManager::removeUserDefinedFilter(const QString &filter) +{ + QTC_ASSERT(!d->m_needsSetup, return); + + if (d->m_helpEngine->removeCustomFilter(filter)) + emit m_instance->collectionFileChanged(); +} + +void HelpManager::addUserDefinedFilter(const QString &filter, const QStringList &attr) +{ + QTC_ASSERT(!d->m_needsSetup, return); + + if (d->m_helpEngine->addCustomFilter(filter, attr)) + emit m_instance->collectionFileChanged(); +} + +void HelpManager::aboutToShutdown() +{ + if (d && d->m_registerFuture.isRunning()) { + d->m_registerFuture.cancel(); + d->m_registerFuture.waitForFinished(); + } +} + +// -- private + +void HelpManager::setupHelpManager() +{ + if (!d->m_needsSetup) + return; + d->m_needsSetup = false; + + d->readSettings(); + + // create the help engine + d->m_helpEngine = new QHelpEngineCore(collectionFilePath(), m_instance); + d->m_helpEngine->setupData(); + + for (const QString &filePath : d->documentationFromInstaller()) + d->m_filesToRegister.insert(filePath); + + d->cleanUpDocumentation(); + + if (!d->m_nameSpacesToUnregister.isEmpty()) { + m_instance->unregisterDocumentation(d->m_nameSpacesToUnregister.toList()); + d->m_nameSpacesToUnregister.clear(); + } + + if (!d->m_filesToRegister.isEmpty()) { + m_instance->registerDocumentation(d->m_filesToRegister.toList()); + d->m_filesToRegister.clear(); + } + + QHash<QString, QVariant>::const_iterator it; + for (it = d->m_customValues.constBegin(); it != d->m_customValues.constEnd(); ++it) + setCustomValue(it.key(), it.value()); + + emit Core::HelpManager::Signals::instance()->setupFinished(); +} + +void HelpManagerPrivate::cleanUpDocumentation() +{ + // mark documentation for removal for which there is no documentation file anymore + // mark documentation for removal that is neither user registered, nor marked for registration + const QStringList ®isteredDocs = m_helpEngine->registeredDocumentations(); + for (const QString &nameSpace : registeredDocs) { + const QString filePath = m_helpEngine->documentationFileName(nameSpace); + if (!QFileInfo::exists(filePath) + || (!m_filesToRegister.contains(filePath) + && !m_userRegisteredFiles.contains(filePath))) { + m_nameSpacesToUnregister.insert(nameSpace); + } + } +} + +HelpManagerPrivate::~HelpManagerPrivate() +{ + writeSettings(); + delete m_helpEngine; + m_helpEngine = nullptr; +} + +const QStringList HelpManagerPrivate::documentationFromInstaller() +{ + QSettings *installSettings = ICore::settings(); + const QStringList documentationPaths = installSettings->value(QLatin1String("Help/InstalledDocumentation")) + .toStringList(); + QStringList documentationFiles; + for (const QString &path : documentationPaths) { + QFileInfo pathInfo(path); + if (pathInfo.isFile() && pathInfo.isReadable()) { + documentationFiles << pathInfo.absoluteFilePath(); + } else if (pathInfo.isDir()) { + const QFileInfoList files(QDir(path).entryInfoList(QStringList(QLatin1String("*.qch")), + QDir::Files | QDir::Readable)); + for (const QFileInfo &fileInfo : files) + documentationFiles << fileInfo.absoluteFilePath(); + } + } + return documentationFiles; +} + +void HelpManagerPrivate::readSettings() +{ + m_userRegisteredFiles = ICore::settings()->value(QLatin1String(kUserDocumentationKey)) + .toStringList().toSet(); +} + +void HelpManagerPrivate::writeSettings() +{ + const QStringList list = m_userRegisteredFiles.toList(); + ICore::settings()->setValue(QLatin1String(kUserDocumentationKey), list); +} + +} // Internal +} // Core diff --git a/src/plugins/help/helpmanager.h b/src/plugins/help/helpmanager.h new file mode 100644 index 0000000000..97b0b4e427 --- /dev/null +++ b/src/plugins/help/helpmanager.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <coreplugin/helpmanager_implementation.h> + +QT_FORWARD_DECLARE_CLASS(QUrl) + +#include <QFutureInterface> +#include <QVariant> + +namespace Help { +namespace Internal { + +class HelpManager : public QObject, public Core::HelpManager::Implementation +{ + Q_OBJECT + +public: + using Filters = QHash<QString, QStringList>; + + explicit HelpManager(QObject *parent = nullptr); + ~HelpManager() override; + + static HelpManager *instance(); + static QString collectionFilePath(); + + void registerDocumentation(const QStringList &fileNames) override; + void unregisterDocumentation(const QStringList &nameSpaces) override; + + static void registerUserDocumentation(const QStringList &filePaths); + static QSet<QString> userDocumentationPaths(); + + static QMap<QString, QUrl> linksForKeyword(const QString &key); + QMap<QString, QUrl> linksForIdentifier(const QString &id) override; + + static QUrl findFile(const QUrl &url); + QByteArray fileData(const QUrl &url) override; + + static QStringList registeredNamespaces(); + static QString namespaceFromFile(const QString &file); + static QString fileFromNamespace(const QString &nameSpace); + + static void setCustomValue(const QString &key, const QVariant &value); + static QVariant customValue(const QString &key, const QVariant &value = QVariant()); + + static Filters filters(); + static Filters fixedFilters(); + + static Filters userDefinedFilters(); + static void removeUserDefinedFilter(const QString &filter); + static void addUserDefinedFilter(const QString &filter, const QStringList &attr); + + static void aboutToShutdown(); + + void handleHelpRequest( + const QUrl &url, + Core::HelpManager::HelpViewerLocation location = Core::HelpManager::HelpModeAlways) override; + + + static void setupHelpManager(); + static void registerDocumentationNow(QFutureInterface<bool> &futureInterface, + const QStringList &fileNames); +signals: + void collectionFileChanged(); + void helpRequested(const QUrl &url, Core::HelpManager::HelpViewerLocation location); +}; + +} // Internal +} // Core diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp index 405d05bebc..2ce03e0789 100644 --- a/src/plugins/help/helpplugin.cpp +++ b/src/plugins/help/helpplugin.cpp @@ -33,6 +33,7 @@ #include "helpconstants.h" #include "helpfindsupport.h" #include "helpindexfilter.h" +#include "helpmanager.h" #include "helpmode.h" #include "helpviewer.h" #include "localhelpmanager.h" @@ -145,7 +146,7 @@ public: HelpViewer *externalHelpViewer(); HelpViewer *helpModeHelpViewer(); HelpWidget *helpWidgetForWindow(QWidget *window); - HelpViewer *viewerForHelpViewerLocation(HelpManager::HelpViewerLocation location); + HelpViewer *viewerForHelpViewerLocation(Core::HelpManager::HelpViewerLocation location); void showInHelpViewer(const QUrl &url, HelpViewer *viewer); void doSetupIfNeeded(); @@ -160,7 +161,7 @@ public: GeneralSettingsPage m_generalSettingsPage; bool m_setupNeeded = true; - LocalHelpManager m_helpManager; + LocalHelpManager m_localHelpManager; OpenPagesManager m_openPagesManager; QString m_contextHelpHighlightId; @@ -173,11 +174,19 @@ public: }; static HelpPluginPrivate *dd = nullptr; +static HelpManager *m_helpManager = nullptr; + +HelpPlugin::HelpPlugin() +{ + m_helpManager = new HelpManager; +} HelpPlugin::~HelpPlugin() { delete dd; dd = nullptr; + delete m_helpManager; + m_helpManager = nullptr; } bool HelpPlugin::initialize(const QStringList &arguments, QString *error) @@ -221,8 +230,10 @@ HelpPluginPrivate::HelpPluginPrivate() connect(&m_filterSettingsPage, &FilterSettingsPage::filtersChanged, this, &HelpPluginPrivate::setupHelpEngineIfNeeded); - connect(HelpManager::instance(), &HelpManager::documentationChanged, - this, &HelpPluginPrivate::setupHelpEngineIfNeeded); + connect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::documentationChanged, + this, + &HelpPluginPrivate::setupHelpEngineIfNeeded); connect(HelpManager::instance(), &HelpManager::collectionFileChanged, this, &HelpPluginPrivate::setupHelpEngineIfNeeded); @@ -319,7 +330,13 @@ void HelpPlugin::extensionsInitialized() QStringList filesToRegister; // we might need to register creators inbuild help filesToRegister.append(ICore::documentationPath() + "/qtcreator.qch"); - HelpManager::registerDocumentation(filesToRegister); + Core::HelpManager::registerDocumentation(filesToRegister); +} + +bool HelpPlugin::delayedInitialize() +{ + HelpManager::setupHelpManager(); + return true; } ExtensionSystem::IPlugin::ShutdownFlag HelpPlugin::aboutToShutdown() @@ -524,7 +541,7 @@ void HelpPluginPrivate::setupHelpEngineIfNeeded() { LocalHelpManager::setEngineNeedsUpdate(); if (ModeManager::currentModeId() == m_mode.id() - || LocalHelpManager::contextHelpOption() == HelpManager::ExternalHelpAlways) + || LocalHelpManager::contextHelpOption() == Core::HelpManager::ExternalHelpAlways) LocalHelpManager::setupGuiHelpEngine(); } @@ -564,29 +581,30 @@ HelpWidget *HelpPluginPrivate::helpWidgetForWindow(QWidget *window) return m_centralWidget; } -HelpViewer *HelpPlugin::viewerForHelpViewerLocation(HelpManager::HelpViewerLocation location) +HelpViewer *HelpPlugin::viewerForHelpViewerLocation(Core::HelpManager::HelpViewerLocation location) { return dd->viewerForHelpViewerLocation(location); } -HelpViewer *HelpPluginPrivate::viewerForHelpViewerLocation(HelpManager::HelpViewerLocation location) +HelpViewer *HelpPluginPrivate::viewerForHelpViewerLocation( + Core::HelpManager::HelpViewerLocation location) { - HelpManager::HelpViewerLocation actualLocation = location; - if (location == HelpManager::SideBySideIfPossible) - actualLocation = canShowHelpSideBySide() ? HelpManager::SideBySideAlways - : HelpManager::HelpModeAlways; + Core::HelpManager::HelpViewerLocation actualLocation = location; + if (location == Core::HelpManager::SideBySideIfPossible) + actualLocation = canShowHelpSideBySide() ? Core::HelpManager::SideBySideAlways + : Core::HelpManager::HelpModeAlways; - if (actualLocation == HelpManager::ExternalHelpAlways) + if (actualLocation == Core::HelpManager::ExternalHelpAlways) return externalHelpViewer(); - if (actualLocation == HelpManager::SideBySideAlways) { + if (actualLocation == Core::HelpManager::SideBySideAlways) { createRightPaneContextViewer(); RightPaneWidget::instance()->setWidget(m_rightPaneSideBarWidget); RightPaneWidget::instance()->setShown(true); return m_rightPaneSideBarWidget->currentViewer(); } - QTC_CHECK(actualLocation == HelpManager::HelpModeAlways); + QTC_CHECK(actualLocation == Core::HelpManager::HelpModeAlways); return helpModeHelpViewer(); } @@ -658,7 +676,7 @@ void HelpPluginPrivate::showContextHelp(const QString &contextHelpId) HelpViewer *viewer = viewerForContextHelp(); QTC_ASSERT(viewer, return); - QMap<QString, QUrl> links = HelpManager::linksForIdentifier(contextHelpId); + QMap<QString, QUrl> links = Core::HelpManager::linksForIdentifier(contextHelpId); // Maybe the id is already an URL if (links.isEmpty() && LocalHelpManager::isValidUrl(contextHelpId)) links.insert(contextHelpId, contextHelpId); @@ -702,7 +720,8 @@ void HelpPluginPrivate::highlightSearchTermsInContextHelp() m_contextHelpHighlightId.clear(); } -void HelpPluginPrivate::handleHelpRequest(const QUrl &url, HelpManager::HelpViewerLocation location) +void HelpPluginPrivate::handleHelpRequest(const QUrl &url, + Core::HelpManager::HelpViewerLocation location) { static const QString qtcreatorUnversionedID = "org.qt-project.qtcreator"; if (url.host() == qtcreatorUnversionedID) { diff --git a/src/plugins/help/helpplugin.h b/src/plugins/help/helpplugin.h index 902bc2b86a..0335891498 100644 --- a/src/plugins/help/helpplugin.h +++ b/src/plugins/help/helpplugin.h @@ -43,7 +43,7 @@ class HelpPlugin : public ExtensionSystem::IPlugin Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Help.json") public: - HelpPlugin() = default; + HelpPlugin(); ~HelpPlugin() final; static HelpViewer *viewerForHelpViewerLocation(Core::HelpManager::HelpViewerLocation location); @@ -53,6 +53,7 @@ public: private: bool initialize(const QStringList &arguments, QString *errorMessage) final; void extensionsInitialized() final; + bool delayedInitialize() final; ShutdownFlag aboutToShutdown() final; }; diff --git a/src/plugins/help/localhelpmanager.cpp b/src/plugins/help/localhelpmanager.cpp index ed17596399..731d938873 100644 --- a/src/plugins/help/localhelpmanager.cpp +++ b/src/plugins/help/localhelpmanager.cpp @@ -27,6 +27,7 @@ #include "bookmarkmanager.h" #include "helpconstants.h" +#include "helpmanager.h" #include "helpviewer.h" #include <app/app_version.h> @@ -266,7 +267,7 @@ void LocalHelpManager::setupGuiHelpEngine() { if (m_needsCollectionFile) { m_needsCollectionFile = false; - helpEngine().setCollectionFile(Core::HelpManager::collectionFilePath()); + helpEngine().setCollectionFile(HelpManager::collectionFilePath()); m_guiNeedsSetup = true; } diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp index 0dc0f6f209..b6294f32fb 100644 --- a/src/plugins/qtsupport/exampleslistmodel.cpp +++ b/src/plugins/qtsupport/exampleslistmodel.cpp @@ -115,10 +115,10 @@ ExampleSetModel::ExampleSetModel() connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsLoaded, this, &ExampleSetModel::qtVersionManagerLoaded); - if (auto helpManager = Core::HelpManager::instance()) { - connect(helpManager, &Core::HelpManager::setupFinished, - this, &ExampleSetModel::helpManagerInitialized); - } + connect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::setupFinished, + this, + &ExampleSetModel::helpManagerInitialized); } void ExampleSetModel::recreateModel(const QList<BaseQtVersion *> &qtVersions) @@ -236,8 +236,10 @@ ExamplesListModel::ExamplesListModel(QObject *parent) { connect(&m_exampleSetModel, &ExampleSetModel::selectedExampleSetChanged, this, &ExamplesListModel::updateExamples); - connect(Core::HelpManager::instance(), &Core::HelpManager::documentationChanged, - this, &ExamplesListModel::updateExamples); + connect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::documentationChanged, + this, + &ExamplesListModel::updateExamples); } static QString fixStringForTags(const QString &string) @@ -682,7 +684,7 @@ void ExampleSetModel::tryToInitialize() return; if (!m_qtVersionManagerInitialized) return; - if (Core::HelpManager::instance() && !m_helpManagerInitialized) + if (!m_helpManagerInitialized) return; m_initalized = true; |