diff --git a/src/plugins/extensionmanager/extensionmanager_test.qrc b/src/plugins/extensionmanager/extensionmanager_test.qrc index e7e934e5316..4c4d59f002d 100644 --- a/src/plugins/extensionmanager/extensionmanager_test.qrc +++ b/src/plugins/extensionmanager/extensionmanager_test.qrc @@ -1,8 +1,6 @@ - testdata/augmentedplugindata.json testdata/defaultpacks.json testdata/thirdpartyplugins.json - testdata/varieddata.json diff --git a/src/plugins/extensionmanager/extensionmanagerwidget.cpp b/src/plugins/extensionmanager/extensionmanagerwidget.cpp index f2f0f0446fd..fbfa59098db 100644 --- a/src/plugins/extensionmanager/extensionmanagerwidget.cpp +++ b/src/plugins/extensionmanager/extensionmanagerwidget.cpp @@ -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(); + 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(); 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(); - - auto toContentParagraph = [](const QString &text) { - const QString pHtml = QString::fromLatin1("

%2

") - .arg(contentTF.lineHeight()).arg(text); - return pHtml; - }; + m_currentDownloadUrl = current.data(RoleDownloadUrl).toString(); { - const TextData textData = current.data(RoleDescriptionText).value(); - 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(); - 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"(%3 >)") - .arg(link.second) - .arg(creatorColor(Theme::Token_Text_Accent).name()) - .arg(anchor); - }); - linksHtml = links.join("
"); - 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(); - 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 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("
"); + const QString pHtml = QString::fromLatin1("

%2

") + .arg(contentTF.lineHeight()).arg(lines); + return pHtml; + }; + 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("
"))); + 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("
"))); + 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(); + const QStringList plugins = current.data(RolePlugins).toStringList(); const bool hasExtensions = isPack && !plugins.isEmpty(); if (hasExtensions) { - const QStringList extensions = transform(plugins, &QPair::first); - m_packExtensions->setText(toContentParagraph(extensions.join("
"))); + 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 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; diff --git a/src/plugins/extensionmanager/extensionsbrowser.cpp b/src/plugins/extensionmanager/extensionsbrowser.cpp index 135fa82e6bc..3bcf0b22b2e 100644 --- a/src/plugins/extensionmanager/extensionsbrowser.cpp +++ b/src/plugins/extensionmanager/extensionsbrowser.cpp @@ -277,7 +277,7 @@ public: painter->setFont(countTF.font()); painter->setPen(countTF.color()); - const PluginsData plugins = index.data(RolePlugins).value(); + 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; diff --git a/src/plugins/extensionmanager/extensionsbrowser.h b/src/plugins/extensionmanager/extensionsbrowser.h index f132f598810..0b4254e02cd 100644 --- a/src/plugins/extensionmanager/extensionsbrowser.h +++ b/src/plugins/extensionmanager/extensionsbrowser.h @@ -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); diff --git a/src/plugins/extensionmanager/extensionsmodel.cpp b/src/plugins/extensionmanager/extensionsmodel.cpp index 0de838c3983..9415ee02978 100644 --- a/src/plugins/extensionmanager/extensionsmodel.cpp +++ b/src/plugins/extensionmanager/extensionsmodel.cpp @@ -5,7 +5,8 @@ #include "extensionmanagertr.h" -#include "utils/algorithm.h" +#include +#include #include #include @@ -21,272 +22,197 @@ #include #include -#include - 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; - -struct Plugin -{ - QString copyright; - Dependencies dependencies; - bool isInternal = false; - QString id; - QString name; - QString packageUrl; - QString vendor; - QString version; -}; -using Plugins = QList; - -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; - -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()) { - 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)); diff --git a/src/plugins/extensionmanager/extensionsmodel.h b/src/plugins/extensionmanager/extensionsmodel.h index e837c2ae452..e8f47828ad0 100644 --- a/src/plugins/extensionmanager/extensionsmodel.h +++ b/src/plugins/extensionmanager/extensionsmodel.h @@ -3,6 +3,8 @@ #pragma once +#include + #include namespace ExtensionSystem { @@ -11,13 +13,6 @@ class PluginSpec; namespace ExtensionManager::Internal { -using QPairList = QList >; - -using ImagesData = QPairList; // { , ... } -using LinksData = QPairList; // { , ... } -using PluginsData = QPairList; // { , ... } -using TextData = QList >; // { , ... } - 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) diff --git a/src/plugins/extensionmanager/testdata/augmentedplugindata.json b/src/plugins/extensionmanager/testdata/augmentedplugindata.json deleted file mode 100644 index 32efe15b708..00000000000 --- a/src/plugins/extensionmanager/testdata/augmentedplugindata.json +++ /dev/null @@ -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" - } - ] -} diff --git a/src/plugins/extensionmanager/testdata/defaultpacks.json b/src/plugins/extensionmanager/testdata/defaultpacks.json index d26b0a42c60..95b6d5920da 100644 --- a/src/plugins/extensionmanager/testdata/defaultpacks.json +++ b/src/plugins/extensionmanager/testdata/defaultpacks.json @@ -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." - ], - "header": "Get started" - } + "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" ], - "links": [ - { - "url": "https://doc.qt.io/qtcreator/creator-coding-navigating.html", - "link_text": "Online documentation" - } + "plugins": [ + "bineditor", + "debugger", + "diffeditor", + "imageviewer", + "macros", + "languageclient", + "resourceeditor" ] - }, - "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" } } - ] + } }, { - "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": [ - "Tools for developing Qt C++ applications." - ], - "header": "Get started" - } + "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." + ], + "plugins": [ + "clangcodemodel", + "clangformat", + "classview", + "cppeditor" ] - }, - "plugins": [ - { "meta_data": { "Name": "ClangCodeModel" } }, - { "meta_data": { "Name": "ClangFormat" } }, - { "meta_data": { "Name": "ClassView" } }, - { "meta_data": { "Name": "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": [ - "Tools for developing Qt Quick applications." - ], - "header": "Get started" - } + "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." + ], + "plugins": [ + "qmljseditor", + "qmljstools", + "qmlpreview", + "qmlprofiler" ] - }, - "plugins": [ - { "meta_data": { "Name": "QmlJSEditor" } }, - { "meta_data": { "Name": "QmlJSTools" } }, - { "meta_data": { "Name": "QmlPreview" } }, - { "meta_data": { "Name": "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": [ - "Tools for creating Qt Quick UIs." - ], - "header": "Get started" - } + "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." + ], + "plugins": [ + "qmldesigner" ] - }, - "plugins": [ - { "meta_data": { "Name": "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": [ - "Visual tool for creating Qt widget-based UIs." - ], - "header": "Get started" - } + "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." + ], + "plugins": [ + "designer" ] - }, - "plugins": [ - { "meta_data": { "Name": "Designer" } } - ] + } } + ] } diff --git a/src/plugins/extensionmanager/testdata/thirdpartyplugins.json b/src/plugins/extensionmanager/testdata/thirdpartyplugins.json index 910854a68f8..2f79b994920 100644 --- a/src/plugins/extensionmanager/testdata/thirdpartyplugins.json +++ b/src/plugins/extensionmanager/testdata/thirdpartyplugins.json @@ -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": [ + "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." + ], + "Url" : "https://github.com/CJCombrink/SpellChecker-Plugin", + "DocumentationUrl" : "https://github.com/CJCombrink/SpellChecker-Plugin?tab=readme-ov-file#spellchecker-plugin" + }, + "sources": [ { - "text": [ - "Spellcheck comments in source files." - ], - "header": "Get started" - } - ], - "links": [ + "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", - "link_text": "GitHub page" + "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" + } } + ] } diff --git a/src/plugins/extensionmanager/testdata/varieddata.json b/src/plugins/extensionmanager/testdata/varieddata.json deleted file mode 100644 index 245d08ad844..00000000000 --- a/src/plugins/extensionmanager/testdata/varieddata.json +++ /dev/null @@ -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 - } - ] -}