diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 59cb95790b0..2ec7c5a3795 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -8,7 +8,6 @@ add_subdirectory(serialterminal) add_subdirectory(extensionmanager) add_subdirectory(helloworld) add_subdirectory(imageviewer) -add_subdirectory(marketplace) add_subdirectory(screenrecorder) add_subdirectory(updateinfo) add_subdirectory(welcome) diff --git a/src/plugins/marketplace/CMakeLists.txt b/src/plugins/marketplace/CMakeLists.txt deleted file mode 100644 index e883b460b73..00000000000 --- a/src/plugins/marketplace/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -add_qtc_plugin(Marketplace - PLUGIN_DEPENDS Core - SOURCES - marketplaceplugin.cpp - marketplacetr.h - productlistmodel.cpp - productlistmodel.h - qtmarketplacewelcomepage.cpp - qtmarketplacewelcomepage.h -) diff --git a/src/plugins/marketplace/Marketplace.json.in b/src/plugins/marketplace/Marketplace.json.in deleted file mode 100644 index eb9405abb3f..00000000000 --- a/src/plugins/marketplace/Marketplace.json.in +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Id" : "marketplace", - "DisplayName" : "Qt Marketplace", - "Name" : "Marketplace", - "Version" : "${IDE_VERSION}", - "CompatVersion" : "${IDE_VERSION_COMPAT}", - "DisabledByDefault" : true, - "VendorId" : "theqtcompany", - "Vendor" : "The Qt Company Ltd", - "Copyright" : "${IDE_COPYRIGHT}", - "License" : [ "Commercial Usage", - "", - "Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.", - "", - "GNU General Public License Usage", - "", - "Alternatively, this file may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this file. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html." - ], - "Description" : "Install applications from Qt Marketplace.", - "LongDescription" : [], - "Url" : "https://www.qt.io", - "DocumentationUrl" : "https://marketplace.qt.io", - ${IDE_PLUGIN_DEPENDENCIES} -} diff --git a/src/plugins/marketplace/marketplace.qbs b/src/plugins/marketplace/marketplace.qbs deleted file mode 100644 index 1bb20ad5ae8..00000000000 --- a/src/plugins/marketplace/marketplace.qbs +++ /dev/null @@ -1,20 +0,0 @@ -import qbs - -QtcPlugin { - name: "Marketplace" - - Depends { name: "Core" } - Depends { name: "Utils" } - - Depends { name: "Qt.widgets" } - Depends { name: "Qt.network" } - - files: [ - "marketplaceplugin.cpp", - "marketplacetr.h", - "productlistmodel.cpp", - "productlistmodel.h", - "qtmarketplacewelcomepage.cpp", - "qtmarketplacewelcomepage.h", - ] -} diff --git a/src/plugins/marketplace/marketplaceplugin.cpp b/src/plugins/marketplace/marketplaceplugin.cpp deleted file mode 100644 index fb9dfd09201..00000000000 --- a/src/plugins/marketplace/marketplaceplugin.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qtmarketplacewelcomepage.h" - -#include - -namespace Marketplace::Internal { - -class MarketplacePlugin final : public ExtensionSystem::IPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Marketplace.json") - - void initialize() final - { - setupQtMarketPlaceWelcomePage(this); - } -}; - -} // Marketplace::Internal - -#include "marketplaceplugin.moc" diff --git a/src/plugins/marketplace/marketplacetr.h b/src/plugins/marketplace/marketplacetr.h deleted file mode 100644 index 3f20fbf574b..00000000000 --- a/src/plugins/marketplace/marketplacetr.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Marketplace { - -struct Tr -{ - Q_DECLARE_TR_FUNCTIONS(QtC::Marketplace) -}; - -} // namespace Marketplace diff --git a/src/plugins/marketplace/productlistmodel.cpp b/src/plugins/marketplace/productlistmodel.cpp deleted file mode 100644 index 4d36c84db48..00000000000 --- a/src/plugins/marketplace/productlistmodel.cpp +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "productlistmodel.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Core; - -namespace Marketplace { -namespace Internal { - -class ProductItemDelegate : public Core::ListItemDelegate -{ -public: - void clickAction(const Core::ListItem *item) const override - { - QTC_ASSERT(item, return); - auto productItem = static_cast(item); - const QUrl url(QString("https://marketplace.qt.io/products/").append(productItem->handle)); - QDesktopServices::openUrl(url); - } -}; - -static const QNetworkRequest constructRequest(const QString &collection) -{ - QString url("https://marketplace.qt.io"); - if (collection.isEmpty()) - url.append("/collections.json"); - else - url.append("/collections/").append(collection).append("/products.json"); - - return QNetworkRequest(url); -} - -static const QString plainTextFromHtml(const QString &original) -{ - QString plainText(original); - static const QRegularExpression breakReturn("<\\s*br/?\\s*>", - QRegularExpression::CaseInsensitiveOption); - static const QRegularExpression allTags("<[^>]*>"); - static const QRegularExpression moreThan2NewLines("\n{3,}"); - plainText.replace(breakReturn, "\n"); // "translate"
into newline - plainText.remove(allTags); // remove all tags - plainText = plainText.trimmed(); - plainText.replace(moreThan2NewLines, "\n\n"); // consolidate some newlines - - // FIXME the description text is usually too long and needs to get elided sensibly - return (plainText.length() > 157) ? plainText.left(157).append("...") : plainText; -} - -static int priority(const QString &collection) -{ - if (collection == "featured") - return 10; - if (collection == "from-qt-partners") - return 20; - return 50; -} - -SectionedProducts::SectionedProducts(QWidget *parent) - : SectionedGridView(parent) - , m_productDelegate(new ProductItemDelegate) -{ - setItemDelegate(m_productDelegate); - setPixmapFunction([this](const QString &url) -> QPixmap { - queueImageForDownload(url); - return {}; - }); - connect(m_productDelegate, - &ProductItemDelegate::tagClicked, - this, - &SectionedProducts::onTagClicked); -} - -SectionedProducts::~SectionedProducts() -{ - delete m_productDelegate; -} - -void SectionedProducts::updateCollections() -{ - emit toggleProgressIndicator(true); - QNetworkReply *reply = Utils::NetworkAccessManager::instance()->get(constructRequest({})); - connect(reply, &QNetworkReply::finished, - this, [this, reply]() { onFetchCollectionsFinished(reply); }); -} - -void SectionedProducts::onFetchCollectionsFinished(QNetworkReply *reply) -{ - QTC_ASSERT(reply, return); - const QScopeGuard cleanup([reply] { reply->deleteLater(); }); - - if (reply->error() == QNetworkReply::NoError) { - const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); - if (doc.isNull()) - return; - - const QJsonArray collections = doc.object().value("collections").toArray(); - for (int i = 0, end = collections.size(); i < end; ++i) { - const QJsonObject obj = collections.at(i).toObject(); - const auto handle = obj.value("handle").toString(); - const int productsCount = obj.value("products_count").toInt(); - - if (productsCount > 0 && handle != "all-products" && handle != "qt-education-1") { - m_collectionTitles.insert(handle, obj.value("title").toString()); - m_pendingCollections.append(handle); - } - } - if (!m_pendingCollections.isEmpty()) - fetchCollectionsContents(); - } else { - QVariant status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (status.isValid() && status.toInt() == 430) - QTimer::singleShot(30000, this, &SectionedProducts::updateCollections); - else - emit errorOccurred(reply->error(), reply->errorString()); - } -} - -void SectionedProducts::onFetchSingleCollectionFinished(QNetworkReply *reply) -{ - emit toggleProgressIndicator(false); - - QTC_ASSERT(reply, return); - const QScopeGuard cleanup([reply] { reply->deleteLater(); }); - - QList productsForCollection; - if (reply->error() == QNetworkReply::NoError) { - const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); - if (doc.isNull()) - return; - - QString collectionHandle = reply->url().path(); - if (QTC_GUARD(collectionHandle.endsWith("/products.json"))) { - collectionHandle.chop(14); - collectionHandle = collectionHandle.mid(collectionHandle.lastIndexOf('/') + 1); - } - - const QList presentItems = items(); - const QJsonArray products = doc.object().value("products").toArray(); - for (int i = 0, end = products.size(); i < end; ++i) { - const QJsonObject obj = products.at(i).toObject(); - const QString handle = obj.value("handle").toString(); - - bool foundItem = Utils::findOrDefault(presentItems, [handle](const Core::ListItem *it) { - return static_cast(it)->handle == handle; - }); - if (foundItem) - continue; - - ProductItem *product = new ProductItem; - product->name = obj.value("title").toString(); - product->description = plainTextFromHtml(obj.value("body_html").toString()); - - product->handle = handle; - const QJsonArray tags = obj.value("tags").toArray(); - for (const auto &val : tags) - product->tags.append(val.toString()); - - const auto images = obj.value("images").toArray(); - if (!images.isEmpty()) { - auto imgObj = images.first().toObject(); - const QJsonValue imageSrc = imgObj.value("src"); - if (imageSrc.isString()) - product->imageUrl = imageSrc.toString(); - } - - productsForCollection.append(product); - } - - if (!productsForCollection.isEmpty()) { - Section section{m_collectionTitles.value(collectionHandle), priority(collectionHandle)}; - addNewSection(section, productsForCollection); - } - } else { - // bad.. but we still might be able to fetch another collection - qWarning() << "Failed to fetch collection:" << reply->errorString() << reply->error(); - } - - if (!m_pendingCollections.isEmpty()) // more collections? go ahead.. - fetchCollectionsContents(); - else if (m_productModels.isEmpty()) - emit errorOccurred(0, "Failed to fetch any collection."); -} - -void SectionedProducts::fetchCollectionsContents() -{ - QTC_ASSERT(!m_pendingCollections.isEmpty(), return); - const QString collection = m_pendingCollections.dequeue(); - - QNetworkReply *reply - = Utils::NetworkAccessManager::instance()->get(constructRequest(collection)); - connect(reply, &QNetworkReply::finished, - this, [this, reply]() { onFetchSingleCollectionFinished(reply); }); -} - -void SectionedProducts::queueImageForDownload(const QString &url) -{ - m_pendingImages.insert(url); - if (!m_isDownloadingImage) - fetchNextImage(); -} - -static void updateModelIndexesForUrl(ListModel *model, const QString &url) -{ - const QList items = model->items(); - for (int row = 0, end = items.size(); row < end; ++row) { - if (items.at(row)->imageUrl == url) { - const QModelIndex index = model->index(row); - emit model->dataChanged(index, index, {ListModel::ItemImageRole, Qt::DisplayRole}); - } - } -} - -void SectionedProducts::fetchNextImage() -{ - if (m_pendingImages.isEmpty()) { - m_isDownloadingImage = false; - return; - } - - const auto it = m_pendingImages.constBegin(); - const QString nextUrl = *it; - m_pendingImages.erase(it); - - if (QPixmapCache::find(nextUrl, nullptr)) { - // this image is already cached it might have been added while downloading - for (ListModel *model : std::as_const(m_productModels)) - updateModelIndexesForUrl(model, nextUrl); - fetchNextImage(); - return; - } - - m_isDownloadingImage = true; - QNetworkReply *reply = Utils::NetworkAccessManager::instance()->get(QNetworkRequest(nextUrl)); - connect(reply, &QNetworkReply::finished, - this, [this, reply]() { onImageDownloadFinished(reply); }); -} - -void SectionedProducts::onImageDownloadFinished(QNetworkReply *reply) -{ - QTC_ASSERT(reply, return); - const QScopeGuard cleanup([reply] { reply->deleteLater(); }); - - if (reply->error() == QNetworkReply::NoError) { - const QByteArray data = reply->readAll(); - QPixmap pixmap; - const QUrl imageUrl = reply->request().url(); - const QString imageFormat = QFileInfo(imageUrl.fileName()).suffix(); - if (pixmap.loadFromData(data, imageFormat.toLatin1())) { - const QString url = imageUrl.toString(); - const int dpr = qApp->devicePixelRatio(); - pixmap = pixmap.scaled(WelcomePageHelpers::WelcomeThumbnailSize * dpr, - Qt::KeepAspectRatio, - Qt::SmoothTransformation); - pixmap.setDevicePixelRatio(dpr); - QPixmapCache::insert(url, pixmap); - for (ListModel *model : std::as_const(m_productModels)) - updateModelIndexesForUrl(model, url); - } - } // handle error not needed - it's okay'ish to have no images as long as the rest works - - fetchNextImage(); -} - -void SectionedProducts::addNewSection(const Section §ion, const QList &items) -{ - QTC_ASSERT(!items.isEmpty(), return); - m_productModels.append(addSection(section, items)); -} - -void SectionedProducts::onTagClicked(const QString &tag) -{ - setCurrentIndex(1 /* search */); - emit tagClicked(tag); -} - -QList SectionedProducts::items() -{ - QList result; - for (const ListModel *model : std::as_const(m_productModels)) - result.append(model->items()); - return result; -} - -} // namespace Internal -} // namespace Marketplace diff --git a/src/plugins/marketplace/productlistmodel.h b/src/plugins/marketplace/productlistmodel.h deleted file mode 100644 index 8da6e01ee90..00000000000 --- a/src/plugins/marketplace/productlistmodel.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -#include -#include - -QT_BEGIN_NAMESPACE -class QNetworkReply; -QT_END_NAMESPACE - -namespace Marketplace { -namespace Internal { - -class ProductGridView; -class ProductItemDelegate; - -class ProductItem : public Core::ListItem -{ -public: - QString handle; -}; - -class SectionedProducts : public Core::SectionedGridView -{ - Q_OBJECT -public: - explicit SectionedProducts(QWidget *parent); - ~SectionedProducts() override; - void updateCollections(); - void queueImageForDownload(const QString &url); - -signals: - void errorOccurred(int errorCode, const QString &errorString); - void toggleProgressIndicator(bool show); - void tagClicked(const QString &tag); - -private: - void onFetchCollectionsFinished(QNetworkReply *reply); - void onFetchSingleCollectionFinished(QNetworkReply *reply); - void fetchCollectionsContents(); - - void fetchNextImage(); - void onImageDownloadFinished(QNetworkReply *reply); - void addNewSection(const Core::Section §ion, const QList &items); - void onTagClicked(const QString &tag); - - QList items(); - - QQueue m_pendingCollections; - QSet m_pendingImages; - QMap m_collectionTitles; - QList m_productModels; - ProductItemDelegate *m_productDelegate = nullptr; - bool m_isDownloadingImage = false; - int m_columnCount = 1; -}; - -} // namespace Internal -} // namespace Marketplace - -Q_DECLARE_METATYPE(Marketplace::Internal::ProductItem *) diff --git a/src/plugins/marketplace/qtmarketplacewelcomepage.cpp b/src/plugins/marketplace/qtmarketplacewelcomepage.cpp deleted file mode 100644 index cfa37c3d7c0..00000000000 --- a/src/plugins/marketplace/qtmarketplacewelcomepage.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qtmarketplacewelcomepage.h" - -#include "marketplacetr.h" -#include "productlistmodel.h" - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace Core; -using namespace Utils; - -namespace Marketplace::Internal { - -class QtMarketplacePageWidget final : public QWidget -{ -public: - QtMarketplacePageWidget() - { - m_searcher = new Core::SearchBox(this); - m_searcher->setPlaceholderText(Tr::tr("Search in Marketplace...")); - - m_errorLabel = new QLabel(this); - m_errorLabel->setVisible(false); - - m_sectionedProducts = new SectionedProducts(this); - auto progressIndicator = new Utils::ProgressIndicator(ProgressIndicatorSize::Large, this); - progressIndicator->attachToWidget(m_sectionedProducts); - progressIndicator->hide(); - - using namespace StyleHelper::SpacingTokens; - - using namespace Layouting; - Column { - Row { - m_searcher, - m_errorLabel, - customMargins(0, 0, ExVPaddingGapXl, 0), - }, - m_sectionedProducts, - spacing(ExVPaddingGapXl), - customMargins(ExVPaddingGapXl, ExVPaddingGapXl, 0, 0), - }.attachTo(this); - - connect(m_sectionedProducts, &SectionedProducts::toggleProgressIndicator, - progressIndicator, &Utils::ProgressIndicator::setVisible); - connect(m_sectionedProducts, &SectionedProducts::errorOccurred, this, - [this, progressIndicator](int, const QString &message) { - progressIndicator->hide(); - progressIndicator->deleteLater(); - m_errorLabel->setAlignment(Qt::AlignHCenter); - QFont f(m_errorLabel->font()); - f.setPixelSize(20); - m_errorLabel->setFont(f); - const QString txt - = Tr::tr( - "

