diff options
Diffstat (limited to 'src/plugins/extensionmanager/extensionmanagerwidget.cpp')
-rw-r--r-- | src/plugins/extensionmanager/extensionmanagerwidget.cpp | 649 |
1 files changed, 464 insertions, 185 deletions
diff --git a/src/plugins/extensionmanager/extensionmanagerwidget.cpp b/src/plugins/extensionmanager/extensionmanagerwidget.cpp index 363b8cb90ee..dea2f9e806f 100644 --- a/src/plugins/extensionmanager/extensionmanagerwidget.cpp +++ b/src/plugins/extensionmanager/extensionmanagerwidget.cpp @@ -33,16 +33,56 @@ #include <utils/utilsicons.h> #include <QAction> +#include <QApplication> +#include <QBuffer> #include <QCheckBox> +#include <QHBoxLayout> +#include <QImageReader> #include <QMessageBox> -#include <QTextBrowser> +#include <QMovie> +#include <QPainter> #include <QProgressDialog> +#include <QScrollArea> +#include <QSignalMapper> using namespace Core; using namespace Utils; +using namespace StyleHelper; +using namespace WelcomePageHelpers; namespace ExtensionManager::Internal { +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; +}; + class CollapsingWidget : public QWidget { public: @@ -68,6 +108,169 @@ private: int m_width = 100; }; +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; + const bool isRemotePlugin = !(isPack || ExtensionsModel::pluginSpecForName(name)); + 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; +}; + class PluginStatusWidget : public QWidget { public: @@ -128,49 +331,149 @@ private: QString m_pluginName; }; +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; +}; + class ExtensionManagerWidgetPrivate { public: QString currentItemName; - ExtensionsBrowser *leftColumn; + ExtensionsBrowser *extensionBrowser; CollapsingWidget *secondaryDescriptionWidget; - QTextBrowser *primaryDescription; - QTextBrowser *secondaryDescription; + HeadingWidget *headingWidget; + QWidget *primaryContent; + QWidget *secondaryContent; + QLabel *description; + QLabel *linksTitle; + QLabel *links; + QLabel *imageTitle; + QLabel *image; + QBuffer imageDataBuffer; + QMovie imageMovie; + QLabel *tagsTitle; + TagList *tags; + QLabel *platformsTitle; + QLabel *platforms; + QLabel *dependenciesTitle; + QLabel *dependencies; + QLabel *packExtensionsTitle; + QLabel *packExtensions; PluginStatusWidget *pluginStatus; - QAbstractButton *installButton; PluginsData currentItemPlugins; - Tasking::TaskTreeRunner taskTreeRunner; + Tasking::TaskTreeRunner dlTaskTreeRunner; + Tasking::TaskTreeRunner imgTaskTreeRunner; }; ExtensionManagerWidget::ExtensionManagerWidget(QWidget *parent) : ResizeSignallingWidget(parent) , d(new ExtensionManagerWidgetPrivate) { - d->leftColumn = new ExtensionsBrowser; - + d->extensionBrowser = new ExtensionsBrowser; auto descriptionColumns = new QWidget; - 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); + d->headingWidget = new HeadingWidget; + d->description = tfLabel(contentTF, false); + d->description->setWordWrap(true); + d->linksTitle = sectionTitle(h6CapitalTF, Tr::tr("More information")); + d->links = tfLabel(contentTF, false); + d->imageTitle = sectionTitle(h6CapitalTF, {}); + d->image = new QLabel; + d->imageMovie.setDevice(&d->imageDataBuffer); + using namespace Layouting; + auto primary = new QWidget; + const auto spL = spacing(SpacingTokens::VPaddingL); + Column { + d->description, + Column { d->linksTitle, d->links, spL }, + Column { d->imageTitle, d->image, spL }, + st, + noMargin, spacing(SpacingTokens::ExVPaddingGapXl), + }.attachTo(primary); + d->primaryContent = toScrollableColumn(primary); + + d->tagsTitle = sectionTitle(h6TF, Tr::tr("Tags")); + d->tags = new TagList; + d->platformsTitle = sectionTitle(h6TF, Tr::tr("Platforms")); + d->platforms = tfLabel(contentTF, false); + d->dependenciesTitle = sectionTitle(h6TF, Tr::tr("Dependencies")); + d->dependencies = tfLabel(contentTF, false); + d->packExtensionsTitle = sectionTitle(h6TF, Tr::tr("Extensions in pack")); + d->packExtensions = tfLabel(contentTF, false); d->pluginStatus = new PluginStatusWidget; - d->installButton = new Button(Tr::tr("Install..."), Button::MediumPrimary); - d->installButton->hide(); + auto secondary = new QWidget; + const auto spXxs = spacing(SpacingTokens::VPaddingXxs); + Column { + sectionTitle(h6CapitalTF, Tr::tr("Extension details")), + Column { + Column { d->tagsTitle, d->tags, spXxs }, + Column { d->platformsTitle, d->platforms, spXxs }, + Column { d->dependenciesTitle, d->dependencies, spXxs }, + Column { d->packExtensionsTitle, d->packExtensions, spXxs }, + spacing(SpacingTokens::VPaddingL), + }, + st, + noMargin, spacing(SpacingTokens::ExVPaddingGapXl), + }.attachTo(secondary); + d->secondaryContent = toScrollableColumn(secondary); - using namespace Layouting; Row { WelcomePageHelpers::createRule(Qt::Vertical), Column { - d->secondaryDescription, + d->secondaryContent, d->pluginStatus, - d->installButton, }, noMargin, spacing(0), }.attachTo(d->secondaryDescriptionWidget); @@ -178,34 +481,44 @@ ExtensionManagerWidget::ExtensionManagerWidget(QWidget *parent) Row { WelcomePageHelpers::createRule(Qt::Vertical), Row { - d->primaryDescription, - noMargin, + Column { + Column { + d->headingWidget, + customMargins(SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl, + SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl), + }, + d->primaryContent, + }, }, d->secondaryDescriptionWidget, noMargin, spacing(0), }.attachTo(descriptionColumns); Row { - Space(StyleHelper::SpacingTokens::ExVPaddingGapXl), - d->leftColumn, + Space(SpacingTokens::ExVPaddingGapXl), + d->extensionBrowser, descriptionColumns, noMargin, spacing(0), }.attachTo(this); WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default); - connect(d->leftColumn, &ExtensionsBrowser::itemSelected, + connect(d->extensionBrowser, &ExtensionsBrowser::itemSelected, this, &ExtensionManagerWidget::updateView); connect(this, &ResizeSignallingWidget::resized, this, [this](const QSize &size) { - const int intendedLeftColumnWidth = size.width() - 580; - d->leftColumn->adjustToWidth(intendedLeftColumnWidth); + const int intendedBrowserColumnWidth = size.width() - 580; + d->extensionBrowser->adjustToWidth(intendedBrowserColumnWidth); const bool secondaryDescriptionVisible = size.width() > 970; const int secondaryDescriptionWidth = secondaryDescriptionVisible ? 264 : 0; d->secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth); }); - connect(d->installButton, &QAbstractButton::pressed, this, [this]() { + connect(d->headingWidget, &HeadingWidget::pluginInstallationRequested, this, [this](){ fetchAndInstallPlugin(QUrl::fromUserInput(d->currentItemPlugins.constFirst().second)); }); + connect(d->tags, &TagList::tagSelected, d->extensionBrowser, &ExtensionsBrowser::setFilter); + connect(d->headingWidget, &HeadingWidget::vendorClicked, + d->extensionBrowser, &ExtensionsBrowser::setFilter); + updateView({}); } @@ -216,199 +529,116 @@ ExtensionManagerWidget::~ExtensionManagerWidget() void ExtensionManagerWidget::updateView(const QModelIndex ¤t) { - const QString h5Css = - StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH5)) - + "; margin-top: 0px;"; - const QString h6Css = - StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6)) - + "; margin-top: 28px;"; - const QString h6CapitalCss = - StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6Capital)) - + QString::fromLatin1("; margin-top: 0px; color: %1;") - .arg(creatorColor(Theme::Token_Text_Muted).name()); - const QString bodyStyle = QString::fromLatin1("color: %1; background-color: %2; " - "margin-left: %3px; margin-right: %3px;") - .arg(creatorColor(Theme::Token_Text_Default).name()) - .arg(creatorColor(Theme::Token_Background_Muted).name()) - .arg(StyleHelper::SpacingTokens::ExVPaddingGapXl); - const QString htmlStart = QString(R"( - <html> - <body style="%1"><br/> - )").arg(bodyStyle); - const QString htmlEnd = QString(R"( - </body></html> - )"); - - if (!current.isValid()) { - const QString emptyHtml = htmlStart + htmlEnd; - d->primaryDescription->setText(emptyHtml); - d->secondaryDescription->setText(emptyHtml); + d->headingWidget->update(current); + + const bool showContent = current.isValid(); + d->primaryContent->setVisible(showContent); + d->secondaryContent->setVisible(showContent); + if (!showContent) return; - } 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); - { - QString description = htmlStart; + 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; + }; - QString descriptionHtml; - { - const TextData textData = current.data(RoleDescriptionText).value<TextData>(); + { + 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; 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) + QString::fromLatin1("<div style=\"%1\">%2</div>%3") + .arg(descriptionHtml.isEmpty() ? h4Css : h5Css) .arg(text.first) - .arg(text.second.join("<br/>")); + .arg(toContentParagraph(text.second.join("<br/>"))); descriptionHtml.append(paragraph); } + descriptionHtml.prepend(QString::fromLatin1("<body style=\"color:%1;\">") + .arg(creatorColor(Theme::Token_Text_Default).name())); + descriptionHtml.append("</body>"); + d->description->setText(descriptionHtml); } - description.append(descriptionHtml); + d->description->setVisible(hasDescription); - 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()) { + const bool hasLinks = !linksData.isEmpty(); + if (hasLinks) { QString linksHtml; const QStringList links = 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); + return QString::fromLatin1(R"(<a href="%1" style="color:%2">%3 ></a>)") + .arg(link.second) + .arg(creatorColor(Theme::Token_Text_Accent).name()) + .arg(anchor); }); linksHtml = links.join("<br/>"); - description.append(QString::fromLatin1("<p>%1</p>").arg(linksHtml)); + d->links->setText(toContentParagraph(linksHtml)); } + d->linksTitle->setVisible(hasLinks); + d->links->setVisible(hasLinks); + d->imgTaskTreeRunner.reset(); + d->imageMovie.stop(); + d->imageDataBuffer.close(); + d->image->clear(); const ImagesData imagesData = current.data(RoleDescriptionImages).value<ImagesData>(); - if (!imagesData.isEmpty()) { - const QString examplesBoxCss = - QString::fromLatin1("height: 168px; background-color: %1; ") - .arg(creatorColor(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)); - } - - // 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> - )")); + const bool hasImages = !imagesData.isEmpty(); + if (hasImages) { + const ImagesData::Type &image = imagesData.constFirst(); // Only show one image + d->imageTitle->setText(image.first); + fetchAndDisplayImage(image.second); } - - description.append(htmlEnd); - d->primaryDescription->setText(description); + d->imageTitle->setVisible(hasImages); + d->image->setVisible(hasImages); } { - QString description = htmlStart; - - description.append(QString(R"( - <p style="%1">%2</p> - )").arg(h6CapitalCss) - .arg(Tr::tr("Extension details"))); - 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(creatorColor(Theme::Token_Stroke_Subtle).name()); - const QStringList tagsFmt = 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(" "))); - } + d->tags->setTags(tags); + const bool hasTags = !tags.isEmpty(); + d->tagsTitle->setVisible(hasTags); + d->tags->setVisible(hasTags); 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 bool hasPlatforms = !platforms.isEmpty(); + if (hasPlatforms) + d->platforms->setText(toContentParagraph(platforms.join("<br/>"))); + d->platformsTitle->setVisible(hasPlatforms); + d->platforms->setVisible(hasPlatforms); 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)); - } - - if (isPack) { - const PluginsData plugins = current.data(RolePlugins).value<PluginsData>(); + const bool hasDependencies = !dependencies.isEmpty(); + if (hasDependencies) + d->dependencies->setText(toContentParagraph(dependencies.join("<br/>"))); + d->dependenciesTitle->setVisible(hasDependencies); + d->dependencies->setVisible(hasDependencies); + + const PluginsData plugins = current.data(RolePlugins).value<PluginsData>(); + const bool hasExtensions = isPack && !plugins.isEmpty(); + if (hasExtensions) { const QStringList extensions = transform(plugins, &QPair<QString, QString>::first); - const QString extensionsFmt = extensions.join("<br/>"); - description.append(QString(R"( - <div style="%1">%2</div> - <p>%3</p> - )").arg(h6Css) - .arg(Tr::tr("Extensions in pack")) - .arg(extensionsFmt)); + d->packExtensions->setText(toContentParagraph(extensions.join("<br/>"))); } - - description.append(htmlEnd); - d->secondaryDescription->setText(description); + d->packExtensionsTitle->setVisible(hasExtensions); + d->packExtensions->setVisible(hasExtensions); } } @@ -469,7 +699,56 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url) onGroupDone(onPluginInstallation), }; - d->taskTreeRunner.start(group); + d->dlTaskTreeRunner.start(group); +} + +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; + d->imageDataBuffer.setData(storage->imageData); + if (!d->imageDataBuffer.open(QIODevice::ReadOnly)) + return; + QImageReader reader(&d->imageDataBuffer); + const bool animated = reader.supportsAnimation(); + if (animated) { + d->image->setMovie(&d->imageMovie); + d->imageMovie.start(); + } else { + const QPixmap pixmap = QPixmap::fromImage(reader.read()); + d->image->setPixmap(pixmap); + } + }; + + Group group{ + storage, + NetworkQueryTask{onFetchSetup, onFetchDone}, + onGroupDone(onShowImage), + }; + + d->imgTaskTreeRunner.start(group); } } // ExtensionManager::Internal + +#include "extensionmanagerwidget.moc" |