forked from qt-creator/qt-creator
ExtensionManager: Adapt service response parser to latest version
api/v1 on https://qc-extensions.qt.io/api-doc Change-Id: Ic559029f61d238810e21e586ab7a64c5e847e38d Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="/extensionmanager">
|
||||
<file>testdata/augmentedplugindata.json</file>
|
||||
<file>testdata/defaultpacks.json</file>
|
||||
<file>testdata/thirdpartyplugins.json</file>
|
||||
<file>testdata/varieddata.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@@ -57,14 +57,14 @@ namespace ExtensionManager::Internal {
|
||||
|
||||
Q_LOGGING_CATEGORY(widgetLog, "qtc.extensionmanager.widget", QtWarningMsg)
|
||||
|
||||
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};
|
||||
constexpr TextFormat h5TF
|
||||
{contentTF.themeColor, UiElement::UiElementH5};
|
||||
constexpr TextFormat h6TF
|
||||
{contentTF.themeColor, UiElement::UiElementH6};
|
||||
constexpr TextFormat h6CapitalTF
|
||||
{Theme::Token_Text_Muted, UiElement::UiElementH6Capital};
|
||||
|
||||
static QLabel *sectionTitle(const TextFormat &tf, const QString &title)
|
||||
{
|
||||
@@ -215,9 +215,9 @@ public:
|
||||
m_dlCount->setText(QString::number(dlCount));
|
||||
m_dlCountItems->setVisible(showDlCount);
|
||||
|
||||
const auto pluginData = current.data(RolePlugins).value<PluginsData>();
|
||||
const QStringList plugins = current.data(RolePlugins).toStringList();
|
||||
if (current.data(RoleItemType).toInt() == ItemTypePack) {
|
||||
const int pluginsCount = pluginData.count();
|
||||
const int pluginsCount = plugins.count();
|
||||
const QString details = Tr::tr("Pack contains %n plugins.", nullptr, pluginsCount);
|
||||
m_details->setText(details);
|
||||
} else {
|
||||
@@ -227,9 +227,10 @@ public:
|
||||
const ItemType itemType = current.data(RoleItemType).value<ItemType>();
|
||||
const bool isPack = itemType == ItemTypePack;
|
||||
const bool isRemotePlugin = !(isPack || pluginSpecForId(current.data(RoleId).toString()));
|
||||
installButton->setVisible(isRemotePlugin && !pluginData.empty());
|
||||
const QString downloadUrl = current.data(RoleDownloadUrl).toString();
|
||||
installButton->setVisible(isRemotePlugin && !downloadUrl.isEmpty());
|
||||
if (installButton->isVisible())
|
||||
installButton->setToolTip(pluginData.constFirst().second);
|
||||
installButton->setToolTip(downloadUrl);
|
||||
}
|
||||
|
||||
signals:
|
||||
@@ -383,25 +384,17 @@ public:
|
||||
private:
|
||||
void updateView(const QModelIndex ¤t);
|
||||
void fetchAndInstallPlugin(const QUrl &url);
|
||||
void fetchAndDisplayImage(const QUrl &url);
|
||||
|
||||
QString m_currentItemName;
|
||||
ExtensionsModel *m_extensionModel;
|
||||
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_compatVersionTitle;
|
||||
QLabel *m_compatVersion;
|
||||
QLabel *m_platformsTitle;
|
||||
QLabel *m_platforms;
|
||||
QLabel *m_dependenciesTitle;
|
||||
@@ -409,14 +402,14 @@ private:
|
||||
QLabel *m_packExtensionsTitle;
|
||||
QLabel *m_packExtensions;
|
||||
PluginStatusWidget *m_pluginStatus;
|
||||
PluginsData m_currentItemPlugins;
|
||||
QString m_currentDownloadUrl;
|
||||
Tasking::TaskTreeRunner m_dlTaskTreeRunner;
|
||||
Tasking::TaskTreeRunner m_imgTaskTreeRunner;
|
||||
};
|
||||
|
||||
ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
{
|
||||
m_extensionBrowser = new ExtensionsBrowser;
|
||||
m_extensionModel = new ExtensionsModel(this);
|
||||
m_extensionBrowser = new ExtensionsBrowser(m_extensionModel);
|
||||
auto descriptionColumns = new QWidget;
|
||||
m_secondaryDescriptionWidget = new CollapsingWidget;
|
||||
|
||||
@@ -425,21 +418,12 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
m_description->setWordWrap(true);
|
||||
m_description->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
m_description->setOpenExternalLinks(true);
|
||||
m_linksTitle = sectionTitle(h6CapitalTF, Tr::tr("More information"));
|
||||
m_links = tfLabel(contentTF, false);
|
||||
m_links->setOpenExternalLinks(true);
|
||||
m_links->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
m_imageTitle = sectionTitle(h6CapitalTF, {});
|
||||
m_image = new QLabel;
|
||||
m_imageMovie.setDevice(&m_imageDataBuffer);
|
||||
|
||||
using namespace Layouting;
|
||||
auto primary = new QWidget;
|
||||
const auto spL = spacing(SpacingTokens::VPaddingL);
|
||||
Column {
|
||||
m_description,
|
||||
Column { m_linksTitle, m_links, spL },
|
||||
Column { m_imageTitle, m_image, spL },
|
||||
st,
|
||||
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
|
||||
}.attachTo(primary);
|
||||
@@ -447,8 +431,6 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
|
||||
m_tagsTitle = sectionTitle(h6TF, Tr::tr("Tags"));
|
||||
m_tags = new TagList;
|
||||
m_compatVersionTitle = sectionTitle(h6TF, Tr::tr("Compatibility"));
|
||||
m_compatVersion = tfLabel(contentTF, false);
|
||||
m_platformsTitle = sectionTitle(h6TF, Tr::tr("Platforms"));
|
||||
m_platforms = tfLabel(contentTF, false);
|
||||
m_dependenciesTitle = sectionTitle(h6TF, Tr::tr("Dependencies"));
|
||||
@@ -463,7 +445,6 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
sectionTitle(h6CapitalTF, Tr::tr("Extension details")),
|
||||
Column {
|
||||
Column { m_tagsTitle, m_tags, spXxs },
|
||||
Column { m_compatVersionTitle, m_compatVersion, spXxs },
|
||||
Column { m_platformsTitle, m_platforms, spXxs },
|
||||
Column { m_dependenciesTitle, m_dependencies, spXxs },
|
||||
Column { m_packExtensionsTitle, m_packExtensions, spXxs },
|
||||
@@ -522,7 +503,7 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
m_secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
|
||||
});
|
||||
connect(m_headingWidget, &HeadingWidget::pluginInstallationRequested, this, [this](){
|
||||
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentItemPlugins.constFirst().second));
|
||||
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentDownloadUrl));
|
||||
});
|
||||
connect(m_tags, &TagList::tagSelected, m_extensionBrowser, &ExtensionsBrowser::setFilter);
|
||||
connect(m_headingWidget, &HeadingWidget::vendorClicked,
|
||||
@@ -546,7 +527,10 @@ static QString markdownToHtml(const QString &markdown)
|
||||
blockFormat.setBottomMargin(SpacingTokens::VGapL);
|
||||
QTextCursor cursor(block);
|
||||
cursor.mergeBlockFormat(blockFormat);
|
||||
const TextFormat headingTf = blockFormat.headingLevel() == 1 ? h5TF : h6TF;
|
||||
const TextFormat headingTf =
|
||||
blockFormat.headingLevel() == 1 ? h5TF
|
||||
: blockFormat.headingLevel() == 2 ? h6TF
|
||||
: h6CapitalTF;
|
||||
const QFont headingFont = headingTf.font();
|
||||
for (auto it = block.begin(); !(it.atEnd()); ++it) {
|
||||
QTextFragment fragment = it.fragment();
|
||||
@@ -555,9 +539,10 @@ static QString markdownToHtml(const QString &markdown)
|
||||
cursor.setPosition(fragment.position());
|
||||
cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
|
||||
if (blockFormat.hasProperty(QTextFormat::HeadingLevel)) {
|
||||
charFormat.setFontCapitalization(headingFont.capitalization());
|
||||
charFormat.setFontFamilies(headingFont.families());
|
||||
charFormat.setFontWeight(headingFont.weight());
|
||||
charFormat.setFontPointSize(headingFont.pointSizeF());
|
||||
charFormat.setFontWeight(headingFont.weight());
|
||||
charFormat.setForeground(headingTf.color());
|
||||
} else if (charFormat.isAnchor()) {
|
||||
charFormat.setForeground(creatorColor(Theme::Token_Text_Accent));
|
||||
@@ -587,96 +572,60 @@ void ExtensionManagerWidget::updateView(const QModelIndex ¤t)
|
||||
m_currentItemName = current.data(RoleName).toString();
|
||||
const bool isPack = current.data(RoleItemType) == ItemTypePack;
|
||||
m_pluginStatus->setPluginId(isPack ? QString() : current.data(RoleId).toString());
|
||||
m_currentItemPlugins = current.data(RolePlugins).value<PluginsData>();
|
||||
m_currentDownloadUrl = current.data(RoleDownloadUrl).toString();
|
||||
|
||||
auto toContentParagraph = [](const QString &text) {
|
||||
{
|
||||
const QStringList description = {
|
||||
"# " + m_currentItemName,
|
||||
current.data(RoleDescriptionShort).toString(),
|
||||
"",
|
||||
current.data(RoleDescriptionLong).toString()
|
||||
};
|
||||
const QString descriptionMarkdown = description.join("\n");
|
||||
m_description->setText(markdownToHtml(descriptionMarkdown));
|
||||
}
|
||||
|
||||
{
|
||||
auto idToDisplayName = [this](const QString &id) {
|
||||
const QModelIndex dependencyIndex = m_extensionModel->indexOfId(id);
|
||||
return dependencyIndex.data(RoleName).toString();
|
||||
};
|
||||
|
||||
auto toContentParagraph = [](const QStringList &text) {
|
||||
const QString lines = text.join("<br/>");
|
||||
const QString pHtml = QString::fromLatin1("<p style=\"margin-top:0;margin-bottom:0;"
|
||||
"line-height:%1px\">%2</p>")
|
||||
.arg(contentTF.lineHeight()).arg(text);
|
||||
.arg(contentTF.lineHeight()).arg(lines);
|
||||
return pHtml;
|
||||
};
|
||||
|
||||
{
|
||||
const TextData textData = current.data(RoleDescriptionText).value<TextData>();
|
||||
const bool hasDescription = !textData.isEmpty();
|
||||
if (hasDescription) {
|
||||
QString descriptionMarkdown;
|
||||
for (const TextData::Type &text : textData) {
|
||||
if (!text.first.isEmpty()) {
|
||||
const QLatin1String headingMark(descriptionMarkdown.isEmpty() ? "#" : "\n\n##");
|
||||
descriptionMarkdown.append(headingMark + " " + text.first + "\n");
|
||||
}
|
||||
descriptionMarkdown.append(text.second.join("\n"));
|
||||
}
|
||||
m_description->setText(markdownToHtml(descriptionMarkdown));
|
||||
}
|
||||
m_description->setVisible(hasDescription);
|
||||
|
||||
const LinksData linksData = current.data(RoleDescriptionLinks).value<LinksData>();
|
||||
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(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/>");
|
||||
m_links->setText(toContentParagraph(linksHtml));
|
||||
}
|
||||
m_linksTitle->setVisible(hasLinks);
|
||||
m_links->setVisible(hasLinks);
|
||||
|
||||
m_imgTaskTreeRunner.reset();
|
||||
m_imageMovie.stop();
|
||||
m_imageDataBuffer.close();
|
||||
m_image->clear();
|
||||
const ImagesData imagesData = current.data(RoleDescriptionImages).value<ImagesData>();
|
||||
const bool hasImages = !imagesData.isEmpty();
|
||||
if (hasImages) {
|
||||
const ImagesData::Type &image = imagesData.constFirst(); // Only show one image
|
||||
m_imageTitle->setText(image.first);
|
||||
fetchAndDisplayImage(image.second);
|
||||
}
|
||||
m_imageTitle->setVisible(hasImages);
|
||||
m_image->setVisible(hasImages);
|
||||
}
|
||||
|
||||
{
|
||||
const QStringList tags = current.data(RoleTags).toStringList();
|
||||
m_tags->setTags(tags);
|
||||
const bool hasTags = !tags.isEmpty();
|
||||
m_tagsTitle->setVisible(hasTags);
|
||||
m_tags->setVisible(hasTags);
|
||||
|
||||
const QString compatVersion = current.data(RoleCompatVersion).toString();
|
||||
const bool hasCompatVersion = !compatVersion.isEmpty();
|
||||
if (hasCompatVersion)
|
||||
m_compatVersion->setText(compatVersion);
|
||||
m_compatVersionTitle->setVisible(hasCompatVersion);
|
||||
m_compatVersion->setVisible(hasCompatVersion);
|
||||
|
||||
const QStringList platforms = current.data(RolePlatforms).toStringList();
|
||||
const bool hasPlatforms = !platforms.isEmpty();
|
||||
if (hasPlatforms)
|
||||
m_platforms->setText(toContentParagraph(platforms.join("<br/>")));
|
||||
m_platforms->setText(toContentParagraph(platforms));
|
||||
m_platformsTitle->setVisible(hasPlatforms);
|
||||
m_platforms->setVisible(hasPlatforms);
|
||||
|
||||
const QStringList dependencies = current.data(RoleDependencies).toStringList();
|
||||
const bool hasDependencies = !dependencies.isEmpty();
|
||||
if (hasDependencies)
|
||||
m_dependencies->setText(toContentParagraph(dependencies.join("<br/>")));
|
||||
if (hasDependencies) {
|
||||
const QStringList displayNames = transform(dependencies, idToDisplayName);
|
||||
m_dependencies->setText(toContentParagraph(displayNames));
|
||||
}
|
||||
m_dependenciesTitle->setVisible(hasDependencies);
|
||||
m_dependencies->setVisible(hasDependencies);
|
||||
|
||||
const PluginsData plugins = current.data(RolePlugins).value<PluginsData>();
|
||||
const QStringList plugins = current.data(RolePlugins).toStringList();
|
||||
const bool hasExtensions = isPack && !plugins.isEmpty();
|
||||
if (hasExtensions) {
|
||||
const QStringList extensions = transform(plugins, &QPair<QString, QString>::first);
|
||||
m_packExtensions->setText(toContentParagraph(extensions.join("<br/>")));
|
||||
const QStringList displayNames = transform(plugins, idToDisplayName);
|
||||
m_packExtensions->setText(toContentParagraph(displayNames));
|
||||
}
|
||||
m_packExtensionsTitle->setVisible(hasExtensions);
|
||||
m_packExtensions->setVisible(hasExtensions);
|
||||
@@ -743,60 +692,6 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
|
||||
m_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());
|
||||
qCDebug(widgetLog).noquote() << "Sending image request:" << url.toDisplayString();
|
||||
};
|
||||
const auto onFetchDone = [storage](const NetworkQuery &query, DoneWith result) {
|
||||
qCDebug(widgetLog) << "Got image QNetworkReply:" << query.reply()->error();
|
||||
if (result == DoneWith::Success)
|
||||
storage->imageData = query.reply()->readAll();
|
||||
};
|
||||
|
||||
const auto onShowImage = [storage, this]() {
|
||||
if (storage->imageData.isEmpty())
|
||||
return;
|
||||
m_imageDataBuffer.setData(storage->imageData);
|
||||
qCDebug(widgetLog).noquote() << "Image reponse size:"
|
||||
<< QLocale::system().formattedDataSize(
|
||||
m_imageDataBuffer.size());
|
||||
if (!m_imageDataBuffer.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
QImageReader reader(&m_imageDataBuffer);
|
||||
const bool animated = reader.supportsAnimation();
|
||||
if (animated) {
|
||||
m_image->setMovie(&m_imageMovie);
|
||||
m_imageMovie.start();
|
||||
} else {
|
||||
const QPixmap pixmap = QPixmap::fromImage(reader.read());
|
||||
m_image->setPixmap(pixmap);
|
||||
}
|
||||
qCDebug(widgetLog) << "Image dimensions:" << reader.size();
|
||||
qCDebug(widgetLog) << "Image is animated:" << animated;
|
||||
};
|
||||
|
||||
Group group{
|
||||
storage,
|
||||
NetworkQueryTask{onFetchSetup, onFetchDone},
|
||||
onGroupDone(onShowImage),
|
||||
};
|
||||
|
||||
m_imgTaskTreeRunner.start(group);
|
||||
}
|
||||
|
||||
QWidget *createExtensionManagerWidget()
|
||||
{
|
||||
return new ExtensionManagerWidget;
|
||||
|
@@ -277,7 +277,7 @@ public:
|
||||
|
||||
painter->setFont(countTF.font());
|
||||
painter->setPen(countTF.color());
|
||||
const PluginsData plugins = index.data(RolePlugins).value<PluginsData>();
|
||||
const QStringList plugins = index.data(RolePlugins).toStringList();
|
||||
painter->drawText(smallCircle, countTF.drawTextFlags, QString::number(plugins.count()));
|
||||
}
|
||||
{
|
||||
@@ -487,10 +487,12 @@ public:
|
||||
SpinnerSolution::Spinner *m_spinner;
|
||||
};
|
||||
|
||||
ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
|
||||
ExtensionsBrowser::ExtensionsBrowser(ExtensionsModel *model, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, d(new ExtensionsBrowserPrivate)
|
||||
{
|
||||
d->model = model;
|
||||
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||
|
||||
static const TextFormat titleTF
|
||||
@@ -501,8 +503,6 @@ ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
|
||||
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);
|
||||
@@ -622,28 +622,11 @@ void ExtensionsBrowser::showEvent(QShowEvent *event)
|
||||
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"
|
||||
// Available: "defaultpacks", "thirdpartyplugins"
|
||||
// d->model->setExtensionsJson(testData("defaultpacks")); return;
|
||||
#endif // WITH_TESTS
|
||||
|
||||
@@ -655,14 +638,8 @@ void ExtensionsBrowser::fetchExtensions()
|
||||
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());
|
||||
const QString url = "%1/api/v1/search";
|
||||
const QString request = url.arg(settings().externalRepoUrl());
|
||||
query.setRequest(QNetworkRequest(QUrl::fromUserInput(request)));
|
||||
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
||||
qCDebug(browserLog).noquote() << "Sending JSON request:" << request;
|
||||
|
@@ -13,12 +13,14 @@ class TextFormat;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
class ExtensionsModel;
|
||||
|
||||
class ExtensionsBrowser final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ExtensionsBrowser(QWidget *parent = nullptr);
|
||||
ExtensionsBrowser(ExtensionsModel *model, QWidget *parent = nullptr);
|
||||
~ExtensionsBrowser();
|
||||
|
||||
void setFilter(const QString &filter);
|
||||
|
@@ -5,7 +5,8 @@
|
||||
|
||||
#include "extensionmanagertr.h"
|
||||
|
||||
#include "utils/algorithm.h"
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/icore.h>
|
||||
@@ -21,272 +22,197 @@
|
||||
#include <QStandardItemModel>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace ExtensionSystem;
|
||||
using namespace Core;
|
||||
using namespace Utils;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
const char EXTENSION_KEY_ID[] = "id";
|
||||
|
||||
Q_LOGGING_CATEGORY(modelLog, "qtc.extensionmanager.model", QtWarningMsg)
|
||||
|
||||
struct Dependency
|
||||
{
|
||||
QString id;
|
||||
QString version;
|
||||
};
|
||||
using Dependencies = QList<Dependency>;
|
||||
|
||||
struct Plugin
|
||||
{
|
||||
QString copyright;
|
||||
Dependencies dependencies;
|
||||
bool isInternal = false;
|
||||
QString id;
|
||||
QString name;
|
||||
QString packageUrl;
|
||||
QString vendor;
|
||||
QString version;
|
||||
};
|
||||
using Plugins = QList<Plugin>;
|
||||
|
||||
struct Description {
|
||||
ImagesData images;
|
||||
LinksData links;
|
||||
TextData text;
|
||||
};
|
||||
|
||||
struct Extension {
|
||||
QString compatVersion;
|
||||
QString copyright;
|
||||
Description description;
|
||||
int downloadCount = -1;
|
||||
QString id;
|
||||
QString license;
|
||||
QString name;
|
||||
QStringList platforms;
|
||||
Plugins plugins;
|
||||
qint64 size = 0;
|
||||
QStringList tags;
|
||||
ItemType type = ItemTypePack;
|
||||
QString vendor;
|
||||
QString vendorId;
|
||||
QString version;
|
||||
};
|
||||
using Extensions = QList<Extension>;
|
||||
|
||||
static const Dependencies dependenciesFromJson(const QJsonObject &obj)
|
||||
{
|
||||
const QJsonArray dependenciesArray = obj.value("Dependencies").toArray();
|
||||
Dependencies dependencies;
|
||||
for (const QJsonValueConstRef &dependencyVal : dependenciesArray) {
|
||||
const QJsonObject dependencyObj = dependencyVal.toObject();
|
||||
const QJsonObject metaDataObj = dependencyObj.value("meta_data").toObject();
|
||||
dependencies.append({
|
||||
.id = metaDataObj.value("Id").toString(),
|
||||
.version = metaDataObj.value("Version").toString(),
|
||||
});
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
static Plugin pluginFromJson(const QJsonObject &obj)
|
||||
{
|
||||
const QJsonObject metaDataObj = obj.value("meta_data").toObject();
|
||||
|
||||
return {
|
||||
.copyright = metaDataObj.value("Copyright").toString(),
|
||||
.dependencies = dependenciesFromJson(metaDataObj),
|
||||
.isInternal = obj.value("is_internal").toBool(false),
|
||||
.id = metaDataObj.value("Id").toString(),
|
||||
.name = metaDataObj.value("Name").toString(),
|
||||
.packageUrl = obj.value("url").toString(),
|
||||
.vendor = metaDataObj.value("Vendor").toString(),
|
||||
.version = metaDataObj.value("Version").toString(),
|
||||
};
|
||||
}
|
||||
|
||||
static Description descriptionFromJson(const QJsonObject &obj)
|
||||
{
|
||||
TextData descriptionText;
|
||||
const QJsonArray paragraphsArray = obj.value("paragraphs").toArray();
|
||||
for (const QJsonValueConstRef ¶graphVal : paragraphsArray) {
|
||||
const QJsonObject ¶graphObj = paragraphVal.toObject();
|
||||
const QJsonArray &textArray = paragraphObj.value("text").toArray();
|
||||
QStringList textLines;
|
||||
for (const QJsonValueConstRef &textVal : textArray)
|
||||
textLines.append(textVal.toString());
|
||||
descriptionText.append({
|
||||
paragraphObj.value("header").toString(),
|
||||
textLines,
|
||||
});
|
||||
}
|
||||
|
||||
LinksData links;
|
||||
const QJsonArray linksArray = obj.value("links").toArray();
|
||||
for (const QJsonValueConstRef &linkVal : linksArray) {
|
||||
const QJsonObject &linkObj = linkVal.toObject();
|
||||
links.append({
|
||||
linkObj.value("link_text").toString(),
|
||||
linkObj.value("url").toString(),
|
||||
});
|
||||
}
|
||||
|
||||
ImagesData images;
|
||||
const QJsonArray imagesArray = obj.value("images").toArray();
|
||||
for (const QJsonValueConstRef &imageVal : imagesArray) {
|
||||
const QJsonObject &imageObj = imageVal.toObject();
|
||||
images.append({
|
||||
imageObj.value("image_label").toString(),
|
||||
imageObj.value("url").toString(),
|
||||
});
|
||||
}
|
||||
|
||||
const Description description = {
|
||||
.images = images,
|
||||
.links = links,
|
||||
.text = descriptionText,
|
||||
};
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
static Extension extensionFromJson(const QJsonObject &obj)
|
||||
{
|
||||
Plugins plugins;
|
||||
const QJsonArray pluginsArray = obj.value("plugins").toArray();
|
||||
for (const QJsonValueConstRef &pluginVal : pluginsArray)
|
||||
plugins.append(pluginFromJson(pluginVal.toObject()));
|
||||
|
||||
QStringList tags;
|
||||
const QJsonArray tagsArray = obj.value("tags").toArray();
|
||||
for (const QJsonValueConstRef &tagVal : tagsArray)
|
||||
tags.append(tagVal.toString());
|
||||
|
||||
QStringList platforms;
|
||||
const QJsonArray platformsArray = obj.value("platforms").toArray();
|
||||
for (const QJsonValueConstRef &platformsVal : platformsArray)
|
||||
platforms.append(platformsVal.toString());
|
||||
|
||||
const QJsonObject descriptionObj = obj.value("description").toObject();
|
||||
const Description description = descriptionFromJson(descriptionObj);
|
||||
|
||||
const Extension extension = {
|
||||
.compatVersion = obj.value("compatibility").toString(),
|
||||
.copyright = obj.value("copyright").toString(),
|
||||
.description = description,
|
||||
.downloadCount = obj.value("download_count").toInt(-1),
|
||||
.id = obj.value("id").toString(),
|
||||
.license = obj.value("license").toString(),
|
||||
.name = obj.value("name").toString(),
|
||||
.platforms = platforms,
|
||||
.plugins = plugins,
|
||||
.size = obj.value("total_size").toInteger(),
|
||||
.tags = tags,
|
||||
.type = obj.value("is_pack").toBool(true) ? ItemTypePack : ItemTypeExtension,
|
||||
.vendor = obj.value("vendor").toString(),
|
||||
.vendorId = obj.value("vendor_id").toString(),
|
||||
.version = obj.value("version").toString(),
|
||||
};
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
static Extensions parseExtensionsRepoReply(const QByteArray &jsonData)
|
||||
{
|
||||
// https://qc-extensions.qt.io/api-docs
|
||||
Extensions parsedExtensions;
|
||||
const QJsonObject jsonObj = QJsonDocument::fromJson(jsonData).object();
|
||||
const QJsonArray items = jsonObj.value("items").toArray();
|
||||
for (const QJsonValueConstRef &itemVal : items) {
|
||||
const QJsonObject itemObj = itemVal.toObject();
|
||||
const Extension extension = extensionFromJson(itemObj);
|
||||
parsedExtensions.append(extension);
|
||||
}
|
||||
return parsedExtensions;
|
||||
}
|
||||
|
||||
static Extension extensionFromPluginSpec(const PluginSpec *pluginSpec)
|
||||
{
|
||||
const Dependencies dependencies
|
||||
= transform(pluginSpec->dependencies(), [](const PluginDependency &pd) -> Dependency {
|
||||
return {
|
||||
.id = pd.id,
|
||||
.version = pd.version,
|
||||
};
|
||||
});
|
||||
const Plugin plugin = {
|
||||
.copyright = pluginSpec->copyright(),
|
||||
.dependencies = dependencies,
|
||||
.id = pluginSpec->id(),
|
||||
.name = pluginSpec->name(),
|
||||
.packageUrl = {},
|
||||
.vendor = pluginSpec->vendor(),
|
||||
.version = pluginSpec->version(),
|
||||
};
|
||||
|
||||
const QStringList lines = pluginSpec->description().split('\n')
|
||||
+ pluginSpec->longDescription().split('\n');
|
||||
const TextData text = {{pluginSpec->name(), lines}};
|
||||
LinksData links;
|
||||
if (const QString url = pluginSpec->url(); !url.isEmpty())
|
||||
links.append({{}, url});
|
||||
if (const QString docUrl = pluginSpec->documentationUrl(); !docUrl.isEmpty())
|
||||
links.append({{Tr::tr("Documentation")}, docUrl});
|
||||
const Description description = {
|
||||
.images = {},
|
||||
.links = links,
|
||||
.text = text,
|
||||
};
|
||||
|
||||
const QString platformsPattern = pluginSpec->platformSpecification().pattern();
|
||||
const QStringList platforms = platformsPattern.isEmpty()
|
||||
? QStringList({"macOS", "Windows", "Linux"})
|
||||
: QStringList(platformsPattern);
|
||||
|
||||
const Extension extension = {
|
||||
.compatVersion = pluginSpec->compatVersion(),
|
||||
.copyright = pluginSpec->copyright(),
|
||||
.description = description,
|
||||
.id = pluginSpec->id(),
|
||||
.license = pluginSpec->license(),
|
||||
.name = pluginSpec->name(),
|
||||
.platforms = platforms,
|
||||
.plugins = {plugin},
|
||||
.tags = {},
|
||||
.type = ItemTypeExtension,
|
||||
.vendor = pluginSpec->vendor(),
|
||||
.vendorId = pluginSpec->vendorId(),
|
||||
.version = pluginSpec->version(),
|
||||
};
|
||||
return extension;
|
||||
}
|
||||
|
||||
class ExtensionsModelPrivate
|
||||
{
|
||||
public:
|
||||
void setExtensions(const Extensions &extensions);
|
||||
void addUnlistedLocalExtensions();
|
||||
void addUnlistedLocalPlugins();
|
||||
|
||||
Extensions extensions;
|
||||
static QVariant dataFromRemotePack(const QJsonObject &json, int role);
|
||||
static QVariant dataFromRemotePlugin(const QJsonObject &json, int role);
|
||||
QVariant dataFromRemoteExtension(int index, int role) const;
|
||||
QVariant dataFromLocalPlugin(int index, int role) const;
|
||||
|
||||
QJsonArray responseItems;
|
||||
PluginSpecs localPlugins;
|
||||
};
|
||||
|
||||
void ExtensionsModelPrivate::setExtensions(const Extensions &extensions)
|
||||
void ExtensionsModelPrivate::addUnlistedLocalPlugins()
|
||||
{
|
||||
this->extensions = extensions;
|
||||
qCDebug(modelLog) << "Number of extensions from JSON:" << this->extensions.count();
|
||||
addUnlistedLocalExtensions();
|
||||
qCDebug(modelLog) << "Number of extensions with added local ones:" << this->extensions.count();
|
||||
QStringList responseExtensions;
|
||||
for (const QJsonValueConstRef &responseItem : responseItems)
|
||||
responseExtensions << responseItem.toObject().value("id").toString();
|
||||
|
||||
localPlugins.clear();
|
||||
for (PluginSpec *plugin : PluginManager::plugins())
|
||||
if (!responseExtensions.contains(plugin->id()))
|
||||
localPlugins.append(plugin);
|
||||
|
||||
qCDebug(modelLog) << "Number of extensions from JSON:" << responseExtensions.count();
|
||||
qCDebug(modelLog) << "Number of added local plugins:" << localPlugins.count();
|
||||
}
|
||||
|
||||
void ExtensionsModelPrivate::addUnlistedLocalExtensions()
|
||||
QString joinedStringList(const QJsonValue &json)
|
||||
{
|
||||
const QStringList listedModelExtensions = transform(extensions, &Extension::name);
|
||||
for (const PluginSpec *plugin : PluginManager::plugins())
|
||||
if (!listedModelExtensions.contains(plugin->name()))
|
||||
extensions.append(extensionFromPluginSpec(plugin));
|
||||
if (json.isArray()) {
|
||||
const QStringList lines = json.toVariant().toStringList();
|
||||
return lines.join("\n");
|
||||
}
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
QString descriptionWithLinks(const QString &description, const QString &url,
|
||||
const QString &documentationUrl)
|
||||
{
|
||||
QStringList fragments;
|
||||
const QString mdLink("[%1](%2)");
|
||||
if (!url.isEmpty())
|
||||
fragments.append(mdLink.arg(url).arg(url));
|
||||
if (!documentationUrl.isEmpty())
|
||||
fragments.append(mdLink.arg(Tr::tr("Documentation")).arg(documentationUrl));
|
||||
if (!fragments.isEmpty())
|
||||
fragments.prepend("### " + Tr::tr("More Information"));
|
||||
fragments.prepend(description);
|
||||
return fragments.join("\n\n");
|
||||
}
|
||||
|
||||
QVariant ExtensionsModelPrivate::dataFromRemotePack(const QJsonObject &json, int role)
|
||||
{
|
||||
switch (role) {
|
||||
case RoleDescriptionLong:
|
||||
return joinedStringList(json.value("long_description"));
|
||||
case RoleDescriptionShort:
|
||||
return joinedStringList(json.value("description"));
|
||||
case RoleItemType:
|
||||
return ItemTypePack;
|
||||
case RolePlugins:
|
||||
return json.value("plugins").toVariant().toStringList();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant ExtensionsModelPrivate::dataFromRemotePlugin(const QJsonObject &json, int role)
|
||||
{
|
||||
const QJsonObject metaData = json.value("metadata").toObject();
|
||||
|
||||
switch (role) {
|
||||
case RoleCopyright:
|
||||
return metaData.value("Copyright");
|
||||
case RoleDownloadUrl: {
|
||||
const QJsonArray sources = json.value("sources").toArray();
|
||||
const QString thisPlatform = customOsTypeToString(HostOsInfo::hostOs());
|
||||
const QString thisArch = QSysInfo::currentCpuArchitecture();
|
||||
for (const QJsonValue source : sources) {
|
||||
const QJsonObject sourceObject = source.toObject();
|
||||
const QJsonObject platform = sourceObject.value("platform").toObject();
|
||||
if (platform.isEmpty() // Might be a Lua plugin
|
||||
|| (platform.value("name").toString() == thisPlatform
|
||||
&& platform.value("architecture") == thisArch))
|
||||
return sourceObject.value("url").toString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RoleItemType:
|
||||
return ItemTypeExtension;
|
||||
case RoleDescriptionLong: {
|
||||
const QString description = joinedStringList(metaData.value("LongDescription"));
|
||||
const QString url = metaData.value("Url").toString();
|
||||
const QString documentationUrl = metaData.value("DocumentationUrl").toString();
|
||||
return descriptionWithLinks(description, url, documentationUrl);
|
||||
}
|
||||
case RoleDescriptionShort:
|
||||
return joinedStringList(metaData.value("Description"));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant ExtensionsModelPrivate::dataFromRemoteExtension(int index, int role) const
|
||||
{
|
||||
const QJsonObject json = responseItems.at(index).toObject();
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case RoleName:
|
||||
return json.value("display_name");
|
||||
case RoleDownloadCount:
|
||||
return json.value("downloads");
|
||||
case RoleId:
|
||||
return json.value(EXTENSION_KEY_ID);
|
||||
case RoleTags:
|
||||
return json.value("tags").toVariant().toStringList();
|
||||
case RoleVendor:
|
||||
return json.value("display_vendor");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const QJsonObject pluginObject = json.value("plugin").toObject();
|
||||
if (!pluginObject.isEmpty())
|
||||
return dataFromRemotePlugin(pluginObject, role);
|
||||
|
||||
const QJsonObject packObject = json.value("pack").toObject();
|
||||
if (!packObject.isEmpty())
|
||||
return dataFromRemotePack(packObject, role);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant ExtensionsModelPrivate::dataFromLocalPlugin(int index, int role) const
|
||||
{
|
||||
const PluginSpec *pluginSpec = localPlugins.at(index);
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case RoleName:
|
||||
return pluginSpec->displayName();
|
||||
case RoleCopyright:
|
||||
return pluginSpec->copyright();
|
||||
case RoleDependencies: {
|
||||
const QStringList dependencies
|
||||
= transform(pluginSpec->dependencies(), &PluginDependency::id);
|
||||
return dependencies;
|
||||
}
|
||||
case RoleDescriptionLong:
|
||||
return descriptionWithLinks(pluginSpec->longDescription(), pluginSpec->url(),
|
||||
pluginSpec->documentationUrl());
|
||||
case RoleDescriptionShort:
|
||||
return pluginSpec->description();
|
||||
case RoleId:
|
||||
return pluginSpec->id();
|
||||
case RoleItemType:
|
||||
return ItemTypeExtension;
|
||||
case RolePlatforms: {
|
||||
const QString platformsPattern = pluginSpec->platformSpecification().pattern();
|
||||
const QStringList platforms = platformsPattern.isEmpty()
|
||||
? QStringList({customOsTypeToString(OsTypeMac),
|
||||
customOsTypeToString(OsTypeWindows),
|
||||
customOsTypeToString(OsTypeLinux)})
|
||||
: QStringList(platformsPattern);
|
||||
return platforms;
|
||||
}
|
||||
case RoleVendor:
|
||||
return pluginSpec->vendor();
|
||||
case RoleVendorId:
|
||||
return pluginSpec->vendorId();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ExtensionsModel::ExtensionsModel(QObject *parent)
|
||||
@@ -302,77 +228,7 @@ ExtensionsModel::~ExtensionsModel()
|
||||
|
||||
int ExtensionsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
|
||||
{
|
||||
return d->extensions.count();
|
||||
}
|
||||
|
||||
static QStringList dependenciesFromExtension(const Extension &extension)
|
||||
{
|
||||
QStringList dependencies;
|
||||
for (const Plugin &plugin : extension.plugins) {
|
||||
for (const Dependency &dependency : plugin.dependencies) {
|
||||
const QString withVersion
|
||||
= QString::fromLatin1("%1 (%2)").arg(dependency.id).arg(dependency.version);
|
||||
dependencies.append(withVersion);
|
||||
}
|
||||
}
|
||||
dependencies.sort();
|
||||
dependencies.removeDuplicates();
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
static QVariant dataFromExtension(const Extension &extension, int role)
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case RoleName:
|
||||
return Utils::findOr(
|
||||
QStringList{extension.name, extension.id},
|
||||
"No name found",
|
||||
std::not_fn(&QString::isEmpty));
|
||||
case RoleCompatVersion:
|
||||
return extension.compatVersion;
|
||||
case RoleCopyright:
|
||||
return !extension.copyright.isEmpty() ? extension.copyright : QVariant();
|
||||
case RoleDependencies:
|
||||
return dependenciesFromExtension(extension);
|
||||
case RoleDescriptionImages:
|
||||
return QVariant::fromValue(extension.description.images);
|
||||
case RoleDescriptionLinks:
|
||||
return QVariant::fromValue(extension.description.links);
|
||||
case RoleDescriptionText:
|
||||
return QVariant::fromValue(extension.description.text);
|
||||
case RoleDownloadCount:
|
||||
return extension.downloadCount;
|
||||
case RoleId:
|
||||
return extension.id;
|
||||
case RoleItemType:
|
||||
return extension.type;
|
||||
case RoleLicense:
|
||||
return extension.license;
|
||||
case RoleLocation:
|
||||
break;
|
||||
case RolePlatforms:
|
||||
return extension.platforms;
|
||||
case RolePlugins: {
|
||||
PluginsData plugins;
|
||||
for (const Plugin &plugin : extension.plugins)
|
||||
plugins.append(qMakePair(plugin.id, plugin.packageUrl));
|
||||
return QVariant::fromValue(plugins);
|
||||
}
|
||||
case RoleSize:
|
||||
return extension.size;
|
||||
case RoleTags:
|
||||
return extension.tags;
|
||||
case RoleVendor:
|
||||
return !extension.vendor.isEmpty() ? extension.vendor : QVariant();
|
||||
case RoleVersion:
|
||||
return !extension.version.isEmpty() ? extension.version : QVariant();
|
||||
case RoleVendorId:
|
||||
return extension.vendorId;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
return d->responseItems.count() + d->localPlugins.count();
|
||||
}
|
||||
|
||||
ExtensionState extensionState(const QModelIndex &index)
|
||||
@@ -392,10 +248,8 @@ static QString searchText(const QModelIndex &index)
|
||||
QStringList searchTexts;
|
||||
searchTexts.append(index.data(RoleName).toString());
|
||||
searchTexts.append(index.data(RoleTags).toStringList());
|
||||
for (const auto &data : index.data(RoleDescriptionText).value<TextData>()) {
|
||||
searchTexts.append(data.first);
|
||||
searchTexts.append(data.second);
|
||||
}
|
||||
searchTexts.append(index.data(RoleDescriptionShort).toString());
|
||||
searchTexts.append(index.data(RoleDescriptionLong).toString());
|
||||
searchTexts.append(index.data(RoleVendor).toString());
|
||||
return searchTexts.join(" ");
|
||||
}
|
||||
@@ -407,28 +261,56 @@ QVariant ExtensionsModel::data(const QModelIndex &index, int role) const
|
||||
if (role == RoleSearchText)
|
||||
return searchText(index);
|
||||
|
||||
const Extension &extension = d->extensions.at(index.row());
|
||||
const QVariant extensionData = dataFromExtension(extension, role);
|
||||
// If data is unavailable, retrieve it from the first contained plugin
|
||||
if (extensionData.isNull() && !extension.plugins.isEmpty()) {
|
||||
const QString firstPluginId = extension.plugins.constFirst().id;
|
||||
const Extension firstPluginExtension
|
||||
= findOrDefault(d->extensions, Utils::equal(&Extension::id, firstPluginId));
|
||||
if (firstPluginExtension.name.isEmpty())
|
||||
return {};
|
||||
return dataFromExtension(firstPluginExtension, role);
|
||||
const bool isRemoteExtension = index.row() < d->responseItems.count();
|
||||
const int itemIndex = index.row() - (isRemoteExtension ? 0 : d->responseItems.count());
|
||||
|
||||
return isRemoteExtension ? d->dataFromRemoteExtension(itemIndex, role)
|
||||
: d->dataFromLocalPlugin(itemIndex, role);
|
||||
}
|
||||
|
||||
QModelIndex ExtensionsModel::indexOfId(const QString &extensionId) const
|
||||
{
|
||||
const int localIndex = indexOf(d->localPlugins, equal(&PluginSpec::id, extensionId));
|
||||
if (localIndex >= 0)
|
||||
return index(d->responseItems.count() + localIndex);
|
||||
|
||||
for (int remoteIndex = 0; const QJsonValueConstRef vlaue : d->responseItems) {
|
||||
if (vlaue.toObject().value(EXTENSION_KEY_ID) == extensionId)
|
||||
return index(remoteIndex);
|
||||
++remoteIndex;
|
||||
}
|
||||
return extensionData;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ExtensionsModel::setExtensionsJson(const QByteArray &json)
|
||||
{
|
||||
const Extensions extensions = parseExtensionsRepoReply(json);
|
||||
beginResetModel();
|
||||
d->setExtensions(extensions);
|
||||
QJsonParseError error;
|
||||
const QJsonObject jsonObj = QJsonDocument::fromJson(json, &error).object();
|
||||
qCDebug(modelLog) << "QJsonParseError:" << error.errorString();
|
||||
d->responseItems = jsonObj.value("items").toArray();
|
||||
d->addUnlistedLocalPlugins();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
PluginSpec *pluginSpecForId(const QString &pluginId)
|
||||
{
|
||||
return findOrDefault(PluginManager::plugins(), equal(&PluginSpec::id, pluginId));
|
||||
|
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/osspecificaspects.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
namespace ExtensionSystem {
|
||||
@@ -11,13 +13,6 @@ class PluginSpec;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
using QPairList = QList<QPair<QString, QString> >;
|
||||
|
||||
using ImagesData = QPairList; // { <caption, url>, ... }
|
||||
using LinksData = QPairList; // { <name, url>, ... }
|
||||
using PluginsData = QPairList; // { <name, url>, ... }
|
||||
using TextData = QList<QPair<QString, QStringList> >; // { <header, text>, ... }
|
||||
|
||||
enum ItemType {
|
||||
ItemTypePack,
|
||||
ItemTypeExtension,
|
||||
@@ -32,22 +27,19 @@ enum ExtensionState {
|
||||
|
||||
enum Role {
|
||||
RoleName = Qt::UserRole,
|
||||
RoleCompatVersion,
|
||||
RoleCopyright,
|
||||
RoleDependencies,
|
||||
RoleDescriptionImages,
|
||||
RoleDescriptionLinks,
|
||||
RoleDescriptionText,
|
||||
RoleDescriptionLong,
|
||||
RoleDescriptionShort,
|
||||
RoleDownloadCount,
|
||||
RoleDownloadUrl,
|
||||
RoleExtensionState,
|
||||
RoleId,
|
||||
RoleItemType,
|
||||
RoleLicense,
|
||||
RoleLocation,
|
||||
RolePlatforms,
|
||||
RolePlugins,
|
||||
RoleSearchText,
|
||||
RoleSize,
|
||||
RoleTags,
|
||||
RoleVendor,
|
||||
RoleVendorId,
|
||||
@@ -63,12 +55,14 @@ public:
|
||||
int rowCount(const QModelIndex &parent = {}) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
|
||||
QModelIndex indexOfId(const QString &extensionId) const;
|
||||
void setExtensionsJson(const QByteArray &json);
|
||||
|
||||
private:
|
||||
class ExtensionsModelPrivate *d = nullptr;
|
||||
};
|
||||
|
||||
QString customOsTypeToString(Utils::OsType osType);
|
||||
ExtensionSystem::PluginSpec *pluginSpecForId(const QString &pluginId);
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
@@ -76,6 +70,3 @@ QObject *createExtensionsModelTest();
|
||||
#endif
|
||||
|
||||
} // ExtensionManager::Internal
|
||||
|
||||
Q_DECLARE_METATYPE(ExtensionManager::Internal::QPairList)
|
||||
Q_DECLARE_METATYPE(ExtensionManager::Internal::TextData)
|
||||
|
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "ScreenRecorder",
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"header": "Screen Recorder plugin",
|
||||
"text": [
|
||||
"With FFmpeg, you can record your screens and save the recordings as animated images or videos.",
|
||||
"To record screens:",
|
||||
"",
|
||||
"- Select Tools > Screen Recording.",
|
||||
"- Select to select the screen to record from and to set the recorded screen area.",
|
||||
"- Select to start recording.",
|
||||
"- Select when you are done recording.",
|
||||
"- Select Crop and Trim to edit the recording.",
|
||||
"- Select Export to save the recording as an animated image or a video."
|
||||
]
|
||||
},
|
||||
{
|
||||
"header": "Set the screen and area to record",
|
||||
"text": [
|
||||
"Set the screen and the area to record in the Screen Recording Options dialog.",
|
||||
"To select a screen and area:",
|
||||
"",
|
||||
"- In Display, select the display to record.",
|
||||
"- In Recorded screen area, drag the guides to set the x and y coordinates of the starting point for the recording area, as well as the width and height of the area.",
|
||||
"- Select OK to return to the Record Screen dialog."
|
||||
]
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"image_label": "Create animated imges like this",
|
||||
"url": "https://bugreports.qt.io/secure/attachment/156058/156058_DragAndCopyOnLinux.gif"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"link_text": "Documentation",
|
||||
"url": "https://doc.qt.io/qtcreator/creator-how-to-record-screens.html"
|
||||
},
|
||||
{
|
||||
"link_text": "Homepage",
|
||||
"url": "https://www.qt.io/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_pack": false,
|
||||
"plugins": [
|
||||
{
|
||||
"meta_data": {
|
||||
"Name": "ScreenRecorder",
|
||||
"Dependencies": [
|
||||
{
|
||||
"meta_data": {
|
||||
"Name": "Core",
|
||||
"Version": "13.0.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": "13.0.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [ "Utility", "Docs" ],
|
||||
"vendor": "The Qt Company Ltd"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,126 +1,114 @@
|
||||
{
|
||||
"items": [
|
||||
|
||||
{
|
||||
"name": "Essentials",
|
||||
"id": "essentials",
|
||||
"display_name": "Essentials",
|
||||
"tags": [ "Essentials" ],
|
||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
||||
"license": "os",
|
||||
"is_pack": true,
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"Basic services, such as editing and debugging code, viewing images, and adding resources to applications."
|
||||
"license": "open-source",
|
||||
"vendor_id": "theqtcompany",
|
||||
"display_vendor": "The Qt Company",
|
||||
"version": "14.0.2",
|
||||
"pack": {
|
||||
"description": "Get started",
|
||||
"long_description": [
|
||||
"Basic services, such as editing and debugging code, viewing images, and adding resources to applications.",
|
||||
"",
|
||||
"Online documentation: https://doc.qt.io/qtcreator/creator-coding-navigating.html"
|
||||
],
|
||||
"header": "Get started"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"url": "https://doc.qt.io/qtcreator/creator-coding-navigating.html",
|
||||
"link_text": "Online documentation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{ "meta_data": { "Name": "BinEditor" } },
|
||||
{ "meta_data": { "Name": "Debugger" } },
|
||||
{ "meta_data": { "Name": "DiffEditor" } },
|
||||
{ "meta_data": { "Name": "ImageViewer" } },
|
||||
{ "meta_data": { "Name": "Macros" } },
|
||||
{ "meta_data": { "Name": "LanguageClient" } },
|
||||
{ "meta_data": { "Name": "ResourceEditor" } }
|
||||
"bineditor",
|
||||
"debugger",
|
||||
"diffeditor",
|
||||
"imageviewer",
|
||||
"macros",
|
||||
"languageclient",
|
||||
"resourceeditor"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "C++ Support",
|
||||
"id": "cppsupport",
|
||||
"display_name": "C++ Support",
|
||||
"tags": [ "Programming Language", "C++" ],
|
||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
||||
"license": "os",
|
||||
"is_pack": true,
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"license": "open-source",
|
||||
"vendor_id": "theqtcompany",
|
||||
"display_vendor": "The Qt Company",
|
||||
"version": "14.0.2",
|
||||
"pack": {
|
||||
"description": "Get started",
|
||||
"long_description": [
|
||||
"Tools for developing Qt C++ applications."
|
||||
],
|
||||
"header": "Get started"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{ "meta_data": { "Name": "ClangCodeModel" } },
|
||||
{ "meta_data": { "Name": "ClangFormat" } },
|
||||
{ "meta_data": { "Name": "ClassView" } },
|
||||
{ "meta_data": { "Name": "CppEditor" } }
|
||||
"clangcodemodel",
|
||||
"clangformat",
|
||||
"classview",
|
||||
"cppeditor"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "QML Support",
|
||||
"id": "qmlsupport",
|
||||
"display_name": "QML Support",
|
||||
"tags": [ "Programming Language", "QML" ],
|
||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
||||
"license": "os",
|
||||
"is_pack": true,
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"license": "open-source",
|
||||
"vendor_id": "theqtcompany",
|
||||
"display_vendor": "The Qt Company",
|
||||
"version": "14.0.2",
|
||||
"pack": {
|
||||
"description": "Get started",
|
||||
"long_description": [
|
||||
"Tools for developing Qt Quick applications."
|
||||
],
|
||||
"header": "Get started"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{ "meta_data": { "Name": "QmlJSEditor" } },
|
||||
{ "meta_data": { "Name": "QmlJSTools" } },
|
||||
{ "meta_data": { "Name": "QmlPreview" } },
|
||||
{ "meta_data": { "Name": "QmlProfiler" } }
|
||||
"qmljseditor",
|
||||
"qmljstools",
|
||||
"qmlpreview",
|
||||
"qmlprofiler"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Visual QML Editor",
|
||||
"id": "visualqmleditor",
|
||||
"display_name": "Visual QML Editor",
|
||||
"tags": [ "Visual UI editor", "qml", "Quick" ],
|
||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
||||
"license": "os",
|
||||
"is_pack": true,
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"license": "open-source",
|
||||
"vendor_id": "theqtcompany",
|
||||
"display_vendor": "The Qt Company",
|
||||
"version": "14.0.2",
|
||||
"pack": {
|
||||
"description": "Get started",
|
||||
"long_description": [
|
||||
"Tools for creating Qt Quick UIs."
|
||||
],
|
||||
"header": "Get started"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{ "meta_data": { "Name": "QmlDesigner" } }
|
||||
"qmldesigner"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Visual Widget Editor",
|
||||
"id": "visualwidgeteditor",
|
||||
"display_name": "Visual Widget Editor",
|
||||
"tags": [ "Visual UI editor", "C++", "Widgets" ],
|
||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
||||
"license": "os",
|
||||
"is_pack": true,
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"license": "open-source",
|
||||
"vendor_id": "theqtcompany",
|
||||
"display_vendor": "The Qt Company",
|
||||
"version": "14.0.2",
|
||||
"pack": {
|
||||
"description": "Get started",
|
||||
"long_description": [
|
||||
"Visual tool for creating Qt widget-based UIs."
|
||||
],
|
||||
"header": "Get started"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{ "meta_data": { "Name": "Designer" } }
|
||||
"designer"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
@@ -1,38 +1,49 @@
|
||||
{
|
||||
"items": [
|
||||
|
||||
{
|
||||
"name": "SpellChecker",
|
||||
"id": "spellchecker",
|
||||
"display_name": "SpellChecker",
|
||||
"tags": [ "Editor" ],
|
||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
||||
"license": "os",
|
||||
"is_pack": false,
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"license": "open-source",
|
||||
"vendor_id": "carelcombrink",
|
||||
"display_vendor": "Carel Combrink",
|
||||
"downloads": 2333,
|
||||
"plugin": {
|
||||
"metadata": {
|
||||
"Copyright": "(C) 2015 - 2024 Carel Combrink",
|
||||
"Description": "Foo",
|
||||
"LongDescription": [
|
||||
"Spellcheck comments in source files."
|
||||
],
|
||||
"header": "Get started"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
"Url" : "https://github.com/CJCombrink/SpellChecker-Plugin",
|
||||
"DocumentationUrl" : "https://github.com/CJCombrink/SpellChecker-Plugin?tab=readme-ov-file#spellchecker-plugin"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://github.com/CJCombrink/SpellChecker-Plugin",
|
||||
"link_text": "GitHub page"
|
||||
"url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.7.0/SpellChecker-Plugin_QtC14.0.0_linux_x64.tar.gz",
|
||||
"platform": {
|
||||
"name": "Linux",
|
||||
"architecture": "x86_64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.7.0/SpellChecker-Plugin_QtC14.0.0_macos_x64.tar.gz",
|
||||
"platform": {
|
||||
"name": "macOS",
|
||||
"architecture": "x86_64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.7.0/SpellChecker-Plugin_QtC14.0.0_win64_x64.zip",
|
||||
"platform": {
|
||||
"name": "Windows",
|
||||
"architecture": "x86_64"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"meta_data": {
|
||||
"Name": "SpellChecker",
|
||||
"Copyright": "(C) 2015 - 2024 Carel Combrink"
|
||||
},
|
||||
"url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.6.0/SpellChecker-Plugin_QtC13.0.0_macos_x64.tar.gz"
|
||||
}
|
||||
],
|
||||
"vendor": "Carel Combrink",
|
||||
"copyright": "(C) 2015 - 2024 Carel Combrink"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Few tags",
|
||||
"tags": [ "Tag one", "Tag two"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Many tags",
|
||||
"tags": [ "Tag_01", "Tag_02", "Tag_03", "Tag_04", "Tag_05", "Tag_06", "Tag_07", "Tag_08", "Tag_09", "Tag_10", "Tag_11", "Tag_12", "Tag_13", "Tag_14", "Tag_15", "Tag_16", "Tag_17", "Tag_18", "Tag_19", "Tag_20", "Tag_21", "Tag_22", "Tag_23", "Tag_24", "Tag_25", "Tag_26", "Tag_27", "Tag_28", "Tag_29", "Tag_30", "And_a_very_long_tag_without_spaces", "Ok, a last long tag without spaces, but that sgould be enough"],
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"... and a few long ones"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "One static image",
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"png"
|
||||
]
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"image_label": "Screenshot",
|
||||
"url": "https://bugreports.qt.io/secure/attachment/147354/VirtualNodesShownAsNotExisting.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "One animated image",
|
||||
"description": {
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": [
|
||||
"gif (animated)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"image_label": "Screencast",
|
||||
"url": "https://bugreports.qt.io/secure/attachment/156058/156058_DragAndCopyOnLinux.gif"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Vendor, no download count",
|
||||
"vendor": "Vendor name"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "No vendor, but download count",
|
||||
"download_count": 12345
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Vendor and download count",
|
||||
"vendor": "Vendor name",
|
||||
"download_count": 12345
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user