diff --git a/discover/DiscoverObject.cpp b/discover/DiscoverObject.cpp index e326bf8a..97d0dec0 100644 --- a/discover/DiscoverObject.cpp +++ b/discover/DiscoverObject.cpp @@ -1,487 +1,498 @@ /* * 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 // KDE includes #include #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 class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) public: void classBegin() override {} void componentComplete() override { if (dynamicSortFilter()) sort(0); } }; class KIOAccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: KIOAccessManagerFactory() = default; ~KIOAccessManagerFactory() = default; QNetworkAccessManager *create(QObject *parent) override { return new KIO::AccessManager(parent); } }; DiscoverObject::DiscoverObject(CompactMode mode) : 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(new KIOAccessManagerFactory()); 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"); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); + + qmlRegisterType(); + qmlRegisterType(); + qmlRegisterType(); 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); //Here we set up a cache for the screenshots delete m_engine->networkAccessManagerFactory(); m_engine->setNetworkAccessManagerFactory(m_networkAccessManagerFactory.data()); m_engine->rootContext()->setContextProperty(QStringLiteral("app"), this); + m_engine->rootContext()->setContextProperty(QStringLiteral("discoverAboutData"), QVariant::fromValue(KAboutData::applicationData())); + m_engine->rootContext()->setContextProperty(QStringLiteral("discoverAboutLibraries"), i18n("
  • KDE Frameworks %1
  • Qt %2 (built against %3)
  • The %4 windowing system
", + QStringLiteral(KCOREADDONS_VERSION_STRING), + QString::fromLocal8Bit(qVersion()), + QStringLiteral(QT_VERSION_STR), + QGuiApplication::platformName())); 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(); for (auto b : ResourcesModel::global()->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", false); } } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, 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]() { auto res = ResourcesModel::global()->resourceForFile(localfile); qCDebug(DISCOVER_LOG) << "all initialized..." << res; if (res) { emit openApplicationInternal(res); } 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(QLatin1String("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::finished, this, [this, url, stream]() { const auto res = stream->resources(); 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::copyTextToClipboard(const QString& text) { qGuiApp->clipboard()->setText(text); } #include "DiscoverObject.moc" diff --git a/discover/main.cpp b/discover/main.cpp index 5bb2ff01..99a0302e 100644 --- a/discover/main.cpp +++ b/discover/main.cpp @@ -1,168 +1,169 @@ /* * 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("test"), QStringLiteral("Test file"), QStringLiteral("file.qml"))); parser->addPositionalArgument(QStringLiteral("urls"), i18n("Supports appstream: url scheme")); DiscoverBackendsFactory::setupCommandLine(parser); KAboutData::applicationData().setupCommandLine(parser); parser->addHelpOption(); parser->addVersionOption(); 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"))) 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-2018 Plasma Development Team")); - about.addAuthor(i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@blue-systems.com")); + 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); mainWindow = new DiscoverObject(s_decodeCompactMode->value(parser->value(QStringLiteral("compact")), DiscoverObject::Full)); 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("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/AboutPage.qml b/discover/qml/AboutPage.qml new file mode 100644 index 00000000..b287ef34 --- /dev/null +++ b/discover/qml/AboutPage.qml @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018 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. + */ + +import QtQuick 2.1 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.3 +import org.kde.kirigami 2.5 as Kirigami + +Kirigami.Page +{ + id: page + property var aboutData: discoverAboutData + property var aboutLibraries: discoverAboutLibraries + + contextualActions: [ + KirigamiActionBridge { action: app.action("help_report_bug") } + ] + + title: i18n("About") + header: ColumnLayout { + GridLayout { + columns: 2 + Layout.fillWidth: true + Layout.preferredHeight: Kirigami.Units.iconSizes.huge + + Kirigami.Icon { + Layout.rowSpan: 2 + Layout.fillHeight: true + Layout.minimumWidth: height + Layout.rightMargin: Kirigami.Units.largeSpacing + source: page.aboutData.programLogo || page.aboutData.programIconName + } + Kirigami.Heading { + Layout.fillWidth: true + text: page.aboutData.displayName + " " + page.aboutData.version + } + Kirigami.Heading { + Layout.fillWidth: true + level: 2 + text: page.aboutData.shortDescription + } + } + TabBar { + Layout.fillWidth: true + id: bar + TabButton { text: i18n("About") } + TabButton { text: i18n("Libraries") } + TabButton { text: i18n("Authors") } + } + } + + Component { + id: licencePage + Kirigami.ScrollablePage { + property alias text: content.text + TextArea { + id: content + readOnly: true + } + } + } + + SwipeView { + anchors.fill: parent + currentIndex: bar.currentIndex + interactive: false + ColumnLayout { + Label { + text: aboutData.shortDescription + visible: text.length > 0 + } + Label { + text: aboutData.otherText + visible: text.length > 0 + } + Label { + text: aboutData.copyrightStatement + visible: text.length > 0 + } + UrlButton { + url: aboutData.homepage + visible: url.length > 0 + } + + Repeater { + id: rep + model: aboutData.licenses + delegate: LinkButton { + text: modelData.name + onClicked: applicationWindow().pageStack.push(licencePage, { text: modelData.text, title: modelData.name } ) + } + } + + Item { + Layout.fillHeight: true + } + } + Label { + id: libraries + text: page.aboutLibraries + } + Kirigami.CardsListView { + header: Label { + readonly property string bugAddress: aboutData.bugAddress || "https://bugs.kde.org" + readonly property string bugDisplay: aboutData.bugAddress ? ("mailto:" + aboutData.bugAddress) : "https://bugs.kde.org" + text: i18n("Please use " + bugDisplay + " to report bugs.\n") + } + model: aboutData.authors + delegate: Kirigami.AbstractCard { + contentItem: RowLayout { + Layout.preferredHeight: Kirigami.Units.iconSizes.medium + Kirigami.Icon { + Layout.fillHeight: true + Layout.minimumWidth: Kirigami.Units.iconSizes.medium + Layout.maximumWidth: Kirigami.Units.iconSizes.medium + source: "https://www.gravatar.com/avatar/" + Qt.md5(modelData.emailAddress) + "?d=404&s=" + Kirigami.Units.iconSizes.medium + fallback: "user" + } + Label { + Layout.fillWidth: true + text: i18n("%1 <%2>", modelData.name, modelData.emailAddress) + } + } + } + } + } +} diff --git a/discover/qml/DiscoverDrawer.qml b/discover/qml/DiscoverDrawer.qml index 5201dc6e..83382320 100644 --- a/discover/qml/DiscoverDrawer.qml +++ b/discover/qml/DiscoverDrawer.qml @@ -1,201 +1,189 @@ /*************************************************************************** * Copyright © 2015 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 General Public License * * along with this program. If not, see . * ***************************************************************************/ 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.kirigami 2.0 as Kirigami import "navigation.js" as Navigation Kirigami.GlobalDrawer { id: drawer // FIXME: Dirty workaround for 385992 width: Kirigami.Units.gridUnit * 14 property bool wideScreen: false bannerImageSource: "qrc:/banners/banner.svg" //make the left and bottom margins for search field the same topPadding: drawer.wideScreen ? -toploader.height - leftPadding : 0 bottomPadding: 0 resetMenuOnTriggered: false onBannerClicked: { Navigation.openHome(); if (modal) drawerOpen = false } property string currentSearchText onCurrentSubMenuChanged: { if (currentSubMenu) currentSubMenu.trigger() else if (currentSearchText.length > 0) window.leftPage.category = null else Navigation.openHome() } function suggestSearchText(text) { toploader.item.text = text toploader.item.forceActiveFocus() } topContent: ConditionalLoader { id: toploader condition: drawer.wideScreen Layout.fillWidth: true componentFalse: Item { Layout.minimumHeight: 1 } componentTrue: SearchField { id: searchField visible: window.leftPage && (window.leftPage.searchFor != null || window.leftPage.hasOwnProperty("search")) page: window.leftPage onCurrentSearchTextChanged: { var curr = window.leftPage; if (pageStack.depth>1) pageStack.pop() if (currentSearchText === "" && window.currentTopLevel === "" && !window.leftPage.category) { Navigation.openHome() } else if (!curr.hasOwnProperty("search")) { if (currentSearchText) { Navigation.clearStack() Navigation.openApplicationList( { search: currentSearchText }) } } else { curr.search = currentSearchText; curr.forceActiveFocus() } } } } ColumnLayout { spacing: 0 Layout.fillWidth: true Layout.leftMargin: -drawer.leftPadding Layout.rightMargin: -drawer.rightPadding Kirigami.Separator { Layout.fillWidth: true } ProgressView { separatorVisible: false } ActionListItem { action: searchAction } ActionListItem { action: installedAction } ActionListItem { action: sourcesAction } ActionListItem { - action: Kirigami.Action { - text: i18n("Help") - icon.name: "help-feedback" - onTriggered: helpMenu.open() - } - - readonly property var p0: Menu { - id: helpMenu - title: i18n("Help") - - MenuItem { action: ActionBridge { action: app.action("help_about_app") } } - MenuItem { action: ActionBridge { action: app.action("help_report_bug") } } - } + action: aboutAction } ActionListItem { objectName: "updateButton" action: updateAction backgroundColor: ResourcesModel.updatesCount>0 ? "orange" : Kirigami.Theme.viewBackgroundColor } states: [ State { name: "full" when: drawer.wideScreen PropertyChanges { target: drawer; drawerOpen: true } }, State { name: "compact" when: !drawer.wideScreen PropertyChanges { target: drawer; drawerOpen: false } } ] } Component { id: categoryActionComponent Kirigami.Action { property QtObject category readonly property bool itsMe: window.leftPage && window.leftPage.hasOwnProperty("category") && (window.leftPage.category == category) text: category ? category.name : "" checked: itsMe visible: (!window.leftPage || !window.leftPage.subcategories || window.leftPage.subcategories === undefined || currentSearchText.length === 0 || (category && category.contains(window.leftPage.subcategories)) ) onTriggered: { if (!window.leftPage.canNavigate) Navigation.openCategory(category, currentSearchText) else { if (pageStack.depth>1) pageStack.pop() pageStack.currentIndex = 0 window.leftPage.category = category } } } } function createCategoryActions(categories) { var actions = [] for(var i in categories) { var cat = categories[i]; var catAction = categoryActionComponent.createObject(drawer, {category: cat}); catAction.children = createCategoryActions(cat.subcategories); actions.push(catAction) } return actions; } actions: createCategoryActions(CategoryModel.rootCategories) modal: !drawer.wideScreen handleVisible: !drawer.wideScreen } diff --git a/discover/qml/DiscoverWindow.qml b/discover/qml/DiscoverWindow.qml index 76cd9bfe..a16cbb9e 100644 --- a/discover/qml/DiscoverWindow.qml +++ b/discover/qml/DiscoverWindow.qml @@ -1,237 +1,245 @@ 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.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 : "" 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.")); } 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 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("Checking for updates...") : i18n("No Updates") ) : i18nc("Update section name", "Update (%1)", ResourcesModel.updatesCount) component: topUpdateComp objectName: "update" } + TopLevelPageData { + id: aboutAction + iconName: "help-feedback" + text: i18n("Help") + component: topAboutComp + objectName: "about" + } TopLevelPageData { id: sourcesAction text: i18n("Sources") 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 the application, there are tasks that need to be done.")) 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: "" Kirigami.Heading { text: page.error anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } 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 textFormat: Text.StyledText wrapMode: Text.WordWrap } RowLayout { Layout.alignment: Qt.AlignRight Button { text: i18n("Proceed") icon.name: "dialog-ok" onClicked: { transaction.proceed() sheet.acted = true sheet.close() } } Button { Layout.alignment: Qt.AlignRight text: i18n("Cancel") icon.name: "dialog-cancel" onClicked: { transaction.cancel() sheet.acted = true sheet.close() } } } } 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 } } diff --git a/discover/resources.qrc b/discover/resources.qrc index 25d46247..6ab069b0 100644 --- a/discover/resources.qrc +++ b/discover/resources.qrc @@ -1,40 +1,41 @@ qml/TopLevelPageData.qml qml/ApplicationsListPage.qml qml/ApplicationPage.qml qml/ReviewsPage.qml qml/AddonsView.qml qml/ApplicationDelegate.qml qml/InstallApplicationButton.qml qml/Rating.qml qml/UpdatesPage.qml qml/ReviewDialog.qml qml/ProgressView.qml qml/BrowsingPage.qml qml/InstalledPage.qml qml/SearchPage.qml qml/SourcesPage.qml qml/ReviewDelegate.qml qml/AddSourceDialog.qml qml/ConditionalLoader.qml qml/ConditionalObject.qml qml/LinkButton.qml qml/UrlButton.qml qml/ApplicationScreenshots.qml qml/LabelBackground.qml qml/ActionBridge.qml qml/KirigamiActionBridge.qml qml/DiscoverPage.qml qml/DiscoverWindow.qml qml/DiscoverDrawer.qml qml/ActionListItem.qml qml/LoadingPage.qml qml/SearchField.qml qml/Shadow.qml qml/DiscoverPopup.qml + qml/AboutPage.qml qml/navigation.js