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>
|
<RCC>
|
||||||
<qresource prefix="/extensionmanager">
|
<qresource prefix="/extensionmanager">
|
||||||
<file>testdata/augmentedplugindata.json</file>
|
|
||||||
<file>testdata/defaultpacks.json</file>
|
<file>testdata/defaultpacks.json</file>
|
||||||
<file>testdata/thirdpartyplugins.json</file>
|
<file>testdata/thirdpartyplugins.json</file>
|
||||||
<file>testdata/varieddata.json</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@@ -57,14 +57,14 @@ namespace ExtensionManager::Internal {
|
|||||||
|
|
||||||
Q_LOGGING_CATEGORY(widgetLog, "qtc.extensionmanager.widget", QtWarningMsg)
|
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
|
constexpr TextFormat contentTF
|
||||||
{Theme::Token_Text_Default, UiElement::UiElementBody2};
|
{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)
|
static QLabel *sectionTitle(const TextFormat &tf, const QString &title)
|
||||||
{
|
{
|
||||||
@@ -215,9 +215,9 @@ public:
|
|||||||
m_dlCount->setText(QString::number(dlCount));
|
m_dlCount->setText(QString::number(dlCount));
|
||||||
m_dlCountItems->setVisible(showDlCount);
|
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) {
|
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);
|
const QString details = Tr::tr("Pack contains %n plugins.", nullptr, pluginsCount);
|
||||||
m_details->setText(details);
|
m_details->setText(details);
|
||||||
} else {
|
} else {
|
||||||
@@ -227,9 +227,10 @@ public:
|
|||||||
const ItemType itemType = current.data(RoleItemType).value<ItemType>();
|
const ItemType itemType = current.data(RoleItemType).value<ItemType>();
|
||||||
const bool isPack = itemType == ItemTypePack;
|
const bool isPack = itemType == ItemTypePack;
|
||||||
const bool isRemotePlugin = !(isPack || pluginSpecForId(current.data(RoleId).toString()));
|
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())
|
if (installButton->isVisible())
|
||||||
installButton->setToolTip(pluginData.constFirst().second);
|
installButton->setToolTip(downloadUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -383,25 +384,17 @@ public:
|
|||||||
private:
|
private:
|
||||||
void updateView(const QModelIndex ¤t);
|
void updateView(const QModelIndex ¤t);
|
||||||
void fetchAndInstallPlugin(const QUrl &url);
|
void fetchAndInstallPlugin(const QUrl &url);
|
||||||
void fetchAndDisplayImage(const QUrl &url);
|
|
||||||
|
|
||||||
QString m_currentItemName;
|
QString m_currentItemName;
|
||||||
|
ExtensionsModel *m_extensionModel;
|
||||||
ExtensionsBrowser *m_extensionBrowser;
|
ExtensionsBrowser *m_extensionBrowser;
|
||||||
CollapsingWidget *m_secondaryDescriptionWidget;
|
CollapsingWidget *m_secondaryDescriptionWidget;
|
||||||
HeadingWidget *m_headingWidget;
|
HeadingWidget *m_headingWidget;
|
||||||
QWidget *m_primaryContent;
|
QWidget *m_primaryContent;
|
||||||
QWidget *m_secondaryContent;
|
QWidget *m_secondaryContent;
|
||||||
QLabel *m_description;
|
QLabel *m_description;
|
||||||
QLabel *m_linksTitle;
|
|
||||||
QLabel *m_links;
|
|
||||||
QLabel *m_imageTitle;
|
|
||||||
QLabel *m_image;
|
|
||||||
QBuffer m_imageDataBuffer;
|
|
||||||
QMovie m_imageMovie;
|
|
||||||
QLabel *m_tagsTitle;
|
QLabel *m_tagsTitle;
|
||||||
TagList *m_tags;
|
TagList *m_tags;
|
||||||
QLabel *m_compatVersionTitle;
|
|
||||||
QLabel *m_compatVersion;
|
|
||||||
QLabel *m_platformsTitle;
|
QLabel *m_platformsTitle;
|
||||||
QLabel *m_platforms;
|
QLabel *m_platforms;
|
||||||
QLabel *m_dependenciesTitle;
|
QLabel *m_dependenciesTitle;
|
||||||
@@ -409,14 +402,14 @@ private:
|
|||||||
QLabel *m_packExtensionsTitle;
|
QLabel *m_packExtensionsTitle;
|
||||||
QLabel *m_packExtensions;
|
QLabel *m_packExtensions;
|
||||||
PluginStatusWidget *m_pluginStatus;
|
PluginStatusWidget *m_pluginStatus;
|
||||||
PluginsData m_currentItemPlugins;
|
QString m_currentDownloadUrl;
|
||||||
Tasking::TaskTreeRunner m_dlTaskTreeRunner;
|
Tasking::TaskTreeRunner m_dlTaskTreeRunner;
|
||||||
Tasking::TaskTreeRunner m_imgTaskTreeRunner;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ExtensionManagerWidget::ExtensionManagerWidget()
|
ExtensionManagerWidget::ExtensionManagerWidget()
|
||||||
{
|
{
|
||||||
m_extensionBrowser = new ExtensionsBrowser;
|
m_extensionModel = new ExtensionsModel(this);
|
||||||
|
m_extensionBrowser = new ExtensionsBrowser(m_extensionModel);
|
||||||
auto descriptionColumns = new QWidget;
|
auto descriptionColumns = new QWidget;
|
||||||
m_secondaryDescriptionWidget = new CollapsingWidget;
|
m_secondaryDescriptionWidget = new CollapsingWidget;
|
||||||
|
|
||||||
@@ -425,21 +418,12 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
|||||||
m_description->setWordWrap(true);
|
m_description->setWordWrap(true);
|
||||||
m_description->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
m_description->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||||
m_description->setOpenExternalLinks(true);
|
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;
|
using namespace Layouting;
|
||||||
auto primary = new QWidget;
|
auto primary = new QWidget;
|
||||||
const auto spL = spacing(SpacingTokens::VPaddingL);
|
const auto spL = spacing(SpacingTokens::VPaddingL);
|
||||||
Column {
|
Column {
|
||||||
m_description,
|
m_description,
|
||||||
Column { m_linksTitle, m_links, spL },
|
|
||||||
Column { m_imageTitle, m_image, spL },
|
|
||||||
st,
|
st,
|
||||||
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
|
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
|
||||||
}.attachTo(primary);
|
}.attachTo(primary);
|
||||||
@@ -447,8 +431,6 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
|||||||
|
|
||||||
m_tagsTitle = sectionTitle(h6TF, Tr::tr("Tags"));
|
m_tagsTitle = sectionTitle(h6TF, Tr::tr("Tags"));
|
||||||
m_tags = new TagList;
|
m_tags = new TagList;
|
||||||
m_compatVersionTitle = sectionTitle(h6TF, Tr::tr("Compatibility"));
|
|
||||||
m_compatVersion = tfLabel(contentTF, false);
|
|
||||||
m_platformsTitle = sectionTitle(h6TF, Tr::tr("Platforms"));
|
m_platformsTitle = sectionTitle(h6TF, Tr::tr("Platforms"));
|
||||||
m_platforms = tfLabel(contentTF, false);
|
m_platforms = tfLabel(contentTF, false);
|
||||||
m_dependenciesTitle = sectionTitle(h6TF, Tr::tr("Dependencies"));
|
m_dependenciesTitle = sectionTitle(h6TF, Tr::tr("Dependencies"));
|
||||||
@@ -463,7 +445,6 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
|||||||
sectionTitle(h6CapitalTF, Tr::tr("Extension details")),
|
sectionTitle(h6CapitalTF, Tr::tr("Extension details")),
|
||||||
Column {
|
Column {
|
||||||
Column { m_tagsTitle, m_tags, spXxs },
|
Column { m_tagsTitle, m_tags, spXxs },
|
||||||
Column { m_compatVersionTitle, m_compatVersion, spXxs },
|
|
||||||
Column { m_platformsTitle, m_platforms, spXxs },
|
Column { m_platformsTitle, m_platforms, spXxs },
|
||||||
Column { m_dependenciesTitle, m_dependencies, spXxs },
|
Column { m_dependenciesTitle, m_dependencies, spXxs },
|
||||||
Column { m_packExtensionsTitle, m_packExtensions, spXxs },
|
Column { m_packExtensionsTitle, m_packExtensions, spXxs },
|
||||||
@@ -522,7 +503,7 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
|||||||
m_secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
|
m_secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
|
||||||
});
|
});
|
||||||
connect(m_headingWidget, &HeadingWidget::pluginInstallationRequested, this, [this](){
|
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_tags, &TagList::tagSelected, m_extensionBrowser, &ExtensionsBrowser::setFilter);
|
||||||
connect(m_headingWidget, &HeadingWidget::vendorClicked,
|
connect(m_headingWidget, &HeadingWidget::vendorClicked,
|
||||||
@@ -546,7 +527,10 @@ static QString markdownToHtml(const QString &markdown)
|
|||||||
blockFormat.setBottomMargin(SpacingTokens::VGapL);
|
blockFormat.setBottomMargin(SpacingTokens::VGapL);
|
||||||
QTextCursor cursor(block);
|
QTextCursor cursor(block);
|
||||||
cursor.mergeBlockFormat(blockFormat);
|
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();
|
const QFont headingFont = headingTf.font();
|
||||||
for (auto it = block.begin(); !(it.atEnd()); ++it) {
|
for (auto it = block.begin(); !(it.atEnd()); ++it) {
|
||||||
QTextFragment fragment = it.fragment();
|
QTextFragment fragment = it.fragment();
|
||||||
@@ -555,9 +539,10 @@ static QString markdownToHtml(const QString &markdown)
|
|||||||
cursor.setPosition(fragment.position());
|
cursor.setPosition(fragment.position());
|
||||||
cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
|
cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
|
||||||
if (blockFormat.hasProperty(QTextFormat::HeadingLevel)) {
|
if (blockFormat.hasProperty(QTextFormat::HeadingLevel)) {
|
||||||
|
charFormat.setFontCapitalization(headingFont.capitalization());
|
||||||
charFormat.setFontFamilies(headingFont.families());
|
charFormat.setFontFamilies(headingFont.families());
|
||||||
charFormat.setFontWeight(headingFont.weight());
|
|
||||||
charFormat.setFontPointSize(headingFont.pointSizeF());
|
charFormat.setFontPointSize(headingFont.pointSizeF());
|
||||||
|
charFormat.setFontWeight(headingFont.weight());
|
||||||
charFormat.setForeground(headingTf.color());
|
charFormat.setForeground(headingTf.color());
|
||||||
} else if (charFormat.isAnchor()) {
|
} else if (charFormat.isAnchor()) {
|
||||||
charFormat.setForeground(creatorColor(Theme::Token_Text_Accent));
|
charFormat.setForeground(creatorColor(Theme::Token_Text_Accent));
|
||||||
@@ -587,96 +572,60 @@ void ExtensionManagerWidget::updateView(const QModelIndex ¤t)
|
|||||||
m_currentItemName = current.data(RoleName).toString();
|
m_currentItemName = current.data(RoleName).toString();
|
||||||
const bool isPack = current.data(RoleItemType) == ItemTypePack;
|
const bool isPack = current.data(RoleItemType) == ItemTypePack;
|
||||||
m_pluginStatus->setPluginId(isPack ? QString() : current.data(RoleId).toString());
|
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;"
|
const QString pHtml = QString::fromLatin1("<p style=\"margin-top:0;margin-bottom:0;"
|
||||||
"line-height:%1px\">%2</p>")
|
"line-height:%1px\">%2</p>")
|
||||||
.arg(contentTF.lineHeight()).arg(text);
|
.arg(contentTF.lineHeight()).arg(lines);
|
||||||
return pHtml;
|
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();
|
const QStringList tags = current.data(RoleTags).toStringList();
|
||||||
m_tags->setTags(tags);
|
m_tags->setTags(tags);
|
||||||
const bool hasTags = !tags.isEmpty();
|
const bool hasTags = !tags.isEmpty();
|
||||||
m_tagsTitle->setVisible(hasTags);
|
m_tagsTitle->setVisible(hasTags);
|
||||||
m_tags->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 QStringList platforms = current.data(RolePlatforms).toStringList();
|
||||||
const bool hasPlatforms = !platforms.isEmpty();
|
const bool hasPlatforms = !platforms.isEmpty();
|
||||||
if (hasPlatforms)
|
if (hasPlatforms)
|
||||||
m_platforms->setText(toContentParagraph(platforms.join("<br/>")));
|
m_platforms->setText(toContentParagraph(platforms));
|
||||||
m_platformsTitle->setVisible(hasPlatforms);
|
m_platformsTitle->setVisible(hasPlatforms);
|
||||||
m_platforms->setVisible(hasPlatforms);
|
m_platforms->setVisible(hasPlatforms);
|
||||||
|
|
||||||
const QStringList dependencies = current.data(RoleDependencies).toStringList();
|
const QStringList dependencies = current.data(RoleDependencies).toStringList();
|
||||||
const bool hasDependencies = !dependencies.isEmpty();
|
const bool hasDependencies = !dependencies.isEmpty();
|
||||||
if (hasDependencies)
|
if (hasDependencies) {
|
||||||
m_dependencies->setText(toContentParagraph(dependencies.join("<br/>")));
|
const QStringList displayNames = transform(dependencies, idToDisplayName);
|
||||||
|
m_dependencies->setText(toContentParagraph(displayNames));
|
||||||
|
}
|
||||||
m_dependenciesTitle->setVisible(hasDependencies);
|
m_dependenciesTitle->setVisible(hasDependencies);
|
||||||
m_dependencies->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();
|
const bool hasExtensions = isPack && !plugins.isEmpty();
|
||||||
if (hasExtensions) {
|
if (hasExtensions) {
|
||||||
const QStringList extensions = transform(plugins, &QPair<QString, QString>::first);
|
const QStringList displayNames = transform(plugins, idToDisplayName);
|
||||||
m_packExtensions->setText(toContentParagraph(extensions.join("<br/>")));
|
m_packExtensions->setText(toContentParagraph(displayNames));
|
||||||
}
|
}
|
||||||
m_packExtensionsTitle->setVisible(hasExtensions);
|
m_packExtensionsTitle->setVisible(hasExtensions);
|
||||||
m_packExtensions->setVisible(hasExtensions);
|
m_packExtensions->setVisible(hasExtensions);
|
||||||
@@ -743,60 +692,6 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
|
|||||||
m_dlTaskTreeRunner.start(group);
|
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()
|
QWidget *createExtensionManagerWidget()
|
||||||
{
|
{
|
||||||
return new ExtensionManagerWidget;
|
return new ExtensionManagerWidget;
|
||||||
|
@@ -277,7 +277,7 @@ public:
|
|||||||
|
|
||||||
painter->setFont(countTF.font());
|
painter->setFont(countTF.font());
|
||||||
painter->setPen(countTF.color());
|
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()));
|
painter->drawText(smallCircle, countTF.drawTextFlags, QString::number(plugins.count()));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -487,10 +487,12 @@ public:
|
|||||||
SpinnerSolution::Spinner *m_spinner;
|
SpinnerSolution::Spinner *m_spinner;
|
||||||
};
|
};
|
||||||
|
|
||||||
ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
|
ExtensionsBrowser::ExtensionsBrowser(ExtensionsModel *model, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, d(new ExtensionsBrowserPrivate)
|
, d(new ExtensionsBrowserPrivate)
|
||||||
{
|
{
|
||||||
|
d->model = model;
|
||||||
|
|
||||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||||
|
|
||||||
static const TextFormat titleTF
|
static const TextFormat titleTF
|
||||||
@@ -501,8 +503,6 @@ ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
|
|||||||
d->searchBox = new SearchBox;
|
d->searchBox = new SearchBox;
|
||||||
d->searchBox->setPlaceholderText(Tr::tr("Search"));
|
d->searchBox->setPlaceholderText(Tr::tr("Search"));
|
||||||
|
|
||||||
d->model = new ExtensionsModel(this);
|
|
||||||
|
|
||||||
d->searchProxyModel = new QSortFilterProxyModel(this);
|
d->searchProxyModel = new QSortFilterProxyModel(this);
|
||||||
d->searchProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
d->searchProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||||
d->searchProxyModel->setFilterRole(RoleSearchText);
|
d->searchProxyModel->setFilterRole(RoleSearchText);
|
||||||
@@ -622,28 +622,11 @@ void ExtensionsBrowser::showEvent(QShowEvent *event)
|
|||||||
QWidget::showEvent(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()
|
void ExtensionsBrowser::fetchExtensions()
|
||||||
{
|
{
|
||||||
#ifdef WITH_TESTS
|
#ifdef WITH_TESTS
|
||||||
// Uncomment for testing with local json data.
|
// Uncomment for testing with local json data.
|
||||||
// Available: "augmentedplugindata", "defaultpacks", "varieddata", "thirdpartyplugins"
|
// Available: "defaultpacks", "thirdpartyplugins"
|
||||||
// d->model->setExtensionsJson(testData("defaultpacks")); return;
|
// d->model->setExtensionsJson(testData("defaultpacks")); return;
|
||||||
#endif // WITH_TESTS
|
#endif // WITH_TESTS
|
||||||
|
|
||||||
@@ -655,14 +638,8 @@ void ExtensionsBrowser::fetchExtensions()
|
|||||||
using namespace Tasking;
|
using namespace Tasking;
|
||||||
|
|
||||||
const auto onQuerySetup = [this](NetworkQuery &query) {
|
const auto onQuerySetup = [this](NetworkQuery &query) {
|
||||||
const QString url = "%1/api/v1/search?request=";
|
const QString url = "%1/api/v1/search";
|
||||||
const QString requestTemplate
|
const QString request = url.arg(settings().externalRepoUrl());
|
||||||
= 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.setRequest(QNetworkRequest(QUrl::fromUserInput(request)));
|
||||||
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
||||||
qCDebug(browserLog).noquote() << "Sending JSON request:" << request;
|
qCDebug(browserLog).noquote() << "Sending JSON request:" << request;
|
||||||
|
@@ -13,12 +13,14 @@ class TextFormat;
|
|||||||
|
|
||||||
namespace ExtensionManager::Internal {
|
namespace ExtensionManager::Internal {
|
||||||
|
|
||||||
|
class ExtensionsModel;
|
||||||
|
|
||||||
class ExtensionsBrowser final : public QWidget
|
class ExtensionsBrowser final : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ExtensionsBrowser(QWidget *parent = nullptr);
|
ExtensionsBrowser(ExtensionsModel *model, QWidget *parent = nullptr);
|
||||||
~ExtensionsBrowser();
|
~ExtensionsBrowser();
|
||||||
|
|
||||||
void setFilter(const QString &filter);
|
void setFilter(const QString &filter);
|
||||||
|
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
#include "extensionmanagertr.h"
|
#include "extensionmanagertr.h"
|
||||||
|
|
||||||
#include "utils/algorithm.h"
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
|
|
||||||
#include <coreplugin/coreconstants.h>
|
#include <coreplugin/coreconstants.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
@@ -21,272 +22,197 @@
|
|||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QVersionNumber>
|
#include <QVersionNumber>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
using namespace ExtensionSystem;
|
using namespace ExtensionSystem;
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace ExtensionManager::Internal {
|
namespace ExtensionManager::Internal {
|
||||||
|
|
||||||
|
const char EXTENSION_KEY_ID[] = "id";
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(modelLog, "qtc.extensionmanager.model", QtWarningMsg)
|
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
|
class ExtensionsModelPrivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void setExtensions(const Extensions &extensions);
|
void addUnlistedLocalPlugins();
|
||||||
void addUnlistedLocalExtensions();
|
|
||||||
|
|
||||||
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;
|
QStringList responseExtensions;
|
||||||
qCDebug(modelLog) << "Number of extensions from JSON:" << this->extensions.count();
|
for (const QJsonValueConstRef &responseItem : responseItems)
|
||||||
addUnlistedLocalExtensions();
|
responseExtensions << responseItem.toObject().value("id").toString();
|
||||||
qCDebug(modelLog) << "Number of extensions with added local ones:" << this->extensions.count();
|
|
||||||
|
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);
|
if (json.isArray()) {
|
||||||
for (const PluginSpec *plugin : PluginManager::plugins())
|
const QStringList lines = json.toVariant().toStringList();
|
||||||
if (!listedModelExtensions.contains(plugin->name()))
|
return lines.join("\n");
|
||||||
extensions.append(extensionFromPluginSpec(plugin));
|
}
|
||||||
|
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)
|
ExtensionsModel::ExtensionsModel(QObject *parent)
|
||||||
@@ -302,77 +228,7 @@ ExtensionsModel::~ExtensionsModel()
|
|||||||
|
|
||||||
int ExtensionsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
|
int ExtensionsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
return d->extensions.count();
|
return d->responseItems.count() + d->localPlugins.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 {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtensionState extensionState(const QModelIndex &index)
|
ExtensionState extensionState(const QModelIndex &index)
|
||||||
@@ -392,10 +248,8 @@ static QString searchText(const QModelIndex &index)
|
|||||||
QStringList searchTexts;
|
QStringList searchTexts;
|
||||||
searchTexts.append(index.data(RoleName).toString());
|
searchTexts.append(index.data(RoleName).toString());
|
||||||
searchTexts.append(index.data(RoleTags).toStringList());
|
searchTexts.append(index.data(RoleTags).toStringList());
|
||||||
for (const auto &data : index.data(RoleDescriptionText).value<TextData>()) {
|
searchTexts.append(index.data(RoleDescriptionShort).toString());
|
||||||
searchTexts.append(data.first);
|
searchTexts.append(index.data(RoleDescriptionLong).toString());
|
||||||
searchTexts.append(data.second);
|
|
||||||
}
|
|
||||||
searchTexts.append(index.data(RoleVendor).toString());
|
searchTexts.append(index.data(RoleVendor).toString());
|
||||||
return searchTexts.join(" ");
|
return searchTexts.join(" ");
|
||||||
}
|
}
|
||||||
@@ -407,28 +261,56 @@ QVariant ExtensionsModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == RoleSearchText)
|
if (role == RoleSearchText)
|
||||||
return searchText(index);
|
return searchText(index);
|
||||||
|
|
||||||
const Extension &extension = d->extensions.at(index.row());
|
const bool isRemoteExtension = index.row() < d->responseItems.count();
|
||||||
const QVariant extensionData = dataFromExtension(extension, role);
|
const int itemIndex = index.row() - (isRemoteExtension ? 0 : d->responseItems.count());
|
||||||
// If data is unavailable, retrieve it from the first contained plugin
|
|
||||||
if (extensionData.isNull() && !extension.plugins.isEmpty()) {
|
return isRemoteExtension ? d->dataFromRemoteExtension(itemIndex, role)
|
||||||
const QString firstPluginId = extension.plugins.constFirst().id;
|
: d->dataFromLocalPlugin(itemIndex, role);
|
||||||
const Extension firstPluginExtension
|
}
|
||||||
= findOrDefault(d->extensions, Utils::equal(&Extension::id, firstPluginId));
|
|
||||||
if (firstPluginExtension.name.isEmpty())
|
QModelIndex ExtensionsModel::indexOfId(const QString &extensionId) const
|
||||||
return {};
|
{
|
||||||
return dataFromExtension(firstPluginExtension, role);
|
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)
|
void ExtensionsModel::setExtensionsJson(const QByteArray &json)
|
||||||
{
|
{
|
||||||
const Extensions extensions = parseExtensionsRepoReply(json);
|
|
||||||
beginResetModel();
|
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();
|
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)
|
PluginSpec *pluginSpecForId(const QString &pluginId)
|
||||||
{
|
{
|
||||||
return findOrDefault(PluginManager::plugins(), equal(&PluginSpec::id, pluginId));
|
return findOrDefault(PluginManager::plugins(), equal(&PluginSpec::id, pluginId));
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/osspecificaspects.h>
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
namespace ExtensionSystem {
|
namespace ExtensionSystem {
|
||||||
@@ -11,13 +13,6 @@ class PluginSpec;
|
|||||||
|
|
||||||
namespace ExtensionManager::Internal {
|
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 {
|
enum ItemType {
|
||||||
ItemTypePack,
|
ItemTypePack,
|
||||||
ItemTypeExtension,
|
ItemTypeExtension,
|
||||||
@@ -32,22 +27,19 @@ enum ExtensionState {
|
|||||||
|
|
||||||
enum Role {
|
enum Role {
|
||||||
RoleName = Qt::UserRole,
|
RoleName = Qt::UserRole,
|
||||||
RoleCompatVersion,
|
|
||||||
RoleCopyright,
|
RoleCopyright,
|
||||||
RoleDependencies,
|
RoleDependencies,
|
||||||
RoleDescriptionImages,
|
RoleDescriptionLong,
|
||||||
RoleDescriptionLinks,
|
RoleDescriptionShort,
|
||||||
RoleDescriptionText,
|
|
||||||
RoleDownloadCount,
|
RoleDownloadCount,
|
||||||
|
RoleDownloadUrl,
|
||||||
RoleExtensionState,
|
RoleExtensionState,
|
||||||
RoleId,
|
RoleId,
|
||||||
RoleItemType,
|
RoleItemType,
|
||||||
RoleLicense,
|
RoleLicense,
|
||||||
RoleLocation,
|
|
||||||
RolePlatforms,
|
RolePlatforms,
|
||||||
RolePlugins,
|
RolePlugins,
|
||||||
RoleSearchText,
|
RoleSearchText,
|
||||||
RoleSize,
|
|
||||||
RoleTags,
|
RoleTags,
|
||||||
RoleVendor,
|
RoleVendor,
|
||||||
RoleVendorId,
|
RoleVendorId,
|
||||||
@@ -63,12 +55,14 @@ public:
|
|||||||
int rowCount(const QModelIndex &parent = {}) const;
|
int rowCount(const QModelIndex &parent = {}) const;
|
||||||
QVariant data(const QModelIndex &index, int role) const;
|
QVariant data(const QModelIndex &index, int role) const;
|
||||||
|
|
||||||
|
QModelIndex indexOfId(const QString &extensionId) const;
|
||||||
void setExtensionsJson(const QByteArray &json);
|
void setExtensionsJson(const QByteArray &json);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class ExtensionsModelPrivate *d = nullptr;
|
class ExtensionsModelPrivate *d = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString customOsTypeToString(Utils::OsType osType);
|
||||||
ExtensionSystem::PluginSpec *pluginSpecForId(const QString &pluginId);
|
ExtensionSystem::PluginSpec *pluginSpecForId(const QString &pluginId);
|
||||||
|
|
||||||
#ifdef WITH_TESTS
|
#ifdef WITH_TESTS
|
||||||
@@ -76,6 +70,3 @@ QObject *createExtensionsModelTest();
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // ExtensionManager::Internal
|
} // 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": [
|
"items": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Essentials",
|
"id": "essentials",
|
||||||
|
"display_name": "Essentials",
|
||||||
"tags": [ "Essentials" ],
|
"tags": [ "Essentials" ],
|
||||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
"license": "open-source",
|
||||||
"license": "os",
|
"vendor_id": "theqtcompany",
|
||||||
"is_pack": true,
|
"display_vendor": "The Qt Company",
|
||||||
"description": {
|
"version": "14.0.2",
|
||||||
"paragraphs": [
|
"pack": {
|
||||||
{
|
"description": "Get started",
|
||||||
"text": [
|
"long_description": [
|
||||||
"Basic services, such as editing and debugging code, viewing images, and adding resources to applications."
|
"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": [
|
"plugins": [
|
||||||
{ "meta_data": { "Name": "BinEditor" } },
|
"bineditor",
|
||||||
{ "meta_data": { "Name": "Debugger" } },
|
"debugger",
|
||||||
{ "meta_data": { "Name": "DiffEditor" } },
|
"diffeditor",
|
||||||
{ "meta_data": { "Name": "ImageViewer" } },
|
"imageviewer",
|
||||||
{ "meta_data": { "Name": "Macros" } },
|
"macros",
|
||||||
{ "meta_data": { "Name": "LanguageClient" } },
|
"languageclient",
|
||||||
{ "meta_data": { "Name": "ResourceEditor" } }
|
"resourceeditor"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "C++ Support",
|
"id": "cppsupport",
|
||||||
|
"display_name": "C++ Support",
|
||||||
"tags": [ "Programming Language", "C++" ],
|
"tags": [ "Programming Language", "C++" ],
|
||||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
"license": "open-source",
|
||||||
"license": "os",
|
"vendor_id": "theqtcompany",
|
||||||
"is_pack": true,
|
"display_vendor": "The Qt Company",
|
||||||
"description": {
|
"version": "14.0.2",
|
||||||
"paragraphs": [
|
"pack": {
|
||||||
{
|
"description": "Get started",
|
||||||
"text": [
|
"long_description": [
|
||||||
"Tools for developing Qt C++ applications."
|
"Tools for developing Qt C++ applications."
|
||||||
],
|
],
|
||||||
"header": "Get started"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{ "meta_data": { "Name": "ClangCodeModel" } },
|
"clangcodemodel",
|
||||||
{ "meta_data": { "Name": "ClangFormat" } },
|
"clangformat",
|
||||||
{ "meta_data": { "Name": "ClassView" } },
|
"classview",
|
||||||
{ "meta_data": { "Name": "CppEditor" } }
|
"cppeditor"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "QML Support",
|
"id": "qmlsupport",
|
||||||
|
"display_name": "QML Support",
|
||||||
"tags": [ "Programming Language", "QML" ],
|
"tags": [ "Programming Language", "QML" ],
|
||||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
"license": "open-source",
|
||||||
"license": "os",
|
"vendor_id": "theqtcompany",
|
||||||
"is_pack": true,
|
"display_vendor": "The Qt Company",
|
||||||
"description": {
|
"version": "14.0.2",
|
||||||
"paragraphs": [
|
"pack": {
|
||||||
{
|
"description": "Get started",
|
||||||
"text": [
|
"long_description": [
|
||||||
"Tools for developing Qt Quick applications."
|
"Tools for developing Qt Quick applications."
|
||||||
],
|
],
|
||||||
"header": "Get started"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{ "meta_data": { "Name": "QmlJSEditor" } },
|
"qmljseditor",
|
||||||
{ "meta_data": { "Name": "QmlJSTools" } },
|
"qmljstools",
|
||||||
{ "meta_data": { "Name": "QmlPreview" } },
|
"qmlpreview",
|
||||||
{ "meta_data": { "Name": "QmlProfiler" } }
|
"qmlprofiler"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Visual QML Editor",
|
"id": "visualqmleditor",
|
||||||
|
"display_name": "Visual QML Editor",
|
||||||
"tags": [ "Visual UI editor", "qml", "Quick" ],
|
"tags": [ "Visual UI editor", "qml", "Quick" ],
|
||||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
"license": "open-source",
|
||||||
"license": "os",
|
"vendor_id": "theqtcompany",
|
||||||
"is_pack": true,
|
"display_vendor": "The Qt Company",
|
||||||
"description": {
|
"version": "14.0.2",
|
||||||
"paragraphs": [
|
"pack": {
|
||||||
{
|
"description": "Get started",
|
||||||
"text": [
|
"long_description": [
|
||||||
"Tools for creating Qt Quick UIs."
|
"Tools for creating Qt Quick UIs."
|
||||||
],
|
],
|
||||||
"header": "Get started"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{ "meta_data": { "Name": "QmlDesigner" } }
|
"qmldesigner"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Visual Widget Editor",
|
"id": "visualwidgeteditor",
|
||||||
|
"display_name": "Visual Widget Editor",
|
||||||
"tags": [ "Visual UI editor", "C++", "Widgets" ],
|
"tags": [ "Visual UI editor", "C++", "Widgets" ],
|
||||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
"license": "open-source",
|
||||||
"license": "os",
|
"vendor_id": "theqtcompany",
|
||||||
"is_pack": true,
|
"display_vendor": "The Qt Company",
|
||||||
"description": {
|
"version": "14.0.2",
|
||||||
"paragraphs": [
|
"pack": {
|
||||||
{
|
"description": "Get started",
|
||||||
"text": [
|
"long_description": [
|
||||||
"Visual tool for creating Qt widget-based UIs."
|
"Visual tool for creating Qt widget-based UIs."
|
||||||
],
|
],
|
||||||
"header": "Get started"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{ "meta_data": { "Name": "Designer" } }
|
"designer"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,38 +1,49 @@
|
|||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "SpellChecker",
|
"id": "spellchecker",
|
||||||
|
"display_name": "SpellChecker",
|
||||||
"tags": [ "Editor" ],
|
"tags": [ "Editor" ],
|
||||||
"platforms": [ "macOS", "Windows", "Linux" ],
|
"license": "open-source",
|
||||||
"license": "os",
|
"vendor_id": "carelcombrink",
|
||||||
"is_pack": false,
|
"display_vendor": "Carel Combrink",
|
||||||
"description": {
|
"downloads": 2333,
|
||||||
"paragraphs": [
|
"plugin": {
|
||||||
{
|
"metadata": {
|
||||||
"text": [
|
"Copyright": "(C) 2015 - 2024 Carel Combrink",
|
||||||
|
"Description": "Foo",
|
||||||
|
"LongDescription": [
|
||||||
"Spellcheck comments in source files."
|
"Spellcheck comments in source files."
|
||||||
],
|
],
|
||||||
"header": "Get started"
|
"Url" : "https://github.com/CJCombrink/SpellChecker-Plugin",
|
||||||
}
|
"DocumentationUrl" : "https://github.com/CJCombrink/SpellChecker-Plugin?tab=readme-ov-file#spellchecker-plugin"
|
||||||
],
|
},
|
||||||
"links": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"url": "https://github.com/CJCombrink/SpellChecker-Plugin",
|
"url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.7.0/SpellChecker-Plugin_QtC14.0.0_linux_x64.tar.gz",
|
||||||
"link_text": "GitHub page"
|
"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