Could not fetch data from Qt Marketplace.

Try with your browser " - "instead: https://marketplace.qt.io" - "


Error: %1

").arg(message); - m_errorLabel->setText(txt); - m_errorLabel->setVisible(true); - m_searcher->setVisible(false); - connect(m_errorLabel, &QLabel::linkActivated, - this, []() { QDesktopServices::openUrl(QUrl("https://marketplace.qt.io")); }); - }); - - connect(m_searcher, - &QLineEdit::textChanged, - m_sectionedProducts, - &SectionedProducts::setSearchStringDelayed); - connect(m_sectionedProducts, &SectionedProducts::tagClicked, - this, &QtMarketplacePageWidget::onTagClicked); - } - - void showEvent(QShowEvent *event) final - { - if (!m_initialized) { - m_initialized = true; - m_sectionedProducts->updateCollections(); - } - QWidget::showEvent(event); - } - - void onTagClicked(const QString &tag) - { - const QString text = m_searcher->text(); - m_searcher->setText((text.startsWith("tag:\"") ? text.trimmed() + " " : QString()) - + QString("tag:\"%1\" ").arg(tag)); - } - -private: - SectionedProducts *m_sectionedProducts = nullptr; - QLabel *m_errorLabel = nullptr; - QLineEdit *m_searcher = nullptr; - bool m_initialized = false; -}; - -// QtMarketplaceWelcomePage - -class QtMarketplaceWelcomePage final : public IWelcomePage -{ -public: - QtMarketplaceWelcomePage() = default; - - QString title() const final { return Tr::tr("Marketplace"); } - int priority() const final { return 60; } - Utils::Id id() const final { return "Marketplace"; } - QWidget *createWidget() const final { return new QtMarketplacePageWidget; } -}; - -void setupQtMarketPlaceWelcomePage(QObject *guard) -{ - auto page = new QtMarketplaceWelcomePage; - page->setParent(guard); -} - -} // Marketplace::Internal diff --git a/src/plugins/marketplace/qtmarketplacewelcomepage.h b/src/plugins/marketplace/qtmarketplacewelcomepage.h deleted file mode 100644 index 99deafbefe6..00000000000 --- a/src/plugins/marketplace/qtmarketplacewelcomepage.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Marketplace::Internal { - -void setupQtMarketPlaceWelcomePage(QObject *guard); - -} // namespace Marketplace::Internal diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index 7293d34d683..cd882fcd1ec 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -56,7 +56,6 @@ Project { "languageclient/lualanguageclient/lualanguageclient.qbs", "lua/lua.qbs", "macros/macros.qbs", - "marketplace/marketplace.qbs", "mcusupport/mcusupport.qbs", "mercurial/mercurial.qbs", "modeleditor/modeleditor.qbs",