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-06-25 16:43:07 +02:00
|
|
|
#include <extensionsystem/pluginview.h>
|
2023-11-13 16:35:01 +01:00
|
|
|
|
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-06-06 14:40:58 +02:00
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QBuffer>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <QCheckBox>
|
2024-06-06 14:40:58 +02:00
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QImageReader>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <QMessageBox>
|
2024-06-06 14:40:58 +02:00
|
|
|
#include <QMovie>
|
|
|
|
|
#include <QPainter>
|
2024-05-29 16:00:22 +02:00
|
|
|
#include <QProgressDialog>
|
2024-06-06 14:40:58 +02:00
|
|
|
#include <QScrollArea>
|
|
|
|
|
#include <QSignalMapper>
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
using namespace Core;
|
|
|
|
|
using namespace Utils;
|
2024-06-06 14:40:58 +02:00
|
|
|
using namespace StyleHelper;
|
|
|
|
|
using namespace WelcomePageHelpers;
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
namespace ExtensionManager::Internal {
|
|
|
|
|
|
2024-06-06 14:40:58 +02:00
|
|
|
constexpr TextFormat h5TF
|
|
|
|
|
{Theme::Token_Text_Default, UiElement::UiElementH5};
|
|
|
|
|
constexpr TextFormat h6TF
|
|
|
|
|
{h5TF.themeColor, UiElement::UiElementH6};
|
|
|
|
|
constexpr TextFormat h6CapitalTF
|
|
|
|
|
{Theme::Token_Text_Muted, UiElement::UiElementH6Capital};
|
|
|
|
|
constexpr TextFormat contentTF
|
|
|
|
|
{Theme::Token_Text_Default, UiElement::UiElementBody2};
|
|
|
|
|
|
|
|
|
|
static QLabel *sectionTitle(const TextFormat &tf, const QString &title)
|
|
|
|
|
{
|
|
|
|
|
QLabel *label = tfLabel(tf, true);
|
|
|
|
|
label->setText(title);
|
|
|
|
|
return label;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static QWidget *toScrollableColumn(QWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
widget->setContentsMargins(SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl,
|
|
|
|
|
SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl);
|
|
|
|
|
widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
|
|
|
|
|
|
|
|
|
|
auto scrollArea = new QScrollArea;
|
|
|
|
|
scrollArea->setWidget(widget);
|
|
|
|
|
scrollArea->setWidgetResizable(true);
|
|
|
|
|
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
|
scrollArea->setFrameStyle(QFrame::NoFrame);
|
|
|
|
|
|
|
|
|
|
return scrollArea;
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-13 16:35:01 +01:00
|
|
|
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-06-06 14:40:58 +02:00
|
|
|
class HeadingWidget : public QWidget
|
|
|
|
|
{
|
|
|
|
|
static constexpr QSize iconBgS{68, 68};
|
|
|
|
|
static constexpr int dividerH = 16;
|
|
|
|
|
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
explicit HeadingWidget(QWidget *parent = nullptr)
|
|
|
|
|
: QWidget(parent)
|
|
|
|
|
{
|
|
|
|
|
m_icon = new QLabel;
|
|
|
|
|
m_icon->setFixedSize(iconBgS);
|
|
|
|
|
|
|
|
|
|
static const TextFormat titleTF
|
|
|
|
|
{Theme::Token_Text_Default, UiElementH4};
|
|
|
|
|
static const TextFormat vendorTF
|
|
|
|
|
{Theme::Token_Text_Accent, UiElementLabelMedium};
|
|
|
|
|
static const TextFormat dlTF
|
|
|
|
|
{Theme::Token_Text_Muted, vendorTF.uiElement};
|
|
|
|
|
static const TextFormat detailsTF
|
|
|
|
|
{Theme::Token_Text_Default, UiElementBody2};
|
|
|
|
|
|
|
|
|
|
m_title = tfLabel(titleTF);
|
|
|
|
|
m_vendor = new Button({}, Button::SmallLink);
|
|
|
|
|
m_vendor->setContentsMargins({});
|
|
|
|
|
m_divider = new QLabel;
|
|
|
|
|
m_divider->setFixedSize(1, dividerH);
|
|
|
|
|
WelcomePageHelpers::setBackgroundColor(m_divider, dlTF.themeColor);
|
|
|
|
|
m_dlIcon = new QLabel;
|
|
|
|
|
const QPixmap dlIcon = Icon({{":/extensionmanager/images/download.png", dlTF.themeColor}},
|
|
|
|
|
Icon::Tint).pixmap();
|
|
|
|
|
m_dlIcon->setPixmap(dlIcon);
|
|
|
|
|
m_dlCount = tfLabel(dlTF);
|
|
|
|
|
m_dlCount->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
|
|
|
|
|
m_details = tfLabel(detailsTF);
|
|
|
|
|
installButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
|
|
|
|
|
installButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
|
|
|
|
installButton->hide();
|
|
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
Row {
|
|
|
|
|
m_icon,
|
|
|
|
|
Column {
|
|
|
|
|
m_title,
|
|
|
|
|
st,
|
|
|
|
|
Row {
|
|
|
|
|
m_vendor,
|
|
|
|
|
Widget {
|
|
|
|
|
bindTo(&m_dlCountItems),
|
|
|
|
|
Row {
|
|
|
|
|
Space(SpacingTokens::HGapXs),
|
|
|
|
|
m_divider,
|
|
|
|
|
Space(SpacingTokens::HGapXs),
|
|
|
|
|
m_dlIcon,
|
|
|
|
|
Space(SpacingTokens::HGapXxs),
|
|
|
|
|
m_dlCount,
|
|
|
|
|
noMargin, spacing(0),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
st,
|
|
|
|
|
m_details,
|
|
|
|
|
spacing(0),
|
|
|
|
|
},
|
|
|
|
|
Column {
|
|
|
|
|
installButton,
|
|
|
|
|
st,
|
|
|
|
|
},
|
|
|
|
|
noMargin, spacing(SpacingTokens::ExPaddingGapL),
|
|
|
|
|
}.attachTo(this);
|
|
|
|
|
|
|
|
|
|
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
|
|
|
|
|
m_dlCountItems->setVisible(false);
|
|
|
|
|
|
|
|
|
|
connect(installButton, &QAbstractButton::pressed,
|
|
|
|
|
this, &HeadingWidget::pluginInstallationRequested);
|
|
|
|
|
connect(m_vendor, &QAbstractButton::pressed, this, [this]() {
|
|
|
|
|
emit vendorClicked(m_currentVendor);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
update({});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void update(const QModelIndex ¤t)
|
|
|
|
|
{
|
|
|
|
|
if (!current.isValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_icon->setPixmap(icon(current));
|
|
|
|
|
|
|
|
|
|
const QString name = current.data(RoleName).toString();
|
|
|
|
|
m_title->setText(name);
|
|
|
|
|
|
|
|
|
|
m_currentVendor = current.data(RoleVendor).toString();
|
|
|
|
|
m_vendor->setText(m_currentVendor);
|
|
|
|
|
|
|
|
|
|
const int dlCount = current.data(RoleDownloadCount).toInt();
|
|
|
|
|
const bool showDlCount = dlCount > 0;
|
|
|
|
|
if (showDlCount)
|
|
|
|
|
m_dlCount->setText(QString::number(dlCount));
|
|
|
|
|
m_dlCountItems->setVisible(showDlCount);
|
|
|
|
|
|
|
|
|
|
const auto pluginData = current.data(RolePlugins).value<PluginsData>();
|
|
|
|
|
if (current.data(RoleItemType).toInt() == ItemTypePack) {
|
|
|
|
|
const int pluginsCount = pluginData.count();
|
|
|
|
|
const QString details = Tr::tr("Pack contains %n plugins.", nullptr, pluginsCount);
|
|
|
|
|
m_details->setText(details);
|
|
|
|
|
} else {
|
|
|
|
|
m_details->setText({});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ItemType itemType = current.data(RoleItemType).value<ItemType>();
|
|
|
|
|
const bool isPack = itemType == ItemTypePack;
|
2024-06-26 12:25:40 +02:00
|
|
|
const bool isRemotePlugin = !(isPack || pluginSpecForName(name));
|
2024-06-06 14:40:58 +02:00
|
|
|
installButton->setVisible(isRemotePlugin && !pluginData.empty());
|
|
|
|
|
if (installButton->isVisible())
|
|
|
|
|
installButton->setToolTip(pluginData.constFirst().second);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void pluginInstallationRequested();
|
|
|
|
|
void vendorClicked(const QString &vendor);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
static QPixmap icon(const QModelIndex &index)
|
|
|
|
|
{
|
|
|
|
|
const qreal dpr = qApp->devicePixelRatio();
|
|
|
|
|
QPixmap pixmap(iconBgS * dpr);
|
|
|
|
|
pixmap.fill(Qt::transparent);
|
|
|
|
|
pixmap.setDevicePixelRatio(dpr);
|
|
|
|
|
const QRect bgR(QPoint(), pixmap.deviceIndependentSize().toSize());
|
|
|
|
|
|
|
|
|
|
QPainter p(&pixmap);
|
|
|
|
|
QLinearGradient gradient(bgR.topRight(), bgR.bottomLeft());
|
|
|
|
|
gradient.setStops(iconGradientStops(index));
|
|
|
|
|
constexpr int iconRectRounding = 4;
|
|
|
|
|
WelcomePageHelpers::drawCardBackground(&p, bgR, gradient, Qt::NoPen, iconRectRounding);
|
|
|
|
|
|
|
|
|
|
// Icon
|
|
|
|
|
constexpr Theme::Color color = Theme::Token_Basic_White;
|
|
|
|
|
static const QIcon pack = Icon({{":/extensionmanager/images/packbig.png", color}},
|
|
|
|
|
Icon::Tint).icon();
|
|
|
|
|
static const QIcon extension = Icon({{":/extensionmanager/images/extensionbig.png",
|
|
|
|
|
color}}, Icon::Tint).icon();
|
|
|
|
|
const ItemType itemType = index.data(RoleItemType).value<ItemType>();
|
|
|
|
|
(itemType == ItemTypePack ? pack : extension).paint(&p, bgR);
|
|
|
|
|
|
|
|
|
|
return pixmap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QLabel *m_icon;
|
|
|
|
|
QLabel *m_title;
|
|
|
|
|
Button *m_vendor;
|
|
|
|
|
QLabel *m_divider;
|
|
|
|
|
QLabel *m_dlIcon;
|
|
|
|
|
QLabel *m_dlCount;
|
|
|
|
|
QWidget *m_dlCountItems;
|
|
|
|
|
QLabel *m_details;
|
|
|
|
|
QAbstractButton *installButton;
|
|
|
|
|
QString m_currentVendor;
|
|
|
|
|
};
|
|
|
|
|
|
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;
|
2024-06-25 10:34:55 +02:00
|
|
|
m_checkBox = new QCheckBox(Tr::tr("Load on start"));
|
|
|
|
|
m_restartButton = new Button(Tr::tr("Restart Now"), Button::MediumPrimary);
|
2024-06-25 16:43:07 +02:00
|
|
|
m_restartButton->setVisible(false);
|
|
|
|
|
m_pluginView.hide();
|
2024-05-29 16:00:22 +02:00
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
Column {
|
|
|
|
|
m_label,
|
|
|
|
|
m_checkBox,
|
2024-06-25 16:43:07 +02:00
|
|
|
m_restartButton,
|
2024-05-29 16:00:22 +02:00
|
|
|
}.attachTo(this);
|
|
|
|
|
|
|
|
|
|
connect(m_checkBox, &QCheckBox::clicked, this, [this](bool checked) {
|
2024-06-26 12:25:40 +02:00
|
|
|
ExtensionSystem::PluginSpec *spec = pluginSpecForName(m_pluginName);
|
2024-05-29 16:00:22 +02:00
|
|
|
if (spec == nullptr)
|
|
|
|
|
return;
|
2024-06-25 16:43:07 +02:00
|
|
|
const bool doIt = m_pluginView.data().setPluginsEnabled({spec}, checked);
|
|
|
|
|
if (doIt) {
|
|
|
|
|
m_restartButton->show();
|
|
|
|
|
ExtensionSystem::PluginManager::writeSettings();
|
|
|
|
|
} else {
|
|
|
|
|
m_checkBox->setChecked(!checked);
|
|
|
|
|
}
|
2024-05-29 16:00:22 +02:00
|
|
|
});
|
|
|
|
|
|
2024-06-25 16:43:07 +02:00
|
|
|
connect(m_restartButton, &QAbstractButton::clicked,
|
|
|
|
|
ICore::instance(), &ICore::restart, Qt::QueuedConnection);
|
|
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setPluginName(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
m_pluginName = name;
|
|
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void update()
|
|
|
|
|
{
|
2024-06-26 12:25:40 +02:00
|
|
|
const ExtensionSystem::PluginSpec *spec = pluginSpecForName(m_pluginName);
|
2024-05-29 16:00:22 +02:00
|
|
|
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;
|
2024-06-25 16:43:07 +02:00
|
|
|
QAbstractButton *m_restartButton;
|
2024-05-29 16:00:22 +02:00
|
|
|
QString m_pluginName;
|
2024-06-25 16:43:07 +02:00
|
|
|
ExtensionSystem::PluginView m_pluginView{this};
|
2024-05-29 16:00:22 +02:00
|
|
|
};
|
|
|
|
|
|
2024-06-06 14:40:58 +02:00
|
|
|
class TagList : public QWidget
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
explicit TagList(QWidget *parent = nullptr)
|
|
|
|
|
: QWidget(parent)
|
|
|
|
|
{
|
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout(this);
|
|
|
|
|
setLayout(layout);
|
|
|
|
|
layout->setContentsMargins({});
|
|
|
|
|
m_signalMapper = new QSignalMapper(this);
|
|
|
|
|
connect(m_signalMapper, &QSignalMapper::mappedString, this, &TagList::tagSelected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setTags(const QStringList &tags)
|
|
|
|
|
{
|
|
|
|
|
if (m_container) {
|
|
|
|
|
delete m_container;
|
|
|
|
|
m_container = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!tags.empty()) {
|
|
|
|
|
m_container = new QWidget(this);
|
|
|
|
|
layout()->addWidget(m_container);
|
|
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
Flow flow {};
|
|
|
|
|
flow.setNoMargins();
|
|
|
|
|
flow.setSpacing(SpacingTokens::HGapXs);
|
|
|
|
|
|
|
|
|
|
for (const QString &tag : tags) {
|
|
|
|
|
QAbstractButton *tagButton = new Button(tag, Button::Tag);
|
|
|
|
|
connect(tagButton, &QAbstractButton::clicked,
|
|
|
|
|
m_signalMapper, qOverload<>(&QSignalMapper::map));
|
|
|
|
|
m_signalMapper->setMapping(tagButton, tag);
|
|
|
|
|
flow.addItem(tagButton);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flow.attachTo(m_container);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateGeometry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void tagSelected(const QString &tag);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QWidget *m_container = nullptr;
|
|
|
|
|
QSignalMapper *m_signalMapper;
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-26 08:38:51 +02:00
|
|
|
class ExtensionManagerWidget final : public Core::ResizeSignallingWidget
|
2024-05-29 16:00:22 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2024-06-26 08:38:51 +02:00
|
|
|
ExtensionManagerWidget();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void updateView(const QModelIndex ¤t);
|
|
|
|
|
void fetchAndInstallPlugin(const QUrl &url);
|
|
|
|
|
void fetchAndDisplayImage(const QUrl &url);
|
|
|
|
|
|
|
|
|
|
QString m_currentItemName;
|
|
|
|
|
ExtensionsBrowser *m_extensionBrowser;
|
|
|
|
|
CollapsingWidget *m_secondaryDescriptionWidget;
|
|
|
|
|
HeadingWidget *m_headingWidget;
|
|
|
|
|
QWidget *m_primaryContent;
|
|
|
|
|
QWidget *m_secondaryContent;
|
|
|
|
|
QLabel *m_description;
|
|
|
|
|
QLabel *m_linksTitle;
|
|
|
|
|
QLabel *m_links;
|
|
|
|
|
QLabel *m_imageTitle;
|
|
|
|
|
QLabel *m_image;
|
|
|
|
|
QBuffer m_imageDataBuffer;
|
|
|
|
|
QMovie m_imageMovie;
|
|
|
|
|
QLabel *m_tagsTitle;
|
|
|
|
|
TagList *m_tags;
|
|
|
|
|
QLabel *m_platformsTitle;
|
|
|
|
|
QLabel *m_platforms;
|
|
|
|
|
QLabel *m_dependenciesTitle;
|
|
|
|
|
QLabel *m_dependencies;
|
|
|
|
|
QLabel *m_packExtensionsTitle;
|
|
|
|
|
QLabel *m_packExtensions;
|
|
|
|
|
PluginStatusWidget *m_pluginStatus;
|
|
|
|
|
PluginsData m_currentItemPlugins;
|
|
|
|
|
Tasking::TaskTreeRunner m_dlTaskTreeRunner;
|
|
|
|
|
Tasking::TaskTreeRunner m_imgTaskTreeRunner;
|
2024-05-29 16:00:22 +02:00
|
|
|
};
|
|
|
|
|
|
2024-06-26 08:38:51 +02:00
|
|
|
ExtensionManagerWidget::ExtensionManagerWidget()
|
2024-05-29 16:00:22 +02:00
|
|
|
{
|
2024-06-26 08:38:51 +02:00
|
|
|
m_extensionBrowser = new ExtensionsBrowser;
|
2023-11-13 16:35:01 +01:00
|
|
|
auto descriptionColumns = new QWidget;
|
2024-06-26 08:38:51 +02:00
|
|
|
m_secondaryDescriptionWidget = new CollapsingWidget;
|
|
|
|
|
|
|
|
|
|
m_headingWidget = new HeadingWidget;
|
|
|
|
|
m_description = tfLabel(contentTF, false);
|
|
|
|
|
m_description->setWordWrap(true);
|
|
|
|
|
m_linksTitle = sectionTitle(h6CapitalTF, Tr::tr("More information"));
|
|
|
|
|
m_links = tfLabel(contentTF, false);
|
|
|
|
|
m_links->setOpenExternalLinks(true);
|
|
|
|
|
m_imageTitle = sectionTitle(h6CapitalTF, {});
|
|
|
|
|
m_image = new QLabel;
|
|
|
|
|
m_imageMovie.setDevice(&m_imageDataBuffer);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-06-06 14:40:58 +02:00
|
|
|
using namespace Layouting;
|
|
|
|
|
auto primary = new QWidget;
|
|
|
|
|
const auto spL = spacing(SpacingTokens::VPaddingL);
|
|
|
|
|
Column {
|
2024-06-26 08:38:51 +02:00
|
|
|
m_description,
|
|
|
|
|
Column { m_linksTitle, m_links, spL },
|
|
|
|
|
Column { m_imageTitle, m_image, spL },
|
2024-06-06 14:40:58 +02:00
|
|
|
st,
|
|
|
|
|
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
|
|
|
|
|
}.attachTo(primary);
|
2024-06-26 08:38:51 +02:00
|
|
|
m_primaryContent = toScrollableColumn(primary);
|
|
|
|
|
|
|
|
|
|
m_tagsTitle = sectionTitle(h6TF, Tr::tr("Tags"));
|
|
|
|
|
m_tags = new TagList;
|
|
|
|
|
m_platformsTitle = sectionTitle(h6TF, Tr::tr("Platforms"));
|
|
|
|
|
m_platforms = tfLabel(contentTF, false);
|
|
|
|
|
m_dependenciesTitle = sectionTitle(h6TF, Tr::tr("Dependencies"));
|
|
|
|
|
m_dependencies = tfLabel(contentTF, false);
|
|
|
|
|
m_packExtensionsTitle = sectionTitle(h6TF, Tr::tr("Extensions in pack"));
|
|
|
|
|
m_packExtensions = tfLabel(contentTF, false);
|
|
|
|
|
m_pluginStatus = new PluginStatusWidget;
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-06-06 14:40:58 +02:00
|
|
|
auto secondary = new QWidget;
|
|
|
|
|
const auto spXxs = spacing(SpacingTokens::VPaddingXxs);
|
|
|
|
|
Column {
|
|
|
|
|
sectionTitle(h6CapitalTF, Tr::tr("Extension details")),
|
|
|
|
|
Column {
|
2024-06-26 08:38:51 +02:00
|
|
|
Column { m_tagsTitle, m_tags, spXxs },
|
|
|
|
|
Column { m_platformsTitle, m_platforms, spXxs },
|
|
|
|
|
Column { m_dependenciesTitle, m_dependencies, spXxs },
|
|
|
|
|
Column { m_packExtensionsTitle, m_packExtensions, spXxs },
|
2024-06-06 14:40:58 +02:00
|
|
|
spacing(SpacingTokens::VPaddingL),
|
|
|
|
|
},
|
|
|
|
|
st,
|
|
|
|
|
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
|
|
|
|
|
}.attachTo(secondary);
|
2024-06-26 08:38:51 +02:00
|
|
|
m_secondaryContent = toScrollableColumn(secondary);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
|
|
|
|
Row {
|
2024-02-12 17:19:41 +01:00
|
|
|
WelcomePageHelpers::createRule(Qt::Vertical),
|
2024-05-29 16:00:22 +02:00
|
|
|
Column {
|
2024-06-26 08:38:51 +02:00
|
|
|
m_secondaryContent,
|
|
|
|
|
m_pluginStatus,
|
2024-05-29 16:00:22 +02:00
|
|
|
},
|
2024-05-14 10:33:01 +02:00
|
|
|
noMargin, spacing(0),
|
2024-06-26 08:38:51 +02:00
|
|
|
}.attachTo(m_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-06-06 14:40:58 +02:00
|
|
|
Column {
|
|
|
|
|
Column {
|
2024-06-26 08:38:51 +02:00
|
|
|
m_headingWidget,
|
2024-06-06 14:40:58 +02:00
|
|
|
customMargins(SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl,
|
|
|
|
|
SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl),
|
|
|
|
|
},
|
2024-06-26 08:38:51 +02:00
|
|
|
m_primaryContent,
|
2024-06-06 14:40:58 +02:00
|
|
|
},
|
2023-11-13 16:35:01 +01:00
|
|
|
},
|
2024-06-26 08:38:51 +02:00
|
|
|
m_secondaryDescriptionWidget,
|
2024-05-14 10:33:01 +02:00
|
|
|
noMargin, spacing(0),
|
2023-11-13 16:35:01 +01:00
|
|
|
}.attachTo(descriptionColumns);
|
|
|
|
|
|
|
|
|
|
Row {
|
2024-06-06 14:40:58 +02:00
|
|
|
Space(SpacingTokens::ExVPaddingGapXl),
|
2024-06-26 08:38:51 +02:00
|
|
|
m_extensionBrowser,
|
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-06-26 08:38:51 +02:00
|
|
|
connect(m_extensionBrowser, &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) {
|
2024-06-06 14:40:58 +02:00
|
|
|
const int intendedBrowserColumnWidth = size.width() - 580;
|
2024-06-26 08:38:51 +02:00
|
|
|
m_extensionBrowser->adjustToWidth(intendedBrowserColumnWidth);
|
2023-11-13 16:35:01 +01:00
|
|
|
const bool secondaryDescriptionVisible = size.width() > 970;
|
|
|
|
|
const int secondaryDescriptionWidth = secondaryDescriptionVisible ? 264 : 0;
|
2024-06-26 08:38:51 +02:00
|
|
|
m_secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
|
2024-05-29 16:00:22 +02:00
|
|
|
});
|
2024-06-26 08:38:51 +02:00
|
|
|
connect(m_headingWidget, &HeadingWidget::pluginInstallationRequested, this, [this](){
|
|
|
|
|
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentItemPlugins.constFirst().second));
|
2023-11-13 16:35:01 +01:00
|
|
|
});
|
2024-06-26 08:38:51 +02:00
|
|
|
connect(m_tags, &TagList::tagSelected, m_extensionBrowser, &ExtensionsBrowser::setFilter);
|
|
|
|
|
connect(m_headingWidget, &HeadingWidget::vendorClicked,
|
|
|
|
|
m_extensionBrowser, &ExtensionsBrowser::setFilter);
|
2024-06-06 14:40:58 +02:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
updateView({});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExtensionManagerWidget::updateView(const QModelIndex ¤t)
|
2023-11-13 16:35:01 +01:00
|
|
|
{
|
2024-06-26 08:38:51 +02:00
|
|
|
m_headingWidget->update(current);
|
2024-06-06 14:40:58 +02:00
|
|
|
|
|
|
|
|
const bool showContent = current.isValid();
|
2024-06-26 08:38:51 +02:00
|
|
|
m_primaryContent->setVisible(showContent);
|
|
|
|
|
m_secondaryContent->setVisible(showContent);
|
|
|
|
|
m_headingWidget->setVisible(showContent);
|
|
|
|
|
m_pluginStatus->setVisible(showContent);
|
2024-06-06 14:40:58 +02:00
|
|
|
if (!showContent)
|
2023-11-13 16:35:01 +01:00
|
|
|
return;
|
|
|
|
|
|
2024-06-26 08:38:51 +02:00
|
|
|
m_currentItemName = current.data().toString();
|
2024-05-29 16:00:22 +02:00
|
|
|
const bool isPack = current.data(RoleItemType) == ItemTypePack;
|
2024-06-26 08:38:51 +02:00
|
|
|
m_pluginStatus->setPluginName(isPack ? QString() : m_currentItemName);
|
|
|
|
|
m_currentItemPlugins = current.data(RolePlugins).value<PluginsData>();
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-06-06 14:40:58 +02:00
|
|
|
auto toContentParagraph = [](const QString &text) {
|
|
|
|
|
const QString pHtml = QString::fromLatin1("<p style=\"margin-top:0;margin-bottom:0;"
|
|
|
|
|
"line-height:%1px\">%2</p>")
|
|
|
|
|
.arg(contentTF.lineHeight()).arg(text);
|
|
|
|
|
return pHtml;
|
|
|
|
|
};
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-06-06 14:40:58 +02:00
|
|
|
{
|
|
|
|
|
const TextData textData = current.data(RoleDescriptionText).value<TextData>();
|
|
|
|
|
const bool hasDescription = !textData.isEmpty();
|
|
|
|
|
if (hasDescription) {
|
|
|
|
|
const QString headerCssTemplate =
|
|
|
|
|
";margin-top:%1;margin-bottom:%2;padding-top:0;padding-bottom:0;";
|
|
|
|
|
const QString h4Css = fontToCssProperties(uiFont(UiElementH4))
|
|
|
|
|
+ headerCssTemplate.arg(0).arg(SpacingTokens::VGapL);
|
|
|
|
|
const QString h5Css = fontToCssProperties(uiFont(UiElementH5))
|
|
|
|
|
+ headerCssTemplate.arg(SpacingTokens::ExVPaddingGapXl)
|
|
|
|
|
.arg(SpacingTokens::VGapL);
|
|
|
|
|
QString descriptionHtml;
|
2024-05-29 16:00:22 +02:00
|
|
|
for (const TextData::Type &text : textData) {
|
|
|
|
|
if (text.second.isEmpty())
|
|
|
|
|
continue;
|
|
|
|
|
const QString paragraph =
|
2024-06-06 14:40:58 +02:00
|
|
|
QString::fromLatin1("<div style=\"%1\">%2</div>%3")
|
|
|
|
|
.arg(descriptionHtml.isEmpty() ? h4Css : h5Css)
|
2024-05-29 16:00:22 +02:00
|
|
|
.arg(text.first)
|
2024-06-06 14:40:58 +02:00
|
|
|
.arg(toContentParagraph(text.second.join("<br/>")));
|
2024-05-29 16:00:22 +02:00
|
|
|
descriptionHtml.append(paragraph);
|
|
|
|
|
}
|
2024-06-06 14:40:58 +02:00
|
|
|
descriptionHtml.prepend(QString::fromLatin1("<body style=\"color:%1;\">")
|
|
|
|
|
.arg(creatorColor(Theme::Token_Text_Default).name()));
|
|
|
|
|
descriptionHtml.append("</body>");
|
2024-06-26 08:38:51 +02:00
|
|
|
m_description->setText(descriptionHtml);
|
2024-05-29 16:00:22 +02:00
|
|
|
}
|
2024-06-26 08:38:51 +02:00
|
|
|
m_description->setVisible(hasDescription);
|
2024-05-29 16:00:22 +02:00
|
|
|
|
|
|
|
|
const LinksData linksData = current.data(RoleDescriptionLinks).value<LinksData>();
|
2024-06-06 14:40:58 +02:00
|
|
|
const bool hasLinks = !linksData.isEmpty();
|
|
|
|
|
if (hasLinks) {
|
2024-05-29 16:00:22 +02:00
|
|
|
QString linksHtml;
|
2024-05-30 19:20:48 +02:00
|
|
|
const QStringList links = transform(linksData, [](const LinksData::Type &link) {
|
2024-05-29 16:00:22 +02:00
|
|
|
const QString anchor = link.first.isEmpty() ? link.second : link.first;
|
2024-06-06 14:40:58 +02:00
|
|
|
return QString::fromLatin1(R"(<a href="%1" style="color:%2">%3 ></a>)")
|
|
|
|
|
.arg(link.second)
|
|
|
|
|
.arg(creatorColor(Theme::Token_Text_Accent).name())
|
|
|
|
|
.arg(anchor);
|
2024-05-29 16:00:22 +02:00
|
|
|
});
|
|
|
|
|
linksHtml = links.join("<br/>");
|
2024-06-26 08:38:51 +02:00
|
|
|
m_links->setText(toContentParagraph(linksHtml));
|
2024-05-29 16:00:22 +02:00
|
|
|
}
|
2024-06-26 08:38:51 +02:00
|
|
|
m_linksTitle->setVisible(hasLinks);
|
|
|
|
|
m_links->setVisible(hasLinks);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-06-26 08:38:51 +02:00
|
|
|
m_imgTaskTreeRunner.reset();
|
|
|
|
|
m_imageMovie.stop();
|
|
|
|
|
m_imageDataBuffer.close();
|
|
|
|
|
m_image->clear();
|
2024-05-29 16:00:22 +02:00
|
|
|
const ImagesData imagesData = current.data(RoleDescriptionImages).value<ImagesData>();
|
2024-06-06 14:40:58 +02:00
|
|
|
const bool hasImages = !imagesData.isEmpty();
|
|
|
|
|
if (hasImages) {
|
|
|
|
|
const ImagesData::Type &image = imagesData.constFirst(); // Only show one image
|
2024-06-26 08:38:51 +02:00
|
|
|
m_imageTitle->setText(image.first);
|
2024-06-06 14:40:58 +02:00
|
|
|
fetchAndDisplayImage(image.second);
|
2024-05-29 16:00:22 +02:00
|
|
|
}
|
2024-06-26 08:38:51 +02:00
|
|
|
m_imageTitle->setVisible(hasImages);
|
|
|
|
|
m_image->setVisible(hasImages);
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2024-05-29 16:00:22 +02:00
|
|
|
const QStringList tags = current.data(RoleTags).toStringList();
|
2024-06-26 08:38:51 +02:00
|
|
|
m_tags->setTags(tags);
|
2024-06-06 14:40:58 +02:00
|
|
|
const bool hasTags = !tags.isEmpty();
|
2024-06-26 08:38:51 +02:00
|
|
|
m_tagsTitle->setVisible(hasTags);
|
|
|
|
|
m_tags->setVisible(hasTags);
|
2023-11-13 16:35:01 +01:00
|
|
|
|
2024-05-29 16:00:22 +02:00
|
|
|
const QStringList platforms = current.data(RolePlatforms).toStringList();
|
2024-06-06 14:40:58 +02:00
|
|
|
const bool hasPlatforms = !platforms.isEmpty();
|
|
|
|
|
if (hasPlatforms)
|
2024-06-26 08:38:51 +02:00
|
|
|
m_platforms->setText(toContentParagraph(platforms.join("<br/>")));
|
|
|
|
|
m_platformsTitle->setVisible(hasPlatforms);
|
|
|
|
|
m_platforms->setVisible(hasPlatforms);
|
2024-05-29 16:00:22 +02:00
|
|
|
|
|
|
|
|
const QStringList dependencies = current.data(RoleDependencies).toStringList();
|
2024-06-06 14:40:58 +02:00
|
|
|
const bool hasDependencies = !dependencies.isEmpty();
|
|
|
|
|
if (hasDependencies)
|
2024-06-26 08:38:51 +02:00
|
|
|
m_dependencies->setText(toContentParagraph(dependencies.join("<br/>")));
|
|
|
|
|
m_dependenciesTitle->setVisible(hasDependencies);
|
|
|
|
|
m_dependencies->setVisible(hasDependencies);
|
2024-06-06 14:40:58 +02:00
|
|
|
|
|
|
|
|
const PluginsData plugins = current.data(RolePlugins).value<PluginsData>();
|
|
|
|
|
const bool hasExtensions = isPack && !plugins.isEmpty();
|
|
|
|
|
if (hasExtensions) {
|
2024-05-30 19:20:48 +02:00
|
|
|
const QStringList extensions = transform(plugins, &QPair<QString, QString>::first);
|
2024-06-26 08:38:51 +02:00
|
|
|
m_packExtensions->setText(toContentParagraph(extensions.join("<br/>")));
|
2023-11-13 16:35:01 +01:00
|
|
|
}
|
2024-06-26 08:38:51 +02:00
|
|
|
m_packExtensionsTitle->setVisible(hasExtensions);
|
|
|
|
|
m_packExtensions->setVisible(hasExtensions);
|
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() {
|
2024-06-25 10:34:55 +02:00
|
|
|
progressDialog.reset(new QProgressDialog(
|
|
|
|
|
Tr::tr("Downloading..."), Tr::tr("Cancel"), 0, 0, ICore::dialogParent()));
|
|
|
|
|
progressDialog->setWindowTitle(Tr::tr("Download Extension"));
|
2024-05-29 16:00:22 +02:00
|
|
|
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"),
|
2024-06-25 10:34:55 +02:00
|
|
|
Tr::tr("Cannot download extension") + "\n\n" + storage->url.toString() + "\n\n"
|
2024-05-29 16:00:22 +02:00
|
|
|
+ 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),
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-26 08:38:51 +02:00
|
|
|
m_dlTaskTreeRunner.start(group);
|
2024-06-06 14:40:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExtensionManagerWidget::fetchAndDisplayImage(const QUrl &url)
|
|
|
|
|
{
|
|
|
|
|
using namespace Tasking;
|
|
|
|
|
|
|
|
|
|
struct StorageStruct
|
|
|
|
|
{
|
|
|
|
|
QByteArray imageData;
|
|
|
|
|
QUrl url;
|
|
|
|
|
};
|
|
|
|
|
Storage<StorageStruct> storage;
|
|
|
|
|
|
|
|
|
|
const auto onFetchSetup = [url, storage](NetworkQuery &query) {
|
|
|
|
|
storage->url = url;
|
|
|
|
|
query.setRequest(QNetworkRequest(url));
|
|
|
|
|
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
|
|
|
|
};
|
|
|
|
|
const auto onFetchDone = [storage](const NetworkQuery &query, DoneWith result) {
|
|
|
|
|
if (result == DoneWith::Success)
|
|
|
|
|
storage->imageData = query.reply()->readAll();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onShowImage = [storage, this]() {
|
|
|
|
|
if (storage->imageData.isEmpty())
|
|
|
|
|
return;
|
2024-06-26 08:38:51 +02:00
|
|
|
m_imageDataBuffer.setData(storage->imageData);
|
|
|
|
|
if (!m_imageDataBuffer.open(QIODevice::ReadOnly))
|
2024-06-06 14:40:58 +02:00
|
|
|
return;
|
2024-06-26 08:38:51 +02:00
|
|
|
QImageReader reader(&m_imageDataBuffer);
|
2024-06-06 14:40:58 +02:00
|
|
|
const bool animated = reader.supportsAnimation();
|
|
|
|
|
if (animated) {
|
2024-06-26 08:38:51 +02:00
|
|
|
m_image->setMovie(&m_imageMovie);
|
|
|
|
|
m_imageMovie.start();
|
2024-06-06 14:40:58 +02:00
|
|
|
} else {
|
|
|
|
|
const QPixmap pixmap = QPixmap::fromImage(reader.read());
|
2024-06-26 08:38:51 +02:00
|
|
|
m_image->setPixmap(pixmap);
|
2024-06-06 14:40:58 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Group group{
|
|
|
|
|
storage,
|
|
|
|
|
NetworkQueryTask{onFetchSetup, onFetchDone},
|
|
|
|
|
onGroupDone(onShowImage),
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-26 08:38:51 +02:00
|
|
|
m_imgTaskTreeRunner.start(group);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QWidget *createExtensionManagerWidget()
|
|
|
|
|
{
|
|
|
|
|
return new ExtensionManagerWidget;
|
2024-05-29 16:00:22 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-13 16:35:01 +01:00
|
|
|
} // ExtensionManager::Internal
|
2024-06-06 14:40:58 +02:00
|
|
|
|
|
|
|
|
#include "extensionmanagerwidget.moc"
|