Marketplace: Remove plugin

Change-Id: I44a753ad3638a8a858de6a256f60f3a0a6a9efac
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
hjk
2024-12-03 14:45:46 +01:00
parent d6a54cd322
commit 0e839d3090
11 changed files with 0 additions and 606 deletions

View File

@@ -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)

View File

@@ -1,10 +0,0 @@
add_qtc_plugin(Marketplace
PLUGIN_DEPENDS Core
SOURCES
marketplaceplugin.cpp
marketplacetr.h
productlistmodel.cpp
productlistmodel.h
qtmarketplacewelcomepage.cpp
qtmarketplacewelcomepage.h
)

View File

@@ -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}
}

View File

@@ -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",
]
}

View File

@@ -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 <extensionsystem/iplugin.h>
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"

View File

@@ -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 <QCoreApplication>
namespace Marketplace {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(QtC::Marketplace)
};
} // namespace Marketplace

View File

@@ -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 <utils/algorithm.h>
#include <utils/networkaccessmanager.h>
#include <utils/qtcassert.h>
#include <QApplication>
#include <QDesktopServices>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPixmapCache>
#include <QRegularExpression>
#include <QScopeGuard>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
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<const ProductItem *>(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" <br/> 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<Core::ListItem *> 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<Core::ListItem *> 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<const ProductItem *>(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<ListItem *> 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 &section, const QList<Core::ListItem *> &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<Core::ListItem *> SectionedProducts::items()
{
QList<Core::ListItem *> result;
for (const ListModel *model : std::as_const(m_productModels))
result.append(model->items());
return result;
}
} // namespace Internal
} // namespace Marketplace

View File

@@ -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 <coreplugin/welcomepagehelper.h>
#include <QQueue>
#include <QStackedWidget>
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 &section, const QList<Core::ListItem *> &items);
void onTagClicked(const QString &tag);
QList<Core::ListItem *> items();
QQueue<QString> m_pendingCollections;
QSet<QString> m_pendingImages;
QMap<QString, QString> m_collectionTitles;
QList<Core::ListModel *> m_productModels;
ProductItemDelegate *m_productDelegate = nullptr;
bool m_isDownloadingImage = false;
int m_columnCount = 1;
};
} // namespace Internal
} // namespace Marketplace
Q_DECLARE_METATYPE(Marketplace::Internal::ProductItem *)

View File

@@ -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 <coreplugin/welcomepagehelper.h>
#include <utils/fancylineedit.h>
#include <utils/layoutbuilder.h>
#include <utils/progressindicator.h>
#include <utils/qtcassert.h>
#include <utils/theme/theme.h>
#include <QDesktopServices>
#include <QLabel>
#include <QLineEdit>
#include <QShowEvent>
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(
"<p>Could not fetch data from Qt Marketplace.</p><p>Try with your browser "
"instead: <a href='https://marketplace.qt.io'>https://marketplace.qt.io</a>"
"</p><br/><p><small><i>Error: %1</i></small></p>").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

View File

@@ -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 <QObject>
namespace Marketplace::Internal {
void setupQtMarketPlaceWelcomePage(QObject *guard);
} // namespace Marketplace::Internal

View File

@@ -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",