diff --git a/discover/DiscoverObject.cpp b/discover/DiscoverObject.cpp index 8e20307d..6cce8b7b 100644 --- a/discover/DiscoverObject.cpp +++ b/discover/DiscoverObject.cpp @@ -1,524 +1,529 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DiscoverObject.h" #include "PaginateModel.h" #include "UnityLauncher.h" #include "FeaturedModel.h" #include "CachedNetworkAccessManager.h" #include "DiscoverDeclarativePlugin.h" #include "DiscoverBackendsFactory.h" // Qt includes #include #include "discover_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include // #include // DiscoverCommon includes #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_FEEDBACK #include "plasmauserfeedback.h" #endif #include "discoversettings.h" class CachedNetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory { virtual QNetworkAccessManager * create(QObject *parent) override { return new CachedNetworkAccessManager(QStringLiteral("images"), parent); } }; class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) public: void classBegin() override {} void componentComplete() override { if (dynamicSortFilter()) sort(0); } }; DiscoverObject::DiscoverObject(CompactMode mode, const QVariantMap &initialProperties) : QObject() , m_engine(new QQmlApplicationEngine) , m_mode(mode) , m_networkAccessManagerFactory(new CachedNetworkAccessManagerFactory) { setObjectName(QStringLiteral("DiscoverMain")); m_engine->rootContext()->setContextObject(new KLocalizedContext(m_engine)); auto factory = m_engine->networkAccessManagerFactory(); m_engine->setNetworkAccessManagerFactory(nullptr); delete factory; m_engine->setNetworkAccessManagerFactory(m_networkAccessManagerFactory.data()); qmlRegisterType("org.kde.discover.app", 1, 0, "UnityLauncher"); qmlRegisterType("org.kde.discover.app", 1, 0, "PaginateModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "KConcatenateRowsProxyModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "FeaturedModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "QSortFilterProxyModel"); #ifdef WITH_FEEDBACK qmlRegisterSingletonType("org.kde.discover.app", 1, 0, "UserFeedbackSettings", [](QQmlEngine*, QJSEngine*) -> QObject* { return new PlasmaUserFeedback(KSharedConfig::openConfig(QStringLiteral("PlasmaUserFeedback"), KConfig::NoGlobals)); }); #endif qmlRegisterSingletonType("org.kde.discover.app", 1, 0, "DiscoverSettings", [](QQmlEngine*, QJSEngine*) -> QObject* { auto r = new DiscoverSettings; connect(r, &DiscoverSettings::installedPageSortingChanged, r, &DiscoverSettings::save); connect(r, &DiscoverSettings::appsListPageSortingChanged, r, &DiscoverSettings::save); return r; }); #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); #else qmlRegisterAnonymousType("org.kde.discover.app", 1); qmlRegisterAnonymousType("org.kde.discover.app", 1); qmlRegisterAnonymousType("org.kde.discover.app", 1); qmlRegisterAnonymousType("org.kde.discover.app", 1); qmlRegisterAnonymousType("org.kde.discover.app", 1); qmlRegisterAnonymousType("org.kde.discover.app", 1); #endif qmlRegisterUncreatableType("org.kde.discover.app", 1, 0, "DiscoverMainWindow", QStringLiteral("don't do that")); setupActions(); auto uri = "org.kde.discover"; DiscoverDeclarativePlugin* plugin = new DiscoverDeclarativePlugin; plugin->setParent(this); plugin->initializeEngine(m_engine, uri); plugin->registerTypes(uri); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) m_engine->setInitialProperties(initialProperties); #endif m_engine->rootContext()->setContextProperty(QStringLiteral("app"), this); m_engine->rootContext()->setContextProperty(QStringLiteral("discoverAboutData"), QVariant::fromValue(KAboutData::applicationData())); connect(m_engine, &QQmlApplicationEngine::objectCreated, this, &DiscoverObject::integrateObject); m_engine->load(QUrl(QStringLiteral("qrc:/qml/DiscoverWindow.qml"))); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this](){ const auto objs = m_engine->rootObjects(); for(auto o: objs) delete o; }); auto action = new OneTimeAction( [this]() { bool found = DiscoverBackendsFactory::hasRequestedBackends(); const auto backends = ResourcesModel::global()->backends(); for (auto b : backends) found |= b->hasApplications(); if (!found) Q_EMIT openErrorPage(i18n("No application back-ends found, please report to your distribution.")); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::allInitialized, action, &OneTimeAction::trigger); } else { action->trigger(); } } DiscoverObject::~DiscoverObject() { delete m_engine; } bool DiscoverObject::isRoot() { return ::getuid() == 0; } QStringList DiscoverObject::modes() const { QStringList ret; QObject* obj = rootObject(); for(int i = obj->metaObject()->propertyOffset(); imetaObject()->propertyCount(); i++) { QMetaProperty p = obj->metaObject()->property(i); QByteArray compName = p.name(); if(compName.startsWith("top") && compName.endsWith("Comp")) { ret += QString::fromLatin1(compName.mid(3, compName.length()-7)); } } return ret; } void DiscoverObject::openMode(const QString& _mode) { QObject* obj = rootObject(); if (!obj) { qCWarning(DISCOVER_LOG) << "could not get the main object"; return; } if(!modes().contains(_mode, Qt::CaseInsensitive)) qCWarning(DISCOVER_LOG) << "unknown mode" << _mode << modes(); QString mode = _mode; mode[0] = mode[0].toUpper(); const QByteArray propertyName = "top"+mode.toLatin1()+"Comp"; const QVariant modeComp = obj->property(propertyName.constData()); if (!modeComp.isValid()) qCWarning(DISCOVER_LOG) << "couldn't open mode" << _mode; else obj->setProperty("currentTopLevel", modeComp); } void DiscoverObject::openMimeType(const QString& mime) { emit listMimeInternal(mime); } void DiscoverObject::openCategory(const QString& category) { setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, category]() { Category* cat = CategoryModel::global()->findCategoryByName(category); if (cat) { emit listCategoryInternal(cat); } else { showPassiveNotification(i18n("Could not find category '%1'", category)); setRootObjectProperty("defaultStartup", true); } } , this); if (CategoryModel::global()->rootCategories().isEmpty()) { connect(CategoryModel::global(), &CategoryModel::rootCategoriesChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::openLocalPackage(const QUrl& localfile) { if (!QFile::exists(localfile.toLocalFile())) { // showPassiveNotification(i18n("Trying to open unexisting file '%1'", localfile.toString())); qCWarning(DISCOVER_LOG) << "Trying to open unexisting file" << localfile; return; } setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, localfile]() { AbstractResourcesBackend::Filters f; f.resourceUrl = localfile; auto stream = new StoredResultsStream({ResourcesModel::global()->search(f)}); connect(stream, &StoredResultsStream::finishedResources, this, [this, localfile](const QVector &res) { if (res.count() == 1) { emit openApplicationInternal(res.first()); } else { QMimeDatabase db; auto mime = db.mimeTypeForUrl(localfile); auto fIsFlatpakBackend = [](AbstractResourcesBackend* backend) { return backend->metaObject()->className() == QByteArray("FlatpakBackend"); }; if (mime.name().startsWith(QLatin1String("application/vnd.flatpak")) && !kContains(ResourcesModel::global()->backends(), fIsFlatpakBackend)) { openApplication(QUrl(QStringLiteral("appstream://org.kde.discover.flatpak"))); showPassiveNotification(i18n("Cannot interact with flatpak resources without the flatpak backend %1. Please install it first.", localfile.toDisplayString())); } else { setRootObjectProperty("defaultStartup", true); showPassiveNotification(i18n("Couldn't open %1", localfile.toDisplayString())); } } }); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::openApplication(const QUrl& url) { Q_ASSERT(!url.isEmpty()); setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, url]() { AbstractResourcesBackend::Filters f; f.resourceUrl = url; auto stream = new StoredResultsStream({ResourcesModel::global()->search(f)}); connect(stream, &StoredResultsStream::finishedResources, this, [this, url](const QVector &res) { if (res.count() >= 1) { emit openApplicationInternal(res.first()); } else { setRootObjectProperty("defaultStartup", true); Q_EMIT openErrorPage(i18n("Couldn't open %1", url.toDisplayString())); } }); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::integrateObject(QObject* object) { if (!object) { qCWarning(DISCOVER_LOG) << "Errors when loading the GUI"; QTimer::singleShot(0, QCoreApplication::instance(), [](){ QCoreApplication::instance()->exit(1); }); return; } Q_ASSERT(object == rootObject()); KConfigGroup window(KSharedConfig::openConfig(), "Window"); if (window.hasKey("geometry")) rootObject()->setGeometry(window.readEntry("geometry", QRect())); if (window.hasKey("visibility")) { QWindow::Visibility visibility(QWindow::Visibility(window.readEntry("visibility", QWindow::Windowed))); rootObject()->setVisibility(qMax(visibility, QQuickView::AutomaticVisibility)); } object->installEventFilter(this); connect(object, &QObject::destroyed, qGuiApp, &QCoreApplication::quit); object->setParent(m_engine); connect(qGuiApp, &QGuiApplication::commitDataRequest, this, [this](QSessionManager &sessionManager) { if (ResourcesModel::global()->isBusy()) { Q_EMIT preventedClose(); sessionManager.cancel(); } }); } bool DiscoverObject::eventFilter(QObject * object, QEvent * event) { if (object!=rootObject()) return false; if (event->type() == QEvent::Close) { if (ResourcesModel::global()->isBusy()) { qCWarning(DISCOVER_LOG) << "not closing because there's still pending tasks"; Q_EMIT preventedClose(); return true; } KConfigGroup window(KSharedConfig::openConfig(), "Window"); window.writeEntry("geometry", rootObject()->geometry()); window.writeEntry("visibility", rootObject()->visibility()); // } else if (event->type() == QEvent::ShortcutOverride) { // qCWarning(DISCOVER_LOG) << "Action conflict" << event; } return false; } void DiscoverObject::setupActions() { if (KAuthorized::authorizeAction(QStringLiteral("help_report_bug")) && !KAboutData::applicationData().bugAddress().isEmpty()) { auto mReportBugAction = KStandardAction::reportBug(this, &DiscoverObject::reportBug, this); m_collection[mReportBugAction->objectName()] = mReportBugAction; } if (KAuthorized::authorizeAction(QStringLiteral("help_about_app"))) { auto mAboutAppAction = KStandardAction::aboutApp(this, &DiscoverObject::aboutApplication, this); m_collection[mAboutAppAction->objectName()] = mAboutAppAction; } } QAction * DiscoverObject::action(const QString& name) const { return m_collection.value(name); } QString DiscoverObject::iconName(const QIcon& icon) { return icon.name(); } void DiscoverObject::aboutApplication() { static QPointer dialog; if (!dialog) { dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); } void DiscoverObject::reportBug() { static QPointer dialog; if (!dialog) { dialog = new KBugReport(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); } void DiscoverObject::switchApplicationLanguage() { // auto langDialog = new KSwitchLanguageDialog(nullptr); // connect(langDialog, SIGNAL(finished(int)), this, SLOT(dialogFinished())); // langDialog->show(); } void DiscoverObject::setCompactMode(DiscoverObject::CompactMode mode) { if (m_mode != mode) { m_mode = mode; Q_EMIT compactModeChanged(m_mode); } } class DiscoverTestExecutor : public QObject { public: DiscoverTestExecutor(QObject* rootObject, QQmlEngine* engine, const QUrl &url) : QObject(engine) { connect(engine, &QQmlEngine::quit, this, &DiscoverTestExecutor::finish, Qt::QueuedConnection); QQmlComponent* component = new QQmlComponent(engine, url, engine); m_testObject = component->create(engine->rootContext()); if (!m_testObject) { qCWarning(DISCOVER_LOG) << "error loading test" << url << m_testObject << component->errors(); Q_ASSERT(false); } m_testObject->setProperty("appRoot", QVariant::fromValue(rootObject)); connect(engine, &QQmlEngine::warnings, this, &DiscoverTestExecutor::processWarnings); } void processWarnings(const QList &warnings) { foreach(const QQmlError &warning, warnings) { if (warning.url().path().endsWith(QLatin1String("DiscoverTest.qml"))) { qCWarning(DISCOVER_LOG) << "Test failed!" << warnings; qGuiApp->exit(1); } } m_warnings << warnings; } void finish() { //The CI doesn't seem to have icons, remove when it's not an issue anymore m_warnings.erase(std::remove_if(m_warnings.begin(), m_warnings.end(), [](const QQmlError& err) -> bool { return err.description().contains(QLatin1String("QML Image: Failed to get image from provider: image://icon/")); })); if (m_warnings.isEmpty()) qCDebug(DISCOVER_LOG) << "cool no warnings!"; else qCDebug(DISCOVER_LOG) << "test finished successfully despite" << m_warnings; qGuiApp->exit(m_warnings.count()); } private: QObject* m_testObject; QList m_warnings; }; void DiscoverObject::loadTest(const QUrl& url) { new DiscoverTestExecutor(rootObject(), engine(), url); } QWindow* DiscoverObject::rootObject() const { return qobject_cast(m_engine->rootObjects().at(0)); } void DiscoverObject::setRootObjectProperty(const char* name, const QVariant& value) { auto ro = rootObject(); if (!ro) { qCWarning(DISCOVER_LOG) << "please check your installation"; return; } rootObject()->setProperty(name, value); } void DiscoverObject::showPassiveNotification(const QString& msg) { QTimer::singleShot(100, this, [this, msg](){ QMetaObject::invokeMethod(rootObject(), "showPassiveNotification", Qt::QueuedConnection, Q_ARG(QVariant, msg), Q_ARG(QVariant, {}), Q_ARG(QVariant, {}), Q_ARG(QVariant, {})); }); } void DiscoverObject::reboot() { QDBusInterface interface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"), QDBusConnection::sessionBus()); interface.asyncCall(QStringLiteral("logout"), 0, 1, 2); // Options: do not ask again | reboot | force } QRect DiscoverObject::initialGeometry() const { KConfigGroup window(KSharedConfig::openConfig(), "Window"); return window.readEntry("geometry", QRect()); } +QString DiscoverObject::describeSources() const +{ + return rootObject()->property("describeSources").toString(); +} + #include "DiscoverObject.moc" diff --git a/discover/DiscoverObject.h b/discover/DiscoverObject.h index 80ddd24c..45a5ae0e 100644 --- a/discover/DiscoverObject.h +++ b/discover/DiscoverObject.h @@ -1,102 +1,104 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DISCOVEROBJECT_H #define DISCOVEROBJECT_H #include #include class AbstractResource; class Category; class QWindow; class QQmlApplicationEngine; class CachedNetworkAccessManagerFactory; class DiscoverObject : public QObject { Q_OBJECT Q_PROPERTY(CompactMode compactMode READ compactMode WRITE setCompactMode NOTIFY compactModeChanged) Q_PROPERTY(bool isRoot READ isRoot CONSTANT) Q_PROPERTY(QRect initialGeometry READ initialGeometry CONSTANT) public: enum CompactMode { Auto, Compact, Full }; Q_ENUM(CompactMode) explicit DiscoverObject(CompactMode mode, const QVariantMap &initialProperties); ~DiscoverObject() override; QStringList modes() const; void setupActions(); CompactMode compactMode() const { return m_mode; } void setCompactMode(CompactMode mode); bool eventFilter(QObject * object, QEvent * event) override; Q_SCRIPTABLE QAction * action(const QString& name) const; Q_SCRIPTABLE static QString iconName(const QIcon& icon); void loadTest(const QUrl& url); static bool isRoot(); QWindow* rootObject() const; void showPassiveNotification(const QString &msg); QRect initialGeometry() const; + QString describeSources() const; + public Q_SLOTS: void openApplication(const QUrl& app); void openMimeType(const QString& mime); void openCategory(const QString& category); void openMode(const QString& mode); void openLocalPackage(const QUrl &localfile); void reboot(); private Q_SLOTS: void reportBug(); void switchApplicationLanguage(); void aboutApplication(); Q_SIGNALS: void openSearch(const QString &search); void openApplicationInternal(AbstractResource* app); void listMimeInternal(const QString& mime); void listCategoryInternal(Category* cat); void compactModeChanged(DiscoverObject::CompactMode compactMode); void preventedClose(); void unableToFind(const QString &resid); void openErrorPage(const QString &errorMessage); private: void setRootObjectProperty(const char *name, const QVariant &value); void integrateObject(QObject* object); QQmlApplicationEngine* engine() const { return m_engine; } QMap m_collection; QQmlApplicationEngine * const m_engine; CompactMode m_mode; QScopedPointer m_networkAccessManagerFactory; }; #endif // DISCOVEROBJECT_H diff --git a/discover/main.cpp b/discover/main.cpp index 1f10369f..3f20c18d 100644 --- a/discover/main.cpp +++ b/discover/main.cpp @@ -1,173 +1,180 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // #define QT_QML_DEBUG #include #include #include #include #include #include #include #include #include "DiscoverObject.h" #include #include "DiscoverVersion.h" #include #include typedef QHash StringCompactMode; Q_GLOBAL_STATIC_WITH_ARGS(StringCompactMode, s_decodeCompactMode, (StringCompactMode { { QLatin1String("auto"), DiscoverObject::Auto }, { QLatin1String("compact"), DiscoverObject::Compact }, { QLatin1String("full"), DiscoverObject::Full } })) QCommandLineParser* createParser() { QCommandLineParser* parser = new QCommandLineParser; parser->addOption(QCommandLineOption(QStringLiteral("application"), i18n("Directly open the specified application by its package name."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("mime"), i18n("Open with a program that can deal with the given mimetype."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("category"), i18n("Display a list of entries with a category."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("mode"), i18n("Open Discover in a said mode. Modes correspond to the toolbar buttons."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("listmodes"), i18n("List all the available modes."))); parser->addOption(QCommandLineOption(QStringLiteral("compact"), i18n("Compact Mode (auto/compact/full)."), QStringLiteral("mode"), QStringLiteral("auto"))); parser->addOption(QCommandLineOption(QStringLiteral("local-filename"), i18n("Local package file to install"), QStringLiteral("package"))); parser->addOption(QCommandLineOption(QStringLiteral("listbackends"), i18n("List all the available backends."))); parser->addOption(QCommandLineOption(QStringLiteral("search"), i18n("Search string."), QStringLiteral("text"))); + parser->addOption(QCommandLineOption(QStringLiteral("feedback"), i18n("Lists the available options for user feedback"))); parser->addOption(QCommandLineOption(QStringLiteral("test"), QStringLiteral("Test file"), QStringLiteral("file.qml"))); parser->addPositionalArgument(QStringLiteral("urls"), i18n("Supports appstream: url scheme")); DiscoverBackendsFactory::setupCommandLine(parser); KAboutData::applicationData().setupCommandLine(parser); return parser; } void processArgs(QCommandLineParser* parser, DiscoverObject* mainWindow) { if(parser->isSet(QStringLiteral("application"))) mainWindow->openApplication(QUrl(parser->value(QStringLiteral("application")))); else if(parser->isSet(QStringLiteral("mime"))) mainWindow->openMimeType(parser->value(QStringLiteral("mime"))); else if(parser->isSet(QStringLiteral("category"))) mainWindow->openCategory(parser->value(QStringLiteral("category"))); if(parser->isSet(QStringLiteral("mode"))) mainWindow->openMode(parser->value(QStringLiteral("mode"))); if(parser->isSet(QStringLiteral("search"))) Q_EMIT mainWindow->openSearch(parser->value(QStringLiteral("search"))); if(parser->isSet(QStringLiteral("local-filename"))) mainWindow->openLocalPackage(QUrl::fromUserInput(parser->value(QStringLiteral("local-filename")), {}, QUrl::AssumeLocalFile)); foreach(const QString &arg, parser->positionalArguments()) { const QUrl url = QUrl::fromUserInput(arg, {}, QUrl::AssumeLocalFile); if (url.isLocalFile()) mainWindow->openLocalPackage(url); else if (url.scheme() == QLatin1String("apt")) Q_EMIT mainWindow->openSearch(url.host()); else mainWindow->openApplication(url); } } int main(int argc, char** argv) { QApplication app(argc, argv); app.setWindowIcon(QIcon::fromTheme(QStringLiteral("plasmadiscover"))); app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); KCrash::initialize(); KQuickAddons::QtQuickSettings::init(); KLocalizedString::setApplicationDomain("plasma-discover"); KAboutData about(QStringLiteral("discover"), i18n("Discover"), version, i18n("An application explorer"), KAboutLicense::GPL, i18n("© 2010-2019 Plasma Development Team")); about.addAuthor(i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org")); about.addAuthor(i18n("Jonathan Thomas"), QString(), QStringLiteral("echidnaman@kubuntu.org")); about.setProductName("discover/discover"); about.setProgramLogo(app.windowIcon()); about.setTranslator( i18ndc(nullptr, "NAME OF TRANSLATORS", "Your names"), i18ndc(nullptr, "EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(about); DiscoverObject *mainWindow = nullptr; { QScopedPointer parser(createParser()); parser->process(app); about.processCommandLine(parser.data()); DiscoverBackendsFactory::processCommandLine(parser.data(), parser->isSet(QStringLiteral("test"))); if(parser->isSet(QStringLiteral("listbackends"))) { QTextStream(stdout) << i18n("Available backends:\n"); DiscoverBackendsFactory f; foreach(const QString& name, f.allBackendNames(false, true)) QTextStream(stdout) << " * " << name << '\n'; return 0; } if (parser->isSet(QStringLiteral("test"))) { QStandardPaths::setTestModeEnabled(true); } KDBusService* service = new KDBusService(KDBusService::Unique, &app); { auto options = parser->optionNames(); options.removeAll(QStringLiteral("backends")); options.removeAll(QStringLiteral("test")); bool hasOptions = !options.isEmpty() || !parser->positionalArguments().isEmpty(); mainWindow = new DiscoverObject(s_decodeCompactMode->value(parser->value(QStringLiteral("compact")), DiscoverObject::Full), {{QStringLiteral("defaultStartup"), !hasOptions}}); } QObject::connect(&app, &QCoreApplication::aboutToQuit, mainWindow, &DiscoverObject::deleteLater); QObject::connect(service, &KDBusService::activateRequested, mainWindow, [mainWindow](const QStringList &arguments, const QString &/*workingDirectory*/){ if (!mainWindow->rootObject()) QCoreApplication::instance()->quit(); mainWindow->rootObject()->raise(); if (arguments.isEmpty()) return; QScopedPointer parser(createParser()); parser->parse(arguments); processArgs(parser.data(), mainWindow); }); processArgs(parser.data(), mainWindow); if(parser->isSet(QStringLiteral("listmodes"))) { QTextStream(stdout) << i18n("Available modes:\n"); foreach(const QString& mode, mainWindow->modes()) QTextStream(stdout) << " * " << mode << '\n'; delete mainWindow; return 0; } + if(parser->isSet(QStringLiteral("feedback"))) { + QTextStream(stdout) << mainWindow->describeSources() << '\n'; + delete mainWindow; + return 0; + } + if (parser->isSet(QStringLiteral("test"))) { const QUrl testFile = QUrl::fromUserInput(parser->value(QStringLiteral("test")), {}, QUrl::AssumeLocalFile); Q_ASSERT(!testFile.isEmpty() && testFile.isLocalFile()); mainWindow->loadTest(testFile); } } return app.exec(); } diff --git a/discover/qml/DiscoverWindow.qml b/discover/qml/DiscoverWindow.qml index 6cba16a7..2c2cf580 100644 --- a/discover/qml/DiscoverWindow.qml +++ b/discover/qml/DiscoverWindow.qml @@ -1,300 +1,301 @@ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import org.kde.kquickcontrolsaddons 2.0 import org.kde.kirigami 2.5 as Kirigami import "navigation.js" as Navigation Kirigami.ApplicationWindow { id: window readonly property string applicationListComp: ("qrc:/qml/ApplicationsListPage.qml") readonly property string applicationComp: ("qrc:/qml/ApplicationPage.qml") readonly property string reviewsComp: ("qrc:/qml/ReviewsPage.qml") //toplevels readonly property string topBrowsingComp: ("qrc:/qml/BrowsingPage.qml") readonly property string topInstalledComp: ("qrc:/qml/InstalledPage.qml") readonly property string topSearchComp: ("qrc:/qml/SearchPage.qml") readonly property string topUpdateComp: ("qrc:/qml/UpdatesPage.qml") readonly property string topSourcesComp: ("qrc:/qml/SourcesPage.qml") readonly property string topAboutComp: ("qrc:/qml/AboutPage.qml") readonly property string loadingComponent: ("qrc:/qml/LoadingPage.qml") readonly property QtObject stack: window.pageStack property string currentTopLevel: defaultStartup ? topBrowsingComp : loadingComponent property bool defaultStartup: true objectName: "DiscoverMainWindow" title: leftPage ? leftPage.title : "" width: app.initialGeometry.width>=10 ? app.initialGeometry.width : Kirigami.Units.gridUnit * 45 height: app.initialGeometry.height>=10 ? app.initialGeometry.height : Kirigami.Units.gridUnit * 30 visible: true minimumWidth: 300 minimumHeight: 300 pageStack.defaultColumnWidth: Kirigami.Units.gridUnit * 25 pageStack.globalToolBar.style: window.wideScreen ? Kirigami.ApplicationHeaderStyle.ToolBar : Kirigami.ApplicationHeaderStyle.Breadcrumb readonly property var leftPage: window.stack.depth>0 ? window.stack.get(0) : null Component.onCompleted: { if (app.isRoot) showPassiveNotification(i18n("Running as root is discouraged and unnecessary.")); } + readonly property string describeSources: feedbackLoader.item.describeDataSources Loader { id: feedbackLoader source: "Feedback.qml" } TopLevelPageData { iconName: "tools-wizard" text: i18n("Discover") component: topBrowsingComp objectName: "discover" } TopLevelPageData { id: searchAction enabled: !window.wideScreen iconName: "search" text: i18n("Search") component: topSearchComp objectName: "discover" shortcut: "Ctrl+F" } TopLevelPageData { id: installedAction iconName: "view-list-details" text: i18n("Installed") component: topInstalledComp objectName: "installed" } TopLevelPageData { id: updateAction iconName: ResourcesModel.updatesCount>0 ? ResourcesModel.hasSecurityUpdates ? "update-high" : "update-low" : "update-none" text: ResourcesModel.updatesCount<=0 ? (ResourcesModel.isFetching ? i18n("Fetching Updates...") : i18n("Up to Date") ) : i18nc("Update section name", "Update (%1)", ResourcesModel.updatesCount) component: topUpdateComp objectName: "update" } TopLevelPageData { id: aboutAction iconName: "help-feedback" text: i18n("About") component: topAboutComp objectName: "about" } TopLevelPageData { id: sourcesAction iconName: "configure" text: i18n("Settings") component: topSourcesComp objectName: "sources" } Kirigami.Action { id: refreshAction readonly property QtObject action: ResourcesModel.updateAction text: action.text icon.name: "view-refresh" onTriggered: action.trigger() enabled: action.enabled tooltip: shortcut shortcut: "Ctrl+R" } Connections { target: app onOpenApplicationInternal: { Navigation.clearStack() Navigation.openApplication(app) } onListMimeInternal: { currentTopLevel = topBrowsingComp; Navigation.openApplicationMime(mime) } onListCategoryInternal: { currentTopLevel = topBrowsingComp; Navigation.openCategory(cat, "") } onOpenSearch: { Navigation.clearStack() Navigation.openApplicationList({search: search}) } onOpenErrorPage: { Navigation.clearStack() console.warn("error", errorMessage) window.stack.push(errorPageComponent, { error: errorMessage, title: i18n("Sorry...") }) } onPreventedClose: showPassiveNotification(i18n("Could not close Discover, there are tasks that need to be done."), 20000, i18n("Quit Anyway"), function() { Qt.quit() }) onUnableToFind: { showPassiveNotification(i18n("Unable to find resource: %1", resid)); Navigation.openHome() } } Connections { target: ResourcesModel onPassiveMessage: { showPassiveNotification(message) console.log("message:", message) } } Component { id: errorPageComponent Kirigami.Page { id: page property string error: "" readonly property bool isHome: true function searchFor(text) { if (text.length === 0) return; Navigation.openCategory(null, "") } Kirigami.Icon { id: infoIcon; anchors { bottom: parent.verticalCenter margins: Kirigami.Units.largeSpacing horizontalCenter: parent.horizontalCenter } visible: page.error !== "" source: "emblem-warning" height: Kirigami.Units.iconSizes.huge width: height; } Kirigami.Heading { anchors { top: parent.verticalCenter margins: Kirigami.Units.largeSpacing horizontalCenter: parent.horizontalCenter } width: parent.width; wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter visible: page.error !== "" text: page.error } } } Component { id: proceedDialog Kirigami.OverlaySheet { id: sheet showCloseButton: false property QtObject transaction property alias title: heading.text property alias description: desc.text property bool acted: false ColumnLayout { Kirigami.Heading { id: heading } Label { id: desc Layout.fillWidth: true Layout.maximumHeight: clip ? window.height * 0.5 : implicitHeight clip: true textFormat: Text.StyledText wrapMode: Text.WordWrap readonly property var bottomShadow: Shadow { parent: desc anchors { right: parent.right left: parent.left bottom: parent.bottom } visible: desc.clip edge: Qt.BottomEdge height: desc.height * 0.01 } } Button { text: desc.clip ? i18n("Show all") : i18n("Hide") onClicked: desc.clip = !desc.clip } RowLayout { Layout.alignment: Qt.AlignRight Button { text: i18n("Proceed") icon.name: "dialog-ok" onClicked: { transaction.proceed() sheet.acted = true sheet.close() } Keys.onEnterPressed: clicked() Keys.onReturnPressed: clicked() } Button { Layout.alignment: Qt.AlignRight text: i18n("Cancel") icon.name: "dialog-cancel" onClicked: { transaction.cancel() sheet.acted = true sheet.close() } Keys.onEscapePressed: clicked() } } } onSheetOpenChanged: if(!sheetOpen) { sheet.destroy(1000) if (!sheet.acted) transaction.cancel() } } } Instantiator { model: TransactionModel delegate: Connections { target: model.transaction ? model.transaction : null onProceedRequest: { var dialog = proceedDialog.createObject(window, {transaction: transaction, title: title, description: description}) dialog.open() } onPassiveMessage: { window.showPassiveNotification(message) } } } ConditionalObject { id: drawerObject condition: window.wideScreen componentFalse: Kirigami.ContextDrawer {} } contextDrawer: drawerObject.object globalDrawer: DiscoverDrawer { wideScreen: window.wideScreen } onCurrentTopLevelChanged: { window.pageStack.clear() if (currentTopLevel) window.pageStack.push(currentTopLevel, {}, window.status!==Component.Ready) } UnityLauncher { launcherId: "org.kde.discover.desktop" progressVisible: TransactionModel.count > 0 progress: TransactionModel.progress } }