diff --git a/README.md b/README.md index b55f5fab21a1dbf28a8fe693e4d8e7111ef3dfce..a0e1e64cbaec0fa65767bf6e3b2fb76202ee6a2d 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,17 @@ cases the word "Use" usually used as the label for the action which activates th To change this label, you can add an entry like the following to your knsrc file: `UseLabel=Read`, which in this case will show the word "Read" for that action instead of "Use". +### Upload Assistance + +While KNewStuff does not currently handle content uploading, the UI will attempt to guide users in how to upload new entries +on whatever provider their content is currently coming from. If you as an application developer want to explicitly not +suggest that users should do this, add an `UploadEnabled=false` entry to your configuration file. + +Not adding this entry to your configuration will cause KNewStuff to add an entry to NewStuff.Page's contextual actions +with the label "Upload...", which pushes a NewStuff.UploadPage to the stack. At this current time, this page simply +gives instructions on how uploading can be done (with specific attention paid to identifying the KDE Store and giving +more specific instructions for that). + ### Installation Control The `InstallationCommand` and `UninstallCommand` entries can be used to handle items once they have been put into their diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98c708fbd800472687dc97db6838d754e64bc1a9..656d65953ed06bb07c0ec1f9090397e14001b968 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,8 @@ list(APPEND KNewStuff_SRCS ) endif() +qt_add_resources(KNewStuff_RESOURCES uploaddialog.qrc) + ecm_qt_declare_logging_category(KNewStuff_SRCS HEADER knewstuff_debug.h IDENTIFIER KNEWSTUFF @@ -41,11 +43,10 @@ ecm_qt_declare_logging_category(KNewStuff_SRCS ki18n_wrap_ui(KNewStuff_SRCS downloadwidget.ui - uploaddialog.ui kmoretools/ui/kmoretoolsconfigwidget.ui ) -add_library(KF5NewStuff ${KNewStuff_SRCS} ) +add_library(KF5NewStuff ${KNewStuff_SRCS} ${KNewStuff_RESOURCES}) add_library(KF5::NewStuff ALIAS KF5NewStuff) ecm_generate_export_header(KF5NewStuff @@ -55,7 +56,7 @@ ecm_generate_export_header(KF5NewStuff VERSION ${KF_VERSION} DEPRECATED_BASE_VERSION 0 EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} - DEPRECATION_VERSIONS 5.29 5.76 5.77 5.78 5.79 5.80 5.82 + DEPRECATION_VERSIONS 5.29 5.76 5.77 5.78 5.79 5.80 5.82 5.85 ) target_include_directories(KF5NewStuff @@ -76,6 +77,7 @@ target_link_libraries(KF5NewStuff KF5::ItemViews # For buttons on download dialog KF5::IconThemes # For KIcon Qt5::Qml + Qt5::Quick KF5::TextWidgets # For KTextEdit in upload dialog ) diff --git a/src/attica/atticaprovider.cpp b/src/attica/atticaprovider.cpp index 51fc0f30588252d0930cc1b6ae6b195f1b08d3b0..a248cb571466e102d59b6c8f9bcf88117946aa28 100644 --- a/src/attica/atticaprovider.cpp +++ b/src/attica/atticaprovider.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,7 @@ AtticaProvider::AtticaProvider(const QStringList &categories, const QString &add connect(&m_providerManager, SIGNAL(authenticationCredentialsMissing(Provider)), SLOT(authenticationCredentialsMissing(Provider))); connect(this, &Provider::loadComments, this, &AtticaProvider::loadComments); connect(this, &Provider::loadPerson, this, &AtticaProvider::loadPerson); + connect(this, &Provider::loadBasics, this, &AtticaProvider::loadBasics); } AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString &additionalAgentInformation) @@ -423,6 +425,40 @@ void AtticaProvider::loadedPerson(Attica::BaseJob *baseJob) Q_EMIT personLoaded(author); } +void AtticaProvider::loadBasics() +{ + Attica::ItemJob *configJob = m_provider.requestConfig(); + connect(configJob, &BaseJob::finished, this, &AtticaProvider::loadedConfig); + configJob->start(); +} + +void AtticaProvider::loadedConfig(Attica::BaseJob *baseJob) +{ + if (jobSuccess(baseJob)) { + auto *job = static_cast *>(baseJob); + Attica::Config config = job->result(); + setVersion(config.version()); + setSupportsSsl(config.ssl()); + setContactEmail(config.contact()); + QString protocol{QStringLiteral("http")}; + if (config.ssl()) { + protocol = QStringLiteral("https"); + } + // There is usually no protocol in the website and host, but in case + // there is, trust what's there + if (config.website().contains(QLatin1String("://"))) { + setWebsite(QUrl(config.website())); + } else { + setWebsite(QUrl(QLatin1String("%1://%2").arg(protocol).arg(config.website()))); + } + if (config.host().contains(QLatin1String("://"))) { + setHost(QUrl(config.host())); + } else { + setHost(QUrl(QLatin1String("%1://%2").arg(protocol).arg(config.host()))); + } + } +} + void AtticaProvider::accountBalanceLoaded(Attica::BaseJob *baseJob) { if (!jobSuccess(baseJob)) { diff --git a/src/attica/atticaprovider_p.h b/src/attica/atticaprovider_p.h index deab3510a4d101f9eae86dc6829f313da6d4df7d..abb6f0e42d4267692a60a3b2a0b77924042bb2e4 100644 --- a/src/attica/atticaprovider_p.h +++ b/src/attica/atticaprovider_p.h @@ -65,6 +65,12 @@ public: * @see Provider::loadPerson(const QString &username) */ Q_SLOT void loadPerson(const QString &username); + /** + * The slot which causes the provider's basic information to be fetched. + * For the Attica provider, this translates to an OCS Config call + * @see Provider::loadBasics() + */ + Q_SLOT void loadBasics(); bool userCanVote() override { @@ -90,6 +96,7 @@ private Q_SLOTS: void detailsLoaded(Attica::BaseJob *job); void loadedComments(Attica::BaseJob *job); void loadedPerson(Attica::BaseJob *job); + void loadedConfig(Attica::BaseJob *job); private: void checkForUpdates(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e02435f2edff67e4c3d941537b4a528ef10fb955..c7542f69d5c8ca883f1e6baf8d2f3421f2ecd2c8 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -10,6 +10,7 @@ set(KNewStuffCore_SRCS installation.cpp itemsmodel.cpp provider.cpp + providersmodel.cpp security.cpp tagsfilterchecker.cpp xmlloader.cpp @@ -120,6 +121,7 @@ ecm_generate_headers(KNewStuffCore_CamelCase_HEADERS Installation ItemsModel Provider + ProvidersModel Question QuestionListener QuestionManager diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 581987174021bac0d74ae6d54cb79930acbbc53a..821556ca668223368ca44f5ea332eb926dd89404 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -143,6 +143,7 @@ public: Engine::BusyState busyState; QString busyMessage; QString useLabel; + bool uploadEnabled = false; }; Engine::Engine(QObject *parent) @@ -202,23 +203,35 @@ bool Engine::init(const QString &configfile) setBusy(BusyOperation::Initializing, i18n("Initializing")); QScopedPointer conf; + QFileInfo configFileInfo(configfile); // TODO KF6: This is fallback logic for an old location for the knsrc files. This is deprecated in KF5 and should be removed in KF6 - bool isRelativeConfig = QFileInfo(configfile).isRelative(); + bool isRelativeConfig = configFileInfo.isRelative(); QString actualConfig; if (isRelativeConfig) { - // Don't do the expensive search unless the config is relative - actualConfig = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("knsrcfiles/%1").arg(configfile)); + if (configfile.contains(QStringLiteral("/"))) { + // If this is the case, then we've been given an /actual/ relative path, not just the name of a knsrc file + actualConfig = configFileInfo.canonicalFilePath(); + } else { + // Don't do the expensive search unless the config is relative + actualConfig = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("knsrcfiles/%1").arg(configfile)); + } } + // We need to always have a full path in some cases, and the given config name in others, so let's just + // store this in a variable with a useful name + QString configFullPath = actualConfig.isEmpty() ? configfile : actualConfig; QString configFileName{configfile}; if (isRelativeConfig && d->configLocationFallback && actualConfig.isEmpty()) { conf.reset(new KConfig(configfile)); qCWarning(KNEWSTUFFCORE) << "Using a deprecated location for the knsrc file" << configfile << " - please contact the author of the software which provides this file to get it updated to use the new location"; - } else if (isRelativeConfig) { + } else if (isRelativeConfig && actualConfig.isEmpty()) { configFileName = QFileInfo(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("knsrcfiles/%1").arg(configfile))).baseName(); conf.reset(new KConfig(QStringLiteral("knsrcfiles/%1").arg(configfile), KConfig::FullConfig, QStandardPaths::GenericDataLocation)); + } else if (isRelativeConfig) { + configFileName = configFileInfo.baseName(); + conf.reset(new KConfig(actualConfig)); } else { - configFileName = QFileInfo(configfile).baseName(); + configFileName = configFileInfo.baseName(); conf.reset(new KConfig(configfile)); } @@ -247,11 +260,13 @@ bool Engine::init(const QString &configfile) m_adoptionCommand = group.readEntry("AdoptionCommand"); d->useLabel = group.readEntry("UseLabel", i18n("Use")); Q_EMIT useLabelChanged(); + d->uploadEnabled = group.readEntry("UploadEnabled", true); + Q_EMIT uploadEnabledChanged(); m_providerFileUrl = group.readEntry("ProvidersUrl"); if (group.readEntry("UseLocalProvidersFile", "false").toLower() == QLatin1String{"true"}) { // The local providers file is called "appname.providers", to match "appname.knsrc" - m_providerFileUrl = QLatin1String("%1.providers").arg(configFileName.left(configFileName.length() - 6)); + m_providerFileUrl = QUrl::fromLocalFile(QLatin1String("%1.providers").arg(configFullPath.left(configFullPath.length() - 6))).toString(); } d->tagFilter = group.readEntry("TagFilter", QStringList(QStringLiteral("ghns_excluded!=1"))); @@ -466,6 +481,8 @@ void Engine::addProvider(QSharedPointer provider) connect(provider.data(), &Provider::signalInformation, this, [this](const QString &message) { Q_EMIT signalMessage(message); }); + connect(provider.data(), &Provider::basicsLoaded, this, &Engine::providersChanged); + Q_EMIT providersChanged(); } void Engine::providerJobStarted(KJob *job) @@ -1078,6 +1095,11 @@ QSharedPointer KNSCore::Engine::defaultProvider() const return nullptr; } +QStringList Engine::providerIDs() const +{ + return m_providers.keys(); +} + KNSCore::CommentsModel *KNSCore::Engine::commentsForEntry(const KNSCore::EntryInternal &entry) { CommentsModel *model = d->commentsModels[entry]; @@ -1199,3 +1221,8 @@ QString Engine::useLabel() const { return d->useLabel; } + +bool KNSCore::Engine::uploadEnabled() const +{ + return d->uploadEnabled; +} diff --git a/src/core/engine.h b/src/core/engine.h index dd06204469d29382858dc8a3c77dd57d4f05978c..a0d17536c75fc75129885672f6498caad88bbc04 100644 --- a/src/core/engine.h +++ b/src/core/engine.h @@ -78,6 +78,18 @@ class KNEWSTUFFCORE_EXPORT Engine : public QObject */ Q_PROPERTY(QString useLabel READ useLabel NOTIFY useLabelChanged) + /** + * Whether or not the configuration says that the providers are expected to support uploading. + * As it stands, this is used to determine whether or not to show the Upload... action where + * that is displayed (primarily NewStuff.Page). + * @since 5.85 + */ + Q_PROPERTY(bool uploadEnabled READ uploadEnabled NOTIFY uploadEnabledChanged) + + /** + * @since 5.85 + */ + Q_PROPERTY(QStringList providerIDs READ providerIDs NOTIFY providersChanged) public: /** * Constructor. @@ -546,6 +558,20 @@ public: */ QSharedPointer defaultProvider() const; + /** + * The IDs of all providers known by this engine. Use this in combination with + * provider(const QString&) to iterate over all providers. + * @return The string IDs of all known providers + * @since 5.85 + */ + QStringList providerIDs() const; + + /** + * Fired whenever the list of providers changes + * @since 5.85 + */ + Q_SIGNAL void providersChanged(); + /** * This function will return an instance of a model which contains comments for * the entry passed to it. The model may be empty (if there are no comments for @@ -619,6 +645,19 @@ public: */ Q_INVOKABLE void revalidateCacheEntries(); + /** + * Whether or not the configuration says that the providers are expected to support uploading. + * @return True if the providers are expected to support uploading + * @since 5.85 + */ + bool uploadEnabled() const; + + /** + * Fired when the uploadEnabled property changes + * @since 5.85 + */ + Q_SIGNAL void uploadEnabledChanged(); + Q_SIGNALS: /** * Indicates a message to be added to the ui's log, or sent to a messagebox @@ -738,6 +777,7 @@ private: QSharedPointer m_cache; QTimer *m_searchTimer; // The url of the file containing information about content providers + /// TODO KF6 This really wants to be turned into a QUrl (which will have implications for our public API, so not doing it just now) QString m_providerFileUrl; // Categories from knsrc file QStringList m_categories; diff --git a/src/core/provider.cpp b/src/core/provider.cpp index 86cc148e2a6ce52d5cfd10afb7d5c02ecc8c63b4..a8f7e1d9cc704b6c8ef8445abdbeb72b0794a1c4 100644 --- a/src/core/provider.cpp +++ b/src/core/provider.cpp @@ -14,14 +14,42 @@ #include +#include + namespace KNSCore { // TODO KF6 BCI: Add a real d-pointer class ProviderPrivate { public: + Provider *q; QStringList tagFilter; QStringList downloadTagFilter; + + QTimer *basicsThrottle{nullptr}; + QString version; + QUrl website; + QUrl host; + QString contactEmail; + bool supportsSsl{false}; + bool basicsGot{false}; + void updateOnFirstBasicsGet() + { + if (!basicsGot) { + basicsGot = true; + QTimer::singleShot(0, q, &Provider::loadBasics); + } + }; + void throttleBasics() + { + if (!basicsThrottle) { + basicsThrottle = new QTimer(q); + basicsThrottle->setInterval(0); + basicsThrottle->setSingleShot(true); + QObject::connect(basicsThrottle, &QTimer::timeout, q, &Provider::basicsLoaded); + } + basicsThrottle->start(); + } }; typedef QHash ProviderPrivateHash; Q_GLOBAL_STATIC(ProviderPrivateHash, d_func) @@ -51,6 +79,7 @@ QString Provider::SearchRequest::hashForRequest() const Provider::Provider() { + d(this)->q = this; } Provider::~Provider() @@ -102,4 +131,73 @@ QDebug operator<<(QDebug dbg, const Provider::SearchRequest &search) return dbg; } +QString Provider::version() const +{ + d(this)->updateOnFirstBasicsGet(); + return d(this)->version; +} + +void Provider::setVersion(const QString &version) +{ + if (d(this)->version != version) { + d(this)->version = version; + d(this)->throttleBasics(); + } +} + +QUrl Provider::website() const +{ + d(this)->updateOnFirstBasicsGet(); + return d(this)->website; +} + +void Provider::setWebsite(const QUrl &website) +{ + if (d(this)->website != website) { + d(this)->website = website; + d(this)->throttleBasics(); + } +} + +QUrl Provider::host() const +{ + d(this)->updateOnFirstBasicsGet(); + return d(this)->host; +} + +void Provider::setHost(const QUrl &host) +{ + if (d(this)->host != host) { + d(this)->host = host; + d(this)->throttleBasics(); + } +} + +QString Provider::contactEmail() const +{ + d(this)->updateOnFirstBasicsGet(); + return d(this)->contactEmail; +} + +void Provider::setContactEmail(const QString &contactEmail) +{ + if (d(this)->contactEmail != contactEmail) { + d(this)->contactEmail = contactEmail; + d(this)->throttleBasics(); + } +} + +bool Provider::supportsSsl() const +{ + d(this)->updateOnFirstBasicsGet(); + return d(this)->supportsSsl; +} + +void Provider::setSupportsSsl(bool supportsSsl) +{ + if (d(this)->supportsSsl != supportsSsl) { + d(this)->supportsSsl = supportsSsl; + d(this)->throttleBasics(); + } +} } diff --git a/src/core/provider.h b/src/core/provider.h index 75b6b7168ee804a070d88d259e4443168964ec7b..b42d2e8fd2813deeee1525425390554de401c408 100644 --- a/src/core/provider.h +++ b/src/core/provider.h @@ -3,6 +3,7 @@ This file is part of KNewStuff2. SPDX-FileCopyrightText: 2009 Jeremy Whiting SPDX-FileCopyrightText: 2009 Frederik Gladhorn + SPDX-FileCopyrightText: 2021 Dan Leinir Turthra Jensen SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -41,6 +42,11 @@ struct Comment; class KNEWSTUFFCORE_EXPORT Provider : public QObject { Q_OBJECT + Q_PROPERTY(QString version READ version WRITE setVersion NOTIFY basicsLoaded) + Q_PROPERTY(QUrl website READ website WRITE setWebsite NOTIFY basicsLoaded) + Q_PROPERTY(QUrl host READ host WRITE setHost NOTIFY basicsLoaded) + Q_PROPERTY(QString contactEmail READ contactEmail WRITE setContactEmail NOTIFY basicsLoaded) + Q_PROPERTY(bool supportsSsl READ supportsSsl WRITE setSupportsSsl NOTIFY basicsLoaded) public: typedef QList List; @@ -206,6 +212,79 @@ public: * @since 5.63 */ Q_SIGNAL void loadPerson(const QString &username); + /** + * Request loading of the basic information for this provider. The engine listens + * to the basicsLoaded() signal for the result, which is also the signal the respective + * properties listen to. + * + * This is fired automatically on the first attempt to read one of the properties + * which contain this basic information, and you will not need to call it as a user + * of the class (just listen to the properties, which will update when the information + * has been fetched). + * + * @note Implementation detail: All subclasses should connect to this signal + * and point it at a slot which does the actual work, if they support fetching + * this basic information (if the information is set during construction, you will + * not need to worry about this). + * + * TODO: KF6 This should be a virtual function, but can't do it now because BIC + * @see version() + * @see website() + * @see host(); + * @see contactEmail() + * @see supportsSsl() + * @since 5.85 + */ + Q_SIGNAL void loadBasics(); + /** + * @since 5.85 + */ + QString version() const; + /** + * @since 5.85 + */ + void setVersion(const QString &version); + /** + * @since 5.85 + */ + QUrl website() const; + /** + * @since 5.85 + */ + void setWebsite(const QUrl &website); + /** + * @since 5.85 + */ + QUrl host() const; + /** + * @param host The host used for this provider + * @since 5.85 + */ + void setHost(const QUrl &host); + /** + * The general contact email for this provider + * @return The general contact email for this provider + * @since 5.85 + */ + QString contactEmail() const; + /** + * Sets the general contact email address for this provider + * @param contactEmail The general contact email for this provider + * @since 5.85 + */ + void setContactEmail(const QString &contactEmail); + /** + * Whether or not the provider supports SSL connections + * @return True if the server supports SSL connections, false if not + * @since 5.85 + */ + bool supportsSsl() const; + /** + * Set whether or not the provider supports SSL connections + * @param supportsSsl True if the server supports SSL connections, false if not + * @since 5.85 + */ + void setSupportsSsl(bool supportsSsl); virtual bool userCanVote() { @@ -275,6 +354,11 @@ Q_SIGNALS: * @since 5.63 */ void personLoaded(const std::shared_ptr author); + /** + * Fired when the provider's basic information has been fetched and updated + * @since 5.85 + */ + void basicsLoaded(); /** * Fires when the provider has loaded search presets. These represent interesting diff --git a/src/core/providersmodel.cpp b/src/core/providersmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..092f812f6d3c23c8a73d8f2ba01867067041234a --- /dev/null +++ b/src/core/providersmodel.cpp @@ -0,0 +1,118 @@ +/* + SPDX-FileCopyrightText: 2021 Dan Leinir Turthra Jensen + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "providersmodel.h" + +namespace KNSCore +{ +class ProvidersModelPrivate +{ +public: + Engine *engine{nullptr}; + QStringList knownProviders; +}; + +ProvidersModel::ProvidersModel(QObject *parent) + : QAbstractListModel(parent) + , d(new ProvidersModelPrivate) +{ +} + +ProvidersModel::~ProvidersModel() = default; + +QHash KNSCore::ProvidersModel::roleNames() const +{ + static const QHash roles{ + {IdRole, "id"}, + {NameRole, "name"}, + {VersionRole, "version"}, + {WebsiteRole, "website"}, + {HostRole, "host"}, + {ContactEmailRole, "contactEmail"}, + {SupportsSslRole, "supportsSsl"}, + {IconRole, "icon"}, + {ObjectRole, "object"}, + }; + return roles; +} + +int KNSCore::ProvidersModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return d->knownProviders.count(); +} + +QVariant KNSCore::ProvidersModel::data(const QModelIndex &index, int role) const +{ + QVariant result; + if (checkIndex(index) && d->engine) { + QSharedPointer provider = d->engine->provider(d->knownProviders.value(index.row())); + if (provider) { + switch (role) { + case IdRole: + result.setValue(provider->id()); + break; + case NameRole: + result.setValue(provider->name()); + break; + case VersionRole: + result.setValue(provider->version()); + break; + case WebsiteRole: + result.setValue(provider->website()); + break; + case HostRole: + result.setValue(provider->host()); + break; + case ContactEmailRole: + result.setValue(provider->contactEmail()); + break; + case SupportsSslRole: + result.setValue(provider->supportsSsl()); + break; + case IconRole: + result.setValue(provider->icon()); + break; + case ObjectRole: + result.setValue(provider.data()); + break; + default: + break; + } + } + } + return result; +} + +QObject *KNSCore::ProvidersModel::engine() const +{ + return d->engine; +} + +void KNSCore::ProvidersModel::setEngine(QObject *engine) +{ + if (d->engine != engine) { + if (d->engine) { + d->engine->disconnect(this); + } + d->engine = qobject_cast(engine); + Q_EMIT engineChanged(); + if (d->engine) { + connect(d->engine, &Engine::providersChanged, this, [this]() { + beginResetModel(); + d->knownProviders = d->engine->providerIDs(); + endResetModel(); + }); + beginResetModel(); + d->knownProviders = d->engine->providerIDs(); + endResetModel(); + } + } +} + +} diff --git a/src/core/providersmodel.h b/src/core/providersmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..e0626633280254b6370b18e1e820b3cc6aa831b2 --- /dev/null +++ b/src/core/providersmodel.h @@ -0,0 +1,62 @@ +/* + SPDX-FileCopyrightText: 2021 Dan Leinir Turthra Jensen + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KNSCORE_PROVIDERSMODELL_H +#define KNSCORE_PROVIDERSMODELL_H + +#include + +#include "engine.h" +#include "knewstuffcore_export.h" + +#include + +namespace KNSCore +{ +class ProvidersModelPrivate; +/** + * @brief A model which holds information on all known Providers for a specific Engine + * + * @since 5.85 + */ +class KNEWSTUFFCORE_EXPORT ProvidersModel : public QAbstractListModel +{ + Q_OBJECT + /** + * The Engine for which this model displays Providers + */ + Q_PROPERTY(QObject *engine READ engine WRITE setEngine NOTIFY engineChanged) +public: + explicit ProvidersModel(QObject *parent = nullptr); + ~ProvidersModel() override; + + enum Roles { + IdRole = Qt::UserRole + 1, + NameRole, + VersionRole, + WebsiteRole, + HostRole, + ContactEmailRole, + SupportsSslRole, + IconRole, + ObjectRole, ///< The actual Provider object. Do not hold this locally and expect it to disappear at a moment's notice + }; + Q_ENUM(Roles) + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QObject *engine() const; + void setEngine(QObject *engine); + Q_SIGNAL void engineChanged(); + +private: + std::unique_ptr d; +}; +} + +#endif // KNSCORE_PROVIDERSMODELL_H diff --git a/src/qtquick/qml/Dialog.qml b/src/qtquick/qml/Dialog.qml index 24e3b9729e5cb36cd4742bcb29f715e82e67bbc7..0d4f275db1e6c6aa1f5d7f7980b4fcb97ba6c004 100644 --- a/src/qtquick/qml/Dialog.qml +++ b/src/qtquick/qml/Dialog.qml @@ -21,7 +21,7 @@ import QtQuick.Layouts 1.11 as QtLayouts import QtQuick.Dialogs 1.3 as QtDialogs import org.kde.kirigami 2.7 as Kirigami -import org.kde.newstuff 1.62 as NewStuff +import org.kde.newstuff 1.85 as NewStuff QtDialogs.Dialog { id: component @@ -124,6 +124,23 @@ QtDialogs.Dialog { } standardButtons: QtControls.DialogButtonBox.Close onRejected: component.close() + QtControls.Button { + text: i18nd("knewstuff5", "Contribute your own...") + icon.name: "upload-media" + visible: newStuffPage.engine.engine.uploadEnabled + enabled: !(newStuffPage.pageStack.currentItem instanceof NewStuff.UploadPage) + onClicked: { + newStuffPage.pageStack.push(uploadPage); + } + QtControls.DialogButtonBox.buttonRole: QtControls.DialogButtonBox.HelpRole + } + } + } + Component { + id: uploadPage + NewStuff.UploadPage { + objectName: "uploadPage" + engine: newStuffPage.engine } } } diff --git a/src/qtquick/qml/DialogContent.qml b/src/qtquick/qml/DialogContent.qml index 270f38266e6238e6b0c7ea554b12ec515143f3c7..dbdca235114fc1d08e24865e9f9e666b32c56d44 100644 --- a/src/qtquick/qml/DialogContent.qml +++ b/src/qtquick/qml/DialogContent.qml @@ -61,6 +61,7 @@ Kirigami.ApplicationItem { pageStack.globalToolBar.canContainHandles: true pageStack.initialPage: NewStuff.Page { id: newStuffPage + showUploadAction: false function showMessage(message) { // As the Page shows something nice and friendly while loading, // there's no reason to do the passive notification thing for those. diff --git a/src/qtquick/qml/Page.qml b/src/qtquick/qml/Page.qml index 6b87df04bd5c0269a794e0b2dec4277107ef33ed..29a51a4650109ce61dc1904a60974fcbe6bc6d1f 100644 --- a/src/qtquick/qml/Page.qml +++ b/src/qtquick/qml/Page.qml @@ -18,9 +18,9 @@ import QtQuick.Layouts 1.11 as QtLayouts import QtGraphicalEffects 1.11 as QtEffects import org.kde.kcm 1.2 as KCM -import org.kde.kirigami 2.12 as Kirigami +import org.kde.kirigami 2.14 as Kirigami -import org.kde.newstuff 1.83 as NewStuff +import org.kde.newstuff 1.85 as NewStuff import "private" as Private import "private/entrygriddelegates" as EntryGridDelegates @@ -59,6 +59,16 @@ KCM.GridViewKCM { */ signal errorMessage(string message); + /** + * Whether or not to show the Upload... context action + * Usually this will be bound to the engine's property which usually defines + * this, but you can override it programmatically by setting it here. + * @since 5.85 + * @see KNSCore::Engine::uploadEnabled + */ + property alias showUploadAction: uploadAction.visible + + /** * Show the details page for a specific entry. * If you call this function before the engine initialisation has been completed, @@ -338,6 +348,16 @@ KCM.GridViewKCM { QtControls.ActionGroup.group: viewSortingActionGroup } }, + Kirigami.Action { + id: uploadAction + text: i18nd("knewstuff5", "Upload...") + tooltip: i18nd("knewstuff5", "Learn how to add your own hot new stuff to this list") + iconName: "upload-media" + visible: root.showUploadAction && newStuffEngine.engine.uploadEnabled + onTriggered: { + pageStack.push(uploadPage); + } + }, Kirigami.Action { text: i18nd("knewstuff5", "Go to...") iconName: "go-next"; @@ -347,6 +367,7 @@ KCM.GridViewKCM { Kirigami.Action { text: i18nd("knewstuff5", "Search...") iconName: "system-search"; + displayHint: Kirigami.DisplayHint.KeepVisible displayComponent: Kirigami.SearchField { enabled: engine.isValid id: searchField @@ -438,6 +459,12 @@ KCM.GridViewKCM { id: detailsPage; NewStuff.EntryDetails { } } + Component { + id: uploadPage + NewStuff.UploadPage { + engine: newStuffEngine + } + } Item { anchors.fill: parent diff --git a/src/qtquick/qml/UploadPage.qml b/src/qtquick/qml/UploadPage.qml new file mode 100644 index 0000000000000000000000000000000000000000..6b4957a093541323e1825f83b0c39b18c5f84a97 --- /dev/null +++ b/src/qtquick/qml/UploadPage.qml @@ -0,0 +1,127 @@ +/* + SPDX-FileCopyrightText: 2019 Dan Leinir Turthra Jensen + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +/** + * @brief A Kirigami.Page component used for showing how to upload KNS entries to a service + * + * This page shows a short guide for uploading new content to the service provided by a KNewStuff + * provider. This attempts to use the information available through the provider itself, and + * shows a link to the service's web page, and email in case it is not the KDE Store. + * + * While there are not currently any services which support direct OCS API based uploading of + * new content, we still need a way to guide people to how to do this, hence this component's + * simplistic nature. + * + * This component is functionally equivalent to the old UploadDialog + * @see KNewStuff::UploadDialog + * @since 5.85 + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.11 as QtControls +import QtQuick.Layouts 1.11 as QtLayouts + +import org.kde.kcm 1.2 as KCM +import org.kde.kirigami 2.12 as Kirigami + +import org.kde.newstuff 1.85 as NewStuff + +import "private" as Private + +Kirigami.ScrollablePage { + id: component; + /** + * The NewStuffQuick Engine instance used to display content for this item. + * You can either pass in one that has already been set up (such as from a + * NewStuff.Page or NewStuff.Dialog), or you can construct a new one yourself, + * simply by doing something like this (which will use the wallpapers configuration): + \code + NewStuff.UploadPage { + engine: NewStuff.Engine { + configFile: "wallpapers.knsrc" + } + } + \endcode + */ + required property QtObject engine + + title: i18nc("@knewstuff5", "Upload New Stuff: %1", component.engine.name); + NewStuff.QuestionAsker {} + Private.ErrorDisplayer { engine: component.engine; active: component.isCurrentPage; } + + QtLayouts.ColumnLayout { + QtLayouts.Layout.fillWidth: true; + Item { + QtLayouts.Layout.fillWidth: true; + opacity: implicitHeight > 0 ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } } + visible: opacity > 0 + implicitHeight: uploaderBusy.running ? uploaderBusy.height + uploaderBusyInfo.height + Kirigami.Units.largeSpacing * 4 : 0; + QtLayouts.Layout.preferredHeight: implicitHeight; + Behavior on QtLayouts.Layout.preferredHeight { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } } + QtControls.BusyIndicator { + id: uploaderBusy + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.verticalCenter + bottomMargin: Kirigami.Units.largeSpacing + } + running: component.engine.isLoading && component.engine.isValid + } + QtControls.Label { + id: uploaderBusyInfo + anchors { + top: parent.verticalCenter + left: parent.left + right: parent.right + margins: Kirigami.Units.largeSpacing + } + horizontalAlignment: Text.AlignHCenter + text: i18ndc("knewstuff5", "A text shown beside a busy indicator suggesting that data is being fetched", "Updating information...") + } + } + Repeater { + model: NewStuff.ProvidersModel { + engine: component.engine.engine; + } + Kirigami.Card { + enabled: !uploaderBusy.running + banner { + title: { + if (model.name === "api.kde-look.org") { + return i18ndc("knewstuff5", "The name of the KDE Store", "KDE Store"); + } else if (model.name !== "") { + return model.name; + } else if (component.engine.name !== "") { + return component.engine.name; + } else { + return i18ndc("knewstuff5", "An unnamed provider", "Your Provider"); + } + } + titleIcon: model.icon == "" ? "get-hot-new-stuff" : model.icon; + } + actions: [ + Kirigami.Action { + visible: model.website != "" + text: i18ndc("knewstuff5", "Text for an action which causes the specified website to be opened using the user's system default browser", "Open Website: %1", model.website) + onTriggered: Qt.openUrlExternally(model.website); + }, + Kirigami.Action { + visible: model.contactEmail != "" && model.name != "api.kde-look.org" + text: i18ndc("knewstuff5", "Text for an action which will attempt to send an email using the user's system default email client", "Send Email To: %1", model.contactEmail) + onTriggered: Qt.openUrlExternally("mailto:" + model.contactEmail); + } + ] + contentItem: QtControls.Label { + wrapMode: Text.Wrap; + text: model.name === "api.kde-look.org" + ? i18ndc("knewstuff5", "A description of how to upload content to a generic provider", "To upload new entries, or to add content to an existing entry on the KDE Store, please open the website and log in. Once you have done this, you will be able to find the My Products entry in the menu which pops up when you click your user icon. Click on this entry to go to the product management system, where you can work on your products .") + : i18ndc("knewstuff5", "A description of how to upload content to the KDE Store specifically", "To upload new entries, or to add content to an existing entry, please open the provider's website and follow the instructions there. You will likely need to create a user and log in to a product management system, where you will need to follow the instructions for how to add. Alternatively, you might be required to contact the managers of the site directly to get new content added.") + } + } + } + } +} diff --git a/src/qtquick/qmldir b/src/qtquick/qmldir index 159489b22f715f253a6e2ca535f260192a872284..924ba690b1d8a4d01257fc72266fb4880b000dd3 100644 --- a/src/qtquick/qmldir +++ b/src/qtquick/qmldir @@ -10,3 +10,4 @@ EntryDetails 1.1 qml/EntryDetails.qml Page 1.1 qml/Page.qml QuestionAsker 1.1 qml/QuestionAsker.qml Action 1.81 qml/Action.qml +UploadPage 1.85 qml/UploadPage.qml diff --git a/src/qtquick/qmlplugin.cpp b/src/qtquick/qmlplugin.cpp index 8d1261ec736b78e1b235ea625b75bfd90c348958..a0b1830dc7645222af1f20ff1cffb0dc9e48e482 100644 --- a/src/qtquick/qmlplugin.cpp +++ b/src/qtquick/qmlplugin.cpp @@ -18,6 +18,7 @@ #include "searchpresetmodel.h" #include "provider.h" +#include "providersmodel.h" #include "question.h" #include @@ -95,4 +96,7 @@ void QmlPlugins::registerTypes(const char *uri) 83, "SearchPresetModel", QStringLiteral("This should only be created by the Engine, and provides the SearchPresets available in that engine")); + + // Version 1.85 + qmlRegisterType(uri, 1, 85, "ProvidersModel"); } diff --git a/src/staticxml/staticxmlprovider.cpp b/src/staticxml/staticxmlprovider.cpp index acf147069c563c6475cf25f0e4a637c765b0de4c..97ffe65187a18998aab899a9709cd13994bd1954 100644 --- a/src/staticxml/staticxmlprovider.cpp +++ b/src/staticxml/staticxmlprovider.cpp @@ -65,14 +65,36 @@ bool StaticXmlProvider::setProviderXML(const QDomElement &xmldata) mIcon = iconurl; QDomNode n; + QLocale::Language systemLanguage = QLocale::system().language(); + QString firstName; for (n = xmldata.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.tagName() == QLatin1String("title")) { - // QString lang = e.attribute("lang"); - mName = e.text().trimmed(); - qCDebug(KNEWSTUFFCORE) << "add name for provider (" << this << "): " << e.text(); + const QString lang{e.attribute(QLatin1String("lang"))}; + bool useThisTitle{false}; + if (mName.isEmpty() && lang.isEmpty()) { + // If we have no title as yet, and we've also got no language defined, this is the default + // and name we need to set it, even if we might override it later + useThisTitle = true; + } else { + const QLocale locale(lang); + if (systemLanguage == locale.language()) { + useThisTitle = true; + } + } + if (useThisTitle) { + mName = e.text().trimmed(); + qCDebug(KNEWSTUFFCORE) << "add name for provider (" << this << "): " << e.text(); + } + if (firstName.isEmpty()) { + firstName = e.text().trimmed(); + } } } + if (mName.isEmpty()) { + // Just a fallback, because those are quite nice to have... + mName = firstName; + } // Validation if ((mNoUploadUrl.isValid()) && (mUploadUrl.isValid())) { @@ -85,6 +107,12 @@ bool StaticXmlProvider::setProviderXML(const QDomElement &xmldata) return false; } + if (mUploadUrl.isValid()) { + setWebsite(mUploadUrl); + } else { + setWebsite(mNoUploadUrl); + } + mId = mDownloadUrls[QString()].url(); if (mId.isEmpty()) { mId = mDownloadUrls[mDownloadUrls.begin().key()].url(); diff --git a/src/uploaddialog.cpp b/src/uploaddialog.cpp index 390a102a87ab35d9d31870c3916c22343cd602fd..401c0ba8d1faad5e2be178f10aec3369e2dcdb79 100644 --- a/src/uploaddialog.cpp +++ b/src/uploaddialog.cpp @@ -8,410 +8,48 @@ */ #include "uploaddialog.h" -#include "ui/widgetquestionlistener.h" -#include "uploaddialog_p.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#if KNEWSTUFF_BUILD_DEPRECATED_SINCE(5, 80) -#include -#include -#include -#include +#include "knewstuff_debug.h" +#include "uploaddialog_p.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include using namespace KNS3; bool UploadDialogPrivate::init(const QString &configfile) { - QVBoxLayout *layout = new QVBoxLayout; - q->setLayout(layout); - - QWidget *_mainWidget = new QWidget(q); - ui.setupUi(_mainWidget); - - layout->addWidget(_mainWidget); - - backButton = new QPushButton; - KGuiItem::assign(backButton, KStandardGuiItem::back(KStandardGuiItem::UseRTL)); - - nextButton = new QPushButton; - nextButton->setText(i18nc("Opposite to Back", "Next")); - nextButton->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon()); - nextButton->setDefault(true); - - finishButton = new QPushButton; - finishButton->setText(i18n("Finish")); - finishButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); - - buttonBox = new QDialogButtonBox(q); - buttonBox->addButton(backButton, QDialogButtonBox::ActionRole); - buttonBox->addButton(nextButton, QDialogButtonBox::ActionRole); - buttonBox->addButton(finishButton, QDialogButtonBox::AcceptRole); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel); - layout->addWidget(buttonBox); - - atticaHelper = new KNSCore::AtticaHelper(q); - bool success = true; - QFileInfo fi(configfile); - if (!fi.exists()) { - if (!fi.isAbsolute()) - fi.setFile(QStandardPaths::locate(QStandardPaths::GenericConfigLocation, configfile)); - if (!fi.exists()) { - qCCritical(KNEWSTUFF) << "No knsrc file named '" << fi.absoluteFilePath() << "' was found."; - success = false; - } - } - KConfig conf(fi.absoluteFilePath()); - if (conf.accessMode() == KConfig::NoAccess) { - qCCritical(KNEWSTUFF) << "Knsrc file named '" << fi.absoluteFilePath() << "' could not be accessed."; - success = false; - } - - KConfigGroup group; - if (conf.hasGroup("KNewStuff3")) { - qCDebug(KNEWSTUFF) << "Loading KNewStuff3 config: " << fi.absoluteFilePath(); - group = conf.group("KNewStuff3"); + QQuickView *view = new QQuickView; + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + q->setLayout(layout); + q->layout()->addWidget(QWidget::createWindowContainer(view, q)); + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, q); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, q, &UploadDialog::accept); + q->layout()->addWidget(buttonBox); + + view->rootContext()->setContextProperty(QStringLiteral("knsrcfile"), configfile); + view->setSource(QUrl(QStringLiteral("qrc:///uploaddialog.qml"))); + QQuickItem *item = view->rootObject(); + // If there is an error on the QML side of things we get a nullptr + if (item) { + q->resize(view->rootObject()->implicitWidth(), view->rootObject()->implicitHeight()); + // Forward relevant signals + QObject::connect(item, SIGNAL(closed()), q, SLOT(accept())); } else { - qCCritical(KNEWSTUFF) << "A knsrc file was found but it doesn't contain a KNewStuff3 section." << fi.absoluteFilePath(); + qCDebug(KNEWSTUFF) << "Failed to load the UploadDialog components. The QML Engine reported the following errors:" << view->errors(); success = false; } - - if (success) { - const QString providersFileUrl = group.readEntry("ProvidersUrl"); - - categoryNames = group.readEntry("UploadCategories", QStringList()); - // fall back to download categories - if (categoryNames.isEmpty()) { - categoryNames = group.readEntry("Categories", QStringList()); - } - - atticaHelper->addProviderFile(QUrl(providersFileUrl)); - } - - ui.mCategoryCombo->addItems(categoryNames); - - if (categoryNames.size() == 1) { - ui.mCategoryLabel->setVisible(false); - ui.mCategoryCombo->setVisible(false); - } - - qCDebug(KNEWSTUFF) << "Categories: " << categoryNames; - - q->connect(atticaHelper, SIGNAL(providersLoaded(QStringList)), q, SLOT(_k_providersLoaded(QStringList))); - q->connect(atticaHelper, SIGNAL(loginChecked(bool)), q, SLOT(_k_checkCredentialsFinished(bool))); - q->connect(atticaHelper, SIGNAL(licensesLoaded(Attica::License::List)), q, SLOT(_k_licensesLoaded(Attica::License::List))); - q->connect(atticaHelper, SIGNAL(categoriesLoaded(Attica::Category::List)), q, SLOT(_k_categoriesLoaded(Attica::Category::List))); - q->connect(atticaHelper, SIGNAL(contentByCurrentUserLoaded(Attica::Content::List)), q, SLOT(_k_contentByCurrentUserLoaded(Attica::Content::List))); - q->connect(atticaHelper, SIGNAL(contentLoaded(Attica::Content)), q, SLOT(_k_updatedContentFetched(Attica::Content))); - q->connect(atticaHelper, SIGNAL(detailsLinkLoaded(QUrl)), q, SLOT(_k_detailsLinkLoaded(QUrl))); - q->connect(atticaHelper, SIGNAL(currencyLoaded(QString)), q, SLOT(_k_currencyLoaded(QString))); - q->connect(atticaHelper, SIGNAL(previewLoaded(int, QImage)), q, SLOT(_k_previewLoaded(int, QImage))); - atticaHelper->init(); - - q->connect(ui.changePreview1Button, SIGNAL(clicked()), q, SLOT(_k_changePreview1())); - q->connect(ui.changePreview2Button, SIGNAL(clicked()), q, SLOT(_k_changePreview2())); - q->connect(ui.changePreview3Button, SIGNAL(clicked()), q, SLOT(_k_changePreview3())); - - q->connect(ui.providerComboBox, SIGNAL(currentIndexChanged(QString)), q, SLOT(_k_providerChanged(QString))); - q->connect(ui.radioUpdate, SIGNAL(toggled(bool)), q, SLOT(_k_updateContentsToggled(bool))); - - q->connect(ui.registerNewAccountLabel, SIGNAL(linkActivated(QString)), q, SLOT(_k_openRegisterAccountWebpage(QString))); - - // Busy widget - busyWidget = new KPixmapSequenceWidget(); - busyWidget->setSequence(KIconLoader::global()->loadPixmapSequence(QStringLiteral("process-working"), 22)); - busyWidget->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - ui.busyWidget->setLayout(new QHBoxLayout()); - ui.busyWidget->layout()->addWidget(busyWidget); - busyWidget->setVisible(false); - - WidgetQuestionListener::instance(); - return success; } -void UploadDialogPrivate::setBusy(const QString &message) -{ - ui.busyLabel->setText(message); - busyWidget->setVisible(true); -} - -void UploadDialogPrivate::setIdle(const QString &message) -{ - ui.busyLabel->setText(message); - busyWidget->setVisible(false); -} - -void UploadDialogPrivate::_k_showPage(int page) -{ - ui.stackedWidget->setCurrentIndex(page); - setIdle(QString()); - - switch (ui.stackedWidget->currentIndex()) { - case UserPasswordPage: - ui.username->setFocus(); - setBusy(i18n("Fetching provider information...")); - break; - - case FileNewUpdatePage: - atticaHelper->loadLicenses(); - atticaHelper->loadCurrency(); - ui.uploadButton->setFocus(); - setBusy(i18n("Fetching license data from server...")); - break; - - case Details1Page: - if (ui.radioUpdate->isChecked()) { - // Fetch - atticaHelper->loadContent(ui.userContentList->currentItem()->data(Qt::UserRole).toString()); - setBusy(i18n("Fetching content data from server...")); - } - - ui.mNameEdit->setFocus(); - break; - - case UploadFinalPage: - if (previewFile1.isEmpty()) { - ui.uploadPreview1ImageLabel->setVisible(false); - ui.uploadPreview1Label->setVisible(false); - } - if (previewFile2.isEmpty()) { - ui.uploadPreview2ImageLabel->setVisible(false); - ui.uploadPreview2Label->setVisible(false); - } - if (previewFile3.isEmpty()) { - ui.uploadPreview3ImageLabel->setVisible(false); - ui.uploadPreview3Label->setVisible(false); - } - break; - } - - _k_updatePage(); -} - -void UploadDialogPrivate::_k_updatePage() -{ - bool firstPage = ui.stackedWidget->currentIndex() == 0; - backButton->setEnabled(!firstPage && !finished); - - bool nextEnabled = false; - switch (ui.stackedWidget->currentIndex()) { - case UserPasswordPage: - if (ui.providerComboBox->count() > 0 && !ui.username->text().isEmpty() && !ui.password->text().isEmpty()) { - nextEnabled = true; - } - break; - - case FileNewUpdatePage: - // FIXME: check if the file requester contains a valid file - if (!uploadFile.isEmpty() || ui.uploadFileRequester->url().isLocalFile()) { - if (ui.radioNewUpload->isChecked() || ui.userContentList->currentRow() >= 0) { - nextEnabled = true; - } - } - break; - - case Details1Page: - if (!ui.mNameEdit->text().isEmpty()) { - nextEnabled = true; - } - break; - - case Details2Page: - nextEnabled = true; - break; - - case UploadFinalPage: - break; - } - - nextButton->setEnabled(nextEnabled); - finishButton->setEnabled(finished); - - nextButton->setDefault(nextEnabled); - finishButton->setDefault(!nextEnabled); - - if (nextEnabled && buttonBox->button(QDialogButtonBox::Cancel)->hasFocus()) { - nextButton->setFocus(); - } -} - -void UploadDialogPrivate::_k_providersLoaded(const QStringList &providers) -{ - if (providers.isEmpty()) { - setIdle(i18n("Could not fetch provider information.")); - ui.stackedWidget->setEnabled(false); - qWarning() << "Could not load providers."; - return; - } - setIdle(QString()); - ui.providerComboBox->addItems(providers); - ui.providerComboBox->setCurrentIndex(0); - atticaHelper->setCurrentProvider(providers.at(0)); - - QString user; - QString pass; - if (atticaHelper->loadCredentials(user, pass)) { - ui.username->setText(user); - ui.password->setText(pass); - } - _k_updatePage(); -} - -void UploadDialogPrivate::_k_providerChanged(const QString &providerName) -{ - atticaHelper->setCurrentProvider(providerName); - QString registerUrl = atticaHelper->provider().getRegisterAccountUrl(); - if (!registerUrl.isEmpty()) { - ui.registerNewAccountLabel->setText(QStringLiteral("") + i18n("Register a new account") + QStringLiteral("")); - } else { - ui.registerNewAccountLabel->setText(QString()); - } - ui.username->clear(); - ui.password->clear(); - QString user; - QString pass; - if (atticaHelper->loadCredentials(user, pass)) { - ui.username->setText(user); - ui.password->setText(pass); - } - _k_updatePage(); -} - -void UploadDialogPrivate::_k_backPage() -{ - _k_showPage(ui.stackedWidget->currentIndex() - 1); -} - -void UploadDialogPrivate::_k_nextPage() -{ - // TODO: validate credentials after user name/password have been entered - if (ui.stackedWidget->currentIndex() == UserPasswordPage) { - setBusy(i18n("Checking login...")); - nextButton->setEnabled(false); - ui.providerComboBox->setEnabled(false); - ui.username->setEnabled(false); - ui.password->setEnabled(false); - atticaHelper->checkLogin(ui.username->text(), ui.password->text()); - } else { - _k_showPage(ui.stackedWidget->currentIndex() + 1); - } -} - -void UploadDialogPrivate::_k_checkCredentialsFinished(bool success) -{ - ui.providerComboBox->setEnabled(true); - ui.username->setEnabled(true); - ui.password->setEnabled(true); - - if (success) { - atticaHelper->saveCredentials(ui.username->text(), ui.password->text()); - _k_showPage(FileNewUpdatePage); - - atticaHelper->loadCategories(categoryNames); - setBusy(i18n("Fetching your previously updated content...")); - } else { - // TODO check what the actual error is - setIdle(i18n("Could not verify login, please try again.")); - } -} - -void UploadDialogPrivate::_k_licensesLoaded(const Attica::License::List &licenses) -{ - ui.mLicenseCombo->clear(); - for (const Attica::License &license : licenses) { - ui.mLicenseCombo->addItem(license.name(), license.id()); - } -} - -void UploadDialogPrivate::_k_currencyLoaded(const QString ¤cy) -{ - ui.priceCurrency->setText(currency); -} - -void UploadDialogPrivate::_k_contentByCurrentUserLoaded(const Attica::Content::List &contentList) -{ - setIdle(i18n("Fetching your previously updated content finished.")); - - for (const Attica::Content &content : contentList) { - QListWidgetItem *contentItem = new QListWidgetItem(content.name()); - contentItem->setData(Qt::UserRole, content.id()); - ui.userContentList->addItem(contentItem); - } - - if (ui.userContentList->count() > 0) { - ui.userContentList->setCurrentRow(0); - ui.radioUpdate->setEnabled(true); - _k_updatePage(); - } -} - -void UploadDialogPrivate::_k_updatedContentFetched(const Attica::Content &content) -{ - setIdle(i18n("Fetching content data from server finished.")); - - contentId = content.id(); - // fill in ui - ui.mNameEdit->setText(content.name()); - ui.mSummaryEdit->setText(content.description()); - ui.mVersionEdit->setText(content.version()); - ui.changelog->setText(content.changelog()); - ui.priceCheckBox->setChecked(content.attribute(QStringLiteral("downloadbuy1")) == QLatin1Char('1')); - ui.priceSpinBox->setValue(content.attribute(QStringLiteral("downloadbuyprice1")).toDouble()); - ui.priceReasonLineEdit->setText(content.attribute(QStringLiteral("downloadbuyreason1"))); - - bool conversionOk = false; - int licenseNumber = content.license().toInt(&conversionOk); - if (conversionOk) { - // check if that int is in list - int row = ui.mLicenseCombo->findData(licenseNumber, Qt::UserRole); - ui.mLicenseCombo->setCurrentIndex(row); - } else { - ui.mLicenseCombo->setEditText(content.license()); - } - - ui.contentWebsiteLink->setText(QLatin1String("") - + i18nc("A link to the website where the get hot new stuff upload can be seen", "Visit website") + QLatin1String("")); - ui.fetchContentLinkImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); -} - -void UploadDialogPrivate::_k_previewLoaded(int index, const QImage &image) -{ - switch (index) { - case 1: - ui.previewImage1->setPixmap(QPixmap::fromImage(image)); - break; - case 2: - ui.previewImage2->setPixmap(QPixmap::fromImage(image)); - break; - case 3: - ui.previewImage3->setPixmap(QPixmap::fromImage(image)); - break; - } -} - -void UploadDialogPrivate::_k_updateContentsToggled(bool update) -{ - ui.userContentList->setEnabled(update); -} - UploadDialog::UploadDialog(QWidget *parent) : QDialog(parent) , d(new UploadDialogPrivate(this)) @@ -434,141 +72,47 @@ UploadDialog::~UploadDialog() bool UploadDialog::init(const QString &configfile) { - bool success = d->init(configfile); - - setWindowTitle(i18n("Share Hot New Stuff")); - - d->_k_updatePage(); - - connect(d->ui.username, SIGNAL(textChanged(QString)), this, SLOT(_k_updatePage())); - - connect(d->ui.password, SIGNAL(textChanged(QString)), this, SLOT(_k_updatePage())); - connect(d->ui.mNameEdit, SIGNAL(textChanged(QString)), this, SLOT(_k_updatePage())); - connect(d->ui.uploadFileRequester, SIGNAL(textChanged(QString)), this, SLOT(_k_updatePage())); - connect(d->ui.priceCheckBox, SIGNAL(toggled(bool)), this, SLOT(_k_priceToggled(bool))); - - connect(d->ui.uploadButton, SIGNAL(clicked()), this, SLOT(_k_startUpload())); - - connect(d->backButton, SIGNAL(clicked()), this, SLOT(_k_backPage())); - connect(d->nextButton, SIGNAL(clicked()), this, SLOT(_k_nextPage())); - connect(d->buttonBox, &QDialogButtonBox::accepted, this, &UploadDialog::accept); - connect(d->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - QString displayName = QGuiApplication::applicationDisplayName(); - if (displayName.isEmpty()) { - displayName = QCoreApplication::applicationName(); - } - d->ui.mTitleWidget->setText(i18nc("Program name followed by 'Add On Uploader'", "%1 Add-On Uploader", displayName)); - - if (success) { - d->_k_showPage(0); - } - - return success; -} - -void UploadDialog::setUploadFile(const QUrl &payloadFile) -{ - d->uploadFile = payloadFile; - - d->ui.uploadFileLabel->setVisible(false); - d->ui.uploadFileRequester->setVisible(false); - - QFile file(d->uploadFile.toLocalFile()); - if (!file.open(QIODevice::ReadOnly)) { - KMessageBox::error(this, i18n("File not found: %1", d->uploadFile.url()), i18n("Upload Failed")); - } + return d->init(configfile); } -void UploadDialog::setUploadName(const QString &name) +void UploadDialog::setUploadFile(const QUrl &) { - d->ui.mNameEdit->setText(name); } -void UploadDialog::selectCategory(const QString &category) +void UploadDialog::setUploadName(const QString &) { - d->ui.mCategoryCombo->setCurrentIndex(d->ui.mCategoryCombo->findText(category, Qt::MatchFixedString)); } -void UploadDialog::setChangelog(const QString &changelog) +void UploadDialog::selectCategory(const QString &) { - d->ui.changelog->setText(changelog); } -void UploadDialog::setDescription(const QString &description) +void UploadDialog::setChangelog(const QString &) { - d->ui.mSummaryEdit->setText(description); } -void UploadDialog::setPriceEnabled(bool enabled) +void UploadDialog::setDescription(const QString &) { - d->ui.priceCheckBox->setVisible(enabled); - d->ui.priceGroupBox->setVisible(enabled); } -void UploadDialog::setPrice(double price) +void UploadDialog::setPriceEnabled(bool) { - d->ui.priceCheckBox->setEnabled(true); - d->ui.priceSpinBox->setValue(price); } -void UploadDialog::setPriceReason(const QString &reason) +void UploadDialog::setPrice(double) { - d->ui.priceReasonLineEdit->setText(reason); } -void UploadDialog::setVersion(const QString &version) +void UploadDialog::setPriceReason(const QString &) { - d->ui.mVersionEdit->setText(version); } -void UploadDialog::setPreviewImageFile(uint number, const QUrl &file) +void UploadDialog::setVersion(const QString &) { - QPixmap preview(file.toLocalFile()); - switch (number) { - case 0: - d->previewFile1 = file; - d->ui.previewImage1->setPixmap(preview.scaled(d->ui.previewImage1->size())); - break; - case 1: - d->previewFile2 = file; - d->ui.previewImage2->setPixmap(preview.scaled(d->ui.previewImage2->size())); - break; - case 2: - d->previewFile3 = file; - d->ui.previewImage3->setPixmap(preview.scaled(d->ui.previewImage3->size())); - break; - default: - qCCritical(KNEWSTUFF) << "Wrong preview image file number"; - break; - } } -void UploadDialogPrivate::_k_priceToggled(bool priceEnabled) +void UploadDialog::setPreviewImageFile(uint, const QUrl &) { - ui.priceGroupBox->setEnabled(priceEnabled); -} - -void UploadDialogPrivate::_k_categoriesLoaded(const Attica::Category::List &loadedCategories) -{ - categories = loadedCategories; - - // at least one category is needed - if (categories.isEmpty()) { - KMessageBox::error(q, - i18np("The server does not recognize the category %2 to which you are trying to upload.", - "The server does not recognize any of the categories to which you are trying to upload: %2", - categoryNames.size(), - categoryNames.join(QLatin1String(", "))), - i18n("Error")); - // close the dialog - q->reject(); - return; - } - for (const Attica::Category &c : qAsConst(categories)) { - ui.mCategoryCombo->addItem(c.name(), c.id()); - } - atticaHelper->loadContentByCurrentUser(); } void UploadDialog::accept() @@ -576,273 +120,6 @@ void UploadDialog::accept() QDialog::accept(); } -void UploadDialogPrivate::_k_startUpload() -{ - // FIXME: this only works if categories are set in the .knsrc file - // TODO: ask for confirmation when closing the dialog - - backButton->setEnabled(false); - buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - - ui.uploadButton->setEnabled(false); - - // idle back and forth, we need a fix in attica to get at real progress values - ui.uploadProgressBar->setMinimum(0); - ui.uploadProgressBar->setMaximum(0); - ui.uploadProgressBar->setValue(0); - - // check the category - QString categoryName = ui.mCategoryCombo->currentText(); - QList::const_iterator iter = categories.constBegin(); - Attica::Category category; - QList::const_iterator iterEnd = categories.constEnd(); - while (iter != iterEnd) { - if (iter->name() == categoryName) { - category = *iter; - break; - } - ++iter; - } - if (!category.isValid()) { - KMessageBox::error(q, i18n("The selected category \"%1\" is invalid.", categoryName), i18n("Upload Failed")); - return; - } - - // fill in the content object - Attica::Content content; - content.setName(ui.mNameEdit->text()); - QString summary = ui.mSummaryEdit->toPlainText(); - content.addAttribute(QStringLiteral("description"), summary); - content.addAttribute(QStringLiteral("version"), ui.mVersionEdit->text()); - - // for the license, if one of the licenses coming from the server was used, pass its id, otherwise the string - QString licenseId = ui.mLicenseCombo->itemData(ui.mLicenseCombo->currentIndex()).toString(); - if (licenseId.isEmpty()) { - // use other as type and add the string as text - content.addAttribute(QStringLiteral("licensetype"), QStringLiteral("0")); - content.addAttribute(QStringLiteral("license"), ui.mLicenseCombo->currentText()); - } else { - content.addAttribute(QStringLiteral("licensetype"), licenseId); - } - - content.addAttribute(QStringLiteral("changelog"), ui.changelog->toPlainText()); - - // TODO: add additional attributes - // content.addAttribute("downloadlink1", ui.link1->text()); - // content.addAttribute("downloadlink2", ui.link2->text()); - // content.addAttribute("homepage1", ui.homepage->text()); - // content.addAttribute("blog1", ui.blog->text()); - - content.addAttribute(QStringLiteral("downloadbuy1"), ui.priceCheckBox->isChecked() ? QStringLiteral("1") : QStringLiteral("0")); - content.addAttribute(QStringLiteral("downloadbuyprice1"), QString::number(ui.priceSpinBox->value())); - content.addAttribute(QStringLiteral("downloadbuyreason1"), ui.priceReasonLineEdit->text()); - - if (ui.radioNewUpload->isChecked()) { - // upload a new content - Attica::ItemPostJob *job = currentProvider().addNewContent(category, content); - q->connect(job, SIGNAL(finished(Attica::BaseJob *)), q, SLOT(_k_contentAdded(Attica::BaseJob *))); - job->start(); - } else { - // update old content - Attica::ItemPostJob *job = - currentProvider().editContent(category, ui.userContentList->currentItem()->data(Qt::UserRole).toString(), content); - q->connect(job, SIGNAL(finished(Attica::BaseJob *)), q, SLOT(_k_contentAdded(Attica::BaseJob *))); - job->start(); - } -} - -void UploadDialogPrivate::_k_changePreview1() -{ - const QStringList filters = _supportedMimeTypes(); - QPointer dialog = new QFileDialog(q, i18n("Select preview image")); - dialog->setMimeTypeFilters(filters); - if (dialog->exec() == QDialog::Accepted) { - QUrl url = dialog->selectedUrls().first(); - previewFile1 = url; - qCDebug(KNEWSTUFF) << "preview is: " << url.url(); - QPixmap preview(url.toLocalFile()); - ui.previewImage1->setPixmap(preview.scaled(ui.previewImage1->size())); - } - delete dialog; -} - -void UploadDialogPrivate::_k_changePreview2() -{ - const QStringList filters = _supportedMimeTypes(); - QPointer dialog = new QFileDialog(q, i18n("Select preview image")); - dialog->setMimeTypeFilters(filters); - if (dialog->exec() == QDialog::Accepted) { - QUrl url = dialog->selectedUrls().first(); - previewFile2 = url; - QPixmap preview(url.toLocalFile()); - ui.previewImage2->setPixmap(preview.scaled(ui.previewImage1->size())); - } - delete dialog; -} - -void UploadDialogPrivate::_k_changePreview3() -{ - const QStringList filters = _supportedMimeTypes(); - QPointer dialog = new QFileDialog(q, i18n("Select preview image")); - dialog->setMimeTypeFilters(filters); - if (dialog->exec() == QDialog::Accepted) { - QUrl url = dialog->selectedUrls().first(); - previewFile3 = url; - QPixmap preview(url.toLocalFile()); - ui.previewImage3->setPixmap(preview.scaled(ui.previewImage1->size())); - } - delete dialog; -} - -void UploadDialogPrivate::_k_contentAdded(Attica::BaseJob *baseJob) -{ - if (baseJob->metadata().error()) { - if (baseJob->metadata().error() == Attica::Metadata::NetworkError) { - KMessageBox::error(q, i18n("There was a network error."), i18n("Uploading Failed")); - return; - } - if (baseJob->metadata().error() == Attica::Metadata::OcsError) { - if (baseJob->metadata().statusCode() == 102) { - KMessageBox::error(q, i18n("Authentication error."), i18n("Uploading Failed")); - } - } - return; - } - - ui.createContentImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); - - Attica::ItemPostJob *job = static_cast *>(baseJob); - if (job->metadata().error() != Attica::Metadata::NoError) { - KMessageBox::error(q, i18n("Upload failed: %1", job->metadata().message())); - return; - } - - // only when adding new content we get an id returned, otherwise stick with the old one - QString id = job->result().id(); - if (!id.isEmpty()) { - contentId = id; - } - - if (!uploadFile.isEmpty()) { - doUpload(QString(), uploadFile); - } else { - doUpload(QString(), ui.uploadFileRequester->url()); - } - - // FIXME: status labels need to accommodate 3 previews - if (!previewFile1.isEmpty()) { - doUpload(QStringLiteral("1"), previewFile1); - } - if (!previewFile2.isEmpty()) { - doUpload(QStringLiteral("2"), previewFile2); - } - if (!previewFile3.isEmpty()) { - doUpload(QStringLiteral("3"), previewFile3); - } - - if (ui.radioNewUpload->isChecked()) { - atticaHelper->loadDetailsLink(contentId); - } -} - -void UploadDialogPrivate::_k_openRegisterAccountWebpage(QString) -{ - KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromUserInput(atticaHelper->provider().getRegisterAccountUrl()), QStringLiteral("text/html")); - job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, q)); - job->start(); -} - -void UploadDialogPrivate::doUpload(const QString &index, const QUrl &path) -{ - QFile file(path.toLocalFile()); - if (!file.open(QIODevice::ReadOnly)) { - KMessageBox::error(q, i18n("File not found: %1", uploadFile.url()), i18n("Upload Failed")); - q->reject(); - return; - } - - QByteArray fileContents; - fileContents.append(file.readAll()); - file.close(); - - QString fileName = QFileInfo(path.toLocalFile()).fileName(); - - Attica::PostJob *job = nullptr; - if (index.isEmpty()) { - job = currentProvider().setDownloadFile(contentId, fileName, fileContents); - q->connect(job, SIGNAL(finished(Attica::BaseJob *)), q, SLOT(_k_fileUploadFinished(Attica::BaseJob *))); - } else if (index == QLatin1Char('1')) { - job = currentProvider().setPreviewImage(contentId, index, fileName, fileContents); - q->connect(job, SIGNAL(finished(Attica::BaseJob *)), q, SLOT(_k_preview1UploadFinished(Attica::BaseJob *))); - } else if (index == QLatin1Char('2')) { - job = currentProvider().setPreviewImage(contentId, index, fileName, fileContents); - q->connect(job, SIGNAL(finished(Attica::BaseJob *)), q, SLOT(_k_preview2UploadFinished(Attica::BaseJob *))); - } else if (index == QLatin1Char('3')) { - job = currentProvider().setPreviewImage(contentId, index, fileName, fileContents); - q->connect(job, SIGNAL(finished(Attica::BaseJob *)), q, SLOT(_k_preview3UploadFinished(Attica::BaseJob *))); - } - if (job) { - job->start(); - } -} - -void UploadDialogPrivate::_k_fileUploadFinished(Attica::BaseJob *) -{ - ui.uploadContentImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); - finishedContents = true; - uploadFileFinished(); -} - -void UploadDialogPrivate::_k_preview1UploadFinished(Attica::BaseJob *) -{ - ui.uploadPreview1ImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); - finishedPreview1 = true; - uploadFileFinished(); -} - -void UploadDialogPrivate::_k_preview2UploadFinished(Attica::BaseJob *) -{ - ui.uploadPreview2ImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); - finishedPreview2 = true; - uploadFileFinished(); -} - -void UploadDialogPrivate::_k_preview3UploadFinished(Attica::BaseJob *) -{ - ui.uploadPreview3ImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); - finishedPreview3 = true; - uploadFileFinished(); -} - -void UploadDialogPrivate::uploadFileFinished() -{ - // FIXME multiple previews - if (finishedContents && (previewFile1.isEmpty() || finishedPreview1) && (previewFile2.isEmpty() || finishedPreview2) - && (previewFile3.isEmpty() || finishedPreview3)) { - finished = true; - ui.uploadProgressBar->setMinimum(0); - ui.uploadProgressBar->setMaximum(100); - ui.uploadProgressBar->setValue(100); - _k_updatePage(); - } -} - -void UploadDialogPrivate::_k_detailsLinkLoaded(const QUrl &url) -{ - ui.contentWebsiteLink->setText(QLatin1String("") - + i18nc("A link to the website where the get hot new stuff upload can be seen", "Visit website") + QLatin1String("")); - ui.fetchContentLinkImageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok")).pixmap(16)); -} - -QStringList UploadDialogPrivate::_supportedMimeTypes() const -{ - QStringList mimeTypes; - const QList supported = QImageReader::supportedMimeTypes(); - mimeTypes.reserve(supported.count()); - for (const QByteArray &mimeType : supported) { - mimeTypes.append(QString::fromLatin1(mimeType)); - } - return mimeTypes; -} +#endif #include "moc_uploaddialog.cpp" diff --git a/src/uploaddialog.h b/src/uploaddialog.h index 94d475b298e962248331c2e5ab43e6410a40378c..95415ac6b7598fdf7403f7531f7c72cb309f85d3 100644 --- a/src/uploaddialog.h +++ b/src/uploaddialog.h @@ -4,17 +4,20 @@ SPDX-FileCopyrightText: 2007 Josef Spillner SPDX-FileCopyrightText: 2009 Jeremy Whiting SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn + SPDX-FileCopyrightText: 2021 Dan Leinir Turthra Jensen SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KNEWSTUFF3_UI_UPLOADDIALOG_H #define KNEWSTUFF3_UI_UPLOADDIALOG_H +#include "knewstuff_export.h" + +#if KNEWSTUFF_ENABLE_DEPRECATED_SINCE(5, 80) + #include #include -#include "knewstuff_export.h" - namespace Attica { class BaseJob; @@ -28,12 +31,22 @@ class UploadDialogPrivate; /** * @short KNewStuff file upload dialog. * - * Using this dialog, data can easily be uploaded to the Hotstuff servers. + * This dialog shows the user how to add new content to the remote service represented + * by the configuration file passed into it. + * + * @note This dialog originally allowed for performing direct uploads to an OCS service, + * however there is no such service available at this time, and it is unlikely we will + * have one any time soon (as we have an issue where such functionality is essentially + * just a vector for directly visible spam). As such, we have decided to let this dialog + * instead reflect reality, and just give information on how to manually perform those + * uploads through a remote service's web based upload system. * * \par Maintainer: - * Jeremy Whiting (jpwhiting@kde.org) + * Dan Leinir Turthra Jensen (admin@leinir.dk) * * @since 4.4 + * @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation. Use KNS3::QtQuickDialogWrapper which + * includes automatic integration for NewStuff.UploadPage */ class KNEWSTUFF_EXPORT UploadDialog : public QDialog { @@ -43,27 +56,35 @@ public: Create a new upload dialog. @param parent the parent window + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation. Use KNS3::QtQuickDialogWrapper which + includes automatic integration for NewStuff.UploadPage. If the OCS backend supports upload, you can use KNSCore::AtticaHelper to do so, or implement it + manually. */ + KNEWSTUFF_DEPRECATED_VERSION(5, 85, "See API documentation") explicit UploadDialog(QWidget *parent = nullptr); /** Create a new upload dialog. @param parent the parent window + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation. Use KNS3::QtQuickDialogWrapper which + includes automatic integration for NewStuff.UploadPage. If the OCS backend supports upload, you can use KNSCore::AtticaHelper to " + "do so, or implement it manually. */ + KNEWSTUFF_DEPRECATED_VERSION(5, 85, "See API documentation") explicit UploadDialog(const QString &configFile, QWidget *parent = nullptr); /** Destructor. */ ~UploadDialog() override; - /** Set the file to be uploaded. This has to be set for the dialog to work, before displaying the dialog. @param payloadFile the payload data file - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setUploadFile(const QUrl &payloadFile); /** @@ -72,28 +93,32 @@ public: The name field will be left empty if no title was set. @param name the suggested name for the upload - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setUploadName(const QString &name); /** Set the suggested version displayed in the upload dialog. The user can still change this. @param version - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setVersion(const QString &version); /** Set the suggested description displayed in the upload dialog. The user can still change this. @param description - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setDescription(const QString &description); /** Set the suggested changelog displayed in the upload dialog. The user can still change this. @param version version - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setChangelog(const QString &changelog); /** @@ -102,13 +127,15 @@ public: @param number The number of the preview image to set, either 1, 2, or 3. @param file A URL to the file to be used as preview image @since 4.6 - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setPreviewImageFile(uint number, const QUrl &file); /** Enable the UI to let the user to set a price for the uploaded item. @param enabled enable the price option - it is enabled by default @since 4.5 + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation */ void setPriceEnabled(bool enabled); @@ -116,14 +143,16 @@ public: Set the suggested price displayed in the upload dialog. The user can still change this. @param version version - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setPrice(double price); /** Set the suggested rationale why this item costs something to download. The user can still change this. @param version version - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void setPriceReason(const QString &reason); /** @@ -132,7 +161,8 @@ public: It does not add any new category to the list of available categories. @param category the suggested category for the upload - */ + @deprecated Since 5.85, Upload functionality is no longer directly supported and needs complete reimplementation + */ void selectCategory(const QString &category); public Q_SLOTS: @@ -143,40 +173,11 @@ private: UploadDialogPrivate *const d; - Q_PRIVATE_SLOT(d, void _k_nextPage()) - Q_PRIVATE_SLOT(d, void _k_backPage()) - Q_PRIVATE_SLOT(d, void _k_updatePage()) - - Q_PRIVATE_SLOT(d, void _k_providerChanged(QString)) - Q_PRIVATE_SLOT(d, void _k_checkCredentialsFinished(bool)) - Q_PRIVATE_SLOT(d, void _k_contentByCurrentUserLoaded(Attica::Content::List)) - Q_PRIVATE_SLOT(d, void _k_providersLoaded(QStringList)) - Q_PRIVATE_SLOT(d, void _k_categoriesLoaded(Attica::Category::List)) - Q_PRIVATE_SLOT(d, void _k_licensesLoaded(Attica::License::List)) - Q_PRIVATE_SLOT(d, void _k_currencyLoaded(QString)) - Q_PRIVATE_SLOT(d, void _k_previewLoaded(int, QImage)) - - Q_PRIVATE_SLOT(d, void _k_changePreview1()) - Q_PRIVATE_SLOT(d, void _k_changePreview2()) - Q_PRIVATE_SLOT(d, void _k_changePreview3()) - Q_PRIVATE_SLOT(d, void _k_priceToggled(bool)) - Q_PRIVATE_SLOT(d, void _k_updateContentsToggled(bool update)) - - Q_PRIVATE_SLOT(d, void _k_startUpload()) - Q_PRIVATE_SLOT(d, void _k_contentAdded(Attica::BaseJob *)) - Q_PRIVATE_SLOT(d, void _k_fileUploadFinished(Attica::BaseJob *)) - Q_PRIVATE_SLOT(d, void _k_preview1UploadFinished(Attica::BaseJob *)) - Q_PRIVATE_SLOT(d, void _k_preview2UploadFinished(Attica::BaseJob *)) - Q_PRIVATE_SLOT(d, void _k_preview3UploadFinished(Attica::BaseJob *)) - - Q_PRIVATE_SLOT(d, void _k_updatedContentFetched(Attica::Content)) - Q_PRIVATE_SLOT(d, void _k_detailsLinkLoaded(QUrl)) - - Q_PRIVATE_SLOT(d, void _k_openRegisterAccountWebpage(QString)) - Q_DISABLE_COPY(UploadDialog) }; } #endif + +#endif diff --git a/src/uploaddialog.qml b/src/uploaddialog.qml new file mode 100644 index 0000000000000000000000000000000000000000..c201a42c8524bff099d40917fb6228fc84e3f789 --- /dev/null +++ b/src/uploaddialog.qml @@ -0,0 +1,34 @@ +/* + SPDX-FileCopyrightText: 2021 Dan Leinir Turthra Jensen + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +import QtQuick 2.7 +import QtQuick.Layouts 1.11 as QtLayouts +import QtQuick.Window 2.15 +import org.kde.kirigami 2.14 as Kirigami +import org.kde.newstuff 1.85 as NewStuff + +Kirigami.ApplicationItem { + signal closed() + anchors.fill: parent + implicitWidth: Math.min(Kirigami.Units.gridUnit * 44, Screen.width) + implicitHeight: Math.min(Kirigami.Units.gridUnit * 30, Screen.height) + pageStack.defaultColumnWidth: pageStack.width + pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.Auto + pageStack.globalToolBar.canContainHandles: true + pageStack.initialPage: NewStuff.UploadPage { + engine: NewStuff.Engine { + configFile: knsrcfile + } + onVisibleChanged: { + if (!visible) { + applicationWindow.closed(); + } + } + } + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + } +} diff --git a/src/uploaddialog.qrc b/src/uploaddialog.qrc new file mode 100644 index 0000000000000000000000000000000000000000..57b6444d584b30dae7de7868207f778dd432fe36 --- /dev/null +++ b/src/uploaddialog.qrc @@ -0,0 +1,5 @@ + + + uploaddialog.qml + + diff --git a/src/uploaddialog.ui b/src/uploaddialog.ui deleted file mode 100644 index 6932968d3eee1ee4946cd026d45453ee0557d4f1..0000000000000000000000000000000000000000 --- a/src/uploaddialog.ui +++ /dev/null @@ -1,831 +0,0 @@ - - - UploadDialog - - - - 0 - 0 - 538 - 395 - - - - Share Hot New Stuff - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - - - - - - - 4 - - - - - - - Qt::Vertical - - - - 205 - 183 - - - - - - - - QLineEdit::Password - - - - - - - - 0 - 0 - - - - Password: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Username: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - 0 - 0 - - - - Provider: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - - - - - File to upload: - - - - - - - - - - New Upload - - - true - - - - - - - false - - - Update - - - - - - - false - - - - - - - - - - - - - - true - - - - GPL - - - - - LGPL - - - - - BSD - - - - - - - - Description: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - mSummaryEdit - - - - - - - true - - - Changelog: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Version: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - mVersionEdit - - - - - - - License: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - mLicenseCombo - - - - - - - Please fill out the information about your upload in English. - - - - - - - - - - - - - - - - Title: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - mNameEdit - - - - - - - - - - - - - - - - Category: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - mNameEdit - - - - - - - Name of the file as it will appear on the website - - - This should clearly describe the file content. It can be the same text as the title of the kvtml file. - - - - - - - - - - - - - - Preview Images - - - false - - - - - - Select Preview... - - - - - - - Select Preview... - - - - - - - Select Preview... - - - - - - - - 96 - 72 - - - - - 96 - 82 - - - - - - - Qt::AlignCenter - - - - - - - - 96 - 72 - - - - - 96 - 82 - - - - - - - Qt::AlignCenter - - - - - - - - 96 - 72 - - - - - 96 - 82 - - - - - - - Qt::AlignCenter - - - - - - - - - - Set a price for this item - - - - - - - false - - - Price - - - false - - - - - - Price: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 0.010000000000000 - - - 1.000000000000000 - - - - - - - - - - - - - - Reason for price: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - 22 - 22 - - - - - 22 - 22 - - - - - - - - - - - - 22 - 22 - - - - - 22 - 22 - - - - - - - - - - - - 22 - 22 - - - - - 22 - 22 - - - - - - - - - - - - 22 - 22 - - - - - 22 - 22 - - - - - 22 - 16777215 - - - - - - - - - - - Fetch content link from server - - - - - - - - 0 - 0 - - - - Create content on server - - - - - - - - 0 - 0 - - - - Upload content - - - - - - - - 0 - 0 - - - - Upload first preview - - - - - - - - - - true - - - - - - - Qt::Vertical - - - - 91 - 199 - - - - - - - - Note: You can edit, update and delete your content on the website. - - - true - - - - - - - - 0 - 0 - - - - Upload second preview - - - - - - - - 0 - 0 - - - - Upload third preview - - - - - - - - 22 - 22 - - - - - 22 - 22 - - - - - - - - - - - - 22 - 22 - - - - - 22 - 22 - - - - - - - - - - - I ensure that this content does not violate any existing copyright, law or trademark. I agree for my IP address to be logged. (Distributing content without the permission of the copyright holder is illegal.) - - - true - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Start Upload - - - - - - - - - 1 - - - -1 - - - - - - - - - - - - - - KUrlRequester - QWidget -
kurlrequester.h
-
- - KTitleWidget - QWidget -
ktitlewidget.h
-
- - KComboBox - QComboBox -
kcombobox.h
-
- - KTextEdit - QTextEdit -
ktextedit.h
-
-
- - providerComboBox - username - password - uploadFileRequester - radioNewUpload - radioUpdate - userContentList - mNameEdit - mCategoryCombo - mVersionEdit - mLicenseCombo - mSummaryEdit - changelog - priceCheckBox - priceSpinBox - priceReasonLineEdit - uploadButton - - - -
diff --git a/src/uploaddialog_p.h b/src/uploaddialog_p.h index 10879f82aceff8a5c6515619173243650672b1b1..6283c00f7b6a2f0a50172c839cf78af112a7eca1 100644 --- a/src/uploaddialog_p.h +++ b/src/uploaddialog_p.h @@ -8,7 +8,6 @@ #define KNEWSTUFF3_UI_UPLOADDIALOG_P_H #include "ui_uploaddialog.h" -#include "upload/atticahelper_p.h" #include #include @@ -20,10 +19,6 @@ #include -class QDialogButtonBox; -class QPushButton; -class KPixmapSequenceWidget; - namespace KNS3 { class UploadDialogPrivate @@ -31,102 +26,12 @@ class UploadDialogPrivate public: UploadDialogPrivate(UploadDialog *q) : q(q) - , currentPage(UserPasswordPage) - , finished(false) - , finishedPreview1(false) - , finishedPreview2(false) - , finishedPreview3(false) - , finishedContents(false) { } UploadDialog *q; - enum WizardPage { - UserPasswordPage, - FileNewUpdatePage, - Details1Page, - Details2Page, - UploadFinalPage, - }; - WizardPage currentPage; - - Attica::Provider currentProvider() - { - return atticaHelper->provider(); - } - - Ui::UploadDialog ui; - QDialogButtonBox *buttonBox; - QPushButton *finishButton; - QPushButton *nextButton; - QPushButton *backButton; - KPixmapSequenceWidget *busyWidget; - - KNSCore::AtticaHelper *atticaHelper; - - QUrl uploadFile; - QUrl previewFile1; - QUrl previewFile2; - QUrl previewFile3; - QStringList categoryNames; - Attica::Category::List categories; - QString contentId; - bool finished; - bool finishedPreview1; - bool finishedPreview2; - bool finishedPreview3; - bool finishedContents; - bool init(const QString &configfile); - void setBusy(const QString &message); - void setIdle(const QString &message); - - // change to page, set the focus also calls updatePage() - void _k_showPage(int page); - - // check after user input - for example enable the next button - void _k_updatePage(); - - // next wizard page (next button clicked) - void _k_nextPage(); - // go back one page - void _k_backPage(); - - // after all has been done and said, do the uploading - void _k_startUpload(); - - void _k_providersLoaded(const QStringList &providerNames); - void _k_providerChanged(const QString &providerName); - - // validation of login is done, go to next page if successful, otherwise ask again - void _k_checkCredentialsFinished(bool); - void _k_categoriesLoaded(const Attica::Category::List &loadedCategories); - void _k_licensesLoaded(const Attica::License::List &licenses); - void _k_currencyLoaded(const QString ¤cy); - void _k_contentByCurrentUserLoaded(const Attica::Content::List &contentList); - void _k_updatedContentFetched(const Attica::Content &content); - void _k_previewLoaded(int index, const QImage &image); - - void _k_changePreview1(); - void _k_changePreview2(); - void _k_changePreview3(); - void _k_preview1UploadFinished(Attica::BaseJob *); - void _k_preview2UploadFinished(Attica::BaseJob *); - void _k_preview3UploadFinished(Attica::BaseJob *); - - void _k_contentAdded(Attica::BaseJob *); - void _k_fileUploadFinished(Attica::BaseJob *); - - void uploadFileFinished(); - void doUpload(const QString &index, const QUrl &filePath); - - void _k_priceToggled(bool); - void _k_updateContentsToggled(bool update); - void _k_detailsLinkLoaded(const QUrl &url); - void _k_openRegisterAccountWebpage(QString); - - QStringList _supportedMimeTypes() const; }; } diff --git a/tests/khotnewstuff_test.knsrc.in b/tests/khotnewstuff_test.knsrc.in index e4acbf78b43433bed898d1af5daa32be8c192509..a5afa5987a9714c008bf883ee553d8a29de224c2 100644 --- a/tests/khotnewstuff_test.knsrc.in +++ b/tests/khotnewstuff_test.knsrc.in @@ -1,9 +1,6 @@ [KNewStuff2] ProvidersUrl=file://@CMAKE_CURRENT_SOURCE_DIR@/testdata/provider.xml LocalRegistryDir=/tmp/knewstuff2.metafiles - TargetDir=knewstuff2_test -# For more *.knsrc configuration file options, see doc/tutorial.txt. -# For a list of providers, see kstuff/hotstuff/providers in KStuff SVN. - +# For more *.knsrc configuration file options, see README.md diff --git a/tests/khotnewstuff_upload.cpp b/tests/khotnewstuff_upload.cpp index ebda11987485ba6f0f886c935430c4bab531d165..2aba649db47fcea8747b74c36b6b86018f36edd7 100644 --- a/tests/khotnewstuff_upload.cpp +++ b/tests/khotnewstuff_upload.cpp @@ -15,27 +15,24 @@ #include -#include +#include "uploaddialog.h" int main(int argc, char **argv) { + QApplication i(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("khotnewstuff_upload")); QCoreApplication::setApplicationVersion(QStringLiteral("0.4")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); QApplication::setApplicationDisplayName(i18n("KHotNewStuff")); - QApplication i(argc, argv); - if (i.arguments().count() > 1) { QString configfile = QLatin1String(argv[1]); QPointer dialog = new KNS3::UploadDialog(configfile); - if (i.arguments().count() > 2) { - dialog->setUploadFile(QUrl(QLatin1String(argv[2]))); - } dialog->exec(); delete dialog; } else { - std::cout << "Enter the knsrc file to use followed by a filename to upload\n"; + std::cout << "Enter the knsrc file to use\n"; return -1; } return 0; diff --git a/tests/testdata/entry.xml b/tests/testdata/entry.xml index e4b0af8b7f279d8b521734c9e4b8a3c1183068e1..a1023b56784dc590d81e3a5dafb6015b51c74956 100644 --- a/tests/testdata/entry.xml +++ b/tests/testdata/entry.xml @@ -1,6 +1,7 @@ + 1 Entry 1 (ghns excluded) Anonymous Guy GPL @@ -9,11 +10,12 @@ This is what it is all about. http://some.http.server/preview.png http://some.http.server/coolstuff.tar.gz - 10 + 50 0 ghns_excluded=1 + 2 Entry 2 (ghns included) Anonymous Guy GPL @@ -22,11 +24,12 @@ A short description in English (not ghns excluded). http://some.http.server/preview.png http://some.http.server/coolstuff.tar.gz - 10 + 50 0 + 3 Entry 3 (ghns excluded) Anonymous Guy GPL @@ -35,11 +38,12 @@ This is what it is all about. http://some.http.server/preview.png http://some.http.server/coolstuff.tar.gz - 10 + 100 0 ghns_excluded=1 + 4 Entry 4 (ghns included) Anonymous Guy GPL @@ -48,7 +52,7 @@ A short description in English (not ghns excluded). http://some.http.server/preview.png http://some.http.server/coolstuff.tar.gz - 10 + 100 0 diff --git a/tests/testdata/entry2.xml b/tests/testdata/entry2.xml new file mode 100644 index 0000000000000000000000000000000000000000..0921b517281bfe2bed3ca5be492cd8a6aaeaa2bc --- /dev/null +++ b/tests/testdata/entry2.xml @@ -0,0 +1,59 @@ + + + + 1 + Alternative Entry 1 (ghns excluded) + Anonymous Guy + GPL + 1.0 + 2005-06-17 + This is what it is all about. + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 100 + 0 + ghns_excluded=1 + + + 2 + Alternative Entry 2 (ghns included) + Anonymous Guy + GPL + 2.1git2 + 2018-06-05 + A short description in English (not ghns excluded). + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 70 + 0 + + + + 3 + Alternative Entry 3 (ghns excluded) + Anonymous Guy + GPL + 1.0 + 2005-06-17 + This is what it is all about. + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 50 + 0 + ghns_excluded=1 + + + 4 + Alternative Entry 4 (ghns included) + Anonymous Guy + GPL + 2.1git2 + 2018-06-05 + A short description in English (not ghns excluded). + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 10 + 0 + + + diff --git a/tests/testdata/testconfig.knsrc b/tests/testdata/testconfig.knsrc new file mode 100644 index 0000000000000000000000000000000000000000..021459c1e2d80674cb1fedcae7f891429ac055ea --- /dev/null +++ b/tests/testdata/testconfig.knsrc @@ -0,0 +1,5 @@ +[KNewStuff3] +Name=Test Local Multi-providers +UseLocalProvidersFile=true +Categories=app/media +TargetDir=testdirectory diff --git a/tests/testdata/testconfig.providers b/tests/testdata/testconfig.providers new file mode 100644 index 0000000000000000000000000000000000000000..110df97099437d25c665f715f13333d2d01889c9 --- /dev/null +++ b/tests/testdata/testconfig.providers @@ -0,0 +1,25 @@ + + + + Some cool stuff + Viele neue Dinge + + + Another Place With Coolness + Ein Andere Stelle Mit Coolness + +