forked from qt-creator/qt-creator
Merge axivion plugin into 11.0
Change-Id: I60290479f6b4bd8ff87e86f0cc6ded240803d43c
This commit is contained in:
17
src/plugins/axivion/Axivion.json.in
Normal file
17
src/plugins/axivion/Axivion.json.in
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
\"Name\" : \"Axivion\",
|
||||||
|
\"Version\" : \"$$QTCREATOR_VERSION\",
|
||||||
|
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
|
||||||
|
\"Revision\" : \"$$QTC_PLUGIN_REVISION\",
|
||||||
|
\"Experimental\" : true,
|
||||||
|
\"Vendor\" : \"The Qt Company Ltd\",
|
||||||
|
\"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
|
||||||
|
\"License\" : [ \"Commercial Usage\",
|
||||||
|
\"\",
|
||||||
|
\"Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\"
|
||||||
|
],
|
||||||
|
\"Category\" : \"Code Analyzer\",
|
||||||
|
\"Description\" : \"Integration of the axivion dashboard.\",
|
||||||
|
\"Url\" : \"http://www.qt-project.org\",
|
||||||
|
$$dependencyList
|
||||||
|
}
|
||||||
24
src/plugins/axivion/CMakeLists.txt
Normal file
24
src/plugins/axivion/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
find_package(QtCreator COMPONENTS Core REQUIRED)
|
||||||
|
find_package(Qt6 COMPONENTS Network Widgets REQUIRED)
|
||||||
|
|
||||||
|
find_package(QtCreatorLicenseChecker QUIET)
|
||||||
|
if (TARGET QtCreator::LicenseChecker)
|
||||||
|
set(LICENSECHECKER_DEPENDS QtCreator::LicenseChecker)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_qtc_plugin(Axivion
|
||||||
|
PLUGIN_DEPENDS
|
||||||
|
QtCreator::Core QtCreator::ProjectExplorer QtCreator::TextEditor
|
||||||
|
${LICENSECHECKER_DEPENDS}
|
||||||
|
DEPENDS Qt::Network Qt::Widgets QtCreator::ExtensionSystem QtCreator::Utils
|
||||||
|
SOURCES
|
||||||
|
axivion.qrc
|
||||||
|
axivionoutputpane.cpp axivionoutputpane.h
|
||||||
|
axivionplugin.cpp axivionplugin.h
|
||||||
|
axivionprojectsettings.h axivionprojectsettings.cpp
|
||||||
|
axivionquery.h axivionquery.cpp
|
||||||
|
axivionresultparser.h axivionresultparser.cpp
|
||||||
|
axivionsettings.cpp axivionsettings.h
|
||||||
|
axivionsettingspage.cpp axivionsettingspage.h
|
||||||
|
axiviontr.h
|
||||||
|
)
|
||||||
32
src/plugins/axivion/axivion.qbs
Normal file
32
src/plugins/axivion/axivion.qbs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import qbs
|
||||||
|
|
||||||
|
QtcCommercialPlugin {
|
||||||
|
name: "Axivion"
|
||||||
|
|
||||||
|
Depends { name: "Core" }
|
||||||
|
Depends { name: "ProjectExplorer" }
|
||||||
|
Depends { name: "TextEditor" }
|
||||||
|
Depends { name: "ExtensionSystem" }
|
||||||
|
Depends { name: "Utils" }
|
||||||
|
Depends { name: "Qt.widgets" }
|
||||||
|
Depends { name: "Qt.network" }
|
||||||
|
|
||||||
|
files: [
|
||||||
|
"axivion.qrc",
|
||||||
|
"axivionoutputpane.cpp",
|
||||||
|
"axivionoutputpane.h",
|
||||||
|
"axivionplugin.cpp",
|
||||||
|
"axivionplugin.h",
|
||||||
|
"axivionprojectsettings.h",
|
||||||
|
"axivionprojectsettings.cpp",
|
||||||
|
"axivionquery.h",
|
||||||
|
"axivionquery.cpp",
|
||||||
|
"axivionresultparser.h",
|
||||||
|
"axivionresultparser.cpp",
|
||||||
|
"axivionsettings.cpp",
|
||||||
|
"axivionsettings.h",
|
||||||
|
"axivionsettingspage.cpp",
|
||||||
|
"axivionsettingspage.h",
|
||||||
|
"axiviontr.h",
|
||||||
|
]
|
||||||
|
}
|
||||||
6
src/plugins/axivion/axivion.qrc
Normal file
6
src/plugins/axivion/axivion.qrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/axivion">
|
||||||
|
<file>images/axivion.png</file>
|
||||||
|
<file>images/axivion@2x.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
209
src/plugins/axivion/axivionoutputpane.cpp
Normal file
209
src/plugins/axivion/axivionoutputpane.cpp
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionoutputpane.h"
|
||||||
|
|
||||||
|
#include "axivionplugin.h"
|
||||||
|
#include "axivionresultparser.h"
|
||||||
|
#include "axiviontr.h"
|
||||||
|
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QTextBrowser>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class DashboardWidget : public QScrollArea
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DashboardWidget(QWidget *parent = nullptr);
|
||||||
|
void updateUi();
|
||||||
|
bool hasProject() const { return !m_project->text().isEmpty(); }
|
||||||
|
private:
|
||||||
|
QLabel *m_project = nullptr;
|
||||||
|
QLabel *m_loc = nullptr;
|
||||||
|
QFormLayout *m_formLayout = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
DashboardWidget::DashboardWidget(QWidget *parent)
|
||||||
|
: QScrollArea(parent)
|
||||||
|
{
|
||||||
|
QWidget *widget = new QWidget(this);
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||||
|
QFormLayout *projectLayout = new QFormLayout;
|
||||||
|
m_project = new QLabel(this);
|
||||||
|
projectLayout->addRow(Tr::tr("Project:"), m_project);
|
||||||
|
m_loc = new QLabel(this);
|
||||||
|
projectLayout->addRow(Tr::tr("Lines of Code:"), m_loc);
|
||||||
|
layout->addLayout(projectLayout);
|
||||||
|
m_formLayout = new QFormLayout;
|
||||||
|
layout->addLayout(m_formLayout);
|
||||||
|
setWidget(widget);
|
||||||
|
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||||
|
setWidgetResizable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DashboardWidget::updateUi()
|
||||||
|
{
|
||||||
|
const ProjectInfo &info = AxivionPlugin::projectInfo();
|
||||||
|
m_project->setText(info.name);
|
||||||
|
m_loc->setText({});
|
||||||
|
while (m_formLayout->rowCount())
|
||||||
|
m_formLayout->removeRow(0);
|
||||||
|
|
||||||
|
if (info.versions.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ResultVersion &last = info.versions.last();
|
||||||
|
m_loc->setText(QString::number(last.linesOfCode));
|
||||||
|
|
||||||
|
const QString tmpl("%1 %2 +%3 / -%4");
|
||||||
|
auto apply = [&tmpl](int t, int a, int r){
|
||||||
|
QChar tr = (a == r ? '=' : (a < r ? '^' : 'v'));
|
||||||
|
return tmpl.arg(t, 10, 10, QLatin1Char(' ')).arg(tr).arg(a, 5, 10, QLatin1Char(' '))
|
||||||
|
.arg(r, 5, 10, QLatin1Char(' '));
|
||||||
|
};
|
||||||
|
const QList<IssueKind> &issueKinds = info.issueKinds;
|
||||||
|
auto toolTip = [issueKinds](const QString &prefix){
|
||||||
|
for (const IssueKind &kind : issueKinds) {
|
||||||
|
if (kind.prefix == prefix)
|
||||||
|
return kind.nicePlural;
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
};
|
||||||
|
int allTotal = 0, allAdded = 0, allRemoved = 0;
|
||||||
|
for (auto issueCount : std::as_const(last.issueCounts)) {
|
||||||
|
allTotal += issueCount.total;
|
||||||
|
allAdded += issueCount.added;
|
||||||
|
allRemoved += issueCount.removed;
|
||||||
|
const QString txt = apply(issueCount.total, issueCount.added, issueCount.removed);
|
||||||
|
const QString currentToolTip = toolTip(issueCount.issueKind);
|
||||||
|
QLabel *label = new QLabel(issueCount.issueKind, this);
|
||||||
|
label->setToolTip(currentToolTip);
|
||||||
|
QLabel *values = new QLabel(txt, this);
|
||||||
|
values->setToolTip(currentToolTip);
|
||||||
|
m_formLayout->addRow(label, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel *label = new QLabel(apply(allTotal, allAdded, allRemoved), this);
|
||||||
|
m_formLayout->addRow(Tr::tr("Total:"), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionOutputPane::AxivionOutputPane(QObject *parent)
|
||||||
|
: Core::IOutputPane(parent)
|
||||||
|
{
|
||||||
|
m_outputWidget = new QStackedWidget;
|
||||||
|
DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget);
|
||||||
|
m_outputWidget->addWidget(dashboardWidget);
|
||||||
|
QTextBrowser *browser = new QTextBrowser(m_outputWidget);
|
||||||
|
m_outputWidget->addWidget(browser);
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionOutputPane::~AxivionOutputPane()
|
||||||
|
{
|
||||||
|
if (!m_outputWidget->parent())
|
||||||
|
delete m_outputWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *AxivionOutputPane::outputWidget(QWidget *parent)
|
||||||
|
{
|
||||||
|
if (m_outputWidget)
|
||||||
|
m_outputWidget->setParent(parent);
|
||||||
|
else
|
||||||
|
QTC_CHECK(false);
|
||||||
|
return m_outputWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QWidget *> AxivionOutputPane::toolBarWidgets() const
|
||||||
|
{
|
||||||
|
QList<QWidget *> buttons;
|
||||||
|
auto showDashboard = new QToolButton(m_outputWidget);
|
||||||
|
showDashboard->setIcon(Utils::Icons::ONLINE_TOOLBAR.icon());
|
||||||
|
showDashboard->setToolTip(Tr::tr("Show dashboard"));
|
||||||
|
connect(showDashboard, &QToolButton::clicked, this, [this]{
|
||||||
|
QTC_ASSERT(m_outputWidget, return);
|
||||||
|
m_outputWidget->setCurrentIndex(0);
|
||||||
|
});
|
||||||
|
buttons.append(showDashboard);
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AxivionOutputPane::displayName() const
|
||||||
|
{
|
||||||
|
return Tr::tr("Axivion");
|
||||||
|
}
|
||||||
|
|
||||||
|
int AxivionOutputPane::priorityInStatusBar() const
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionOutputPane::clearContents()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionOutputPane::setFocus()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionOutputPane::hasFocus() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionOutputPane::canFocus() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionOutputPane::canNavigate() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionOutputPane::canNext() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionOutputPane::canPrevious() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionOutputPane::goToNext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionOutputPane::goToPrev()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionOutputPane::updateDashboard()
|
||||||
|
{
|
||||||
|
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
|
||||||
|
dashboard->updateUi();
|
||||||
|
m_outputWidget->setCurrentIndex(0);
|
||||||
|
if (dashboard->hasProject())
|
||||||
|
flash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionOutputPane::updateAndShowRule(const QString &ruleHtml)
|
||||||
|
{
|
||||||
|
if (auto browser = static_cast<QTextBrowser *>(m_outputWidget->widget(1))) {
|
||||||
|
browser->setText(ruleHtml);
|
||||||
|
if (!ruleHtml.isEmpty()) {
|
||||||
|
m_outputWidget->setCurrentIndex(1);
|
||||||
|
popup(Core::IOutputPane::NoModeSwitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
42
src/plugins/axivion/axivionoutputpane.h
Normal file
42
src/plugins/axivion/axivionoutputpane.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <coreplugin/ioutputpane.h>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QStackedWidget;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class AxivionOutputPane : public Core::IOutputPane
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit AxivionOutputPane(QObject *parent = nullptr);
|
||||||
|
~AxivionOutputPane();
|
||||||
|
|
||||||
|
// IOutputPane interface
|
||||||
|
QWidget *outputWidget(QWidget *parent) override;
|
||||||
|
QList<QWidget *> toolBarWidgets() const override;
|
||||||
|
QString displayName() const override;
|
||||||
|
int priorityInStatusBar() const override;
|
||||||
|
void clearContents() override;
|
||||||
|
void setFocus() override;
|
||||||
|
bool hasFocus() const override;
|
||||||
|
bool canFocus() const override;
|
||||||
|
bool canNavigate() const override;
|
||||||
|
bool canNext() const override;
|
||||||
|
bool canPrevious() const override;
|
||||||
|
void goToNext() override;
|
||||||
|
void goToPrev() override;
|
||||||
|
|
||||||
|
void updateDashboard();
|
||||||
|
void updateAndShowRule(const QString &ruleHtml);
|
||||||
|
private:
|
||||||
|
QStackedWidget *m_outputWidget = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
360
src/plugins/axivion/axivionplugin.cpp
Normal file
360
src/plugins/axivion/axivionplugin.cpp
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionplugin.h"
|
||||||
|
|
||||||
|
#include "axivionoutputpane.h"
|
||||||
|
#include "axivionprojectsettings.h"
|
||||||
|
#include "axivionquery.h"
|
||||||
|
#include "axivionresultparser.h"
|
||||||
|
#include "axivionsettings.h"
|
||||||
|
#include "axivionsettingspage.h"
|
||||||
|
#include "axiviontr.h"
|
||||||
|
|
||||||
|
#include <coreplugin/editormanager/documentmodel.h>
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <coreplugin/messagemanager.h>
|
||||||
|
|
||||||
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
|
||||||
|
#include <projectexplorer/buildsystem.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <projectexplorer/projectpanelfactory.h>
|
||||||
|
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <texteditor/texteditor.h>
|
||||||
|
#include <texteditor/textmark.h>
|
||||||
|
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
|
#ifdef LICENSECHECKER
|
||||||
|
# include <licensechecker/licensecheckerplugin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
constexpr char AxivionTextMarkId[] = "AxivionTextMark";
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class AxivionPluginPrivate : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project);
|
||||||
|
void onStartupProjectChanged();
|
||||||
|
void fetchProjectInfo(const QString &projectName);
|
||||||
|
void handleProjectInfo(const ProjectInfo &info);
|
||||||
|
void handleOpenedDocs(ProjectExplorer::Project *project);
|
||||||
|
void onDocumentOpened(Core::IDocument *doc);
|
||||||
|
void onDocumentClosed(Core::IDocument * doc);
|
||||||
|
void clearAllMarks();
|
||||||
|
void handleIssuesForFile(const IssuesList &issues);
|
||||||
|
void fetchRuleInfo(const QString &id);
|
||||||
|
|
||||||
|
AxivionSettings m_axivionSettings;
|
||||||
|
AxivionSettingsPage m_axivionSettingsPage{&m_axivionSettings};
|
||||||
|
AxivionOutputPane m_axivionOutputPane;
|
||||||
|
QHash<ProjectExplorer::Project *, AxivionProjectSettings *> m_axivionProjectSettings;
|
||||||
|
ProjectInfo m_currentProjectInfo;
|
||||||
|
bool m_runningQuery = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static AxivionPlugin *s_instance = nullptr;
|
||||||
|
static AxivionPluginPrivate *dd = nullptr;
|
||||||
|
|
||||||
|
class AxivionTextMark : public TextEditor::TextMark
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
AxivionTextMark::AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue)
|
||||||
|
: TextEditor::TextMark(filePath, issue.lineNumber, {Tr::tr("Axivion"), AxivionTextMarkId})
|
||||||
|
, m_id(issue.id)
|
||||||
|
{
|
||||||
|
const QString markText = issue.entity.isEmpty() ? issue.message
|
||||||
|
: issue.entity + ": " + issue.message;
|
||||||
|
setToolTip(issue.errorNumber + " " + markText);
|
||||||
|
setPriority(TextEditor::TextMark::NormalPriority);
|
||||||
|
setLineAnnotation(markText);
|
||||||
|
setActionsProvider([this]{
|
||||||
|
auto action = new QAction;
|
||||||
|
action->setIcon(Utils::Icons::INFO.icon());
|
||||||
|
action->setToolTip(Tr::tr("Show rule details"));
|
||||||
|
QObject::connect(action, &QAction::triggered,
|
||||||
|
dd, [this]{ dd->fetchRuleInfo(m_id); });
|
||||||
|
return QList{action};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionPlugin::AxivionPlugin()
|
||||||
|
{
|
||||||
|
s_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionPlugin::~AxivionPlugin()
|
||||||
|
{
|
||||||
|
if (dd && !dd->m_axivionProjectSettings.isEmpty()) {
|
||||||
|
qDeleteAll(dd->m_axivionProjectSettings);
|
||||||
|
dd->m_axivionProjectSettings.clear();
|
||||||
|
}
|
||||||
|
delete dd;
|
||||||
|
dd = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionPlugin *AxivionPlugin::instance()
|
||||||
|
{
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionPlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
||||||
|
{
|
||||||
|
Q_UNUSED(arguments)
|
||||||
|
Q_UNUSED(errorMessage)
|
||||||
|
|
||||||
|
#ifdef LICENSECHECKER
|
||||||
|
LicenseChecker::LicenseCheckerPlugin *licenseChecker
|
||||||
|
= ExtensionSystem::PluginManager::getObject<LicenseChecker::LicenseCheckerPlugin>();
|
||||||
|
|
||||||
|
if (!licenseChecker || !licenseChecker->hasValidLicense() || !licenseChecker->enterpriseFeatures())
|
||||||
|
return true;
|
||||||
|
#endif // LICENSECHECKER
|
||||||
|
|
||||||
|
dd = new AxivionPluginPrivate;
|
||||||
|
dd->m_axivionSettings.fromSettings(Core::ICore::settings());
|
||||||
|
|
||||||
|
auto panelFactory = new ProjectExplorer::ProjectPanelFactory;
|
||||||
|
panelFactory->setPriority(250);
|
||||||
|
panelFactory->setDisplayName(Tr::tr("Axivion"));
|
||||||
|
panelFactory->setCreateWidgetFunction([](ProjectExplorer::Project *project){
|
||||||
|
return new AxivionProjectSettingsWidget(project);
|
||||||
|
});
|
||||||
|
ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
|
||||||
|
connect(ProjectExplorer::ProjectManager::instance(),
|
||||||
|
&ProjectExplorer::ProjectManager::startupProjectChanged,
|
||||||
|
dd, &AxivionPluginPrivate::onStartupProjectChanged);
|
||||||
|
connect(Core::EditorManager::instance(), &Core::EditorManager::documentOpened,
|
||||||
|
dd, &AxivionPluginPrivate::onDocumentOpened);
|
||||||
|
connect(Core::EditorManager::instance(), &Core::EditorManager::documentClosed,
|
||||||
|
dd, &AxivionPluginPrivate::onDocumentClosed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionSettings *AxivionPlugin::settings()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(dd, return nullptr);
|
||||||
|
return &dd->m_axivionSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionProjectSettings *AxivionPlugin::projectSettings(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(project, return nullptr);
|
||||||
|
QTC_ASSERT(dd, return nullptr);
|
||||||
|
|
||||||
|
return dd->projectSettings(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionPlugin::handleCertificateIssue()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(dd, return false);
|
||||||
|
|
||||||
|
const QString serverHost = QUrl(dd->m_axivionSettings.server.dashboard).host();
|
||||||
|
if (QMessageBox::question(Core::ICore::dialogParent(), Tr::tr("Certificate Error"),
|
||||||
|
Tr::tr("Server certificate for %1 cannot be authenticated.\n"
|
||||||
|
"Do you want to disable SSL verification for this server?\n"
|
||||||
|
"Note: This can expose you to man-in-the-middle attack.")
|
||||||
|
.arg(serverHost))
|
||||||
|
!= QMessageBox::Yes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dd->m_axivionSettings.server.validateCert = false;
|
||||||
|
emit s_instance->settingsChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPlugin::fetchProjectInfo(const QString &projectName)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(dd, return);
|
||||||
|
dd->fetchProjectInfo(projectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectInfo AxivionPlugin::projectInfo()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(dd, return {});
|
||||||
|
return dd->m_currentProjectInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionProjectSettings *AxivionPluginPrivate::projectSettings(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
auto &settings = m_axivionProjectSettings[project];
|
||||||
|
if (!settings)
|
||||||
|
settings = new AxivionProjectSettings(project);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::onStartupProjectChanged()
|
||||||
|
{
|
||||||
|
ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
|
||||||
|
if (!project) {
|
||||||
|
clearAllMarks();
|
||||||
|
m_currentProjectInfo = ProjectInfo();
|
||||||
|
m_axivionOutputPane.updateDashboard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AxivionProjectSettings *projSettings = projectSettings(project);
|
||||||
|
fetchProjectInfo(projSettings->dashboardProjectName());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
|
||||||
|
{
|
||||||
|
if (m_runningQuery) { // re-schedule
|
||||||
|
QTimer::singleShot(3000, [this, projectName]{ fetchProjectInfo(projectName); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearAllMarks();
|
||||||
|
if (projectName.isEmpty()) {
|
||||||
|
m_currentProjectInfo = ProjectInfo();
|
||||||
|
m_axivionOutputPane.updateDashboard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_runningQuery = true;
|
||||||
|
|
||||||
|
AxivionQuery query(AxivionQuery::ProjectInfo, {projectName});
|
||||||
|
AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
|
||||||
|
connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
|
||||||
|
handleProjectInfo(ResultParser::parseProjectInfo(result));
|
||||||
|
});
|
||||||
|
connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); });
|
||||||
|
runner->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::fetchRuleInfo(const QString &id)
|
||||||
|
{
|
||||||
|
if (m_runningQuery) {
|
||||||
|
QTimer::singleShot(3000, [this, id]{ fetchRuleInfo(id); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList args = id.split(':');
|
||||||
|
QTC_ASSERT(args.size() == 2, return);
|
||||||
|
m_runningQuery = true;
|
||||||
|
AxivionQuery query(AxivionQuery::RuleInfo, args);
|
||||||
|
AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
|
||||||
|
connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
|
||||||
|
m_runningQuery = false;
|
||||||
|
m_axivionOutputPane.updateAndShowRule(ResultParser::parseRuleInfo(result));
|
||||||
|
});
|
||||||
|
connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); });
|
||||||
|
runner->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::handleOpenedDocs(ProjectExplorer::Project *project)
|
||||||
|
{
|
||||||
|
if (project && ProjectExplorer::ProjectManager::startupProject() != project)
|
||||||
|
return;
|
||||||
|
const QList<Core::IDocument *> openDocuments = Core::DocumentModel::openedDocuments();
|
||||||
|
for (Core::IDocument *doc : openDocuments)
|
||||||
|
onDocumentOpened(doc);
|
||||||
|
if (project)
|
||||||
|
disconnect(ProjectExplorer::ProjectManager::instance(),
|
||||||
|
&ProjectExplorer::ProjectManager::projectFinishedParsing,
|
||||||
|
this, &AxivionPluginPrivate::handleOpenedDocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::clearAllMarks()
|
||||||
|
{
|
||||||
|
const QList<Core::IDocument *> openDocuments = Core::DocumentModel::openedDocuments();
|
||||||
|
for (Core::IDocument *doc : openDocuments)
|
||||||
|
onDocumentClosed(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::handleProjectInfo(const ProjectInfo &info)
|
||||||
|
{
|
||||||
|
m_runningQuery = false;
|
||||||
|
if (!info.error.isEmpty()) {
|
||||||
|
Core::MessageManager::writeFlashing("Axivion: " + info.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentProjectInfo = info;
|
||||||
|
m_axivionOutputPane.updateDashboard();
|
||||||
|
|
||||||
|
if (m_currentProjectInfo.name.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// handle already opened documents
|
||||||
|
if (auto buildSystem = ProjectExplorer::ProjectManager::startupBuildSystem();
|
||||||
|
!buildSystem || !buildSystem->isParsing()) {
|
||||||
|
handleOpenedDocs(nullptr);
|
||||||
|
} else {
|
||||||
|
connect(ProjectExplorer::ProjectManager::instance(),
|
||||||
|
&ProjectExplorer::ProjectManager::projectFinishedParsing,
|
||||||
|
this, &AxivionPluginPrivate::handleOpenedDocs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc)
|
||||||
|
{
|
||||||
|
if (m_currentProjectInfo.name.isEmpty()) // we do not have a project info (yet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
|
||||||
|
if (!doc || !project->isKnownFile(doc->filePath()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Utils::FilePath relative = doc->filePath().relativeChildPath(project->projectDirectory());
|
||||||
|
// for now only style violations
|
||||||
|
AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo.name, "SV",
|
||||||
|
relative.path() } );
|
||||||
|
AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
|
||||||
|
connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
|
||||||
|
handleIssuesForFile(ResultParser::parseIssuesList(result));
|
||||||
|
});
|
||||||
|
connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); });
|
||||||
|
runner->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::onDocumentClosed(Core::IDocument *doc)
|
||||||
|
{
|
||||||
|
const auto document = qobject_cast<TextEditor::TextDocument *>(doc);
|
||||||
|
if (!document)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const TextEditor::TextMarks marks = document->marks();
|
||||||
|
for (auto m : marks) {
|
||||||
|
if (m->category().id == AxivionTextMarkId)
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionPluginPrivate::handleIssuesForFile(const IssuesList &issues)
|
||||||
|
{
|
||||||
|
if (issues.issues.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
|
||||||
|
if (!project)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Utils::FilePath filePath = project->projectDirectory()
|
||||||
|
.pathAppended(issues.issues.first().filePath);
|
||||||
|
|
||||||
|
const Utils::Id axivionId(AxivionTextMarkId);
|
||||||
|
for (const ShortIssue &issue : std::as_const(issues.issues)) {
|
||||||
|
// FIXME the line location can be wrong (even the whole issue could be wrong)
|
||||||
|
// depending on whether this line has been changed since the last axivion run and the
|
||||||
|
// current state of the file - some magic has to happen here
|
||||||
|
new AxivionTextMark(filePath, issue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
41
src/plugins/axivion/axivionplugin.h
Normal file
41
src/plugins/axivion/axivionplugin.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extensionsystem/iplugin.h>
|
||||||
|
|
||||||
|
namespace ProjectExplorer { class Project; }
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class AxivionSettings;
|
||||||
|
class AxivionProjectSettings;
|
||||||
|
class ProjectInfo;
|
||||||
|
|
||||||
|
class AxivionPlugin final : public ExtensionSystem::IPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Axivion.json")
|
||||||
|
|
||||||
|
public:
|
||||||
|
AxivionPlugin();
|
||||||
|
~AxivionPlugin() final;
|
||||||
|
|
||||||
|
static AxivionPlugin *instance();
|
||||||
|
static AxivionSettings *settings();
|
||||||
|
static AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project);
|
||||||
|
|
||||||
|
static bool handleCertificateIssue();
|
||||||
|
static void fetchProjectInfo(const QString &projectName);
|
||||||
|
static ProjectInfo projectInfo();
|
||||||
|
signals:
|
||||||
|
void settingsChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initialize(const QStringList &arguments, QString *errorMessage) final;
|
||||||
|
void extensionsInitialized() final {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
|
|
||||||
184
src/plugins/axivion/axivionprojectsettings.cpp
Normal file
184
src/plugins/axivion/axivionprojectsettings.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionprojectsettings.h"
|
||||||
|
|
||||||
|
#include "axivionplugin.h"
|
||||||
|
#include "axivionquery.h"
|
||||||
|
#include "axivionresultparser.h"
|
||||||
|
#include "axivionsettings.h"
|
||||||
|
#include "axiviontr.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <utils/infolabel.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
const char PSK_PROJECTNAME[] = "Axivion.ProjectName";
|
||||||
|
|
||||||
|
AxivionProjectSettings::AxivionProjectSettings(ProjectExplorer::Project *project)
|
||||||
|
: m_project{project}
|
||||||
|
{
|
||||||
|
load();
|
||||||
|
connect(project, &ProjectExplorer::Project::settingsLoaded,
|
||||||
|
this, &AxivionProjectSettings::load);
|
||||||
|
connect(project, &ProjectExplorer::Project::aboutToSaveSettings,
|
||||||
|
this, &AxivionProjectSettings::save);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettings::load()
|
||||||
|
{
|
||||||
|
m_dashboardProjectName = m_project->namedSettings(PSK_PROJECTNAME).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettings::save()
|
||||||
|
{
|
||||||
|
m_project->setNamedSettings(PSK_PROJECTNAME, m_dashboardProjectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(ProjectExplorer::Project *project,
|
||||||
|
QWidget *parent)
|
||||||
|
: ProjectExplorer::ProjectSettingsWidget{parent}
|
||||||
|
, m_projectSettings(AxivionPlugin::projectSettings(project))
|
||||||
|
, m_globalSettings(AxivionPlugin::settings())
|
||||||
|
{
|
||||||
|
setUseGlobalSettingsCheckBoxVisible(false);
|
||||||
|
setUseGlobalSettingsLabelVisible(true);
|
||||||
|
setGlobalSettingsId("Axivion.Settings.General"); // FIXME move id to constants
|
||||||
|
// setup ui
|
||||||
|
auto verticalLayout = new QVBoxLayout(this);
|
||||||
|
verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
m_linkedProject = new QLabel(this);
|
||||||
|
verticalLayout->addWidget(m_linkedProject);
|
||||||
|
|
||||||
|
m_dashboardProjects = new QTreeWidget(this);
|
||||||
|
m_dashboardProjects->setHeaderHidden(true);
|
||||||
|
m_dashboardProjects->setRootIsDecorated(false);
|
||||||
|
verticalLayout->addWidget(new QLabel(Tr::tr("Dashboard projects:")));
|
||||||
|
verticalLayout->addWidget(m_dashboardProjects);
|
||||||
|
|
||||||
|
m_infoLabel = new Utils::InfoLabel(this);
|
||||||
|
m_infoLabel->setVisible(false);
|
||||||
|
verticalLayout->addWidget(m_infoLabel);
|
||||||
|
|
||||||
|
auto horizontalLayout = new QHBoxLayout;
|
||||||
|
horizontalLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_fetchProjects = new QPushButton(Tr::tr("Fetch Projects"));
|
||||||
|
horizontalLayout->addWidget(m_fetchProjects);
|
||||||
|
m_link = new QPushButton(Tr::tr("Link Project"));
|
||||||
|
m_link->setEnabled(false);
|
||||||
|
horizontalLayout->addWidget(m_link);
|
||||||
|
m_unlink = new QPushButton(Tr::tr("Unlink Project"));
|
||||||
|
m_unlink->setEnabled(false);
|
||||||
|
horizontalLayout->addWidget(m_unlink);
|
||||||
|
verticalLayout->addLayout(horizontalLayout);
|
||||||
|
|
||||||
|
connect(m_dashboardProjects, &QTreeWidget::itemSelectionChanged,
|
||||||
|
this, &AxivionProjectSettingsWidget::updateEnabledStates);
|
||||||
|
connect(m_fetchProjects, &QPushButton::clicked,
|
||||||
|
this, &AxivionProjectSettingsWidget::fetchProjects);
|
||||||
|
connect(m_link, &QPushButton::clicked,
|
||||||
|
this, &AxivionProjectSettingsWidget::linkProject);
|
||||||
|
connect(m_unlink, &QPushButton::clicked,
|
||||||
|
this, &AxivionProjectSettingsWidget::unlinkProject);
|
||||||
|
connect(AxivionPlugin::instance(), &AxivionPlugin::settingsChanged,
|
||||||
|
this, &AxivionProjectSettingsWidget::onSettingsChanged);
|
||||||
|
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::fetchProjects()
|
||||||
|
{
|
||||||
|
m_dashboardProjects->clear();
|
||||||
|
m_fetchProjects->setEnabled(false);
|
||||||
|
m_infoLabel->setVisible(false);
|
||||||
|
// TODO perform query and populate m_dashboardProjects
|
||||||
|
const AxivionQuery query(AxivionQuery::DashboardInfo);
|
||||||
|
AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
|
||||||
|
connect(runner, &AxivionQueryRunner::resultRetrieved,
|
||||||
|
this, [this](const QByteArray &result){
|
||||||
|
onDashboardInfoReceived(ResultParser::parseDashboardInfo(result));
|
||||||
|
});
|
||||||
|
connect(runner, &AxivionQueryRunner::finished, this, [runner]{ runner->deleteLater(); });
|
||||||
|
runner->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::onDashboardInfoReceived(const DashboardInfo &info)
|
||||||
|
{
|
||||||
|
if (!info.error.isEmpty()) {
|
||||||
|
m_infoLabel->setText(info.error);
|
||||||
|
m_infoLabel->setType(Utils::InfoLabel::Error);
|
||||||
|
m_infoLabel->setVisible(true);
|
||||||
|
updateEnabledStates();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Project &project : info.projects)
|
||||||
|
new QTreeWidgetItem(m_dashboardProjects, {project.name});
|
||||||
|
updateEnabledStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::onSettingsChanged()
|
||||||
|
{
|
||||||
|
m_dashboardProjects->clear();
|
||||||
|
m_infoLabel->setVisible(false);
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::linkProject()
|
||||||
|
{
|
||||||
|
const QList<QTreeWidgetItem *> selected = m_dashboardProjects->selectedItems();
|
||||||
|
QTC_ASSERT(selected.size() == 1, return);
|
||||||
|
|
||||||
|
const QString projectName = selected.first()->text(0);
|
||||||
|
m_projectSettings->setDashboardProjectName(projectName);
|
||||||
|
updateUi();
|
||||||
|
AxivionPlugin::fetchProjectInfo(projectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::unlinkProject()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!m_projectSettings->dashboardProjectName().isEmpty(), return);
|
||||||
|
|
||||||
|
m_projectSettings->setDashboardProjectName({});
|
||||||
|
updateUi();
|
||||||
|
AxivionPlugin::fetchProjectInfo({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::updateUi()
|
||||||
|
{
|
||||||
|
const QString projectName = m_projectSettings->dashboardProjectName();
|
||||||
|
if (projectName.isEmpty())
|
||||||
|
m_linkedProject->setText(Tr::tr("This project is not linked to a dashboard project."));
|
||||||
|
else
|
||||||
|
m_linkedProject->setText(Tr::tr("This project is linked to \"%1\".").arg(projectName));
|
||||||
|
updateEnabledStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionProjectSettingsWidget::updateEnabledStates()
|
||||||
|
{
|
||||||
|
const bool hasDashboardSettings = m_globalSettings->curl.isExecutableFile()
|
||||||
|
&& !m_globalSettings->server.dashboard.isEmpty()
|
||||||
|
&& !m_globalSettings->server.token.isEmpty();
|
||||||
|
const bool linked = !m_projectSettings->dashboardProjectName().isEmpty();
|
||||||
|
const bool linkable = m_dashboardProjects->topLevelItemCount()
|
||||||
|
&& !m_dashboardProjects->selectedItems().isEmpty();
|
||||||
|
|
||||||
|
m_fetchProjects->setEnabled(hasDashboardSettings);
|
||||||
|
m_link->setEnabled(!linked && linkable);
|
||||||
|
m_unlink->setEnabled(linked);
|
||||||
|
|
||||||
|
if (!hasDashboardSettings) {
|
||||||
|
m_infoLabel->setText(Tr::tr("Incomplete or misconfigured settings."));
|
||||||
|
m_infoLabel->setType(Utils::InfoLabel::NotOk);
|
||||||
|
m_infoLabel->setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
67
src/plugins/axivion/axivionprojectsettings.h
Normal file
67
src/plugins/axivion/axivionprojectsettings.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "axivionsettings.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/projectsettingswidget.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QLabel;
|
||||||
|
class QPushButton;
|
||||||
|
class QTreeWidget;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace ProjectExplorer { class Project; }
|
||||||
|
|
||||||
|
namespace Utils { class InfoLabel; }
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class DashboardInfo;
|
||||||
|
|
||||||
|
class AxivionProjectSettings : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AxivionProjectSettings(ProjectExplorer::Project *project);
|
||||||
|
|
||||||
|
void setDashboardProjectName(const QString &name) { m_dashboardProjectName = name; }
|
||||||
|
QString dashboardProjectName() const { return m_dashboardProjectName; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load();
|
||||||
|
void save();
|
||||||
|
|
||||||
|
ProjectExplorer::Project *m_project = nullptr;
|
||||||
|
QString m_dashboardProjectName;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AxivionProjectSettingsWidget : public ProjectExplorer::ProjectSettingsWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AxivionProjectSettingsWidget(ProjectExplorer::Project *project,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void fetchProjects();
|
||||||
|
void onDashboardInfoReceived(const DashboardInfo &info);
|
||||||
|
void onSettingsChanged();
|
||||||
|
void linkProject();
|
||||||
|
void unlinkProject();
|
||||||
|
void updateUi();
|
||||||
|
void updateEnabledStates();
|
||||||
|
|
||||||
|
AxivionProjectSettings *m_projectSettings = nullptr;
|
||||||
|
AxivionSettings *m_globalSettings;
|
||||||
|
QLabel *m_linkedProject = nullptr;
|
||||||
|
QTreeWidget *m_dashboardProjects = nullptr;
|
||||||
|
QPushButton *m_fetchProjects = nullptr;
|
||||||
|
QPushButton *m_link = nullptr;
|
||||||
|
QPushButton *m_unlink = nullptr;
|
||||||
|
Utils::InfoLabel *m_infoLabel = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
97
src/plugins/axivion/axivionquery.cpp
Normal file
97
src/plugins/axivion/axivionquery.cpp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionquery.h"
|
||||||
|
|
||||||
|
#include "axivionplugin.h"
|
||||||
|
#include "axivionsettings.h"
|
||||||
|
|
||||||
|
#include <utils/processenums.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
AxivionQuery::AxivionQuery(QueryType type, const QStringList ¶meters)
|
||||||
|
: m_type(type)
|
||||||
|
, m_parameters(parameters)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AxivionQuery::toString() const
|
||||||
|
{
|
||||||
|
QString query = "/api"; // common for all except RuleInfo
|
||||||
|
switch (m_type) {
|
||||||
|
case NoQuery:
|
||||||
|
return {};
|
||||||
|
case DashboardInfo:
|
||||||
|
return query;
|
||||||
|
case ProjectInfo:
|
||||||
|
QTC_ASSERT(m_parameters.size() == 1, return {});
|
||||||
|
query += "/projects/" + QUrl::toPercentEncoding(m_parameters.first());
|
||||||
|
return query;
|
||||||
|
case IssuesForFileList:
|
||||||
|
QTC_ASSERT(m_parameters.size() == 3, return {});
|
||||||
|
// FIXME shall we validate the kind? (some kinds do not support path filter)
|
||||||
|
query += "/projects/" + QUrl::toPercentEncoding(m_parameters.first())
|
||||||
|
+ "/issues?kind=" + m_parameters.at(1) + "&filter_path="
|
||||||
|
+ QUrl::toPercentEncoding(m_parameters.at(2)) + "&format=csv";
|
||||||
|
return query;
|
||||||
|
case RuleInfo:
|
||||||
|
QTC_ASSERT(m_parameters.size() == 2, return {});
|
||||||
|
query = "/projects/" + QUrl::toPercentEncoding(m_parameters.first())
|
||||||
|
+ "/issues/" + m_parameters.at(1) + "/rule";
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionQueryRunner::AxivionQueryRunner(const AxivionQuery &query, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
const AxivionSettings *settings = AxivionPlugin::settings();
|
||||||
|
const AxivionServer server = settings->server;
|
||||||
|
|
||||||
|
QStringList args = server.curlArguments();
|
||||||
|
args << "-i";
|
||||||
|
args << "--header" << "Authorization: AxToken " + server.token;
|
||||||
|
|
||||||
|
QString url = server.dashboard;
|
||||||
|
while (url.endsWith('/')) url.chop(1);
|
||||||
|
url += query.toString();
|
||||||
|
args << url;
|
||||||
|
|
||||||
|
m_process.setCommand({settings->curl, args});
|
||||||
|
connect(&m_process, &Process::done, this, [this]{
|
||||||
|
if (m_process.result() != ProcessResult::FinishedWithSuccess) {
|
||||||
|
const int exitCode = m_process.exitCode();
|
||||||
|
if (m_process.exitStatus() == QProcess::NormalExit
|
||||||
|
&& (exitCode == 35 || exitCode == 60)
|
||||||
|
&& AxivionPlugin::handleCertificateIssue()) {
|
||||||
|
// prepend -k for re-requesting same query
|
||||||
|
CommandLine cmdline = m_process.commandLine();
|
||||||
|
cmdline.prependArgs({"-k"});
|
||||||
|
m_process.close();
|
||||||
|
m_process.setCommand(cmdline);
|
||||||
|
start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit resultRetrieved(m_process.readAllRawStandardError());
|
||||||
|
} else {
|
||||||
|
emit resultRetrieved(m_process.readAllRawStandardOutput());
|
||||||
|
}
|
||||||
|
emit finished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionQueryRunner::start()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!m_process.isRunning(), return);
|
||||||
|
m_process.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
40
src/plugins/axivion/axivionquery.h
Normal file
40
src/plugins/axivion/axivionquery.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/process.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class AxivionQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum QueryType {NoQuery, DashboardInfo, ProjectInfo, IssuesForFileList, RuleInfo};
|
||||||
|
explicit AxivionQuery(QueryType type, const QStringList ¶meters = {});
|
||||||
|
|
||||||
|
QString toString() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QueryType m_type = NoQuery;
|
||||||
|
QStringList m_parameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AxivionQueryRunner : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit AxivionQueryRunner(const AxivionQuery &query, QObject *parent = nullptr);
|
||||||
|
void start();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
void resultRetrieved(const QByteArray &json);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Utils::Process m_process;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
347
src/plugins/axivion/axivionresultparser.cpp
Normal file
347
src/plugins/axivion/axivionresultparser.cpp
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionresultparser.h"
|
||||||
|
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
static std::pair<QByteArray, QByteArray> splitHeaderAndBody(const QByteArray &input)
|
||||||
|
{
|
||||||
|
QByteArray header;
|
||||||
|
QByteArray json;
|
||||||
|
int emptyLine = input.indexOf("\r\n\r\n"); // we always get \r\n as line separator
|
||||||
|
if (emptyLine != -1) {
|
||||||
|
header = input.left(emptyLine);
|
||||||
|
json = input.mid(emptyLine + 4);
|
||||||
|
} else {
|
||||||
|
json = input;
|
||||||
|
}
|
||||||
|
return {header, json};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int httpStatus(const QByteArray &header)
|
||||||
|
{
|
||||||
|
int firstHeaderEnd = header.indexOf("\r\n");
|
||||||
|
if (firstHeaderEnd == -1)
|
||||||
|
return 600; // unexpected header
|
||||||
|
const QString firstLine = QString::fromUtf8(header.first(firstHeaderEnd));
|
||||||
|
static const QRegularExpression regex(R"(^HTTP/\d\.\d (\d{3}) .*$)");
|
||||||
|
const QRegularExpressionMatch match = regex.match(firstLine);
|
||||||
|
return match.hasMatch() ? match.captured(1).toInt() : 601;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BaseResult prehandleHeader(const QByteArray &header, const QByteArray &body)
|
||||||
|
{
|
||||||
|
BaseResult result;
|
||||||
|
if (header.isEmpty()) {
|
||||||
|
result.error = QString::fromUtf8(body); // we likely had a curl problem
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int status = httpStatus(header);
|
||||||
|
if ((status > 399) || (status > 299 && body.isEmpty())) { // FIXME handle some explicitly?
|
||||||
|
const QString statusStr = QString::number(status);
|
||||||
|
if (body.isEmpty() || body.startsWith('<')) // likely an html response or redirect
|
||||||
|
result.error = QLatin1String("(%1)").arg(statusStr);
|
||||||
|
else
|
||||||
|
result.error = QLatin1String("%1 (%2)").arg(QString::fromUtf8(body)).arg(statusStr);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<BaseResult, QJsonDocument> prehandleHeaderAndBody(const QByteArray &header,
|
||||||
|
const QByteArray &body)
|
||||||
|
{
|
||||||
|
BaseResult result = prehandleHeader(header, body);
|
||||||
|
if (!result.error.isEmpty())
|
||||||
|
return {result, {}};
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(body, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
result.error = error.errorString();
|
||||||
|
return {result, doc};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
result.error = "Not an object.";
|
||||||
|
return {result, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {result, doc};
|
||||||
|
}
|
||||||
|
|
||||||
|
static User::UserType userTypeForString(const QString &type)
|
||||||
|
{
|
||||||
|
if (type == "DASHBOARD_USER")
|
||||||
|
return User::Dashboard;
|
||||||
|
if (type == "VIRTUAL_USER")
|
||||||
|
return User::Virtual;
|
||||||
|
return User::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
static User userFromJson(const QJsonObject &object)
|
||||||
|
{
|
||||||
|
User result;
|
||||||
|
if (object.isEmpty()) {
|
||||||
|
result.error = "Not a user object.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.name = object.value("name").toString();
|
||||||
|
result.displayName = object.value("displayName").toString();
|
||||||
|
result.type = userTypeForString(object.value("type").toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList<User> usersFromJson(const QJsonArray &array)
|
||||||
|
{
|
||||||
|
QList<User> result;
|
||||||
|
for (const QJsonValue &value : array) {
|
||||||
|
User user = userFromJson(value.toObject());
|
||||||
|
if (!user.error.isEmpty()) // add this error to result.error?
|
||||||
|
continue;
|
||||||
|
result.append(user);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IssueCount issueCountFromJson(const QJsonObject &object)
|
||||||
|
{
|
||||||
|
IssueCount result;
|
||||||
|
if (object.isEmpty()) {
|
||||||
|
result.error = "Not an issue count object.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.added = object.value("Added").toInt();
|
||||||
|
result.removed = object.value("Removed").toInt();
|
||||||
|
result.total = object.value("Total").toInt();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList<IssueCount> issueCountsFromJson(const QJsonObject &object)
|
||||||
|
{
|
||||||
|
QList<IssueCount> result;
|
||||||
|
|
||||||
|
const QStringList keys = object.keys();
|
||||||
|
for (const QString &k : keys) {
|
||||||
|
IssueCount issue = issueCountFromJson(object.value(k).toObject());
|
||||||
|
if (!issue.error.isEmpty()) // add this error to result.error?
|
||||||
|
continue;
|
||||||
|
issue.issueKind = k;
|
||||||
|
result.append(issue);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ResultVersion versionFromJson(const QJsonObject &object)
|
||||||
|
{
|
||||||
|
ResultVersion result;
|
||||||
|
if (object.isEmpty()) {
|
||||||
|
result.error = "Not a version object.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const QJsonValue issuesValue = object.value("issueCounts");
|
||||||
|
if (!issuesValue.isObject()) {
|
||||||
|
result.error = "Not an object (issueCounts).";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.issueCounts = issueCountsFromJson(issuesValue.toObject());
|
||||||
|
result.timeStamp = object.value("date").toString();
|
||||||
|
result.name = object.value("name").toString();
|
||||||
|
result.linesOfCode = object.value("linesOfCode").toInt();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList<ResultVersion> versionsFromJson(const QJsonArray &array)
|
||||||
|
{
|
||||||
|
QList<ResultVersion> result;
|
||||||
|
for (const QJsonValue &value : array) {
|
||||||
|
ResultVersion version = versionFromJson(value.toObject());
|
||||||
|
if (!version.error.isEmpty()) // add this error to result.error?
|
||||||
|
continue;
|
||||||
|
result.append(version);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IssueKind issueKindFromJson(const QJsonObject &object)
|
||||||
|
{
|
||||||
|
IssueKind result;
|
||||||
|
if (object.isEmpty()) {
|
||||||
|
result.error = "Not an issue kind object.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.prefix = object.value("prefix").toString();
|
||||||
|
result.niceSingular = object.value("niceSingularName").toString();
|
||||||
|
result.nicePlural = object.value("nicePluralName").toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList<IssueKind> issueKindsFromJson(const QJsonArray &array)
|
||||||
|
{
|
||||||
|
QList<IssueKind> result;
|
||||||
|
for (const QJsonValue &value : array) {
|
||||||
|
IssueKind kind = issueKindFromJson(value.toObject());
|
||||||
|
if (!kind.error.isEmpty()) // add this error to result.error?
|
||||||
|
continue;
|
||||||
|
result.append(kind);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ResultParser {
|
||||||
|
|
||||||
|
DashboardInfo parseDashboardInfo(const QByteArray &input)
|
||||||
|
{
|
||||||
|
DashboardInfo result;
|
||||||
|
|
||||||
|
auto [header, body] = splitHeaderAndBody(input);
|
||||||
|
auto [error, doc] = prehandleHeaderAndBody(header, body);
|
||||||
|
if (!error.error.isEmpty()) {
|
||||||
|
result.error = error.error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const QJsonObject object = doc.object();
|
||||||
|
result.mainUrl = object.value("mainUrl").toString();
|
||||||
|
|
||||||
|
if (!object.contains("projects")) {
|
||||||
|
result.error = "Missing projects information.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const QJsonValue projects = object.value("projects");
|
||||||
|
if (!projects.isArray()) {
|
||||||
|
result.error = "Projects information not an array.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const QJsonArray array = projects.toArray();
|
||||||
|
for (const QJsonValue &val : array) {
|
||||||
|
if (!val.isObject())
|
||||||
|
continue;
|
||||||
|
const QJsonObject projectObject = val.toObject();
|
||||||
|
Project project;
|
||||||
|
project.name = projectObject.value("name").toString();
|
||||||
|
project.url = projectObject.value("url").toString();
|
||||||
|
if (project.name.isEmpty() || project.url.isEmpty())
|
||||||
|
continue;
|
||||||
|
result.projects.append(project);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectInfo parseProjectInfo(const QByteArray &input)
|
||||||
|
{
|
||||||
|
ProjectInfo result;
|
||||||
|
|
||||||
|
auto [header, body] = splitHeaderAndBody(input);
|
||||||
|
auto [error, doc] = prehandleHeaderAndBody(header, body);
|
||||||
|
if (!error.error.isEmpty()) {
|
||||||
|
result.error = error.error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject object = doc.object();
|
||||||
|
result.name = object.value("name").toString();
|
||||||
|
|
||||||
|
const QJsonValue usersValue = object.value("users");
|
||||||
|
if (!usersValue.isArray()) {
|
||||||
|
result.error = "Malformed json response (users).";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.users = usersFromJson(usersValue.toArray());
|
||||||
|
|
||||||
|
const QJsonValue versionsValue = object.value("versions");
|
||||||
|
if (!versionsValue.isArray()) {
|
||||||
|
result.error = "Malformed json response (versions).";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.versions = versionsFromJson(versionsValue.toArray());
|
||||||
|
|
||||||
|
const QJsonValue issueKindsValue = object.value("issueKinds");
|
||||||
|
if (!issueKindsValue.isArray()) {
|
||||||
|
result.error = "Malformed json response (issueKinds).";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.issueKinds = issueKindsFromJson(issueKindsValue.toArray());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QRegularExpression issueCsvLineRegex(const QByteArray &firstCsvLine)
|
||||||
|
{
|
||||||
|
QString pattern = "^";
|
||||||
|
for (const QByteArray &part : firstCsvLine.split(',')) {
|
||||||
|
const QString cleaned = QString::fromUtf8(part).remove(' ').chopped(1).mid(1);
|
||||||
|
pattern.append(QString("\"(?<" + cleaned + ">.*)\","));
|
||||||
|
}
|
||||||
|
pattern.chop(1); // remove last comma
|
||||||
|
pattern.append('$');
|
||||||
|
const QRegularExpression regex(pattern);
|
||||||
|
QTC_ASSERT(regex.isValid(), return {});
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseCsvIssue(const QByteArray &csv, QList<ShortIssue> *issues)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(issues, return);
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
std::optional<QRegularExpression> regex;
|
||||||
|
for (auto &line : csv.split('\n')) {
|
||||||
|
if (first) {
|
||||||
|
regex.emplace(issueCsvLineRegex(line));
|
||||||
|
first = false;
|
||||||
|
if (regex.value().pattern().isEmpty())
|
||||||
|
return;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.isEmpty())
|
||||||
|
continue;
|
||||||
|
const QRegularExpressionMatch match = regex->match(QString::fromUtf8(line));
|
||||||
|
QTC_ASSERT(match.hasMatch(), continue);
|
||||||
|
// FIXME: some of these are not present for all issue kinds! Limited to SV for now
|
||||||
|
ShortIssue issue;
|
||||||
|
issue.id = match.captured("Id");
|
||||||
|
issue.state = match.captured("State");
|
||||||
|
issue.errorNumber = match.captured("ErrorNumber");
|
||||||
|
issue.message = match.captured("Message");
|
||||||
|
issue.entity = match.captured("Entity");
|
||||||
|
issue.filePath = match.captured("Path");
|
||||||
|
issue.severity = match.captured("Severity");
|
||||||
|
issue.lineNumber = match.captured("Line").toInt();
|
||||||
|
issues->append(issue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IssuesList parseIssuesList(const QByteArray &input)
|
||||||
|
{
|
||||||
|
IssuesList result;
|
||||||
|
|
||||||
|
auto [header, body] = splitHeaderAndBody(input);
|
||||||
|
BaseResult headerResult = prehandleHeader(header, body);
|
||||||
|
if (!headerResult.error.isEmpty()) {
|
||||||
|
result.error = headerResult.error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
parseCsvIssue(body, &result.issues);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString parseRuleInfo(const QByteArray &input) // html result!
|
||||||
|
{
|
||||||
|
auto [header, body] = splitHeaderAndBody(input);
|
||||||
|
BaseResult headerResult = prehandleHeader(header, body);
|
||||||
|
if (!headerResult.error.isEmpty())
|
||||||
|
return QString();
|
||||||
|
return QString::fromLocal8Bit(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // ResultParser
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
101
src/plugins/axivion/axivionresultparser.h
Normal file
101
src/plugins/axivion/axivionresultparser.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString error;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Project : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
QString url;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DashboardInfo : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString mainUrl;
|
||||||
|
QList<Project> projects;
|
||||||
|
};
|
||||||
|
|
||||||
|
class User : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
QString displayName;
|
||||||
|
enum UserType { Dashboard, Virtual, Unknown } type;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IssueKind : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString prefix;
|
||||||
|
QString niceSingular;
|
||||||
|
QString nicePlural;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IssueCount : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString issueKind;
|
||||||
|
int total = 0;
|
||||||
|
int added = 0;
|
||||||
|
int removed = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResultVersion : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
QString timeStamp;
|
||||||
|
QList<IssueCount> issueCounts;
|
||||||
|
int linesOfCode = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProjectInfo : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
QList<User> users;
|
||||||
|
QList<ResultVersion> versions;
|
||||||
|
QList<IssueKind> issueKinds;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShortIssue : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString id;
|
||||||
|
QString state;
|
||||||
|
QString errorNumber;
|
||||||
|
QString message;
|
||||||
|
QString entity;
|
||||||
|
QString filePath;
|
||||||
|
QString severity;
|
||||||
|
int lineNumber = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IssuesList : public BaseResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QList<ShortIssue> issues;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ResultParser {
|
||||||
|
|
||||||
|
DashboardInfo parseDashboardInfo(const QByteArray &input);
|
||||||
|
ProjectInfo parseProjectInfo(const QByteArray &input);
|
||||||
|
IssuesList parseIssuesList(const QByteArray &input);
|
||||||
|
QString parseRuleInfo(const QByteArray &input);
|
||||||
|
|
||||||
|
} // ResultParser
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
131
src/plugins/axivion/axivionsettings.cpp
Normal file
131
src/plugins/axivion/axivionsettings.cpp
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionsettings.h"
|
||||||
|
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
const char curlKeyC[] = "Curl";
|
||||||
|
|
||||||
|
AxivionServer::AxivionServer(const Utils::Id &id, const QString &dashboard,
|
||||||
|
const QString &description, const QString &token)
|
||||||
|
: id(id)
|
||||||
|
, dashboard(dashboard)
|
||||||
|
, description(description)
|
||||||
|
, token(token)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionServer::operator==(const AxivionServer &other) const
|
||||||
|
{
|
||||||
|
return id == other.id && dashboard == other.dashboard
|
||||||
|
&& description == other.description && token == other.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AxivionServer::operator!=(const AxivionServer &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject AxivionServer::toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject result;
|
||||||
|
result.insert("id", id.toString());
|
||||||
|
result.insert("dashboard", dashboard);
|
||||||
|
result.insert("description", description);
|
||||||
|
result.insert("token", token);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionServer AxivionServer::fromJson(const QJsonObject &json)
|
||||||
|
{
|
||||||
|
const AxivionServer invalidServer;
|
||||||
|
const QJsonValue id = json.value("id");
|
||||||
|
if (id == QJsonValue::Undefined)
|
||||||
|
return invalidServer;
|
||||||
|
const QJsonValue dashboard = json.value("dashboard");
|
||||||
|
if (dashboard == QJsonValue::Undefined)
|
||||||
|
return invalidServer;
|
||||||
|
const QJsonValue description = json.value("description");
|
||||||
|
if (description == QJsonValue::Undefined)
|
||||||
|
return invalidServer;
|
||||||
|
const QJsonValue token = json.value("token");
|
||||||
|
if (token == QJsonValue::Undefined)
|
||||||
|
return invalidServer;
|
||||||
|
return { Utils::Id::fromString(id.toString()), dashboard.toString(),
|
||||||
|
description.toString(), token.toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList AxivionServer::curlArguments() const
|
||||||
|
{
|
||||||
|
QStringList args { "-sS" }; // silent, but show error
|
||||||
|
if (dashboard.startsWith("https://") && !validateCert)
|
||||||
|
args << "-k";
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionSettings::AxivionSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static Utils::FilePath tokensFilePath(const QSettings *s)
|
||||||
|
{
|
||||||
|
return Utils::FilePath::fromString(s->fileName()).parentDir()
|
||||||
|
.pathAppended("qtcreator/axivion.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeTokenFile(const Utils::FilePath &filePath, const AxivionServer &server)
|
||||||
|
{
|
||||||
|
QJsonDocument doc;
|
||||||
|
doc.setObject(server.toJson());
|
||||||
|
// FIXME error handling?
|
||||||
|
filePath.writeFileContents(doc.toJson());
|
||||||
|
filePath.setPermissions(QFile::ReadUser | QFile::WriteUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AxivionServer readTokenFile(const Utils::FilePath &filePath)
|
||||||
|
{
|
||||||
|
if (!filePath.exists())
|
||||||
|
return {};
|
||||||
|
Utils::expected_str<QByteArray> contents = filePath.fileContents();
|
||||||
|
if (!contents)
|
||||||
|
return {};
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(*contents);
|
||||||
|
if (!doc.isObject())
|
||||||
|
return {};
|
||||||
|
return AxivionServer::fromJson(doc.object());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionSettings::toSettings(QSettings *s) const
|
||||||
|
{
|
||||||
|
writeTokenFile(tokensFilePath(s), server);
|
||||||
|
|
||||||
|
s->beginGroup("Axivion");
|
||||||
|
s->setValue(curlKeyC, curl.toVariant());
|
||||||
|
s->endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionSettings::fromSettings(QSettings *s)
|
||||||
|
{
|
||||||
|
s->beginGroup("Axivion");
|
||||||
|
curl = Utils::FilePath::fromVariant(curlKeyC);
|
||||||
|
s->endGroup();
|
||||||
|
|
||||||
|
server = readTokenFile(tokensFilePath(s));
|
||||||
|
|
||||||
|
if (curl.isEmpty() || !curl.exists()) {
|
||||||
|
const QString curlPath = QStandardPaths::findExecutable(
|
||||||
|
Utils::HostOsInfo::withExecutableSuffix("curl"));
|
||||||
|
if (!curlPath.isEmpty())
|
||||||
|
curl = Utils::FilePath::fromString(curlPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
51
src/plugins/axivion/axivionsettings.h
Normal file
51
src/plugins/axivion/axivionsettings.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
#include <utils/id.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QJsonObject;
|
||||||
|
class QSettings;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class AxivionServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AxivionServer() = default;
|
||||||
|
AxivionServer(const Utils::Id &id, const QString &dashboardUrl,
|
||||||
|
const QString &description, const QString &token);
|
||||||
|
|
||||||
|
bool operator==(const AxivionServer &other) const;
|
||||||
|
bool operator!=(const AxivionServer &other) const;
|
||||||
|
|
||||||
|
QJsonObject toJson() const;
|
||||||
|
static AxivionServer fromJson(const QJsonObject &json);
|
||||||
|
QStringList curlArguments() const;
|
||||||
|
|
||||||
|
Utils::Id id;
|
||||||
|
QString dashboard;
|
||||||
|
QString description;
|
||||||
|
QString token;
|
||||||
|
|
||||||
|
bool validateCert = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AxivionSettings
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AxivionSettings();
|
||||||
|
void toSettings(QSettings *s) const;
|
||||||
|
void fromSettings(QSettings *s);
|
||||||
|
|
||||||
|
AxivionServer server; // shall we have more than one?
|
||||||
|
Utils::FilePath curl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
208
src/plugins/axivion/axivionsettingspage.cpp
Normal file
208
src/plugins/axivion/axivionsettingspage.cpp
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#include "axivionsettingspage.h"
|
||||||
|
|
||||||
|
#include "axivionplugin.h"
|
||||||
|
#include "axivionsettings.h"
|
||||||
|
#include "axiviontr.h"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <utils/layoutbuilder.h>
|
||||||
|
#include <utils/pathchooser.h>
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
// may allow some invalid, but does some minimal check for legality
|
||||||
|
static bool hostValid(const QString &host)
|
||||||
|
{
|
||||||
|
static const QRegularExpression ip(R"(^(\d+).(\d+).(\d+).(\d+)$)");
|
||||||
|
static const QRegularExpression dn(R"(^([a-zA-Z0-9][a-zA-Z0-9-]+\.)+[a-zA-Z0-9][a-zA-Z0-9-]+$)");
|
||||||
|
const QRegularExpressionMatch match = ip.match(host);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
for (int i = 1; i < 5; ++i) {
|
||||||
|
int val = match.captured(i).toInt();
|
||||||
|
if (val < 0 || val > 255)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (host == "localhost") || dn.match(host).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isUrlValid(const QString &in)
|
||||||
|
{
|
||||||
|
const QUrl url(in);
|
||||||
|
return hostValid(url.host()) && (url.scheme() == "https" || url.scheme() == "http");
|
||||||
|
}
|
||||||
|
|
||||||
|
DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, m_mode(mode)
|
||||||
|
{
|
||||||
|
auto labelStyle = mode == Display ? StringAspect::LabelDisplay : StringAspect::LineEditDisplay;
|
||||||
|
m_dashboardUrl.setLabelText(Tr::tr("Dashboard URL:"));
|
||||||
|
m_dashboardUrl.setDisplayStyle(labelStyle);
|
||||||
|
m_dashboardUrl.setValidationFunction([](FancyLineEdit *edit, QString *){
|
||||||
|
return isUrlValid(edit->text());
|
||||||
|
});
|
||||||
|
m_description.setLabelText(Tr::tr("Description:"));
|
||||||
|
m_description.setDisplayStyle(labelStyle);
|
||||||
|
m_description.setPlaceHolderText(Tr::tr("Non-empty description"));
|
||||||
|
|
||||||
|
m_token.setLabelText(Tr::tr("Access token:"));
|
||||||
|
m_token.setDisplayStyle(labelStyle);
|
||||||
|
m_token.setPlaceHolderText(Tr::tr("IDE Access Token"));
|
||||||
|
m_token.setVisible(mode == Edit);
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Form {
|
||||||
|
m_dashboardUrl,
|
||||||
|
m_description,
|
||||||
|
m_token,
|
||||||
|
mode == Edit ? normalMargin : noMargin
|
||||||
|
}
|
||||||
|
}.attachTo(this);
|
||||||
|
|
||||||
|
auto checkValidity = [this] {
|
||||||
|
bool old = m_valid;
|
||||||
|
m_valid = isValid();
|
||||||
|
if (old != m_valid)
|
||||||
|
emit validChanged(m_valid);
|
||||||
|
};
|
||||||
|
if (mode == Edit) {
|
||||||
|
connect(&m_dashboardUrl, &StringAspect::valueChanged,
|
||||||
|
this, checkValidity);
|
||||||
|
connect(&m_description, &StringAspect::valueChanged,
|
||||||
|
this, checkValidity);
|
||||||
|
connect(&m_token, &StringAspect::valueChanged,
|
||||||
|
this, checkValidity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionServer DashboardSettingsWidget::dashboardServer() const
|
||||||
|
{
|
||||||
|
AxivionServer result;
|
||||||
|
if (m_id.isValid())
|
||||||
|
result.id = m_id;
|
||||||
|
else
|
||||||
|
result.id = m_mode == Edit ? Utils::Id::fromName(QUuid::createUuid().toByteArray()) : m_id;
|
||||||
|
result.dashboard = m_dashboardUrl.value();
|
||||||
|
result.description = m_description.value();
|
||||||
|
result.token = m_token.value();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DashboardSettingsWidget::setDashboardServer(const AxivionServer &server)
|
||||||
|
{
|
||||||
|
m_id = server.id;
|
||||||
|
m_dashboardUrl.setValue(server.dashboard);
|
||||||
|
m_description.setValue(server.description);
|
||||||
|
m_token.setValue(server.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DashboardSettingsWidget::isValid() const
|
||||||
|
{
|
||||||
|
return !m_token.value().isEmpty() && !m_description.value().isEmpty()
|
||||||
|
&& isUrlValid(m_dashboardUrl.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
class AxivionSettingsWidget : public Core::IOptionsPageWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AxivionSettingsWidget(AxivionSettings *settings);
|
||||||
|
|
||||||
|
void apply() override;
|
||||||
|
private:
|
||||||
|
void showEditServerDialog();
|
||||||
|
|
||||||
|
AxivionSettings *m_settings;
|
||||||
|
|
||||||
|
Utils::StringAspect m_curlPC;
|
||||||
|
DashboardSettingsWidget *m_dashboardDisplay = nullptr;
|
||||||
|
QPushButton *m_edit = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
AxivionSettingsWidget::AxivionSettingsWidget(AxivionSettings *settings)
|
||||||
|
: m_settings(settings)
|
||||||
|
{
|
||||||
|
using namespace Layouting;
|
||||||
|
|
||||||
|
m_dashboardDisplay = new DashboardSettingsWidget(DashboardSettingsWidget::Display, this);
|
||||||
|
m_dashboardDisplay->setDashboardServer(m_settings->server);
|
||||||
|
m_edit = new QPushButton(Tr::tr("Edit..."), this);
|
||||||
|
m_curlPC.setLabelText(Tr::tr("curl:"));
|
||||||
|
m_curlPC.setDisplayStyle(StringAspect::PathChooserDisplay);
|
||||||
|
m_curlPC.setExpectedKind(PathChooser::ExistingCommand);
|
||||||
|
m_curlPC.setFilePath(m_settings->curl);
|
||||||
|
Grid {
|
||||||
|
Form {
|
||||||
|
m_dashboardDisplay, br,
|
||||||
|
m_curlPC, br,
|
||||||
|
}, Column { m_edit, st }
|
||||||
|
}.attachTo(this);
|
||||||
|
|
||||||
|
connect(m_edit, &QPushButton::clicked, this, &AxivionSettingsWidget::showEditServerDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionSettingsWidget::apply()
|
||||||
|
{
|
||||||
|
m_settings->server = m_dashboardDisplay->dashboardServer();
|
||||||
|
m_settings->curl = m_curlPC.filePath();
|
||||||
|
m_settings->toSettings(Core::ICore::settings());
|
||||||
|
emit AxivionPlugin::instance()->settingsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxivionSettingsWidget::showEditServerDialog()
|
||||||
|
{
|
||||||
|
const AxivionServer old = m_dashboardDisplay->dashboardServer();
|
||||||
|
QDialog d;
|
||||||
|
d.setWindowTitle(Tr::tr("Edit Dashboard Configuration"));
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
DashboardSettingsWidget *dashboardWidget = new DashboardSettingsWidget(DashboardSettingsWidget::Edit, this);
|
||||||
|
dashboardWidget->setDashboardServer(old);
|
||||||
|
layout->addWidget(dashboardWidget);
|
||||||
|
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok, this);
|
||||||
|
auto ok = buttons->button(QDialogButtonBox::Ok);
|
||||||
|
ok->setEnabled(m_dashboardDisplay->isValid());
|
||||||
|
connect(buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, &d, &QDialog::reject);
|
||||||
|
connect(ok, &QPushButton::clicked, &d, &QDialog::accept);
|
||||||
|
connect(dashboardWidget, &DashboardSettingsWidget::validChanged,
|
||||||
|
ok, &QPushButton::setEnabled);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
d.setLayout(layout);
|
||||||
|
d.resize(500, 200);
|
||||||
|
|
||||||
|
if (d.exec() != QDialog::Accepted)
|
||||||
|
return;
|
||||||
|
if (dashboardWidget->isValid()) {
|
||||||
|
const AxivionServer server = dashboardWidget->dashboardServer();
|
||||||
|
if (server != old)
|
||||||
|
m_dashboardDisplay->setDashboardServer(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AxivionSettingsPage::AxivionSettingsPage(AxivionSettings *settings)
|
||||||
|
: m_settings(settings)
|
||||||
|
{
|
||||||
|
setId("Axivion.Settings.General");
|
||||||
|
setDisplayName(Tr::tr("General"));
|
||||||
|
setCategory("XY.Axivion");
|
||||||
|
setDisplayCategory(Tr::tr("Axivion"));
|
||||||
|
setCategoryIconPath(":/axivion/images/axivion.png");
|
||||||
|
setWidgetCreator([this] { return new AxivionSettingsWidget(m_settings); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
52
src/plugins/axivion/axivionsettingspage.h
Normal file
52
src/plugins/axivion/axivionsettingspage.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
|
#include <utils/aspects.h>
|
||||||
|
#include <utils/id.h>
|
||||||
|
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Axivion::Internal {
|
||||||
|
|
||||||
|
class AxivionServer;
|
||||||
|
class AxivionSettings;
|
||||||
|
class AxivionSettingsWidget;
|
||||||
|
|
||||||
|
class DashboardSettingsWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Mode { Display, Edit };
|
||||||
|
explicit DashboardSettingsWidget(Mode m = Display, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
AxivionServer dashboardServer() const;
|
||||||
|
void setDashboardServer(const AxivionServer &server);
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void validChanged(bool valid);
|
||||||
|
private:
|
||||||
|
Mode m_mode = Display;
|
||||||
|
Utils::Id m_id;
|
||||||
|
Utils::StringAspect m_dashboardUrl;
|
||||||
|
Utils::StringAspect m_description;
|
||||||
|
Utils::StringAspect m_token;
|
||||||
|
bool m_valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AxivionSettingsPage : public Core::IOptionsPage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AxivionSettingsPage(AxivionSettings *settings);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AxivionSettings *m_settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion::Internal
|
||||||
15
src/plugins/axivion/axiviontr.h
Normal file
15
src/plugins/axivion/axiviontr.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
namespace Axivion {
|
||||||
|
|
||||||
|
struct Tr
|
||||||
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(Axivion)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Axivion
|
||||||
BIN
src/plugins/axivion/images/axivion.png
Normal file
BIN
src/plugins/axivion/images/axivion.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 293 B |
BIN
src/plugins/axivion/images/axivion@2x.png
Normal file
BIN
src/plugins/axivion/images/axivion@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 545 B |
Reference in New Issue
Block a user