diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 423b17d9284..59cb95790b0 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -23,7 +23,6 @@ add_subdirectory(projectexplorer) add_subdirectory(silversearcher) # Level 3: (only depends on Level 2 and below) -add_subdirectory(axivion) add_subdirectory(compilerexplorer) add_subdirectory(cppeditor) add_subdirectory(haskell) @@ -84,6 +83,7 @@ add_subdirectory(cmakeprojectmanager) # Level 7: add_subdirectory(android) add_subdirectory(autotest) +add_subdirectory(axivion) add_subdirectory(baremetal) add_subdirectory(clangcodemodel) add_subdirectory(clangtools) diff --git a/src/plugins/axivion/CMakeLists.txt b/src/plugins/axivion/CMakeLists.txt index 2eb2a9d197f..a08aa434284 100644 --- a/src/plugins/axivion/CMakeLists.txt +++ b/src/plugins/axivion/CMakeLists.txt @@ -1,10 +1,10 @@ add_qtc_plugin(Axivion PLUGIN_DEPENDS - Core ProjectExplorer TextEditor + Core Debugger ProjectExplorer TextEditor DEPENDS Qt::Network Qt::Widgets ExtensionSystem Utils qtkeychain SOURCES axivion.qrc - axivionoutputpane.cpp axivionoutputpane.h + axivionperspective.cpp axivionperspective.h axivionplugin.cpp axivionplugin.h axivionsettings.cpp axivionsettings.h axiviontr.h diff --git a/src/plugins/axivion/axivion.qbs b/src/plugins/axivion/axivion.qbs index c5b2a8223e9..671b292d9c5 100644 --- a/src/plugins/axivion/axivion.qbs +++ b/src/plugins/axivion/axivion.qbs @@ -4,6 +4,7 @@ QtcPlugin { name: "Axivion" Depends { name: "Core" } + Depends { name: "Debugger" } Depends { name: "ExtensionSystem" } Depends { name: "ProjectExplorer" } Depends { name: "TextEditor" } @@ -14,8 +15,8 @@ QtcPlugin { files: [ "axivion.qrc", - "axivionoutputpane.cpp", - "axivionoutputpane.h", + "axivionperspective.cpp", + "axivionperspective.h", "axivionplugin.cpp", "axivionplugin.h", "axivionsettings.cpp", diff --git a/src/plugins/axivion/axivionoutputpane.cpp b/src/plugins/axivion/axivionperspective.cpp similarity index 78% rename from src/plugins/axivion/axivionoutputpane.cpp rename to src/plugins/axivion/axivionperspective.cpp index 4c23b62e367..1c0572ae304 100644 --- a/src/plugins/axivion/axivionoutputpane.cpp +++ b/src/plugins/axivion/axivionperspective.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "axivionoutputpane.h" +#include "axivionperspective.h" #include "axivionplugin.h" #include "axivionsettings.h" @@ -10,8 +10,12 @@ #include "issueheaderview.h" #include "dynamiclistmodel.h" +#include +#include #include -#include + +#include +#include #include #include @@ -22,6 +26,7 @@ #include #include +#include #include #include #include @@ -42,6 +47,7 @@ #include #include #include +#include #include #include @@ -755,180 +761,217 @@ void IssuesWidget::hideOverlay() m_overlay->hide(); } -class AxivionOutputPane final : public IOutputPane +class AxivionPerspective : public Perspective { public: - explicit AxivionOutputPane(QObject *parent) - : IOutputPane(parent) - { - setId("Axivion"); - setDisplayName(Tr::tr("Axivion")); - setPriorityInStatusBar(-50); + AxivionPerspective() : Perspective("Axivion.Perspective", Tr::tr("Axivion")) {} + void initPerspective(); - m_issuesWidget = new IssuesWidget; - - QPalette pal = m_issuesWidget->palette(); - pal.setColor(QPalette::Window, creatorColor(Theme::Color::BackgroundColorNormal)); - m_issuesWidget->setPalette(pal); - - m_disableInlineIssues = new QToolButton(m_issuesWidget); - m_disableInlineIssues->setIcon(ProjectExplorer::Icons::BUILDSTEP_DISABLE.icon()); - m_disableInlineIssues->setToolTip(Tr::tr("Disable inline issues")); - m_disableInlineIssues->setCheckable(true); - m_disableInlineIssues->setChecked(false); - connect(m_disableInlineIssues, &QToolButton::toggled, - this, [](bool checked) { disableInlineIssues(checked); }); - m_toggleIssues = new QToolButton(m_issuesWidget); - m_toggleIssues->setIcon(Utils::Icons::WARNING_TOOLBAR.icon()); - m_toggleIssues->setToolTip(Tr::tr("Show issue annotations inline")); - m_toggleIssues->setCheckable(true); - m_toggleIssues->setChecked(true); - connect(m_toggleIssues, &QToolButton::toggled, this, [](bool checked) { - if (checked) - TextEditor::TextDocument::showMarksAnnotation("AxivionTextMark"); - else - TextEditor::TextDocument::temporaryHideMarksAnnotation("AxivionTextMark"); - }); - } - - ~AxivionOutputPane() - { - if (!m_issuesWidget->parent()) - delete m_issuesWidget; - } - - QWidget *outputWidget(QWidget *parent) final - { - if (m_issuesWidget) - m_issuesWidget->setParent(parent); - else - QTC_CHECK(false); - return m_issuesWidget; - } - - QList toolBarWidgets() const final - { - return {m_disableInlineIssues, m_toggleIssues}; - } - - void clearContents() final {} - void setFocus() final {} - bool hasFocus() const final { return false; } - bool canFocus() const final { return true; } - bool canNavigate() const final { return true; } - bool canNext() const final { return false; } - bool canPrevious() const final { return false; } - void goToNext() final {} - void goToPrev() final {} - - void handleShowIssues(const QString &kind) - { - m_issuesWidget->updateUi(kind); - } - - void handleShowFilterException(const QString &errorMessage) - { - m_issuesWidget->showOverlay(errorMessage); - } - - void reinitDashboardList(const QString &preferredProject) - { - m_issuesWidget->initDashboardList(preferredProject); - } - - void resetDashboard() - { - m_issuesWidget->resetDashboard(); - } - - bool handleContextMenu(const QString &issue, const ItemViewEvent &e) - { - std::optional tableInfoOpt = m_issuesWidget->currentTableInfo(); - if (!tableInfoOpt) - return false; - const QString baseUri = tableInfoOpt->issueBaseViewUri.value_or(QString()); - if (baseUri.isEmpty()) - return false; - auto info = currentDashboardInfo(); - if (!info) - return false; - - QUrl issueBaseUrl = info->source.resolved(baseUri).resolved(issue); - QUrl dashboardUrl = info->source.resolved(baseUri); - const IssueListSearch search = m_issuesWidget->searchFromUi(); - issueBaseUrl.setQuery(search.toUrlQuery(QueryMode::SimpleQuery)); - dashboardUrl.setQuery(search.toUrlQuery(QueryMode::FilterQuery)); - - QMenu *menu = new QMenu; - auto action = new QAction(Tr::tr("Open Issue in Dashboard"), menu); - menu->addAction(action); - QObject::connect(action, &QAction::triggered, menu, [issueBaseUrl] { - QDesktopServices::openUrl(issueBaseUrl); - }); - action = new QAction(Tr::tr("Open Table in Dashboard"), menu); - QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] { - QDesktopServices::openUrl(dashboardUrl); - }); - menu->addAction(action); - action = new QAction(Tr::tr("Copy Dashboard Link to Clipboard"), menu); - QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] { - if (auto clipboard = QGuiApplication::clipboard()) - clipboard->setText(dashboardUrl.toString()); - }); - menu->addAction(action); - QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); - menu->popup(e.globalPos()); - return true; - } + void handleShowIssues(const QString &kind); + void handleShowFilterException(const QString &errorMessage); + void reinitDashboardList(const QString &preferredProject); + void resetDashboard(); + bool handleContextMenu(const QString &issue, const ItemViewEvent &e); + void setIssueDetailsHtml(const QString &html) { m_issueDetails->setHtml(html); } + void handleAnchorClicked(const QUrl &url); private: IssuesWidget *m_issuesWidget = nullptr; - QToolButton *m_toggleIssues = nullptr; - QToolButton *m_disableInlineIssues = nullptr; + QTextBrowser *m_issueDetails = nullptr; + QAction *m_disableInlineIssues = nullptr; + QAction *m_toggleIssues = nullptr; }; - -static QPointer theAxivionOutputPane; - -void setupAxivionOutputPane(QObject *guard) +void AxivionPerspective::initPerspective() { - theAxivionOutputPane = new AxivionOutputPane(guard); + m_issuesWidget = new IssuesWidget; + m_issuesWidget->setObjectName("AxivionIssuesWidget"); + m_issuesWidget->setWindowTitle(Tr::tr("Issues")); + QPalette pal = m_issuesWidget->palette(); + pal.setColor(QPalette::Window, creatorColor(Theme::Color::BackgroundColorNormal)); + m_issuesWidget->setPalette(pal); + + m_issueDetails = new QTextBrowser; + m_issueDetails->setObjectName("AxivionIssuesDetails"); + m_issueDetails->setWindowTitle(Tr::tr("Issue Details")); + const QString text = Tr::tr( + "Search for issues inside the Axivion dashboard or request issue details for " + "Axivion inline annotations to see them here."); + m_issueDetails->setText("

