2023-11-13 16:35:01 +01:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
2024-01-08 09:52:22 +01:00
|
|
|
#include "extensionmanagerwidget.h"
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-01-08 09:52:22 +01:00
|
|
|
#include "extensionmanagertr.h"
|
2023-11-13 16:35:01 +01:00
|
|
|
#include "extensionsbrowser.h"
|
2024-05-29 16:00:22 +02:00
|
|
|
#include "extensionsmodel.h"
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
#include <coreplugin/coreconstants.h>
|
|
|
|
|
#include <coreplugin/icontext.h>
|
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/iwelcomepage.h>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <coreplugin/plugininstallwizard.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <coreplugin/welcomepagehelper.h>
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <extensionsystem/pluginmanager.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <extensionsystem/pluginspec.h>
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <solutions/tasking/networkquery.h>
|
|
|
|
|
#include <solutions/tasking/tasktree.h>
|
|
|
|
|
#include <solutions/tasking/tasktreerunner.h>
|
|
|
|
|
|
2024-01-08 09:52:22 +01:00
|
|
|
#include <utils/algorithm.h>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <utils/fileutils.h>
|
|
|
|
|
#include <utils/hostosinfo.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <utils/icon.h>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <utils/infolabel.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <utils/layoutbuilder.h>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <utils/networkaccessmanager.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <utils/stylehelper.h>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <utils/temporarydirectory.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <utils/utilsicons.h>
|
|
|
|
|
|
|
|
|
|
#include <QAction>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <QCheckBox>
|
|
|
|
|
#include <QMessageBox>
|
2023-11-13 16:35:01 +01:00
|
|
|
#include <QTextBrowser>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <QProgressDialog>
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
using namespace Core;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace ExtensionManager::Internal {
|
|
|
|
|
|
|
|
|
|
class CollapsingWidget : public QWidget
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit CollapsingWidget(QWidget *parent = nullptr)
|
|
|
|
|
: QWidget(parent)
|
|
|
|
|
{
|
|
|
|
|
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setWidth(int width)
|
|
|
|
|
{
|
|
|
|
|
m_width = width;
|
|
|
|
|
setVisible(width > 0);
|
|
|
|
|
updateGeometry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSize sizeHint() const override
|
|
|
|
|
{
|
|
|
|
|
return {m_width, 0};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
int m_width = 100;
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
class PluginStatusWidget : public QWidget
|
2023-11-13 16:35:01 +01:00
|
|
|
{
|
2024-05-29 16:00:22 +02:00
|
|
|
public:
|
|
|
|
|
explicit PluginStatusWidget(QWidget *parent = nullptr)
|
|
|
|
|
: QWidget(parent)
|
|
|
|
|
{
|
|
|
|
|
m_label = new InfoLabel;
|
|
|
|
|
m_checkBox = new QCheckBox(Tr::tr("Load on Start"));
|
|
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
Column {
|
|
|
|
|
m_label,
|
|
|
|
|
m_checkBox,
|
|
|
|
|
}.attachTo(this);
|
|
|
|
|
|
|
|
|
|
connect(m_checkBox, &QCheckBox::clicked, this, [this](bool checked) {
|
|
|
|
|
ExtensionSystem::PluginSpec *spec = ExtensionsModel::pluginSpecForName(m_pluginName);
|
|
|
|
|
if (spec == nullptr)
|
|
|
|
|
return;
|
|
|
|
|
spec->setEnabledBySettings(checked);
|
|
|
|
|
ExtensionSystem::PluginManager::writeSettings();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setPluginName(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
m_pluginName = name;
|
|
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void update()
|
|
|
|
|
{
|
|
|
|
|
const ExtensionSystem::PluginSpec *spec = ExtensionsModel::pluginSpecForName(m_pluginName);
|
|
|
|
|
setVisible(spec != nullptr);
|
|
|
|
|
if (spec == nullptr)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (spec->hasError()) {
|
|
|
|
|
m_label->setType(InfoLabel::Error);
|
|
|
|
|
m_label->setText(Tr::tr("Error"));
|
|
|
|
|
} else if (spec->state() == ExtensionSystem::PluginSpec::Running) {
|
|
|
|
|
m_label->setType(InfoLabel::Ok);
|
|
|
|
|
m_label->setText(Tr::tr("Loaded"));
|
|
|
|
|
} else {
|
|
|
|
|
m_label->setType(InfoLabel::NotOk);
|
|
|
|
|
m_label->setText(Tr::tr("Not loaded"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_checkBox->setChecked(spec->isRequired() || spec->isEnabledBySettings());
|
|
|
|
|
m_checkBox->setEnabled(!spec->isRequired());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InfoLabel *m_label;
|
|
|
|
|
QCheckBox *m_checkBox;
|
|
|
|
|
QString m_pluginName;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ExtensionManagerWidgetPrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
QString currentItemName;
|
|
|
|
|
ExtensionsBrowser *leftColumn;
|
|
|
|
|
CollapsingWidget *secondaryDescriptionWidget;
|
|
|
|
|
QTextBrowser *primaryDescription;
|
|
|
|
|
QTextBrowser *secondaryDescription;
|
|
|
|
|
PluginStatusWidget *pluginStatus;
|
|
|
|
|
QAbstractButton *installButton;
|
|
|
|
|
PluginsData currentItemPlugins;
|
|
|
|
|
Tasking::TaskTreeRunner taskTreeRunner;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ExtensionManagerWidget::ExtensionManagerWidget(QWidget *parent)
|
|
|
|
|
: ResizeSignallingWidget(parent)
|
|
|
|
|
, d(new ExtensionManagerWidgetPrivate)
|
|
|
|
|
{
|
|
|
|
|
d->leftColumn = new ExtensionsBrowser;
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
auto descriptionColumns = new QWidget;
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
d->secondaryDescriptionWidget = new CollapsingWidget;
|
|
|
|
|
|
|
|
|
|
d->primaryDescription = new QTextBrowser;
|
|
|
|
|
d->primaryDescription->setOpenExternalLinks(true);
|
|
|
|
|
d->primaryDescription->setFrameStyle(QFrame::NoFrame);
|
|
|
|
|
|
|
|
|
|
d->secondaryDescription = new QTextBrowser;
|
|
|
|
|
d->secondaryDescription->setFrameStyle(QFrame::NoFrame);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
d->pluginStatus = new PluginStatusWidget;
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
d->installButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
|
|
|
|
|
d->installButton->hide();
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
Row {
|
2024-02-12 17:19:41 +01:00
|
|
|
WelcomePageHelpers::createRule(Qt::Vertical),
|
2024-05-29 16:00:22 +02:00
|
|
|
Column {
|
|
|
|
|
d->secondaryDescription,
|
|
|
|
|
d->pluginStatus,
|
|
|
|
|
d->installButton,
|
|
|
|
|
},
|
2024-05-14 10:33:01 +02:00
|
|
|
noMargin, spacing(0),
|
2024-05-29 16:00:22 +02:00
|
|
|
}.attachTo(d->secondaryDescriptionWidget);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
Row {
|
2024-02-12 17:19:41 +01:00
|
|
|
WelcomePageHelpers::createRule(Qt::Vertical),
|
2023-11-13 16:35:01 +01:00
|
|
|
Row {
|
2024-05-29 16:00:22 +02:00
|
|
|
d->primaryDescription,
|
2024-05-14 10:33:01 +02:00
|
|
|
noMargin,
|
2023-11-13 16:35:01 +01:00
|
|
|
},
|
2024-05-29 16:00:22 +02:00
|
|
|
d->secondaryDescriptionWidget,
|
2024-05-14 10:33:01 +02:00
|
|
|
noMargin, spacing(0),
|
2023-11-13 16:35:01 +01:00
|
|
|
}.attachTo(descriptionColumns);
|
|
|
|
|
|
|
|
|
|
Row {
|
2024-02-12 17:19:41 +01:00
|
|
|
Space(StyleHelper::SpacingTokens::ExVPaddingGapXl),
|
2024-05-29 16:00:22 +02:00
|
|
|
d->leftColumn,
|
2023-11-13 16:35:01 +01:00
|
|
|
descriptionColumns,
|
2024-05-14 10:33:01 +02:00
|
|
|
noMargin, spacing(0),
|
2023-11-13 16:35:01 +01:00
|
|
|
}.attachTo(this);
|
|
|
|
|
|
2024-02-12 17:19:41 +01:00
|
|
|
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
connect(d->leftColumn, &ExtensionsBrowser::itemSelected,
|
2023-11-13 16:35:01 +01:00
|
|
|
this, &ExtensionManagerWidget::updateView);
|
2023-12-11 13:30:02 +01:00
|
|
|
connect(this, &ResizeSignallingWidget::resized, this, [this](const QSize &size) {
|
2023-11-13 16:35:01 +01:00
|
|
|
const int intendedLeftColumnWidth = size.width() - 580;
|
2024-05-29 16:00:22 +02:00
|
|
|
d->leftColumn->adjustToWidth(intendedLeftColumnWidth);
|
2023-11-13 16:35:01 +01:00
|
|
|
const bool secondaryDescriptionVisible = size.width() > 970;
|
|
|
|
|
const int secondaryDescriptionWidth = secondaryDescriptionVisible ? 264 : 0;
|
2024-05-29 16:00:22 +02:00
|
|
|
d->secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
|
|
|
|
|
});
|
|
|
|
|
connect(d->installButton, &QAbstractButton::pressed, this, [this]() {
|
|
|
|
|
fetchAndInstallPlugin(QUrl::fromUserInput(d->currentItemPlugins.constFirst().second));
|
2023-11-13 16:35:01 +01:00
|
|
|
});
|
2024-05-29 16:00:22 +02:00
|
|
|
updateView({});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExtensionManagerWidget::~ExtensionManagerWidget()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
void ExtensionManagerWidget::updateView(const QModelIndex ¤t)
|
2023-11-13 16:35:01 +01:00
|
|
|
{
|
|
|
|
|
const QString h5Css =
|
|
|
|
|
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH5))
|
2024-05-29 16:00:22 +02:00
|
|
|
+ "; margin-top: 0px;";
|
2023-11-13 16:35:01 +01:00
|
|
|
const QString h6Css =
|
|
|
|
|
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6))
|
|
|
|
|
+ "; margin-top: 28px;";
|
|
|
|
|
const QString h6CapitalCss =
|
|
|
|
|
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6Capital))
|
2024-05-29 16:00:22 +02:00
|
|
|
+ QString::fromLatin1("; margin-top: 0px; color: %1;")
|
2024-05-29 11:45:22 +02:00
|
|
|
.arg(creatorColor(Theme::Token_Text_Muted).name());
|
2024-05-29 16:00:22 +02:00
|
|
|
const QString bodyStyle = QString::fromLatin1("color: %1; background-color: %2; "
|
2023-11-13 16:35:01 +01:00
|
|
|
"margin-left: %3px; margin-right: %3px;")
|
2024-05-29 11:45:22 +02:00
|
|
|
.arg(creatorColor(Theme::Token_Text_Default).name())
|
|
|
|
|
.arg(creatorColor(Theme::Token_Background_Muted).name())
|
2024-02-12 17:19:41 +01:00
|
|
|
.arg(StyleHelper::SpacingTokens::ExVPaddingGapXl);
|
2023-11-13 16:35:01 +01:00
|
|
|
const QString htmlStart = QString(R"(
|
|
|
|
|
<html>
|
2024-05-29 16:00:22 +02:00
|
|
|
<body style="%1"><br/>
|
2023-11-13 16:35:01 +01:00
|
|
|
)").arg(bodyStyle);
|
|
|
|
|
const QString htmlEnd = QString(R"(
|
|
|
|
|
</body></html>
|
|
|
|
|
)");
|
|
|
|
|
|
|
|
|
|
if (!current.isValid()) {
|
|
|
|
|
const QString emptyHtml = htmlStart + htmlEnd;
|
2024-05-29 16:00:22 +02:00
|
|
|
d->primaryDescription->setText(emptyHtml);
|
|
|
|
|
d->secondaryDescription->setText(emptyHtml);
|
2023-11-13 16:35:01 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
d->currentItemName = current.data().toString();
|
|
|
|
|
const bool isPack = current.data(RoleItemType) == ItemTypePack;
|
|
|
|
|
d->pluginStatus->setPluginName(isPack ? QString() : d->currentItemName);
|
|
|
|
|
const bool isRemotePlugin = !(isPack || ExtensionsModel::pluginSpecForName(d->currentItemName));
|
|
|
|
|
d->currentItemPlugins = current.data(RolePlugins).value<PluginsData>();
|
|
|
|
|
d->installButton->setVisible(isRemotePlugin && !d->currentItemPlugins.empty());
|
|
|
|
|
if (!d->currentItemPlugins.empty())
|
|
|
|
|
d->installButton->setToolTip(d->currentItemPlugins.constFirst().second);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
QString description = htmlStart;
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
QString descriptionHtml;
|
|
|
|
|
{
|
|
|
|
|
const TextData textData = current.data(RoleDescriptionText).value<TextData>();
|
|
|
|
|
for (const TextData::Type &text : textData) {
|
|
|
|
|
if (text.second.isEmpty())
|
|
|
|
|
continue;
|
|
|
|
|
const QString paragraph =
|
|
|
|
|
QString::fromLatin1("<div style=\"%1\">%2</div><p>%3</p>")
|
|
|
|
|
.arg(descriptionHtml.isEmpty() ? h5Css : h6Css)
|
|
|
|
|
.arg(text.first)
|
|
|
|
|
.arg(text.second.join("<br/>"));
|
|
|
|
|
descriptionHtml.append(paragraph);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
description.append(descriptionHtml);
|
|
|
|
|
|
|
|
|
|
description.append(QString::fromLatin1("<div style=\"%1\">%2</div>")
|
|
|
|
|
.arg(h6Css)
|
|
|
|
|
.arg(Tr::tr("More information")));
|
|
|
|
|
const LinksData linksData = current.data(RoleDescriptionLinks).value<LinksData>();
|
|
|
|
|
if (!linksData.isEmpty()) {
|
|
|
|
|
QString linksHtml;
|
|
|
|
|
const QStringList links = Utils::transform(linksData, [](const LinksData::Type &link) {
|
|
|
|
|
const QString anchor = link.first.isEmpty() ? link.second : link.first;
|
|
|
|
|
return QString::fromLatin1("<a href=\"%1\">%2 ></a>")
|
|
|
|
|
.arg(link.second).arg(anchor);
|
|
|
|
|
});
|
|
|
|
|
linksHtml = links.join("<br/>");
|
|
|
|
|
description.append(QString::fromLatin1("<p>%1</p>").arg(linksHtml));
|
|
|
|
|
}
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
const ImagesData imagesData = current.data(RoleDescriptionImages).value<ImagesData>();
|
|
|
|
|
if (!imagesData.isEmpty()) {
|
|
|
|
|
const QString examplesBoxCss =
|
|
|
|
|
QString::fromLatin1("height: 168px; background-color: %1; ")
|
|
|
|
|
.arg(creatorTheme()->color(Theme::Token_Background_Default).name());
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
<br/>
|
|
|
|
|
<div style="%1">%2</div>
|
|
|
|
|
<p style="%3">
|
|
|
|
|
<br/><br/><br/><br/><br/>
|
|
|
|
|
TODO: Load imagea asynchronously, and show them in a QLabel.
|
|
|
|
|
Also Use QMovie for animated images.
|
|
|
|
|
<br/><br/><br/><br/><br/>
|
|
|
|
|
</p>
|
|
|
|
|
)").arg(h6CapitalCss)
|
|
|
|
|
.arg(Tr::tr("Examples"))
|
|
|
|
|
.arg(examplesBoxCss));
|
|
|
|
|
}
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
// Library details vanished from the Figma designs. The data is available, though.
|
|
|
|
|
const bool showDetails = false;
|
|
|
|
|
if (showDetails) {
|
|
|
|
|
const QString captionStrongCss = StyleHelper::fontToCssProperties(
|
|
|
|
|
StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
|
|
|
|
|
const QLocale locale;
|
|
|
|
|
const uint size = current.data(RoleSize).toUInt();
|
|
|
|
|
const QString sizeFmt = locale.formattedDataSize(size);
|
|
|
|
|
const FilePath location = FilePath::fromVariant(current.data(RoleLocation));
|
|
|
|
|
const QString version = current.data(RoleVersion).toString();
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
<div style="%1">%2</div>
|
|
|
|
|
<p>
|
|
|
|
|
<table>
|
|
|
|
|
<tr><td style="%3">%4</td><td>%5</td></tr>
|
|
|
|
|
<tr><td style="%3">%6</td><td>%7</td></tr>
|
|
|
|
|
)").arg(h6Css)
|
|
|
|
|
.arg(Tr::tr("Extension library details"))
|
|
|
|
|
.arg(captionStrongCss)
|
|
|
|
|
.arg(Tr::tr("Size"))
|
|
|
|
|
.arg(sizeFmt)
|
|
|
|
|
.arg(Tr::tr("Version"))
|
|
|
|
|
.arg(version));
|
|
|
|
|
if (!location.isEmpty()) {
|
|
|
|
|
const QString locationFmt =
|
|
|
|
|
HostOsInfo::isWindowsHost() ? location.toUserOutput()
|
|
|
|
|
: location.withTildeHomePath();
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
<tr><td style="%3">%1</td><td>%2</td></tr>
|
|
|
|
|
)").arg(Tr::tr("Location"))
|
|
|
|
|
.arg(locationFmt));
|
|
|
|
|
}
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
</table>
|
|
|
|
|
</p>
|
|
|
|
|
)"));
|
|
|
|
|
}
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
description.append(htmlEnd);
|
2024-05-29 16:00:22 +02:00
|
|
|
d->primaryDescription->setText(description);
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
QString description = htmlStart;
|
|
|
|
|
|
|
|
|
|
description.append(QString(R"(
|
2024-05-29 16:00:22 +02:00
|
|
|
<p style="%1">%2</p>
|
2023-11-13 16:35:01 +01:00
|
|
|
)").arg(h6CapitalCss)
|
|
|
|
|
.arg(Tr::tr("Extension details")));
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
const QStringList tags = current.data(RoleTags).toStringList();
|
|
|
|
|
if (!tags.isEmpty()) {
|
|
|
|
|
const QString tagTemplate = QString(R"(
|
|
|
|
|
<td style="border: 1px solid %1; padding: 3px; ">%2</td>
|
|
|
|
|
)").arg(creatorTheme()->color(Theme::Token_Stroke_Subtle).name());
|
|
|
|
|
const QStringList tagsFmt = Utils::transform(tags, [&tagTemplate](const QString &tag) {
|
|
|
|
|
return tagTemplate.arg(tag);
|
|
|
|
|
});
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
<div style="%1">%2</div>
|
|
|
|
|
<p>%3</p>
|
|
|
|
|
)").arg(h6Css)
|
|
|
|
|
.arg(Tr::tr("Related tags"))
|
|
|
|
|
.arg(tagsFmt.join(" ")));
|
|
|
|
|
}
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
const QStringList platforms = current.data(RolePlatforms).toStringList();
|
|
|
|
|
if (!platforms.isEmpty()) {
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
<div style="%1">%2</div>
|
|
|
|
|
<p>%3</p>
|
|
|
|
|
)").arg(h6Css)
|
|
|
|
|
.arg(Tr::tr("Platforms"))
|
|
|
|
|
.arg(platforms.join("<br/>")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QStringList dependencies = current.data(RoleDependencies).toStringList();
|
|
|
|
|
if (!dependencies.isEmpty()) {
|
|
|
|
|
const QString dependenciesFmt = dependencies.join("<br/>");
|
|
|
|
|
description.append(QString(R"(
|
|
|
|
|
<div style="%1">%2</div>
|
|
|
|
|
<p>%3</p>
|
|
|
|
|
)").arg(h6Css)
|
|
|
|
|
.arg(Tr::tr("Dependencies"))
|
|
|
|
|
.arg(dependenciesFmt));
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isPack) {
|
2024-05-29 16:00:22 +02:00
|
|
|
const PluginsData plugins = current.data(RolePlugins).value<PluginsData>();
|
|
|
|
|
const QStringList extensions = Utils::transform(plugins,
|
|
|
|
|
&QPair<QString, QString>::first);
|
|
|
|
|
const QString extensionsFmt = extensions.join("<br/>");
|
2023-11-13 16:35:01 +01:00
|
|
|
description.append(QString(R"(
|
|
|
|
|
<div style="%1">%2</div>
|
|
|
|
|
<p>%3</p>
|
|
|
|
|
)").arg(h6Css)
|
2024-05-29 16:00:22 +02:00
|
|
|
.arg(Tr::tr("Extensions in pack"))
|
|
|
|
|
.arg(extensionsFmt));
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
description.append(htmlEnd);
|
2024-05-29 16:00:22 +02:00
|
|
|
d->secondaryDescription->setText(description);
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
|
|
|
|
|
{
|
|
|
|
|
using namespace Tasking;
|
|
|
|
|
|
|
|
|
|
struct StorageStruct
|
|
|
|
|
{
|
|
|
|
|
StorageStruct() {
|
|
|
|
|
progressDialog.reset(new QProgressDialog(Tr::tr("Downloading Plugin..."),
|
|
|
|
|
Tr::tr("Cancel"), 0, 0,
|
|
|
|
|
Core::ICore::dialogParent()));
|
|
|
|
|
progressDialog->setWindowModality(Qt::ApplicationModal);
|
|
|
|
|
progressDialog->setFixedSize(progressDialog->sizeHint());
|
|
|
|
|
progressDialog->setAutoClose(false);
|
|
|
|
|
progressDialog->show(); // TODO: Should not be needed. Investigate possible QT_BUG
|
|
|
|
|
}
|
|
|
|
|
std::unique_ptr<QProgressDialog> progressDialog;
|
|
|
|
|
QByteArray packageData;
|
|
|
|
|
QUrl url;
|
|
|
|
|
};
|
|
|
|
|
Storage<StorageStruct> storage;
|
|
|
|
|
|
|
|
|
|
const auto onQuerySetup = [url, storage](NetworkQuery &query) {
|
|
|
|
|
storage->url = url;
|
|
|
|
|
query.setRequest(QNetworkRequest(url));
|
|
|
|
|
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
|
|
|
|
};
|
|
|
|
|
const auto onQueryDone = [storage](const NetworkQuery &query, DoneWith result) {
|
|
|
|
|
storage->progressDialog->close();
|
|
|
|
|
if (result == DoneWith::Success) {
|
|
|
|
|
storage->packageData = query.reply()->readAll();
|
|
|
|
|
} else {
|
|
|
|
|
QMessageBox::warning(
|
|
|
|
|
ICore::dialogParent(),
|
|
|
|
|
Tr::tr("Download Error"),
|
|
|
|
|
Tr::tr("Could not download Plugin") + "\n\n" + storage->url.toString() + "\n\n"
|
|
|
|
|
+ Tr::tr("Code: %1.").arg(query.reply()->error()));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onPluginInstallation = [storage]() {
|
|
|
|
|
if (storage->packageData.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
const FilePath source = FilePath::fromUrl(storage->url);
|
|
|
|
|
TempFileSaver saver(TemporaryDirectory::masterDirectoryPath()
|
|
|
|
|
+ "/XXXXXX" + source.fileName());
|
|
|
|
|
|
|
|
|
|
saver.write(storage->packageData);
|
|
|
|
|
if (saver.finalize(ICore::dialogParent()))
|
|
|
|
|
executePluginInstallWizard(saver.filePath());;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Group group{
|
|
|
|
|
storage,
|
|
|
|
|
NetworkQueryTask{onQuerySetup, onQueryDone},
|
|
|
|
|
onGroupDone(onPluginInstallation),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
d->taskTreeRunner.start(group);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 16:35:01 +01:00
|
|
|
} // ExtensionManager::Internal
|