diff --git a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpIndexView.cpp b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpIndexView.cpp index d027955804..77a22144d0 100644 --- a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpIndexView.cpp +++ b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpIndexView.cpp @@ -1,310 +1,325 @@ /*============================================================================ 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 "berryHelpIndexView.h" #include "berryHelpPluginActivator.h" #include "berryHelpEditor.h" #include "berryHelpEditorInput.h" #include "berryQHelpEngineWrapper.h" #include "berryHelpTopicChooser.h" #include #include #include +#include #include #include #include #include namespace berry { HelpIndexWidget::HelpIndexWidget() : QListView(nullptr) { setEditTriggers(QAbstractItemView::NoEditTriggers); setUniformItemSizes(true); connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(showLink(QModelIndex))); } void HelpIndexWidget::showLink(const QModelIndex &index) { if (!index.isValid()) return; QHelpIndexModel *indexModel = qobject_cast(model()); if (!indexModel) return; QVariant v = indexModel->data(index, Qt::DisplayRole); QString name; if (v.isValid()) name = v.toString(); - QMap links = indexModel->linksForKeyword(name); + QHelpEngineWrapper& helpEngine = HelpPluginActivator::getInstance()->getQHelpEngine(); + auto links = helpEngine.documentsForKeyword(name); + if (links.count() == 1) { - emit linkActivated(links.constBegin().value(), name); + emit linkActivated(links[0].url, name); } else if (links.count() > 1) { - emit linksActivated(links, name); + QMap legacyLinks; + + for (const auto& link : links) + legacyLinks.insert(link.title, link.url); + + emit linksActivated(legacyLinks, name); } } void HelpIndexWidget::activateCurrentItem() { showLink(currentIndex()); } void HelpIndexWidget::filterIndices(const QString &filter, const QString &wildcard) { QHelpIndexModel *indexModel = qobject_cast(model()); if (!indexModel) return; QModelIndex idx = indexModel->filter(filter, wildcard); if (idx.isValid()) setCurrentIndex(idx); } HelpIndexView::HelpIndexView() : m_IndexWidget(nullptr) { } HelpIndexView::~HelpIndexView() { } void HelpIndexView::CreateQtPartControl(QWidget* parent) { if (m_IndexWidget == nullptr) { auto layout = new QVBoxLayout(parent); //QLabel *l = new QLabel(tr("&Look for:")); //layout->addWidget(l); m_SearchLineEdit = new ctkSearchBox(parent); m_SearchLineEdit->setClearIcon(QIcon(":/org.blueberry.ui.qt.help/clear.png")); m_SearchLineEdit->setPlaceholderText("Filter..."); m_SearchLineEdit->setContentsMargins(2,2,2,0); //l->setBuddy(m_SearchLineEdit); connect(m_SearchLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterIndices(QString))); m_SearchLineEdit->installEventFilter(this); layout->setContentsMargins({}); layout->setSpacing(2); layout->addWidget(m_SearchLineEdit); QHelpEngineWrapper& helpEngine = HelpPluginActivator::getInstance()->getQHelpEngine(); m_IndexWidget = new HelpIndexWidget(); m_IndexWidget->setModel(helpEngine.indexModel()); connect(helpEngine.indexModel(), SIGNAL(indexCreationStarted()), this, SLOT(setIndexWidgetBusy())); connect(helpEngine.indexModel(), SIGNAL(indexCreated()), this, SLOT(unsetIndexWidgetBusy())); m_IndexWidget->installEventFilter(this); connect(helpEngine.indexModel(), SIGNAL(indexCreationStarted()), this, SLOT(disableSearchLineEdit())); connect(helpEngine.indexModel(), SIGNAL(indexCreated()), this, SLOT(enableSearchLineEdit())); connect(m_IndexWidget, SIGNAL(linkActivated(QUrl,QString)), this, SLOT(linkActivated(QUrl))); connect(m_IndexWidget, SIGNAL(linksActivated(QMap,QString)), this, SLOT(linksActivated(QMap,QString))); connect(m_SearchLineEdit, SIGNAL(returnPressed()), m_IndexWidget, SLOT(activateCurrentItem())); layout->addWidget(m_IndexWidget); m_IndexWidget->viewport()->installEventFilter(this); } } void HelpIndexView::SetFocus() { if (!(m_IndexWidget->hasFocus() || m_SearchLineEdit->hasFocus())) { m_SearchLineEdit->setFocus(); } } void HelpIndexView::filterIndices(const QString &filter) { if (filter.contains(QLatin1Char('*'))) m_IndexWidget->filterIndices(filter, filter); else m_IndexWidget->filterIndices(filter, QString()); } bool HelpIndexView::eventFilter(QObject *obj, QEvent *e) { if (obj == m_SearchLineEdit && e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); QModelIndex idx = m_IndexWidget->currentIndex(); switch (ke->key()) { case Qt::Key_Up: idx = m_IndexWidget->model()->index(idx.row()-1, idx.column(), idx.parent()); if (idx.isValid()) { m_IndexWidget->setCurrentIndex(idx); return true; } break; case Qt::Key_Down: idx = m_IndexWidget->model()->index(idx.row()+1, idx.column(), idx.parent()); if (idx.isValid()) { m_IndexWidget->setCurrentIndex(idx); return true; } break; default: ; // stop complaining } } else if (obj == m_IndexWidget && e->type() == QEvent::ContextMenu) { QContextMenuEvent *ctxtEvent = static_cast(e); QModelIndex idx = m_IndexWidget->indexAt(ctxtEvent->pos()); if (idx.isValid()) { QMenu menu; QAction *curTab = menu.addAction(tr("Open Link")); QAction *newTab = menu.addAction(tr("Open Link in New Tab")); menu.move(m_IndexWidget->mapToGlobal(ctxtEvent->pos())); QAction *action = menu.exec(); if (curTab == action) m_IndexWidget->activateCurrentItem(); else if (newTab == action) { open(m_IndexWidget, idx); } } } else if (m_IndexWidget && obj == m_IndexWidget->viewport() && e->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(e); QModelIndex idx = m_IndexWidget->indexAt(mouseEvent->pos()); if (idx.isValid()) { Qt::MouseButtons button = mouseEvent->button(); if (((button == Qt::LeftButton) && (mouseEvent->modifiers() & Qt::ControlModifier)) - || (button == Qt::MidButton)) + || (button == Qt::MiddleButton)) { open(m_IndexWidget, idx); } } } #ifdef Q_OS_MAC else if (obj == m_IndexWidget && e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) m_IndexWidget->activateCurrentItem(); } #endif return QObject::eventFilter(obj, e); } void HelpIndexView::enableSearchLineEdit() { m_SearchLineEdit->setDisabled(false); filterIndices(m_SearchLineEdit->text()); } void HelpIndexView::disableSearchLineEdit() { m_SearchLineEdit->setDisabled(true); } void HelpIndexView::setIndexWidgetBusy() { m_IndexWidget->setCursor(Qt::WaitCursor); } void HelpIndexView::unsetIndexWidgetBusy() { m_IndexWidget->unsetCursor(); } void HelpIndexView::setSearchLineEditText(const QString &text) { m_SearchLineEdit->setText(text); } QString HelpIndexView::searchLineEditText() const { return m_SearchLineEdit->text(); } void HelpIndexView::focusInEvent(QFocusEvent *e) { if (e->reason() != Qt::MouseFocusReason) { m_SearchLineEdit->selectAll(); m_SearchLineEdit->setFocus(); } } void HelpIndexView::open(HelpIndexWidget* indexWidget, const QModelIndex &index) { QHelpIndexModel *model = qobject_cast(indexWidget->model()); if (model) { QString keyword = model->data(index, Qt::DisplayRole).toString(); - QMap links = model->linksForKeyword(keyword); + + QHelpEngineWrapper& helpEngine = HelpPluginActivator::getInstance()->getQHelpEngine(); + auto links = helpEngine.documentsForKeyword(keyword); QUrl url; if (links.count() > 1) { - HelpTopicChooser tc(m_IndexWidget, keyword, links); + QMap legacyLinks; + + for (const auto& link : links) + legacyLinks.insert(link.title, link.url); + + HelpTopicChooser tc(m_IndexWidget, keyword, legacyLinks); if (tc.exec() == QDialog::Accepted) url = tc.link(); } else if (links.count() == 1) { - url = links.constBegin().value(); + url = links[0].url; } else { return; } IEditorInput::Pointer input(new HelpEditorInput(url)); this->GetSite()->GetPage()->OpenEditor(input, HelpEditor::EDITOR_ID); } } void HelpIndexView::linkActivated(const QUrl& link) { IWorkbenchPage::Pointer page = this->GetSite()->GetPage(); HelpPluginActivator::linkActivated(page, link); } void HelpIndexView::linksActivated(const QMap& links, const QString& keyword) { HelpTopicChooser tc(m_IndexWidget, keyword, links); if (tc.exec() == QDialog::Accepted) { IWorkbenchPage::Pointer page = this->GetSite()->GetPage(); HelpPluginActivator::linkActivated(page, tc.link()); } } } 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 1ef2c93bca..38acff3ef9 100644 --- a/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.cpp +++ b/Plugins/org.blueberry.ui.qt.help/src/internal/berryHelpPluginActivator.cpp @@ -1,474 +1,475 @@ /*============================================================================ 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 "berryQHelpEngineConfiguration.h" #include "berryQHelpEngineWrapper.h" #include #include #include #include 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) { this->instance = this; } 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)); } }