" + text + "

"); + m_issueDetails->setOpenLinks(false); + connect(m_issueDetails, &QTextBrowser::anchorClicked, + this, &AxivionPerspective::handleAnchorClicked); + + m_disableInlineIssues = new QAction(this); + m_disableInlineIssues->setIcon(ProjectExplorer::Icons::BUILDSTEP_DISABLE.icon()); + m_disableInlineIssues->setToolTip(Tr::tr("Disable inline issues")); + m_disableInlineIssues->setCheckable(true); + m_disableInlineIssues->setChecked(false); + connect(m_disableInlineIssues, &QAction::toggled, + this, [](bool checked) { disableInlineIssues(checked); }); + m_toggleIssues = new QAction(this); + m_toggleIssues->setIcon(Utils::Icons::WARNING_TOOLBAR.icon()); + m_toggleIssues->setToolTip(Tr::tr("Show issue annotations inline")); + m_toggleIssues->setCheckable(true); + m_toggleIssues->setChecked(true); + connect(m_toggleIssues, &QAction::toggled, this, [](bool checked) { + if (checked) + TextEditor::TextDocument::showMarksAnnotation("AxivionTextMark"); + else + TextEditor::TextDocument::temporaryHideMarksAnnotation("AxivionTextMark"); + }); + + + addToolBarAction(m_disableInlineIssues); + addToolBarAction(m_toggleIssues); + + addWindow(m_issuesWidget, Perspective::SplitVertical, nullptr); + addWindow(m_issueDetails, Perspective::AddToTab, nullptr, true, Qt::RightDockWidgetArea); + + ActionContainer *menu = ActionManager::actionContainer(Debugger::Constants::M_DEBUG_ANALYZER); + QAction *action = new QAction(Tr::tr("Axivion"), this); + menu->addAction(ActionManager::registerAction(action, "Axivion.Perspective"), + Debugger::Constants::G_ANALYZER_TOOLS); + connect(action, &QAction::triggered, + this, &Perspective::select); +} + +void AxivionPerspective::handleShowIssues(const QString &kind) +{ + m_issuesWidget->updateUi(kind); +} + +void AxivionPerspective::handleShowFilterException(const QString &errorMessage) +{ + m_issuesWidget->showOverlay(errorMessage); +} + +void AxivionPerspective::reinitDashboardList(const QString &preferredProject) +{ + m_issuesWidget->initDashboardList(preferredProject); +} + +void AxivionPerspective::resetDashboard() +{ + m_issuesWidget->resetDashboard(); +} + +bool AxivionPerspective::handleContextMenu(const QString &issue, const ItemViewEvent &e) +{ + std::optional tableInfoOpt = m_issuesWidget->currentTableInfo(); + if (!tableInfoOpt) + return false; + const QString baseUri = tableInfoOpt->issueBaseViewUri.value_or(QString()); + if (baseUri.isEmpty()) + return false; + auto info = currentDashboardInfo(); + if (!info) + return false; + + QUrl issueBaseUrl = info->source.resolved(baseUri).resolved(issue); + QUrl dashboardUrl = info->source.resolved(baseUri); + const IssueListSearch search = m_issuesWidget->searchFromUi(); + issueBaseUrl.setQuery(search.toUrlQuery(QueryMode::SimpleQuery)); + dashboardUrl.setQuery(search.toUrlQuery(QueryMode::FilterQuery)); + + QMenu *menu = new QMenu; + auto action = new QAction(Tr::tr("Open Issue in Dashboard"), menu); + menu->addAction(action); + QObject::connect(action, &QAction::triggered, menu, [issueBaseUrl] { + QDesktopServices::openUrl(issueBaseUrl); + }); + action = new QAction(Tr::tr("Open Table in Dashboard"), menu); + QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] { + QDesktopServices::openUrl(dashboardUrl); + }); + menu->addAction(action); + action = new QAction(Tr::tr("Copy Dashboard Link to Clipboard"), menu); + QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] { + if (auto clipboard = QGuiApplication::clipboard()) + clipboard->setText(dashboardUrl.toString()); + }); + menu->addAction(action); + QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); + menu->popup(e.globalPos()); + return true; +} + +void AxivionPerspective::handleAnchorClicked(const QUrl &url) +{ + if (!url.scheme().isEmpty()) { + const QString detail = Tr::tr("The activated link appears to be external.\n" + "Do you want to open \"%1\" with its default application?") + .arg(url.toString()); + const QMessageBox::StandardButton pressed + = CheckableMessageBox::question(Core::ICore::dialogParent(), + Tr::tr("Open External Links"), + detail, + Key("AxivionOpenExternalLinks")); + if (pressed == QMessageBox::Yes) + QDesktopServices::openUrl(url); + return; + } + const QUrlQuery query(url); + if (query.isEmpty()) + return; + Link link; + if (const QString path = query.queryItemValue("filename", QUrl::FullyDecoded); !path.isEmpty()) + link.targetFilePath = findFileForIssuePath(FilePath::fromUserInput(path)); + if (const QString line = query.queryItemValue("line"); !line.isEmpty()) + link.targetLine = line.toInt(); + // column entry is wrong - so, ignore it + if (link.hasValidTarget() && link.targetFilePath.exists()) + EditorManager::openEditorAt(link); +} + +static QPointer theAxivionPerspective; + +void setupAxivionPerspective() +{ + QTC_ASSERT(!theAxivionPerspective, return); + theAxivionPerspective = new AxivionPerspective(); + theAxivionPerspective->initPerspective(); } void updateDashboard() { - QTC_ASSERT(theAxivionOutputPane, return); - theAxivionOutputPane->handleShowIssues({}); - theAxivionOutputPane->flash(); + QTC_ASSERT(theAxivionPerspective, return); + theAxivionPerspective->handleShowIssues({}); } void reinitDashboard(const QString &preferredProject) { - QTC_ASSERT(theAxivionOutputPane, return); - theAxivionOutputPane->reinitDashboardList(preferredProject); + QTC_ASSERT(theAxivionPerspective, return); + theAxivionPerspective->reinitDashboardList(preferredProject); } void resetDashboard() { - QTC_ASSERT(theAxivionOutputPane, return); - theAxivionOutputPane->resetDashboard(); + QTC_ASSERT(theAxivionPerspective, return); + theAxivionPerspective->resetDashboard(); } static bool issueListContextMenuEvent(const ItemViewEvent &ev) { - QTC_ASSERT(theAxivionOutputPane, return false); + QTC_ASSERT(theAxivionPerspective, return false); const QModelIndexList selectedIndices = ev.selectedRows(); const QModelIndex first = selectedIndices.isEmpty() ? QModelIndex() : selectedIndices.first(); if (!first.isValid()) return false; const QString issue = first.data().toString(); - return theAxivionOutputPane->handleContextMenu(issue, ev); + return theAxivionPerspective->handleContextMenu(issue, ev); } void showFilterException(const QString &errorMessage) { - QTC_ASSERT(theAxivionOutputPane, return); - theAxivionOutputPane->handleShowFilterException(errorMessage); + QTC_ASSERT(theAxivionPerspective, return); + theAxivionPerspective->handleShowFilterException(errorMessage); +} + +void updateIssueDetails(const QString &html) +{ + QTC_ASSERT(theAxivionPerspective, return); + theAxivionPerspective->setIssueDetailsHtml(html); } } // Axivion::Internal diff --git a/src/plugins/axivion/axivionoutputpane.h b/src/plugins/axivion/axivionperspective.h similarity index 82% rename from src/plugins/axivion/axivionoutputpane.h rename to src/plugins/axivion/axivionperspective.h index 8b3aef3a2d8..44c8451c96e 100644 --- a/src/plugins/axivion/axivionoutputpane.h +++ b/src/plugins/axivion/axivionperspective.h @@ -7,10 +7,11 @@ namespace Axivion::Internal { -void setupAxivionOutputPane(QObject *guard); +void setupAxivionPerspective(); void updateDashboard(); void showFilterException(const QString &errorMessage); void reinitDashboard(const QString &projectName); void resetDashboard(); +void updateIssueDetails(const QString &html); } // Axivion::Internal diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp index b75c4bf0b3e..55e44582574 100644 --- a/src/plugins/axivion/axivionplugin.cpp +++ b/src/plugins/axivion/axivionplugin.cpp @@ -3,7 +3,7 @@ #include "axivionplugin.h" -#include "axivionoutputpane.h" +#include "axivionperspective.h" #include "axivionsettings.h" #include "axiviontr.h" #include "credentialquery.h" @@ -13,14 +13,11 @@ #include #include #include -#include #include -#include #include #include -#include #include #include @@ -28,12 +25,10 @@ #include #include -#include #include #include #include -#include #include #include #include @@ -41,13 +36,10 @@ #include #include -#include #include #include #include #include -#include -#include #include #include @@ -224,15 +216,10 @@ public: void handleIssuesForFile(const Dto::FileViewDto &fileView); void disableInlineIssues(bool disable); void fetchIssueInfo(const QString &id); - void setIssueDetails(const QString &issueDetailsHtml); - void handleAnchorClicked(const QUrl &url); void onSessionLoaded(const QString &sessionName); void onAboutToSaveSession(); -signals: - void issueDetailsChanged(const QString &issueDetailsHtml); - public: // active id used for any network communication, defaults to settings' default // set to projects settings' dashboard id on open project @@ -934,18 +921,12 @@ void AxivionPluginPrivate::fetchIssueInfo(const QString &id) if (idx >= 0) fixedHtml = "" + htmlText.mid(idx); - NavigationWidget::activateSubWidget("Axivion.Issue", Side::Right); - dd->setIssueDetails(QString::fromUtf8(fixedHtml)); + updateIssueDetails(QString::fromUtf8(fixedHtml)); }; m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler)); } -void AxivionPluginPrivate::setIssueDetails(const QString &issueDetailsHtml) -{ - emit issueDetailsChanged(issueDetailsHtml); -} - void AxivionPluginPrivate::handleOpenedDocs() { const QList openDocuments = DocumentModel::openedDocuments(); @@ -1050,36 +1031,6 @@ void AxivionPluginPrivate::disableInlineIssues(bool disable) handleOpenedDocs(); } -void AxivionPluginPrivate::handleAnchorClicked(const QUrl &url) -{ - QTC_ASSERT(dd, return); - QTC_ASSERT(dd->m_project, return); - if (!url.scheme().isEmpty()) { - const QString detail = Tr::tr("The activated link appears to be external.\n" - "Do you want to open \"%1\" with its default application?") - .arg(url.toString()); - const QMessageBox::StandardButton pressed - = CheckableMessageBox::question(Core::ICore::dialogParent(), - Tr::tr("Open External Links"), - detail, - Key("AxivionOpenExternalLinks")); - if (pressed == QMessageBox::Yes) - QDesktopServices::openUrl(url); - return; - } - const QUrlQuery query(url); - if (query.isEmpty()) - return; - Link link; - if (const QString path = query.queryItemValue("filename", QUrl::FullyDecoded); !path.isEmpty()) - link.targetFilePath = findFileForIssuePath(FilePath::fromUserInput(path)); - if (const QString line = query.queryItemValue("line"); !line.isEmpty()) - link.targetLine = line.toInt(); - // column entry is wrong - so, ignore it - if (link.hasValidTarget() && link.targetFilePath.exists()) - EditorManager::openEditorAt(link); -} - static constexpr char SV_PROJECTNAME[] = "Axivion.ProjectName"; static constexpr char SV_DASHBOARDID[] = "Axivion.DashboardId"; @@ -1113,39 +1064,6 @@ void AxivionPluginPrivate::onAboutToSaveSession() SessionManager::setSessionValue(SV_PROJECTNAME, projectName); } -class AxivionIssueWidgetFactory final : public INavigationWidgetFactory -{ -public: - AxivionIssueWidgetFactory() - { - setDisplayName(Tr::tr("Axivion")); - setId("Axivion.Issue"); - setPriority(555); - } - - NavigationView createWidget() final - { - QTC_ASSERT(dd, return {}); - QTextBrowser *browser = new QTextBrowser; - const QString text = Tr::tr( - "Search for issues inside the Axivion dashboard or request issue details for " - "Axivion inline annotations to see them here."); - browser->setText("

" + text + "

"); - browser->setOpenLinks(false); - NavigationView view; - view.widget = browser; - connect(dd, &AxivionPluginPrivate::issueDetailsChanged, browser, &QTextBrowser::setHtml); - connect(browser, &QTextBrowser::anchorClicked, - dd, &AxivionPluginPrivate::handleAnchorClicked); - return view; - } -}; - -void setupAxivionIssueWidgetFactory() -{ - static AxivionIssueWidgetFactory issueWidgetFactory; -} - class AxivionPlugin final : public ExtensionSystem::IPlugin { Q_OBJECT @@ -1159,12 +1077,10 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin void initialize() final { - setupAxivionOutputPane(this); + setupAxivionPerspective(); dd = new AxivionPluginPrivate; - setupAxivionIssueWidgetFactory(); - connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, dd, &AxivionPluginPrivate::onStartupProjectChanged); connect(EditorManager::instance(), &EditorManager::documentOpened,