Axivion: Morph into Perspective

Replace the current approach of having separated
widgets for Axivion by a perspective tying them
together.
This gets rid of the Axivion output pane and the
mis-used navigation widget and moves their content
into the Axivion perspective.
This perspective is now part of the Debug view like
other analyzing tools.

Change-Id: I5b40c94ea32a3759fdfda41cbeaae11cc69f66cc
Reviewed-by: Mohammad Mehdi Salem Naraghi <mehdi.salem@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Stenger
2024-09-11 16:33:06 +02:00
committed by hjk
parent 0be60cad2f
commit a640b17824
6 changed files with 196 additions and 235 deletions

View File

@@ -23,7 +23,6 @@ add_subdirectory(projectexplorer)
add_subdirectory(silversearcher) add_subdirectory(silversearcher)
# Level 3: (only depends on Level 2 and below) # Level 3: (only depends on Level 2 and below)
add_subdirectory(axivion)
add_subdirectory(compilerexplorer) add_subdirectory(compilerexplorer)
add_subdirectory(cppeditor) add_subdirectory(cppeditor)
add_subdirectory(haskell) add_subdirectory(haskell)
@@ -84,6 +83,7 @@ add_subdirectory(cmakeprojectmanager)
# Level 7: # Level 7:
add_subdirectory(android) add_subdirectory(android)
add_subdirectory(autotest) add_subdirectory(autotest)
add_subdirectory(axivion)
add_subdirectory(baremetal) add_subdirectory(baremetal)
add_subdirectory(clangcodemodel) add_subdirectory(clangcodemodel)
add_subdirectory(clangtools) add_subdirectory(clangtools)

View File

