diff --git a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.cpp b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.cpp index 38acff3ef9..f849a5dbd2 100644 --- a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.cpp +++ b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.cpp @@ -1,475 +1,622 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "berryHelpPluginActivator.h" #include "berryHelpContentView.h" #include "berryHelpIndexView.h" #include "berryHelpSearchView.h" #include "berryHelpEditor.h" #include "berryHelpEditorInput.h" #include "berryHelpEditorInputFactory.h" #include "berryHelpPerspective.h" +#include "berryHelpWebView.h" #include "berryQHelpEngineConfiguration.h" #include "berryQHelpEngineWrapper.h" #include #include #include #include +#include +#include +#include +#include + +namespace +{ + class HelpDeviceReply final : public QIODevice + { + public: + HelpDeviceReply(const QUrl& request, const QByteArray& fileData); + ~HelpDeviceReply() override; + + qint64 bytesAvailable() const override; + void close() override; + + private: + qint64 readData(char* data, qint64 maxlen) override; + qint64 writeData(const char* data, qint64 maxlen) override; + + QByteArray m_Data; + const qint64 m_OrigLen; + }; + + HelpDeviceReply::HelpDeviceReply(const QUrl&, const QByteArray& fileData) + : m_Data(fileData), + m_OrigLen(fileData.length()) + { + this->setOpenMode(QIODevice::ReadOnly); + + QTimer::singleShot(0, this, &QIODevice::readyRead); + QTimer::singleShot(0, this, &QIODevice::readChannelFinished); + } + + HelpDeviceReply::~HelpDeviceReply() + { + } + + qint64 HelpDeviceReply::bytesAvailable() const + { + return m_Data.length() + QIODevice::bytesAvailable(); + } + + void HelpDeviceReply::close() + { + QIODevice::close(); + this->deleteLater(); + } + + qint64 HelpDeviceReply::readData(char* data, qint64 maxlen) + { + qint64 len = qMin(qint64(m_Data.length()), maxlen); + + if (len) + { + memcpy(data, m_Data.constData(), len); + m_Data.remove(0, len); + } + + return len; + } + + qint64 HelpDeviceReply::writeData(const char*, qint64) + { + return 0; + } + + + class HelpUrlSchemeHandler final : public QWebEngineUrlSchemeHandler + { + public: + explicit HelpUrlSchemeHandler(QObject* parent = nullptr); + ~HelpUrlSchemeHandler() override; + + void requestStarted(QWebEngineUrlRequestJob* job) override; + }; + + HelpUrlSchemeHandler::HelpUrlSchemeHandler(QObject* parent) + : QWebEngineUrlSchemeHandler(parent) + { + } + + HelpUrlSchemeHandler::~HelpUrlSchemeHandler() + { + } + + enum class ResolveUrlResult + { + Error, + Redirect, + Data + }; + + ResolveUrlResult ResolveUrl(const QUrl& url, QUrl& redirectedUrl, QByteArray& data) + { + auto& helpEngine = berry::HelpPluginActivator::getInstance()->getQHelpEngine(); + + const auto targetUrl = helpEngine.findFile(url); + + if (!targetUrl.isValid()) + return ResolveUrlResult::Error; + + if (targetUrl != url) + { + redirectedUrl = targetUrl; + return ResolveUrlResult::Redirect; + } + + data = helpEngine.fileData(targetUrl); + return ResolveUrlResult::Data; + } + + + void HelpUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob* job) + { + QUrl url = job->requestUrl(); + QUrl redirectedUrl; + QByteArray data; + + switch (ResolveUrl(url, redirectedUrl, data)) + { + case ResolveUrlResult::Data: + job->reply( + berry::HelpWebView::mimeFromUrl(url).toLatin1(), + new HelpDeviceReply(url, data)); + break; + + case ResolveUrlResult::Redirect: + job->redirect(redirectedUrl); + break; + + case ResolveUrlResult::Error: + job->reply( + QByteArrayLiteral("text/html"), + new HelpDeviceReply(url, berry::HelpWebView::m_PageNotFoundMessage.arg(url.toString()).toUtf8())); + break; + } + } +} namespace berry { class HelpPerspectiveListener : public IPerspectiveListener { public: Events::Types GetPerspectiveEventTypes() const override; using IPerspectiveListener::PerspectiveChanged; void PerspectiveOpened(const SmartPointer& page, const IPerspectiveDescriptor::Pointer& perspective) override; void PerspectiveChanged(const SmartPointer& page, const IPerspectiveDescriptor::Pointer& perspective, const QString &changeId) override; }; class HelpWindowListener : public IWindowListener { public: HelpWindowListener(); ~HelpWindowListener() override; void WindowClosed(const IWorkbenchWindow::Pointer& window) override; void WindowOpened(const IWorkbenchWindow::Pointer& window) override; private: // We use the same perspective listener for every window QScopedPointer perspListener; }; HelpPluginActivator* HelpPluginActivator::instance = nullptr; HelpPluginActivator::HelpPluginActivator() - : pluginListener(nullptr) + : helpSchemeHandler(const_cast(QWebEngineProfile::defaultProfile()->urlSchemeHandler("qthelp"))), + pluginListener(nullptr) { this->instance = this; + + if (helpSchemeHandler == nullptr) + { + helpSchemeHandler = new HelpUrlSchemeHandler(this); + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("qthelp", helpSchemeHandler); + } } HelpPluginActivator::~HelpPluginActivator() { instance = nullptr; } void HelpPluginActivator::start(ctkPluginContext* context) { BERRY_REGISTER_EXTENSION_CLASS(berry::HelpContentView, context) BERRY_REGISTER_EXTENSION_CLASS(berry::HelpIndexView, context) BERRY_REGISTER_EXTENSION_CLASS(berry::HelpSearchView, context) BERRY_REGISTER_EXTENSION_CLASS(berry::HelpEditor, context) BERRY_REGISTER_EXTENSION_CLASS(berry::HelpEditorInputFactory, context) BERRY_REGISTER_EXTENSION_CLASS(berry::HelpPerspective, context) QFileInfo qhcInfo = context->getDataFile("qthelpcollection.qhc"); helpEngine.reset(new QHelpEngineWrapper(qhcInfo.absoluteFilePath())); helpEngine->setReadOnly(false); if (!helpEngine->setupData()) { BERRY_ERROR << "QHelpEngine set-up failed: " << helpEngine->error().toStdString(); return; } helpEngineConfiguration.reset(new QHelpEngineConfiguration(context, *helpEngine.data())); delete pluginListener; pluginListener = new QCHPluginListener(context, helpEngine.data()); context->connectPluginListener(pluginListener, SLOT(pluginChanged(ctkPluginEvent))); // register all QCH files from all the currently installed plugins pluginListener->processPlugins(); helpEngine->initialDocSetupDone(); // Register a wnd listener which registers a perspective listener for each // new window. The perspective listener opens the help home page in the window // if no other help page is opened yet. wndListener.reset(new HelpWindowListener()); PlatformUI::GetWorkbench()->AddWindowListener(wndListener.data()); // Register an event handler for CONTEXTHELP_REQUESTED events helpContextHandler.reset(new HelpContextHandler); ctkDictionary helpHandlerProps; helpHandlerProps.insert(ctkEventConstants::EVENT_TOPIC, "org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); context->registerService(helpContextHandler.data(), helpHandlerProps); } void HelpPluginActivator::stop(ctkPluginContext* /*context*/) { delete pluginListener; pluginListener = nullptr; if (PlatformUI::IsWorkbenchRunning()) { PlatformUI::GetWorkbench()->RemoveWindowListener(wndListener.data()); } wndListener.reset(); helpEngineConfiguration.reset(); helpEngine.reset(); } HelpPluginActivator *HelpPluginActivator::getInstance() { return instance; } QHelpEngineWrapper& HelpPluginActivator::getQHelpEngine() { return *helpEngine; } void HelpPluginActivator::linkActivated(IWorkbenchPage::Pointer page, const QUrl &link) { IEditorInput::Pointer input(new HelpEditorInput(link)); // see if an editor with the same input is already open IEditorPart::Pointer reuseEditor = page->FindEditor(input); if (reuseEditor) { // just activate it page->Activate(reuseEditor); } else { // reuse the currently active editor, if it is a HelpEditor reuseEditor = page->GetActiveEditor(); if (reuseEditor.IsNotNull() && page->GetReference(reuseEditor)->GetId() == HelpEditor::EDITOR_ID) { page->ReuseEditor(reuseEditor.Cast(), input); page->Activate(reuseEditor); } else { // get the last used HelpEditor instance QList editors = page->FindEditors(IEditorInput::Pointer(nullptr), HelpEditor::EDITOR_ID, IWorkbenchPage::MATCH_ID); if (editors.empty()) { // no HelpEditor is currently open, create a new one page->OpenEditor(input, HelpEditor::EDITOR_ID); } else { // reuse an existing editor reuseEditor = editors.front()->GetEditor(false); page->ReuseEditor(reuseEditor.Cast(), input); page->Activate(reuseEditor); } } } } QCHPluginListener::QCHPluginListener(ctkPluginContext* context, QHelpEngine* helpEngine) : delayRegistration(true), context(context), helpEngine(helpEngine) {} void QCHPluginListener::processPlugins() { QMutexLocker lock(&mutex); processPlugins_unlocked(); } void QCHPluginListener::pluginChanged(const ctkPluginEvent& event) { QMutexLocker lock(&mutex); if (delayRegistration) { this->processPlugins_unlocked(); return; } /* Only should listen for RESOLVED and UNRESOLVED events. * * When a plugin is updated the Framework will publish an UNRESOLVED and * then a RESOLVED event which should cause the plugin to be removed * and then added back into the registry. * * When a plugin is uninstalled the Framework should publish an UNRESOLVED * event and then an UNINSTALLED event so the plugin will have been removed * by the UNRESOLVED event before the UNINSTALLED event is published. */ QSharedPointer plugin = event.getPlugin(); switch (event.getType()) { case ctkPluginEvent::RESOLVED : addPlugin(plugin); break; case ctkPluginEvent::UNRESOLVED : removePlugin(plugin); break; default: break; } } void QCHPluginListener::processPlugins_unlocked() { if (!delayRegistration) return; foreach (QSharedPointer plugin, context->getPlugins()) { if (isPluginResolved(plugin)) addPlugin(plugin); else removePlugin(plugin); } delayRegistration = false; } bool QCHPluginListener::isPluginResolved(QSharedPointer plugin) { return (plugin->getState() & (ctkPlugin::RESOLVED | ctkPlugin::ACTIVE | ctkPlugin::STARTING | ctkPlugin::STOPPING)) != 0; } void QCHPluginListener::removePlugin(QSharedPointer plugin) { // bail out if system plugin if (plugin->getPluginId() == 0) return; QFileInfo qchDirInfo = context->getDataFile("qch_files/" + QString::number(plugin->getPluginId())); if (qchDirInfo.exists()) { QDir qchDir(qchDirInfo.absoluteFilePath()); QStringList qchEntries = qchDir.entryList(QStringList("*.qch")); QStringList qchFiles; foreach(QString qchEntry, qchEntries) { qchFiles << qchDir.absoluteFilePath(qchEntry); } // unregister the cached qch files foreach(QString qchFile, qchFiles) { QString namespaceName = QHelpEngineCore::namespaceName(qchFile); if (namespaceName.isEmpty()) { BERRY_ERROR << "Could not get the namespace for qch file " << qchFile.toStdString(); continue; } else { if (!helpEngine->unregisterDocumentation(namespaceName)) { BERRY_ERROR << "Unregistering qch namespace " << namespaceName.toStdString() << " failed: " << helpEngine->error().toStdString(); } } } // clean the directory foreach(QString qchEntry, qchEntries) { qchDir.remove(qchEntry); } } } void QCHPluginListener::addPlugin(QSharedPointer plugin) { // bail out if system plugin if (plugin->getPluginId() == 0) return; QFileInfo qchDirInfo = context->getDataFile("qch_files/" + QString::number(plugin->getPluginId())); QUrl location(plugin->getLocation()); QFileInfo pluginFileInfo(location.toLocalFile()); if (!qchDirInfo.exists() || qchDirInfo.lastModified() < pluginFileInfo.lastModified()) { removePlugin(plugin); if (!qchDirInfo.exists()) { QDir().mkpath(qchDirInfo.absoluteFilePath()); } QStringList localQCHFiles; QStringList resourceList = plugin->findResources("/", "*.qch", true); foreach(QString resource, resourceList) { QByteArray content = plugin->getResource(resource); QFile localFile(qchDirInfo.absoluteFilePath() + "/" + resource.section('/', -1)); localFile.open(QIODevice::WriteOnly); localFile.write(content); localFile.close(); if (localFile.error() != QFile::NoError) { BERRY_WARN << "Error writing " << localFile.fileName().toStdString() << ": " << localFile.errorString().toStdString(); } else { localQCHFiles << localFile.fileName(); } } foreach(QString qchFile, localQCHFiles) { if (!helpEngine->registerDocumentation(qchFile)) { BERRY_ERROR << "Registering qch file " << qchFile.toStdString() << " failed: " << helpEngine->error().toStdString(); } } } } IPerspectiveListener::Events::Types HelpPerspectiveListener::GetPerspectiveEventTypes() const { return Events::OPENED | Events::CHANGED; } void HelpPerspectiveListener::PerspectiveOpened(const SmartPointer& page, const IPerspectiveDescriptor::Pointer& perspective) { // if no help editor is opened, open one showing the home page if (perspective->GetId() == HelpPerspective::ID && page->FindEditors(IEditorInput::Pointer(nullptr), HelpEditor::EDITOR_ID, IWorkbenchPage::MATCH_ID).empty()) { IEditorInput::Pointer input(new HelpEditorInput()); page->OpenEditor(input, HelpEditor::EDITOR_ID); } } void HelpPerspectiveListener::PerspectiveChanged(const SmartPointer& page, const IPerspectiveDescriptor::Pointer& perspective, const QString &changeId) { if (perspective->GetId() == HelpPerspective::ID && changeId == IWorkbenchPage::CHANGE_RESET) { PerspectiveOpened(page, perspective); } } HelpWindowListener::HelpWindowListener() : perspListener(new HelpPerspectiveListener()) { // Register perspective listener for already opened windows typedef QList WndVec; WndVec windows = PlatformUI::GetWorkbench()->GetWorkbenchWindows(); for (WndVec::iterator i = windows.begin(); i != windows.end(); ++i) { (*i)->AddPerspectiveListener(perspListener.data()); } } HelpWindowListener::~HelpWindowListener() { if (!PlatformUI::IsWorkbenchRunning()) return; typedef QList WndVec; WndVec windows = PlatformUI::GetWorkbench()->GetWorkbenchWindows(); for (WndVec::iterator i = windows.begin(); i != windows.end(); ++i) { (*i)->RemovePerspectiveListener(perspListener.data()); } } void HelpWindowListener::WindowClosed(const IWorkbenchWindow::Pointer& window) { window->RemovePerspectiveListener(perspListener.data()); } void HelpWindowListener::WindowOpened(const IWorkbenchWindow::Pointer& window) { window->AddPerspectiveListener(perspListener.data()); } void HelpContextHandler::handleEvent(const ctkEvent &event) { struct _runner : public Poco::Runnable { _runner(const ctkEvent& ev) : ev(ev) {} void run() override { QUrl helpUrl; if (ev.containsProperty("url")) { helpUrl = QUrl(ev.getProperty("url").toString()); } else { helpUrl = contextUrl(); } HelpPluginActivator::linkActivated(PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(), helpUrl); delete this; } QUrl contextUrl() const { berry::IWorkbench* currentWorkbench = berry::PlatformUI::GetWorkbench(); if (currentWorkbench) { berry::IWorkbenchWindow::Pointer currentWorkbenchWindow = currentWorkbench->GetActiveWorkbenchWindow(); if (currentWorkbenchWindow) { berry::IWorkbenchPage::Pointer currentPage = currentWorkbenchWindow->GetActivePage(); if (currentPage) { berry::IWorkbenchPart::Pointer currentPart = currentPage->GetActivePart(); if (currentPart) { QString pluginID = currentPart->GetSite()->GetPluginId(); QString viewID = currentPart->GetSite()->GetId(); QString loc = "qthelp://" + pluginID + "/bundle/%1.html"; QHelpEngineWrapper& helpEngine = HelpPluginActivator::getInstance()->getQHelpEngine(); // Get view help page if available QUrl contextUrl(loc.arg(viewID.replace(".", "_"))); QUrl url = helpEngine.findFile(contextUrl); if (url.isValid()) return url; else { BERRY_INFO << "Context help url invalid: " << contextUrl.toString().toStdString(); } // If no view help exists get plugin help if available QUrl pluginContextUrl(loc.arg(pluginID.replace(".", "_"))); url = helpEngine.findFile(pluginContextUrl); if (url.isValid()) return url; // Try to get the index.html file of the plug-in contributing the // currently active part. QUrl pluginIndexUrl(loc.arg("index")); url = helpEngine.findFile(pluginIndexUrl); if (url != pluginIndexUrl) { // Use the default page instead of another index.html // (merged via the virtual folder property). url = QUrl(); } return url; } } } } return QUrl(); } ctkEvent ev; }; // sync with GUI thread Display::GetDefault()->AsyncExec(new _runner(event)); } } diff --git a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.h b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.h index 5464fde6f7..da1ee7a7e9 100644 --- a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.h +++ b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.h @@ -1,114 +1,116 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef BERRYLOGPLUGIN_H_ #define BERRYLOGPLUGIN_H_ #include #include #include #include #include #include #include class QHelpEngine; +class QWebEngineUrlSchemeHandler; namespace berry { class QHelpEngineConfiguration; class QHelpEngineWrapper; class QCHPluginListener; class HelpContextHandler : public QObject, public ctkEventHandler { Q_OBJECT Q_INTERFACES(ctkEventHandler) public: void handleEvent(const ctkEvent& event) override; }; class HelpPluginActivator : public QObject, public ctkPluginActivator { Q_OBJECT Q_PLUGIN_METADATA(IID "org_blueberry_ui_qt_help") Q_INTERFACES(ctkPluginActivator) public: HelpPluginActivator(); ~HelpPluginActivator() override; void start(ctkPluginContext* context) override; void stop(ctkPluginContext* context) override; static HelpPluginActivator* getInstance(); static void linkActivated(IWorkbenchPage::Pointer page, const QUrl &link); QHelpEngineWrapper& getQHelpEngine(); private: Q_DISABLE_COPY(HelpPluginActivator) static HelpPluginActivator* instance; QScopedPointer helpEngine; QScopedPointer helpEngineConfiguration; QScopedPointer helpContextHandler; + QWebEngineUrlSchemeHandler* helpSchemeHandler; QCHPluginListener* pluginListener; QScopedPointer wndListener; }; /** * A listener for CTK plugin events. When plugins come and go we look to see * if there are any qch files and update the QHelpEngine accordingly. */ class QCHPluginListener : public QObject { Q_OBJECT public: QCHPluginListener(ctkPluginContext* context, QHelpEngine* helpEngine); void processPlugins(); public Q_SLOTS: void pluginChanged(const ctkPluginEvent& event); private: void processPlugins_unlocked(); bool isPluginResolved(QSharedPointer plugin); void removePlugin(QSharedPointer plugin); void addPlugin(QSharedPointer plugin); QMutex mutex; bool delayRegistration; ctkPluginContext* context; QHelpEngine* helpEngine; }; } #endif /*BERRYLOGPLUGIN_H_*/ diff --git a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.cpp b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.cpp index 1e81da25a5..9c9e2eea2f 100644 --- a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.cpp +++ b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.cpp @@ -1,450 +1,311 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "berryHelpWebView.h" #include "berryHelpPluginActivator.h" #include "berryHelpEditor.h" #include "berryHelpEditorInput.h" #include "berryQHelpEngineWrapper.h" #include #include -#include #include #include #include #include #include -#include -#include -#include namespace berry { struct ExtensionMap { const char *extension; const char *mimeType; } extensionMap[] = { { ".bmp", "image/bmp" }, { ".css", "text/css" }, { ".gif", "image/gif" }, { ".html", "text/html" }, { ".htm", "text/html" }, { ".ico", "image/x-icon" }, { ".jpeg", "image/jpeg" }, { ".jpg", "image/jpeg" }, { ".js", "application/x-javascript" }, { ".mng", "video/x-mng" }, { ".pbm", "image/x-portable-bitmap" }, { ".pgm", "image/x-portable-graymap" }, { ".pdf", "application/pdf" }, { ".png", "image/png" }, { ".ppm", "image/x-portable-pixmap" }, { ".rss", "application/rss+xml" }, { ".svg", "image/svg+xml" }, { ".svgz", "image/svg+xml" }, { ".text", "text/plain" }, { ".tif", "image/tiff" }, { ".tiff", "image/tiff" }, { ".txt", "text/plain" }, { ".xbm", "image/x-xbitmap" }, { ".xml", "text/xml" }, { ".xpm", "image/x-xpm" }, { ".xsl", "text/xsl" }, { ".xhtml", "application/xhtml+xml" }, { ".wml", "text/vnd.wap.wml" }, { ".wmlc", "application/vnd.wap.wmlc" }, { "about:blank", nullptr }, { nullptr, nullptr } }; -class HelpDeviceReply final : public QIODevice -{ -public: - HelpDeviceReply(const QUrl& request, const QByteArray& fileData); - ~HelpDeviceReply() override; - - qint64 bytesAvailable() const override; - void close() override; - -private: - qint64 readData(char* data, qint64 maxlen) override; - qint64 writeData(const char* data, qint64 maxlen) override; - - QByteArray m_Data; - const qint64 m_OrigLen; -}; - -HelpDeviceReply::HelpDeviceReply(const QUrl&, const QByteArray& fileData) - : m_Data(fileData), - m_OrigLen(fileData.length()) -{ - this->setOpenMode(QIODevice::ReadOnly); - - QTimer::singleShot(0, this, &QIODevice::readyRead); - QTimer::singleShot(0, this, &QIODevice::readChannelFinished); -} - -HelpDeviceReply::~HelpDeviceReply() -{ -} - -qint64 HelpDeviceReply::bytesAvailable() const -{ - return m_Data.length() + QIODevice::bytesAvailable(); -} - -void HelpDeviceReply::close() -{ - QIODevice::close(); - this->deleteLater(); -} - -qint64 HelpDeviceReply::readData(char* data, qint64 maxlen) -{ - qint64 len = qMin(qint64(m_Data.length()), maxlen); - - if (len) - { - memcpy(data, m_Data.constData(), len); - m_Data.remove(0, len); - } - - return len; -} - -qint64 HelpDeviceReply::writeData(const char*, qint64) -{ - return 0; -} - - -class HelpUrlSchemeHandler final : public QWebEngineUrlSchemeHandler -{ -public: - explicit HelpUrlSchemeHandler(QObject* parent = nullptr); - ~HelpUrlSchemeHandler() override; - - void requestStarted(QWebEngineUrlRequestJob* job) override; -}; - -HelpUrlSchemeHandler::HelpUrlSchemeHandler(QObject* parent) - : QWebEngineUrlSchemeHandler(parent) -{ -} - -HelpUrlSchemeHandler::~HelpUrlSchemeHandler() -{ -} - -enum class ResolveUrlResult -{ - Error, - Redirect, - Data -}; - -ResolveUrlResult ResolveUrl(const QUrl& url, QUrl& redirectedUrl, QByteArray& data) -{ - auto& helpEngine = HelpPluginActivator::getInstance()->getQHelpEngine(); - - const auto targetUrl = helpEngine.findFile(url); - - if (!targetUrl.isValid()) - return ResolveUrlResult::Error; - - if (targetUrl != url) - { - redirectedUrl = targetUrl; - return ResolveUrlResult::Redirect; - } - - data = helpEngine.fileData(targetUrl); - return ResolveUrlResult::Data; -} - - -void HelpUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob* job) -{ - QUrl url = job->requestUrl(); - QUrl redirectedUrl; - QByteArray data; - - switch (ResolveUrl(url, redirectedUrl, data)) - { - case ResolveUrlResult::Data: - job->reply( - HelpWebView::mimeFromUrl(url).toLatin1(), - new HelpDeviceReply(url, data)); - break; - - case ResolveUrlResult::Redirect: - job->redirect(redirectedUrl); - break; - - case ResolveUrlResult::Error: - job->reply( - QByteArrayLiteral("text/html"), - new HelpDeviceReply(url, HelpWebView::m_PageNotFoundMessage.arg(url.toString()).toUtf8())); - break; - } -} - const QString HelpWebView::m_PageNotFoundMessage = QCoreApplication::translate("org.blueberry.ui.qt.help", "Context Help


