Files
qt-creator/src/plugins/extensionmanager/extensionsbrowser.cpp
Marcus Tillmanns e429a11fd7 ExtensionBrowser: Warn on error
Change-Id: Ie7ceb888593563cd8d50c60d052a855417045b26
Reviewed-by: Artem Sokolovskii <artem.sokolovskii@qt.io>
2024-07-26 13:21:42 +00:00

753 lines
30 KiB
C++

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "extensionsbrowser.h"
#include "extensionmanagertr.h"
#include "extensionsmodel.h"
#include "extensionmanagersettings.h"
#ifdef WITH_TESTS
#include "extensionmanager_test.h"
#endif // WITH_TESTS
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/plugininstallwizard.h>
#include <coreplugin/welcomepagehelper.h>
#include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginspec.h>
#include <extensionsystem/pluginview.h>
#include <extensionsystem/pluginmanager.h>
#include <solutions/spinner/spinner.h>
#include <solutions/tasking/networkquery.h>
#include <solutions/tasking/tasktree.h>
#include <solutions/tasking/tasktreerunner.h>
#include <utils/algorithm.h>
#include <utils/elidinglabel.h>
#include <utils/fancylineedit.h>
#include <utils/hostosinfo.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h>
#include <utils/networkaccessmanager.h>
#include <utils/stylehelper.h>
#include <QApplication>
#include <QItemDelegate>
#include <QLabel>
#include <QListView>
#include <QMessageBox>
#include <QPainter>
#include <QPainterPath>
#include <QStyle>
using namespace Core;
using namespace ExtensionSystem;
using namespace Utils;
using namespace StyleHelper;
using namespace SpacingTokens;
using namespace WelcomePageHelpers;
namespace ExtensionManager::Internal {
Q_LOGGING_CATEGORY(browserLog, "qtc.extensionmanager.browser", QtWarningMsg)
constexpr int gapSize = HGapL;
constexpr int itemWidth = 330;
constexpr int cellWidth = itemWidth + gapSize;
class OptionChooser : public QComboBox
{
public:
OptionChooser(const FilePath &iconMask, const QString &textTemplate, QWidget *parent = nullptr)
: QComboBox(parent)
, m_iconDefault(Icon({{iconMask, m_colorDefault}}, Icon::Tint).icon())
, m_iconActive(Icon({{iconMask, m_colorActive}}, Icon::Tint).icon())
, m_textTemplate(textTemplate)
{
setMouseTracking(true);
connect(this, &QComboBox::currentIndexChanged, this, &QWidget::updateGeometry);
}
protected:
void paintEvent([[maybe_unused]] QPaintEvent *event) override
{
// +------------+------+---------+---------------+------------+
// | | | | (VPaddingXs) | |
// | | | +---------------+ |
// |(HPaddingXs)|(icon)|(HGapXxs)|<template%item>|(HPaddingXs)|
// | | | +---------------+ |
// | | | | (VPaddingXs) | |
// +------------+------+---------+---------------+------------+
const bool active = currentIndex() > 0;
const bool hover = underMouse();
const TextFormat &tF = (active || hover) ? m_itemActiveTf : m_itemDefaultTf;
const QRect iconRect(HPaddingXs, 0, m_iconSize.width(), height());
const int textX = iconRect.right() + 1 + HGapXxs;
const QRect textRect(textX, VPaddingXs,
width() - HPaddingXs - textX, tF.lineHeight());
QPainter p(this);
(active ? m_iconActive : m_iconDefault).paint(&p, iconRect);
p.setPen(tF.color());
p.setFont(tF.font());
const QString elidedText = p.fontMetrics().elidedText(currentFormattedText(),
Qt::ElideRight,
textRect.width() + HPaddingXs);
p.drawText(textRect, tF.drawTextFlags, elidedText);
}
void enterEvent(QEnterEvent *event) override
{
QComboBox::enterEvent(event);
update();
}
void leaveEvent(QEvent *event) override
{
QComboBox::leaveEvent(event);
update();
}
private:
QSize sizeHint() const override
{
const QFontMetrics fm(m_itemDefaultTf.font());
const int textWidth = fm.horizontalAdvance(currentFormattedText());
const int width =
HPaddingXs
+ m_iconSize.width()
+ HGapXxs
+ textWidth
+ HPaddingXs;
const int height =
VPaddingXs
+ m_itemDefaultTf.lineHeight()
+ VPaddingXs;
return {width, height};
}
QString currentFormattedText() const
{
return m_textTemplate.arg(currentText());
}
constexpr static Theme::Color m_colorDefault = Theme::Token_Text_Muted;
constexpr static Theme::Color m_colorActive = Theme::Token_Text_Default;
constexpr static QSize m_iconSize{16, 16};
constexpr static TextFormat m_itemDefaultTf
{m_colorDefault, UiElement::UiElementLabelMedium};
constexpr static TextFormat m_itemActiveTf
{m_colorActive, m_itemDefaultTf.uiElement};
const QIcon m_iconDefault;
const QIcon m_iconActive;
const QString m_textTemplate;
};
static QString extensionStateDisplayString(ExtensionState state)
{
switch (state) {
case InstalledEnabled:
return Tr::tr("Loaded");
case InstalledDisabled:
return Tr::tr("Installed");
default:
return {};
}
return {};
}
class ExtensionItemDelegate : public QItemDelegate
{
public:
constexpr static QSize dividerS{1, 16};
constexpr static TextFormat itemNameTF
{Theme::Token_Text_Default, UiElement::UiElementH6};
constexpr static TextFormat countTF
{Theme::Token_Text_Default, UiElement::UiElementLabelSmall,
Qt::AlignCenter | Qt::TextDontClip};
constexpr static TextFormat vendorTF
{Theme::Token_Text_Muted, UiElement::UiElementLabelSmall,
Qt::AlignVCenter | Qt::TextDontClip};
constexpr static TextFormat stateTF
{vendorTF.themeColor, UiElement::UiElementCaption, vendorTF.drawTextFlags};
constexpr static TextFormat tagsTF
{Theme::Token_Text_Default, UiElement::UiElementCaption};
explicit ExtensionItemDelegate(QObject *parent = nullptr)
: QItemDelegate(parent)
{
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const override
{
// +---------------+-------+---------------+----------------------------------------------------------------------+---------------+---------+
// | | | | (ExPaddingGapL) | | |
// | | | +-----------------------------+---------+--------+---------+-----------+ | |
// | | | | <itemName> |(HGapXxs)|<status>|(HGapXxs)|<checkmark>| | |
// | | | +-----------------------------+---------+--------+---------+-----------+ | |
// | | | | (VGapXxs) | | |
// | | | +--------+--------+--------------+--------+--------+---------+---------+ | |
// |(ExPaddingGapL)|<icon> |(ExPaddingGapL)|<vendor>|(HGapXs)|<divider>(h16)|(HGapXs)|<dlIcon>|(HGapXxs)|<dlCount>|(ExPaddingGapL)|(gapSize)|
// | |(50x50)| +--------+--------+--------------+--------+--------+---------+---------+ | |
// | | | | (VGapXxs) | | |
// | | | +----------------------------------------------------------------------+ | |
// | | | | <tags> | | |
// | | | +----------------------------------------------------------------------+ | |
// | | | | (ExPaddingGapL) | | |
// +---------------+-------+---------------+----------------------------------------------------------------------+---------------+---------+
// | (gapSize) |
// +----------------------------------------------------------------------------------------------------------------------------------------+
const QRect bgRGlobal = option.rect.adjusted(0, 0, -gapSize, -gapSize);
const QRect bgR = bgRGlobal.translated(-option.rect.topLeft());
const int middleColumnW = bgR.width() - ExPaddingGapL - iconBgSizeSmall.width()
- ExPaddingGapL - ExPaddingGapL;
int x = bgR.x();
int y = bgR.y();
x += ExPaddingGapL;
const QRect iconBgR(x, y + (bgR.height() - iconBgSizeSmall.height()) / 2,
iconBgSizeSmall.width(), iconBgSizeSmall.height());
x += iconBgSizeSmall.width() + ExPaddingGapL;
y += ExPaddingGapL;
const QRect itemNameR(x, y, middleColumnW, itemNameTF.lineHeight());
const QString itemName = index.data().toString();
const QSize checkmarkS(12, 12);
const QRect checkmarkR(x + middleColumnW - checkmarkS.width(), y,
checkmarkS.width(), checkmarkS.height());
const ExtensionState state = index.data(RoleExtensionState).value<ExtensionState>();
const QString stateString = extensionStateDisplayString(state);
const bool showState = (state == InstalledEnabled || state == InstalledDisabled)
&& !stateString.isEmpty();
const QFont stateFont = stateTF.font();
const QFontMetrics stateFM(stateFont);
const int stateStringWidth = stateFM.horizontalAdvance(stateString);
const QRect stateR(checkmarkR.x() - HGapXxs - stateStringWidth, y,
stateStringWidth, stateTF.lineHeight());
y += itemNameR.height() + VGapXxs;
const QRect vendorRowR(x, y, middleColumnW, vendorRowHeight());
QRect vendorR = vendorRowR;
y += vendorRowR.height() + VGapXxs;
const QRect tagsR(x, y, middleColumnW, tagsTF.lineHeight());
QTC_CHECK(option.rect.height() - 1 == tagsR.bottom() + ExPaddingGapL + gapSize);
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->translate(bgRGlobal.topLeft());
const bool isPack = index.data(RoleItemType) == ItemTypePack;
{
const bool selected = option.state & QStyle::State_Selected;
const bool hovered = option.state & QStyle::State_MouseOver;
const QColor fillColor =
creatorColor(hovered ? WelcomePageHelpers::cardHoverBackground
: WelcomePageHelpers::cardDefaultBackground);
const QColor strokeColor =
creatorColor(selected ? Theme::Token_Stroke_Strong
: hovered ? WelcomePageHelpers::cardHoverStroke
: WelcomePageHelpers::cardDefaultStroke);
WelcomePageHelpers::drawCardBackground(painter, bgR, fillColor, strokeColor);
}
{
const QPixmap icon = itemIcon(index, SizeSmall);
painter->drawPixmap(iconBgR.topLeft(), icon);
}
if (isPack) {
constexpr int circleSize = 18;
constexpr int circleOverlap = 3; // Protrusion from lower right corner of iconRect
const QRect smallCircle(iconBgR.right() + 1 + circleOverlap - circleSize,
iconBgR.bottom() + 1 + circleOverlap - circleSize,
circleSize, circleSize);
const QColor fillColor = creatorColor(Theme::Token_Foreground_Muted);
const QColor strokeColor = creatorColor(Theme::Token_Stroke_Subtle);
drawCardBackground(painter, smallCircle, fillColor, strokeColor, circleSize / 2);
painter->setFont(countTF.font());
painter->setPen(countTF.color());
const PluginsData plugins = index.data(RolePlugins).value<PluginsData>();
painter->drawText(smallCircle, countTF.drawTextFlags, QString::number(plugins.count()));
}
{
QRect effectiveR = itemNameR;
if (showState)
effectiveR.setRight(stateR.left() - HGapXxs - 1);
painter->setPen(itemNameTF.color());
painter->setFont(itemNameTF.font());
const QString titleElided
= painter->fontMetrics().elidedText(itemName, Qt::ElideRight, effectiveR.width());
painter->drawText(effectiveR, itemNameTF.drawTextFlags, titleElided);
}
if (showState) {
static const QIcon checkmark = Icon({{":/extensionmanager/images/checkmark.png",
stateTF.themeColor}}, Icon::Tint).icon();
checkmark.paint(painter, checkmarkR);
painter->setPen(stateTF.color());
painter->setFont(stateTF.font());
painter->drawText(stateR, stateTF.drawTextFlags, stateString);
}
{
const QString vendor = index.data(RoleVendor).toString();
const QFontMetrics fm(vendorTF.font());
painter->setPen(vendorTF.color());
painter->setFont(vendorTF.font());
if (const int dlCount = index.data(RoleDownloadCount).toInt(); dlCount > 0) {
constexpr QSize dlIconS(16, 16);
const QString dlCountString = QString::number(dlCount);
const int dlCountW = fm.horizontalAdvance(dlCountString);
const int dlItemsW = HGapXs + dividerS.width() + HGapXs + dlIconS.width()
+ HGapXxs + dlCountW;
const int vendorW = fm.horizontalAdvance(vendor);
vendorR.setWidth(qMin(middleColumnW - dlItemsW, vendorW));
QRect dividerR = vendorRowR;
dividerR.setLeft(vendorR.right() + HGapXs);
dividerR.setWidth(dividerS.width());
painter->fillRect(dividerR, vendorTF.color());
QRect dlIconR = vendorRowR;
dlIconR.setLeft(dividerR.right() + HGapXs);
dlIconR.setWidth(dlIconS.width());
static const QIcon dlIcon = Icon({{":/extensionmanager/images/download.png",
vendorTF.themeColor}}, Icon::Tint).icon();
dlIcon.paint(painter, dlIconR);
QRect dlCountR = vendorRowR;
dlCountR.setLeft(dlIconR.right() + HGapXxs);
painter->drawText(dlCountR, vendorTF.drawTextFlags, dlCountString);
}
const QString vendorElided = fm.elidedText(vendor, Qt::ElideRight, vendorR.width());
painter->drawText(vendorR, vendorTF.drawTextFlags, vendorElided);
}
{
const QStringList tagList = index.data(RoleTags).toStringList();
const QString tags = tagList.join(", ");
painter->setPen(tagsTF.color());
painter->setFont(tagsTF.font());
const QString tagsElided
= painter->fontMetrics().elidedText(tags, Qt::ElideRight, tagsR.width());
painter->drawText(tagsR, tagsTF.drawTextFlags, tagsElided);
}
painter->restore();
}
static int vendorRowHeight()
{
return qMax(vendorTF.lineHeight(), dividerS.height());
}
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
[[maybe_unused]] const QModelIndex &index) const override
{
const int middleColumnH =
itemNameTF.lineHeight()
+ VGapXxs
+ vendorRowHeight()
+ VGapXxs
+ tagsTF.lineHeight();
const int height =
ExPaddingGapL
+ qMax(iconBgSizeSmall.height(), middleColumnH)
+ ExPaddingGapL;
return {cellWidth, height + gapSize};
}
};
class SortFilterProxyModel : public QSortFilterProxyModel
{
public:
struct SortOption {
const QString displayName;
const Role role;
const Qt::SortOrder order = Qt::AscendingOrder;
};
struct FilterOption {
const QString displayName;
const std::function<bool(const QModelIndex &)> indexAcceptedFunc;
};
SortFilterProxyModel(QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
setSortCaseSensitivity(Qt::CaseInsensitive);
}
static const QList<SortOption> &sortOptions()
{
static const QList<SortOption> options = {
{Tr::tr("Name"), RoleName},
{Tr::tr("Vendor"), RoleVendor},
{Tr::tr("Popularity"), RoleDownloadCount, Qt::DescendingOrder},
};
return options;
}
void setSortOption(int index)
{
QTC_ASSERT(index < sortOptions().count(), index = 0);
m_sortOptionIndex = index;
const SortOption &option = sortOptions().at(index);
// Ensure some order for cases with insufficient data, e.g. RoleDownloadCount
setSortRole(RoleName);
sort(0);
if (option.role == RoleName)
return; // Already sorted.
setSortRole(option.role);
sort(0, option.order);
}
static const QList<FilterOption> &filterOptions()
{
static const QList<FilterOption> options = {
{
Tr::tr("All"),
[]([[maybe_unused]] const QModelIndex &index) {
return true;
},
},
{
Tr::tr("Extension packs"),
[](const QModelIndex &index) {
return index.data(RoleItemType).value<ItemType>() == ItemTypePack;
},
},
{
Tr::tr("Individual extensions"),
[](const QModelIndex &index) {
return index.data(RoleItemType).value<ItemType>() == ItemTypeExtension;
},
},
};
return options;
}
void setFilterOption(int index)
{
QTC_ASSERT(index < filterOptions().count(), index = 0);
beginResetModel();
m_filterOptionIndex = index;
endResetModel();
}
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
const SortOption &option = sortOptions().at(m_sortOptionIndex);
const ItemType leftType = left.data(RoleItemType).value<ItemType>();
const ItemType rightType = right.data(RoleItemType).value<ItemType>();
if (leftType != rightType)
return option.order == Qt::AscendingOrder ? leftType < rightType
: leftType > rightType;
return QSortFilterProxyModel::lessThan(left, right);
}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
return filterOptions().at(m_filterOptionIndex).indexAcceptedFunc(index);
}
int m_filterOptionIndex = 0;
int m_sortOptionIndex = 0;
};
class ExtensionsBrowserPrivate
{
public:
bool dataFetched = false;
ExtensionsModel *model;
QLineEdit *searchBox;
OptionChooser *filterChooser;
OptionChooser *sortChooser;
QListView *extensionsView;
QItemSelectionModel *selectionModel = nullptr;
QSortFilterProxyModel *searchProxyModel;
SortFilterProxyModel *sortFilterProxyModel;
int columnsCount = 2;
Tasking::TaskTreeRunner taskTreeRunner;
SpinnerSolution::Spinner *m_spinner;
};
ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
: QWidget(parent)
, d(new ExtensionsBrowserPrivate)
{
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
static const TextFormat titleTF
{Theme::Token_Text_Default, UiElementH2};
QLabel *titleLabel = tfLabel(titleTF);
titleLabel->setText(Tr::tr("Manage Extensions"));
d->searchBox = new SearchBox;
d->searchBox->setPlaceholderText(Tr::tr("Search"));
d->model = new ExtensionsModel(this);
d->searchProxyModel = new QSortFilterProxyModel(this);
d->searchProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
d->searchProxyModel->setFilterRole(RoleSearchText);
d->searchProxyModel->setSourceModel(d->model);
d->sortFilterProxyModel = new SortFilterProxyModel(this);
d->sortFilterProxyModel->setSourceModel(d->searchProxyModel);
d->filterChooser = new OptionChooser(":/extensionmanager/images/filter.png",
Tr::tr("Filter by: %1"));
d->filterChooser->addItems(Utils::transform(SortFilterProxyModel::filterOptions(),
&SortFilterProxyModel::FilterOption::displayName));
d->sortChooser = new OptionChooser(":/extensionmanager/images/sort.png", Tr::tr("Sort by: %1"));
d->sortChooser->addItems(Utils::transform(SortFilterProxyModel::sortOptions(),
&SortFilterProxyModel::SortOption::displayName));
d->extensionsView = new QListView;
d->extensionsView->setFrameStyle(QFrame::NoFrame);
d->extensionsView->setItemDelegate(new ExtensionItemDelegate(this));
d->extensionsView->setResizeMode(QListView::Adjust);
d->extensionsView->setSelectionMode(QListView::SingleSelection);
d->extensionsView->setUniformItemSizes(true);
d->extensionsView->setViewMode(QListView::IconMode);
d->extensionsView->setModel(d->sortFilterProxyModel);
d->extensionsView->setMouseTracking(true);
using namespace Layouting;
Column {
Column {
titleLabel,
customMargins(0, VPaddingM, 0, VPaddingM),
},
Row {
d->searchBox,
spacing(gapSize),
customMargins(0, VPaddingM, extraListViewWidth() + gapSize, VPaddingM),
},
Row {
d->filterChooser,
Space(HGapS),
d->sortChooser,
st,
customMargins(0, 0, extraListViewWidth() + gapSize, 0),
},
d->extensionsView,
noMargin, spacing(0),
}.attachTo(this);
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
WelcomePageHelpers::setBackgroundColor(d->extensionsView, Theme::Token_Background_Default);
WelcomePageHelpers::setBackgroundColor(d->extensionsView->viewport(),
Theme::Token_Background_Default);
d->m_spinner = new SpinnerSolution::Spinner(SpinnerSolution::SpinnerSize::Large, this);
d->m_spinner->hide();
auto updateModel = [this] {
d->sortFilterProxyModel->sort(0);
if (d->selectionModel == nullptr) {
d->selectionModel = new QItemSelectionModel(d->sortFilterProxyModel,
d->extensionsView);
d->extensionsView->setSelectionModel(d->selectionModel);
connect(d->extensionsView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &ExtensionsBrowser::itemSelected);
}
};
connect(PluginManager::instance(), &PluginManager::pluginsChanged, this, updateModel);
connect(d->searchBox, &QLineEdit::textChanged,
d->searchProxyModel, &QSortFilterProxyModel::setFilterWildcard);
connect(d->sortChooser, &OptionChooser::currentIndexChanged,
d->sortFilterProxyModel, &SortFilterProxyModel::setSortOption);
connect(d->filterChooser, &OptionChooser::currentIndexChanged,
d->sortFilterProxyModel, &SortFilterProxyModel::setFilterOption);
}
ExtensionsBrowser::~ExtensionsBrowser()
{
delete d;
}
void ExtensionsBrowser::setFilter(const QString &filter)
{
d->searchBox->setText(filter);
}
void ExtensionsBrowser::adjustToWidth(const int width)
{
const int widthForItems = width - extraListViewWidth();
d->columnsCount = qMax(1, qFloor(widthForItems / cellWidth));
updateGeometry();
}
QSize ExtensionsBrowser::sizeHint() const
{
const int columsWidth = d->columnsCount * cellWidth;
return { columsWidth + extraListViewWidth(), 0};
}
int ExtensionsBrowser::extraListViewWidth() const
{
// TODO: Investigate "transient" scrollbar, just for this list view.
constexpr int extraPadding = qMax(0, ExVPaddingGapXl - gapSize);
return d->extensionsView->style()->pixelMetric(QStyle::PM_ScrollBarExtent)
+ extraPadding
+ 1; // Needed
}
void ExtensionsBrowser::showEvent(QShowEvent *event)
{
if (!d->dataFetched) {
d->dataFetched = true;
fetchExtensions();
}
QWidget::showEvent(event);
}
static QString customOsTypeToString(OsType osType)
{
switch (osType) {
case OsTypeWindows:
return "Windows";
case OsTypeLinux:
return "Linux";
case OsTypeMac:
return "macOS";
case OsTypeOtherUnix:
return "Other Unix";
case OsTypeOther:
default:
return "Other";
}
}
void ExtensionsBrowser::fetchExtensions()
{
#ifdef WITH_TESTS
// Uncomment for testing with local json data.
// Available: "augmentedplugindata", "defaultpacks", "varieddata", "thirdpartyplugins"
// d->model->setExtensionsJson(testData("defaultpacks")); return;
#endif // WITH_TESTS
if (!settings().useExternalRepo()) {
d->model->setExtensionsJson({});
return;
}
using namespace Tasking;
const auto onQuerySetup = [this](NetworkQuery &query) {
const QString url = "%1/api/v1/search?request=";
const QString requestTemplate
= R"({"qtc_version":"%1","host_os":"%2","host_os_version":"%3","host_architecture":"%4","page_size":200})";
const QString request = url.arg(settings().externalRepoUrl()) + requestTemplate
.arg(QCoreApplication::applicationVersion())
.arg(customOsTypeToString(HostOsInfo::hostOs()))
.arg(QSysInfo::productVersion())
.arg(QSysInfo::currentCpuArchitecture());
query.setRequest(QNetworkRequest(QUrl::fromUserInput(request)));
query.setNetworkAccessManager(NetworkAccessManager::instance());
qCDebug(browserLog).noquote() << "Sending JSON request:" << request;
d->m_spinner->show();
};
const auto onQueryDone = [this](const NetworkQuery &query, DoneWith result) {
const QByteArray response = query.reply()->readAll();
qCDebug(browserLog).noquote() << "Got JSON QNetworkReply:" << query.reply()->error();
if (result == DoneWith::Success) {
qCDebug(browserLog).noquote() << "JSON response size:"
<< QLocale::system().formattedDataSize(response.size());
d->model->setExtensionsJson(response);
} else {
qCWarning(browserLog).noquote() << response;
d->model->setExtensionsJson({});
}
d->m_spinner->hide();
};
Group group {
NetworkQueryTask{onQuerySetup, onQueryDone},
};
d->taskTreeRunner.start(group);
}
QLabel *tfLabel(const TextFormat &tf, bool singleLine)
{
QLabel *label = singleLine ? new Utils::ElidingLabel : new QLabel;
if (singleLine)
label->setFixedHeight(tf.lineHeight());
label->setFont(tf.font());
label->setAlignment(Qt::Alignment(tf.drawTextFlags));
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
QPalette pal = label->palette();
pal.setColor(QPalette::WindowText, tf.color());
label->setPalette(pal);
return label;
}
QPixmap itemIcon(const QModelIndex &index, Size size)
{
const QSize iconBgS = size == SizeSmall ? iconBgSizeSmall : iconBgSizeBig;
const qreal dpr = qApp->devicePixelRatio();
QPixmap pixmap(iconBgS * dpr);
pixmap.fill(Qt::transparent);
pixmap.setDevicePixelRatio(dpr);
const QRect iconBgR(QPoint(), pixmap.deviceIndependentSize().toSize());
const PluginSpec *ps = pluginSpecForName(index.data(RoleName).toString());
const bool isEnabled = ps == nullptr || ps->isEffectivelyEnabled();
const QGradientStops gradientStops = {
{0, creatorColor(isEnabled ? Theme::Token_Gradient01_Start
: Theme::Token_Gradient02_Start)},
{1, creatorColor(isEnabled ? Theme::Token_Gradient01_End
: Theme::Token_Gradient02_End)},
};
const Theme::Color color = Theme::Token_Basic_White;
static const QIcon packS = Icon({{":/extensionmanager/images/packsmall.png", color}},
Icon::Tint).icon();
static const QIcon packB = Icon({{":/extensionmanager/images/packbig.png", color}},
Icon::Tint).icon();
static const QIcon extensionS = Icon({{":/extensionmanager/images/extensionsmall.png",
color}}, Icon::Tint).icon();
static const QIcon extensionB = Icon({{":/extensionmanager/images/extensionbig.png",
color}}, Icon::Tint).icon();
const ItemType itemType = index.data(RoleItemType).value<ItemType>();
const QIcon &icon = (itemType == ItemTypePack) ? (size == SizeSmall ? packS : packB)
: (size == SizeSmall ? extensionS : extensionB);
const int iconRectRounding = 4;
const qreal iconOpacityDisabled = 0.6;
QPainter p(&pixmap);
QLinearGradient gradient(iconBgR.topRight(), iconBgR.bottomLeft());
gradient.setStops(gradientStops);
WelcomePageHelpers::drawCardBackground(&p, iconBgR, gradient, Qt::NoPen, iconRectRounding);
if (!isEnabled)
p.setOpacity(iconOpacityDisabled);
icon.paint(&p, iconBgR);
return pixmap;
}
} // ExtensionManager::Internal