@@ -1,10 +1,10 @@
add_qtc_plugin(Axivion add_qtc_plugin(Axivion
PLUGIN_DEPENDS PLUGIN_DEPENDS
Core ProjectExplorer TextEditor Core Debugger ProjectExplorer TextEditor
DEPENDS Qt::Network Qt::Widgets ExtensionSystem Utils qtkeychain DEPENDS Qt::Network Qt::Widgets ExtensionSystem Utils qtkeychain
SOURCES SOURCES
axivion.qrc axivion.qrc
axivionoutputpane.cpp axivionoutputpane.h axivionperspective.cpp axivionperspective.h
axivionplugin.cpp axivionplugin.h axivionplugin.cpp axivionplugin.h
axivionsettings.cpp axivionsettings.h axivionsettings.cpp axivionsettings.h
axiviontr.h axiviontr.h

View File

@@ -4,6 +4,7 @@ QtcPlugin {
name: "Axivion" name: "Axivion"
Depends { name: "Core" } Depends { name: "Core" }
Depends { name: "Debugger" }
Depends { name: "ExtensionSystem" } Depends { name: "ExtensionSystem" }
Depends { name: "ProjectExplorer" } Depends { name: "ProjectExplorer" }
Depends { name: "TextEditor" } Depends { name: "TextEditor" }
@@ -14,8 +15,8 @@ QtcPlugin {
files: [ files: [
"axivion.qrc", "axivion.qrc",
"axivionoutputpane.cpp", "axivionperspective.cpp",
"axivionoutputpane.h", "axivionperspective.h",
"axivionplugin.cpp", "axivionplugin.cpp",
"axivionplugin.h", "axivionplugin.h",
"axivionsettings.cpp", "axivionsettings.cpp",

View File

@@ -1,7 +1,7 @@
// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // 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 "axivionplugin.h"
#include "axivionsettings.h" #include "axivionsettings.h"
@@ -10,8 +10,12 @@
#include "issueheaderview.h" #include "issueheaderview.h"
#include "dynamiclistmodel.h" #include "dynamiclistmodel.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/ioutputpane.h>
#include <debugger/analyzer/analyzerconstants.h>
#include <debugger/debuggermainwindow.h>
#include <projectexplorer/projectexplorericons.h> #include <projectexplorer/projectexplorericons.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
@@ -22,6 +26,7 @@
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/checkablemessagebox.h>
#include <utils/guard.h> #include <utils/guard.h>
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
#include <utils/link.h> #include <utils/link.h>
@@ -42,6 +47,7 @@
#include <QPainter> #include <QPainter>
#include <QPushButton> #include <QPushButton>
#include <QScrollArea> #include <QScrollArea>
#include <QTextBrowser>
#include <QToolButton> #include <QToolButton>
#include <QUrlQuery> #include <QUrlQuery>
@@ -755,93 +761,102 @@ void IssuesWidget::hideOverlay()
m_overlay->hide(); m_overlay->hide();
} }
class AxivionOutputPane final : public IOutputPane class AxivionPerspective : public Perspective
{ {
public: public:
explicit AxivionOutputPane(QObject *parent) AxivionPerspective() : Perspective("Axivion.Perspective", Tr::tr("Axivion")) {}
: IOutputPane(parent) void initPerspective();
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;
QTextBrowser *m_issueDetails = nullptr;
QAction *m_disableInlineIssues = nullptr;
QAction *m_toggleIssues = nullptr;
};
void AxivionPerspective::initPerspective()
{ {
setId("Axivion");
setDisplayName(Tr::tr("Axivion"));
setPriorityInStatusBar(-50);
m_issuesWidget = new IssuesWidget; m_issuesWidget = new IssuesWidget;
m_issuesWidget->setObjectName("AxivionIssuesWidget");
m_issuesWidget->setWindowTitle(Tr::tr("Issues"));
QPalette pal = m_issuesWidget->palette(); QPalette pal = m_issuesWidget->palette();
pal.setColor(QPalette::Window, creatorColor(Theme::Color::BackgroundColorNormal)); pal.setColor(QPalette::Window, creatorColor(Theme::Color::BackgroundColorNormal));
m_issuesWidget->setPalette(pal); m_issuesWidget->setPalette(pal);
m_disableInlineIssues = new QToolButton(m_issuesWidget); 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("<p style='text-align:center'>" + text + "</p>");
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->setIcon(ProjectExplorer::Icons::BUILDSTEP_DISABLE.icon());
m_disableInlineIssues->setToolTip(Tr::tr("Disable inline issues")); m_disableInlineIssues->setToolTip(Tr::tr("Disable inline issues"));
m_disableInlineIssues->setCheckable(true); m_disableInlineIssues->setCheckable(true);
m_disableInlineIssues->setChecked(false); m_disableInlineIssues->setChecked(false);
connect(m_disableInlineIssues, &QToolButton::toggled, connect(m_disableInlineIssues, &QAction::toggled,
this, [](bool checked) { disableInlineIssues(checked); }); this, [](bool checked) { disableInlineIssues(checked); });
m_toggleIssues = new QToolButton(m_issuesWidget); m_toggleIssues = new QAction(this);
m_toggleIssues->setIcon(Utils::Icons::WARNING_TOOLBAR.icon()); m_toggleIssues->setIcon(Utils::Icons::WARNING_TOOLBAR.icon());
m_toggleIssues->setToolTip(Tr::tr("Show issue annotations inline")); m_toggleIssues->setToolTip(Tr::tr("Show issue annotations inline"));
m_toggleIssues->setCheckable(true); m_toggleIssues->setCheckable(true);
m_toggleIssues->setChecked(true); m_toggleIssues->setChecked(true);
connect(m_toggleIssues, &QToolButton::toggled, this, [](bool checked) { connect(m_toggleIssues, &QAction::toggled, this, [](bool checked) {
if (checked) if (checked)
TextEditor::TextDocument::showMarksAnnotation("AxivionTextMark"); TextEditor::TextDocument::showMarksAnnotation("AxivionTextMark");
else else
TextEditor::TextDocument::temporaryHideMarksAnnotation("AxivionTextMark"); 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);
} }
~AxivionOutputPane() void AxivionPerspective::handleShowIssues(const QString &kind)
{
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<QWidget *> 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); m_issuesWidget->updateUi(kind);
} }
void handleShowFilterException(const QString &errorMessage) void AxivionPerspective::handleShowFilterException(const QString &errorMessage)
{ {
m_issuesWidget->showOverlay(errorMessage); m_issuesWidget->showOverlay(errorMessage);
} }
void reinitDashboardList(const QString &preferredProject) void AxivionPerspective::reinitDashboardList(const QString &preferredProject)
{ {
m_issuesWidget->initDashboardList(preferredProject); m_issuesWidget->initDashboardList(preferredProject);
} }
void resetDashboard() void AxivionPerspective::resetDashboard()
{ {
m_issuesWidget->resetDashboard(); m_issuesWidget->resetDashboard();
} }
bool handleContextMenu(const QString &issue, const ItemViewEvent &e) bool AxivionPerspective::handleContextMenu(const QString &issue, const ItemViewEvent &e)
{ {
std::optional<Dto::TableInfoDto> tableInfoOpt = m_issuesWidget->currentTableInfo(); std::optional<Dto::TableInfoDto> tableInfoOpt = m_issuesWidget->currentTableInfo();
if (!tableInfoOpt) if (!tableInfoOpt)
@@ -881,54 +896,82 @@ public:
return true; return true;
} }
private: void AxivionPerspective::handleAnchorClicked(const QUrl &url)
IssuesWidget *m_issuesWidget = nullptr;
QToolButton *m_toggleIssues = nullptr;
QToolButton *m_disableInlineIssues = nullptr;
};
static QPointer<AxivionOutputPane> theAxivionOutputPane;
void setupAxivionOutputPane(QObject *guard)
{ {
theAxivionOutputPane = new AxivionOutputPane(guard); 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<AxivionPerspective> theAxivionPerspective;
void setupAxivionPerspective()
{
QTC_ASSERT(!theAxivionPerspective, return);
theAxivionPerspective = new AxivionPerspective();
theAxivionPerspective->initPerspective();
} }
void updateDashboard() void updateDashboard()
{ {
QTC_ASSERT(theAxivionOutputPane, return); QTC_ASSERT(theAxivionPerspective, return);
theAxivionOutputPane->handleShowIssues({}); theAxivionPerspective->handleShowIssues({});
theAxivionOutputPane->flash();
} }
void reinitDashboard(const QString &preferredProject) void reinitDashboard(const QString &preferredProject)
{ {
QTC_ASSERT(theAxivionOutputPane, return); QTC_ASSERT(theAxivionPerspective, return);
theAxivionOutputPane->reinitDashboardList(preferredProject); theAxivionPerspective->reinitDashboardList(preferredProject);
} }
void resetDashboard() void resetDashboard()
{ {
QTC_ASSERT(theAxivionOutputPane, return); QTC_ASSERT(theAxivionPerspective, return);
theAxivionOutputPane->resetDashboard(); theAxivionPerspective->resetDashboard();
} }
static bool issueListContextMenuEvent(const ItemViewEvent &ev) static bool issueListContextMenuEvent(const ItemViewEvent &ev)
{ {
QTC_ASSERT(theAxivionOutputPane, return false); QTC_ASSERT(theAxivionPerspective, return false);
const QModelIndexList selectedIndices = ev.selectedRows(); const QModelIndexList selectedIndices = ev.selectedRows();
const QModelIndex first = selectedIndices.isEmpty() ? QModelIndex() : selectedIndices.first(); const QModelIndex first = selectedIndices.isEmpty() ? QModelIndex() : selectedIndices.first();
if (!first.isValid()) if (!first.isValid())
return false; return false;
const QString issue = first.data().toString(); const QString issue = first.data().toString();
return theAxivionOutputPane->handleContextMenu(issue, ev); return theAxivionPerspective->handleContextMenu(issue, ev);
} }
void showFilterException(const QString &errorMessage) void showFilterException(const QString &errorMessage)
{ {
QTC_ASSERT(theAxivionOutputPane, return); QTC_ASSERT(theAxivionPerspective, return);
theAxivionOutputPane->handleShowFilterException(errorMessage); theAxivionPerspective->handleShowFilterException(errorMessage);
}
void updateIssueDetails(const QString &html)
{
QTC_ASSERT(theAxivionPerspective, return);
theAxivionPerspective->setIssueDetailsHtml(html);
} }
} // Axivion::Internal } // Axivion::Internal

View File

@@ -7,10 +7,11 @@
namespace Axivion::Internal { namespace Axivion::Internal {
void setupAxivionOutputPane(QObject *guard); void setupAxivionPerspective();
void updateDashboard(); void updateDashboard();
void showFilterException(const QString &errorMessage); void showFilterException(const QString &errorMessage);
void reinitDashboard(const QString &projectName); void reinitDashboard(const QString &projectName);
void resetDashboard(); void resetDashboard();
void updateIssueDetails(const QString &html);
} // Axivion::Internal } // Axivion::Internal

View File

@@ -3,7 +3,7 @@
#include "axivionplugin.h" #include "axivionplugin.h"
#include "axivionoutputpane.h" #include "axivionperspective.h"
#include "axivionsettings.h" #include "axivionsettings.h"
#include "axiviontr.h" #include "axiviontr.h"
#include "credentialquery.h" #include "credentialquery.h"
@@ -13,14 +13,11 @@
#include <coreplugin/editormanager/documentmodel.h> #include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/inavigationwidgetfactory.h>
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
#include <coreplugin/navigationwidget.h>
#include <coreplugin/session.h> #include <coreplugin/session.h>
#include <extensionsystem/iplugin.h> #include <extensionsystem/iplugin.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h> #include <projectexplorer/projectmanager.h>
@@ -28,12 +25,10 @@
#include <solutions/tasking/tasktreerunner.h> #include <solutions/tasking/tasktreerunner.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/textmark.h> #include <texteditor/textmark.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/async.h> #include <utils/async.h>
#include <utils/checkablemessagebox.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/fileinprojectfinder.h> #include <utils/fileinprojectfinder.h>
#include <utils/networkaccessmanager.h> #include <utils/networkaccessmanager.h>
@@ -41,13 +36,10 @@
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QAction> #include <QAction>
#include <QDesktopServices>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QTextBrowser>
#include <QTimer>
#include <QUrlQuery> #include <QUrlQuery>
#include <memory> #include <memory>
@@ -224,15 +216,10 @@ public:
void handleIssuesForFile(const Dto::FileViewDto &fileView); void handleIssuesForFile(const Dto::FileViewDto &fileView);
void disableInlineIssues(bool disable); void disableInlineIssues(bool disable);
void fetchIssueInfo(const QString &id); void fetchIssueInfo(const QString &id);
void setIssueDetails(const QString &issueDetailsHtml);
void handleAnchorClicked(const QUrl &url);
void onSessionLoaded(const QString &sessionName); void onSessionLoaded(const QString &sessionName);
void onAboutToSaveSession(); void onAboutToSaveSession();
signals:
void issueDetailsChanged(const QString &issueDetailsHtml);
public: public:
// active id used for any network communication, defaults to settings' default // active id used for any network communication, defaults to settings' default
// set to projects settings' dashboard id on open project // set to projects settings' dashboard id on open project
@@ -934,18 +921,12 @@ void AxivionPluginPrivate::fetchIssueInfo(const QString &id)
if (idx >= 0) if (idx >= 0)
fixedHtml = "<html><body>" + htmlText.mid(idx); fixedHtml = "<html><body>" + htmlText.mid(idx);
NavigationWidget::activateSubWidget("Axivion.Issue", Side::Right); updateIssueDetails(QString::fromUtf8(fixedHtml));
dd->setIssueDetails(QString::fromUtf8(fixedHtml));
}; };
m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler)); m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler));
} }
void AxivionPluginPrivate::setIssueDetails(const QString &issueDetailsHtml)
{
emit issueDetailsChanged(issueDetailsHtml);
}
void AxivionPluginPrivate::handleOpenedDocs() void AxivionPluginPrivate::handleOpenedDocs()
{ {
const QList<IDocument *> openDocuments = DocumentModel::openedDocuments(); const QList<IDocument *> openDocuments = DocumentModel::openedDocuments();
@@ -1050,36 +1031,6 @@ void AxivionPluginPrivate::disableInlineIssues(bool disable)
handleOpenedDocs(); 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_PROJECTNAME[] = "Axivion.ProjectName";
static constexpr char SV_DASHBOARDID[] = "Axivion.DashboardId"; static constexpr char SV_DASHBOARDID[] = "Axivion.DashboardId";
@@ -1113,39 +1064,6 @@ void AxivionPluginPrivate::onAboutToSaveSession()
SessionManager::setSessionValue(SV_PROJECTNAME, projectName); 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("<p style='text-align:center'>" + text + "</p>");
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 class AxivionPlugin final : public ExtensionSystem::IPlugin
{ {
Q_OBJECT Q_OBJECT
@@ -1159,12 +1077,10 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin
void initialize() final void initialize() final
{ {
setupAxivionOutputPane(this); setupAxivionPerspective();
dd = new AxivionPluginPrivate; dd = new AxivionPluginPrivate;
setupAxivionIssueWidgetFactory();
connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
dd, &AxivionPluginPrivate::onStartupProjectChanged); dd, &AxivionPluginPrivate::onStartupProjectChanged);
connect(EditorManager::instance(), &EditorManager::documentOpened, connect(EditorManager::instance(), &EditorManager::documentOpened,