No help page found for identifier


'%1'" "

"); const QString HelpWebView::m_MissingContextMessage = QCoreApplication::translate("org.blueberry.ui.qt.help", "Context Help


Unknown context..

 

Please click inside a view and hit F1 again!

"); class HelpPage final : public QWebEnginePage { public: explicit HelpPage(QObject* parent = nullptr); ~HelpPage() override; private: bool acceptNavigationRequest(const QUrl& url, NavigationType type, bool isMainFrame) override; }; HelpPage::HelpPage(QObject* parent) : QWebEnginePage(parent) { } HelpPage::~HelpPage() { } bool HelpPage::acceptNavigationRequest(const QUrl& url, NavigationType, bool) { if (url.scheme().contains("http")) { QDesktopServices::openUrl(url); return false; } return true; } HelpWebView::HelpWebView(IEditorSite::Pointer, QWidget *parent, qreal zoom) : QWebEngineView(parent), m_LoadFinished(false), - m_HelpEngine(HelpPluginActivator::getInstance()->getQHelpEngine()), - m_HelpSchemeHandler(new HelpUrlSchemeHandler(this)) + m_HelpEngine(HelpPluginActivator::getInstance()->getQHelpEngine()) { - QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("qthelp", m_HelpSchemeHandler); - auto helpPage = new HelpPage(this); this->setPage(helpPage); this->setAcceptDrops(false); auto action = pageAction(QWebEnginePage::OpenLinkInNewWindow); action->setText("Open Link in New Tab"); if (parent == nullptr) action->setVisible(false); this->pageAction(QWebEnginePage::DownloadLinkToDisk)->setVisible(false); this->pageAction(QWebEnginePage::DownloadImageToDisk)->setVisible(false); connect(pageAction(QWebEnginePage::Copy), SIGNAL(changed()), this, SLOT(actionChanged())); connect(pageAction(QWebEnginePage::Back), SIGNAL(changed()), this, SLOT(actionChanged())); connect(pageAction(QWebEnginePage::Forward), SIGNAL(changed()), this, SLOT(actionChanged())); connect(page(), SIGNAL(linkHovered(QString)), this, SIGNAL(highlighted(QString))); connect(this, SIGNAL(urlChanged(QUrl)), this, SIGNAL(sourceChanged(QUrl))); connect(this, SIGNAL(loadStarted()), this, SLOT(setLoadStarted())); connect(this, SIGNAL(loadFinished(bool)), this, SLOT(setLoadFinished(bool))); this->setFont(this->viewerFont()); this->setZoomFactor(zoom == 0.0 ? 1.0 : zoom); } HelpWebView::~HelpWebView() { } QFont HelpWebView::viewerFont() const { QWebEngineSettings* webSettings = settings(); return QFont(webSettings->fontFamily(QWebEngineSettings::StandardFont), webSettings->fontSize(QWebEngineSettings::DefaultFontSize)); } void HelpWebView::setViewerFont(const QFont &font) { QWebEngineSettings *webSettings = settings(); webSettings->setFontFamily(QWebEngineSettings::StandardFont, font.family()); webSettings->setFontSize(QWebEngineSettings::DefaultFontSize, font.pointSize()); } void HelpWebView::scaleUp() { setZoomFactor(zoomFactor() + 0.1); } void HelpWebView::scaleDown() { setZoomFactor(qMax(0.0, zoomFactor() - 0.1)); } void HelpWebView::resetScale() { setZoomFactor(1.0); } bool HelpWebView::handleForwardBackwardMouseButtons(QMouseEvent *e) { if (e->button() == Qt::XButton1) { triggerPageAction(QWebEnginePage::Back); return true; } if (e->button() == Qt::XButton2) { triggerPageAction(QWebEnginePage::Forward); return true; } return false; } void HelpWebView::setSource(const QUrl &url) { if (url.toString().trimmed().isEmpty()) { setHtml(m_MissingContextMessage); } else if (m_HelpEngine.findFile(url).isValid()) { load(url); } else { setHtml(m_PageNotFoundMessage.arg(url.toString())); } } void HelpWebView::wheelEvent(QWheelEvent *e) { if (e->modifiers()& Qt::ControlModifier) { e->accept(); e->angleDelta().y() > 0 ? scaleUp() : scaleDown(); } else { QWebEngineView::wheelEvent(e); } } void HelpWebView::actionChanged() { QAction *a = qobject_cast(sender()); if (a == pageAction(QWebEnginePage::Copy)) emit copyAvailable(a->isEnabled()); else if (a == pageAction(QWebEnginePage::Back)) emit backwardAvailable(a->isEnabled()); else if (a == pageAction(QWebEnginePage::Forward)) emit forwardAvailable(a->isEnabled()); } void HelpWebView::setLoadStarted() { m_LoadFinished = false; } void HelpWebView::setLoadFinished(bool ok) { m_LoadFinished = ok; emit sourceChanged(url()); } QString HelpWebView::mimeFromUrl(const QUrl &url) { const QString &path = url.path(); const int index = path.lastIndexOf(QLatin1Char('.')); const QByteArray &ext = path.mid(index).toUtf8().toLower(); const ExtensionMap *e = extensionMap; while (e->extension) { if (ext == e->extension) return QLatin1String(e->mimeType); ++e; } return QLatin1String(""); } bool HelpWebView::canOpenPage(const QString &url) { return !mimeFromUrl(url).isEmpty(); } bool HelpWebView::isLocalUrl(const QUrl &url) { const QString &scheme = url.scheme(); return scheme.isEmpty() || scheme == QLatin1String("file") || scheme == QLatin1String("qrc") || scheme == QLatin1String("data") || scheme == QLatin1String("qthelp") || scheme == QLatin1String("about"); } bool HelpWebView::launchWithExternalApp(const QUrl &url) { if (isLocalUrl(url)) { const QHelpEngine& helpEngine = HelpPluginActivator::getInstance()->getQHelpEngine(); const QUrl &resolvedUrl = helpEngine.findFile(url); if (!resolvedUrl.isValid()) return false; const QString& path = resolvedUrl.path(); if (!canOpenPage(path)) { QTemporaryFile tmpTmpFile; if (!tmpTmpFile.open()) return false; const QString &extension = QFileInfo(path).completeSuffix(); QFile actualTmpFile(tmpTmpFile.fileName() % QLatin1String(".") % extension); if (!actualTmpFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) return false; actualTmpFile.write(helpEngine.fileData(resolvedUrl)); actualTmpFile.close(); return QDesktopServices::openUrl(QUrl(actualTmpFile.fileName())); } } else if (url.scheme() == QLatin1String("http")) { return QDesktopServices::openUrl(url); } return false; } void HelpWebView::home() { setSource(m_HelpEngine.homePage()); } } diff --git a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.h b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.h index b7d8029759..f6f21548bb 100644 --- a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.h +++ b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpWebView.h @@ -1,104 +1,101 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef BERRYHELPWEBVIEW_H #define BERRYHELPWEBVIEW_H #include #include #include #include #include -class QWebEngineUrlSchemeHandler; - namespace berry { class QHelpEngineWrapper; class HelpWebView : public QWebEngineView { Q_OBJECT public: explicit HelpWebView(IEditorSite::Pointer editorSite, QWidget *parent, qreal zoom = 0.0); ~HelpWebView() override; QFont viewerFont() const; void setViewerFont(const QFont &font); qreal scale() const { return this->zoomFactor(); } bool handleForwardBackwardMouseButtons(QMouseEvent *e); void setSource(const QUrl &url); inline QString documentTitle() const { return title(); } inline bool hasSelection() const { return !selectedText().isEmpty(); } // ### this is suboptimal inline void copy() { return triggerPageAction(QWebEnginePage::Copy); } inline bool isForwardAvailable() const { return pageAction(QWebEnginePage::Forward)->isEnabled(); } inline bool isBackwardAvailable() const { return pageAction(QWebEnginePage::Back)->isEnabled(); } inline bool hasLoadFinished() const { return m_LoadFinished; } static QString mimeFromUrl(const QUrl &url); static bool canOpenPage(const QString &url); static bool isLocalUrl(const QUrl &url); static bool launchWithExternalApp(const QUrl &url); static const QString m_MissingContextMessage; static const QString m_PageNotFoundMessage; public Q_SLOTS: void backward() { back(); } void home(); void scaleUp(); void scaleDown(); void resetScale(); Q_SIGNALS: void copyAvailable(bool enabled); void forwardAvailable(bool enabled); void backwardAvailable(bool enabled); void highlighted(const QString &); void sourceChanged(const QUrl &); protected: void wheelEvent(QWheelEvent *) override; private Q_SLOTS: void actionChanged(); void setLoadStarted(); void setLoadFinished(bool ok); private: bool m_LoadFinished; QHelpEngineWrapper& m_HelpEngine; - QWebEngineUrlSchemeHandler* m_HelpSchemeHandler; }; } #endif // BERRYHELPWEBVIEW_H