diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0e906163..441226f9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,79 +1,75 @@
project(discover)
set(PROJECT_VERSION "5.8.90")
set(PROJECT_VERSION_MAJOR 5)
cmake_minimum_required(VERSION 2.8.12)
find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} "${CMAKE_SOURCE_DIR}/cmake")
find_package(Qt5 5.2.0 REQUIRED CONFIG COMPONENTS Widgets Test Network Xml Concurrent DBus Quick)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(ECMInstallIcons)
include(ECMMarkAsTest)
include(ECMAddTests)
include(GenerateExportHeader)
-find_package(KF5 REQUIRED CoreAddons Config Crash DBusAddons I18n Archive Declarative XmlGui)
-find_package(KF5TextWidgets REQUIRED)
+find_package(KF5 5.24 REQUIRED CoreAddons Config Crash DBusAddons I18n Archive Declarative XmlGui)
+find_package(KF5Kirigami 1.0 REQUIRED)
find_package(packagekitqt5)
if (NOT packagekitqt5_FOUND)
find_package(QApt 3.0.0)
if(QApt_FOUND)
find_package(DebconfKDE 1.0.0 REQUIRED)
find_package(KF5 REQUIRED IconThemes Notifications KIO)
endif()
endif()
-find_package(AppstreamQt 0.9.2)
+find_package(AppstreamQt 0.10)
find_package(KF5Attica 5.23)
find_package(KF5NewStuff 5.23)
-if (${KF5_VERSION} VERSION_GREATER "5.14.0")
- add_definitions(-DWITH_KCRASH_INIT)
-endif()
-
configure_file(DiscoverVersion.h.in DiscoverVersion.h)
add_subdirectory(libdiscover)
add_subdirectory(discover)
add_subdirectory(exporter)
option(WITH_NOTIFIER "Build and install the notifier plasmoid" ON)
if(WITH_NOTIFIER)
find_package(KF5 REQUIRED Notifications KIO)
add_subdirectory(notifier)
endif()
set_package_properties(QApt PROPERTIES
DESCRIPTION "Qt wrapper around the libapt-pkg library"
PURPOSE "Used to support apt-based distribution systems"
TYPE OPTIONAL)
set_package_properties(KF5Attica PROPERTIES
DESCRIPTION "KDE Framework that implements the Open Collaboration Services API"
PURPOSE "Required to build the KNewStuff3 backend"
TYPE OPTIONAL)
set_package_properties(KF5NewStuff PROPERTIES
DESCRIPTION "Qt library that allows to interact with KNewStuff implementations"
PURPOSE "Required to build the KNS backend"
TYPE OPTIONAL)
set_package_properties(Bodega PROPERTIES
DESCRIPTION "Library that exposes Bodega resources"
PURPOSE "Required to build the Bodega backend"
TYPE OPTIONAL)
set_package_properties(packagekitqt5 PROPERTIES
DESCRIPTION "Library that exposes PackageKit resources"
URL "http://www.packagekit.org"
PURPOSE "Required to build the PackageKit backend"
TYPE OPTIONAL)
set_package_properties(AppstreamQt PROPERTIES
DESCRIPTION "Library that lists Appstream resources"
URL "http://www.freedesktop.org"
PURPOSE "Required to build the PackageKit backend"
TYPE OPTIONAL)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/discover/autotests/DiscoverTest.qml b/discover/autotests/DiscoverTest.qml
index a45b32ec..2e763a4d 100644
--- a/discover/autotests/DiscoverTest.qml
+++ b/discover/autotests/DiscoverTest.qml
@@ -1,105 +1,106 @@
import QtQuick 2.1
import QtQuick.Controls 1.4
import QtTest 1.1
import org.kde.discover.app 1.0
Item
{
id: testRoot
signal reset()
property QtObject appRoot
StackViewDelegate {
id: noTransitionsDelegate
popTransition: StackViewTransition { immediate: true }
pushTransition: StackViewTransition { immediate: true }
replaceTransition: StackViewTransition { immediate: true }
}
function verify(condition, msg) {
if (!condition) {
console.trace();
var e = new Error(condition + (msg ? (": " + msg) : ""))
e.object = testRoot;
throw e;
}
}
function compare(valA, valB, msg) {
if (valA !== valB) {
console.trace();
var e = new Error(valA + " !== " + valB + (msg ? (": " + msg) : ""))
e.object = testRoot;
throw e;
}
}
function typeName(obj) {
var name = obj.toString();
var idx = name.indexOf("_QMLTYPE_");
return name.substring(0, idx);
}
function isType(obj, typename) {
return obj && obj.toString().indexOf(typename+"_QMLTYPE_") == 0
}
function findChild(obj, typename) {
if (isType(obj, typename))
return obj;
for(var v in obj.data) {
var v = findChild(obj.data[v], typename)
if (v)
return v
}
return null
}
SignalSpy {
id: spy
}
function waitForSignal(object, name, timeout) {
if (!timeout) timeout = 5000;
spy.signalName = ""
spy.target = object;
spy.signalName = name;
verify(spy);
+ verify(spy.valid);
try {
spy.wait(timeout);
} catch (e) {
- console.warn("wait for signal unsuccessful")
+ console.warn("wait for signal '"+name+"' unsuccessful")
return false;
}
return spy.count>0;
}
function waitForRendering() {
return waitForSignal(Helpers.mainWindow, "frameSwapped")
}
property string currentTest: ""
onCurrentTestChanged: console.log("changed to test", currentTest)
Connections {
target: ResourcesModel
property bool done: false
onIsFetchingChanged: {
if (ResourcesModel.isFetching)
return;
done = true;
for(var v in testRoot) {
if (v.indexOf("test_") == 0) {
testRoot.currentTest = v;
testRoot.reset();
testRoot[v]();
}
}
Qt.quit();
}
}
}
diff --git a/discover/main.cpp b/discover/main.cpp
index 777968dd..eb987938 100644
--- a/discover/main.cpp
+++ b/discover/main.cpp
@@ -1,148 +1,146 @@
/*
* 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 "DiscoverMainWindow.h"
#include
#include "DiscoverVersion.h"
#include
#include
DiscoverMainWindow::CompactMode decodeCompactMode(const QString &str)
{
if (str == QLatin1String("auto"))
return DiscoverMainWindow::Auto;
else if (str == QLatin1String("compact"))
return DiscoverMainWindow::Compact;
else if (str == QLatin1String("full"))
return DiscoverMainWindow::Full;
return DiscoverMainWindow::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("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;
}
bool processArgs(QCommandLineParser* parser, DiscoverMainWindow* mainWindow)
{
if(parser->isSet(QStringLiteral("application")))
mainWindow->openApplication(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")));
foreach(const QString &arg, parser->positionalArguments()) {
QUrl url(arg);
if (url.scheme() == QLatin1String("appstream")) {
mainWindow->openApplication(url.host());
} else {
QTextStream(stdout) << "unrecognized url" << url.toDisplayString() << '\n';
return true;
}
}
return false;
}
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);
-#ifdef WITH_KCRASH_INIT
KCrash::initialize();
-#endif
KLocalizedString::setApplicationDomain("plasma-discover");
KAboutData about(QStringLiteral("discover"), i18n("Discover"), version, i18n("An application explorer"),
KAboutLicense::GPL, i18n("© 2010-2016 Plasma Development Team"));
about.addAuthor(i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@blue-systems.com"));
about.addAuthor(i18n("Jonathan Thomas"), QString(), QStringLiteral("echidnaman@kubuntu.org"));
about.setProductName("discover/discover");
KAboutData::setApplicationData(about);
DiscoverMainWindow *mainWindow = nullptr;
{
QScopedPointer parser(createParser());
parser->process(app);
about.processCommandLine(parser.data());
DiscoverBackendsFactory::processCommandLine(parser.data(), parser->isSet(QStringLiteral("test")));
if (parser->isSet(QStringLiteral("test"))) {
QStandardPaths::setTestModeEnabled(true);
}
KDBusService* service = new KDBusService(KDBusService::Unique, &app);
mainWindow = new DiscoverMainWindow(decodeCompactMode(parser->value(QStringLiteral("compact"))));
QObject::connect(&app, &QApplication::aboutToQuit, mainWindow, &DiscoverMainWindow::deleteLater);
QObject::connect(service, &KDBusService::activateRequested, mainWindow, [mainWindow](const QStringList &arguments, const QString &/*workingDirectory*/){
mainWindow->rootObject()->raise();
if (arguments.isEmpty())
return;
QScopedPointer parser(createParser());
parser->process(arguments);
processArgs(parser.data(), mainWindow);
});
if (processArgs(parser.data(), mainWindow))
return 1;
if(parser->isSet(QStringLiteral("listmodes"))) {
QTextStream(stdout) << i18n("Available modes:\n");
foreach(const QString& mode, mainWindow->modes())
QTextStream(stdout) << " * " << mode << '\n';
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/DiscoverDrawer.qml b/discover/qml/DiscoverDrawer.qml
index cf0b5827..9800771e 100644
--- a/discover/qml/DiscoverDrawer.qml
+++ b/discover/qml/DiscoverDrawer.qml
@@ -1,191 +1,207 @@
/***************************************************************************
* 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.6
+import QtQuick 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import org.kde.discover 1.0
import org.kde.discover.app 1.0
import org.kde.kirigami 1.0 as Kirigami
import "navigation.js" as Navigation
Kirigami.GlobalDrawer {
id: drawer
bannerImageSource: "qrc:/icons/banner.svg"
topPadding: -50
leftPadding: 0
rightPadding: 0
bottomPadding: 0
resetMenuOnTriggered: false
- readonly property var currentRootCategory: window.leftPage ? rootCategory(window.leftPage.category) : null
+ onBannerClicked: {
+ Navigation.openHome();
+ }
+
+ onCurrentSubMenuChanged: {
+ if (currentSubMenu)
+ currentSubMenu.trigger()
+ else if (searchField.text !== "")
+ window.leftPage.category = null
+ else
+ Navigation.openHome()
+
+ }
topContent: TextField {
id: searchField
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
enabled: window.leftPage && (window.leftPage.searchFor != null || window.leftPage.hasOwnProperty("search"))
Component.onCompleted: {
searchField.forceActiveFocus()
}
Shortcut {
sequence: "Ctrl+F"
onActivated: {
searchField.forceActiveFocus()
}
}
placeholderText: (!enabled || window.leftPage.title.length === 0) ? i18n("Search...") : i18n("Search in '%1'...", window.leftPage.title)
onTextChanged: searchTimer.running = true
Connections {
ignoreUnknownSignals: true
target: window.leftPage
onClearSearch: {
searchField.text = ""
// console.log("search cleared")
}
}
Timer {
id: searchTimer
running: false
repeat: false
interval: 200
onTriggered: {
var curr = window.leftPage;
- if (!curr.hasOwnProperty("search"))
+ if (!curr.hasOwnProperty("search")) {
+ Navigation.clearStack()
Navigation.openApplicationList( { search: parent.text })
- else
+ } else
curr.search = parent.text;
}
}
}
ColumnLayout {
spacing: 0
Layout.fillWidth: true
Kirigami.Separator {
Layout.fillWidth: true
}
ProgressView {
separatorVisible: false
}
Kirigami.BasicListItem {
checked: installedAction.checked
icon: installedAction.iconName
label: installedAction.text
separatorVisible: false
onClicked: {
installedAction.trigger()
drawer.resetMenu()
}
}
Kirigami.BasicListItem {
checked: settingsAction.checked
icon: settingsAction.iconName
label: settingsAction.text
separatorVisible: false
onClicked: {
settingsAction.trigger()
drawer.resetMenu()
}
}
Kirigami.BasicListItem {
enabled: updateAction.enabled
checked: updateAction.checked
icon: updateAction.iconName
label: updateAction.text
separatorVisible: false
onClicked: {
updateAction.trigger()
drawer.resetMenu()
}
backgroundColor: enabled ? "orange" : Kirigami.Theme.viewBackgroundColor
}
}
function rootCategory(cat) {
var ret = null
while (cat) {
ret = cat
cat = cat.parent
}
return ret
}
Component {
id: categoryActionComponent
Kirigami.Action {
property QtObject category
readonly property bool itsMe: window.leftPage && window.leftPage.hasOwnProperty("category") && (window.leftPage.category == category)
text: category.name
checkable: itsMe
checked: itsMe
visible: (!window.leftPage
|| !window.leftPage.subcategories
|| window.leftPage.subcategories === undefined
|| searchField.text.length === 0
|| category.contains(window.leftPage.subcategories)
)
onTriggered: {
- Navigation.openCategory(category, searchField.text)
+ if (window.leftPage.category === undefined)
+ Navigation.openCategory(category, searchField.text)
+ else
+ window.leftPage.category = category
}
}
}
function createCategoryActions(parent, categories) {
var actions = []
for(var i in categories) {
var cat = categories[i];
var catAction = categoryActionComponent.createObject(parent, {category: cat});
catAction.children = createCategoryActions(catAction, cat.subcategories);
actions.push(catAction)
}
return actions;
}
CategoryModel {
id: rootCategories
Component.onCompleted: {
resetCategories();
drawer.actions = createCategoryActions(rootCategories, rootCategories.categories)
}
}
modal: Helpers.isCompact
handleVisible: Helpers.isCompact
states: [
State {
name: "full"
when: !Helpers.isCompact
PropertyChanges { target: drawer; opened: true }
}
]
}
diff --git a/discover/qml/DiscoverWindow.qml b/discover/qml/DiscoverWindow.qml
index 90a2f6a2..04ba7cb1 100644
--- a/discover/qml/DiscoverWindow.qml
+++ b/discover/qml/DiscoverWindow.qml
@@ -1,155 +1,156 @@
import QtQuick 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import org.kde.discover 1.0
import org.kde.discover.app 1.0
import org.kde.kirigami 1.0 as Kirigami
import "navigation.js" as Navigation
Kirigami.ApplicationWindow
{
id: window
readonly property Component applicationListComp: Qt.createComponent("qrc:/qml/ApplicationsListPage.qml")
readonly property Component applicationComp: Qt.createComponent("qrc:/qml/ApplicationPage.qml")
readonly property Component reviewsComp: Qt.createComponent("qrc:/qml/ReviewsPage.qml")
//toplevels
readonly property Component topBrowsingComp: Qt.createComponent("qrc:/qml/BrowsingPage.qml")
readonly property Component topInstalledComp: Qt.createComponent("qrc:/qml/InstalledPage.qml")
readonly property Component topUpdateComp: Qt.createComponent("qrc:/qml/UpdatesPage.qml")
readonly property Component topSourcesComp: Qt.createComponent("qrc:/qml/SourcesPage.qml")
readonly property QtObject stack: window.pageStack
property Component currentTopLevel: defaultStartup ? topBrowsingComp : loadingComponent
property bool defaultStartup: true
property bool navigationEnabled: true
objectName: "DiscoverMainWindow"
title: leftPage ? leftPage.title : ""
header: null
visible: true
minimumWidth: 300
minimumHeight: 300
pageStack.defaultColumnWidth: Kirigami.Units.gridUnit * 25
readonly property var leftPage: window.stack.depth>0 ? window.stack.get(0) : null
Component.onCompleted: {
Helpers.mainWindow = window
if (app.isRoot)
showPassiveNotification(i18n("Running as root is discouraged and unnecessary."));
}
function clearSearch() {
if (loader.item)
loader.item.clearSearch();
}
Component {
id: loadingComponent
Kirigami.Page {
title: label.text
Label {
id: label
text: i18n("Loading...")
font.pointSize: 52
anchors.centerIn: parent
}
}
}
ExclusiveGroup { id: appTabs }
property list awesome: [
TopLevelPageData {
iconName: "tools-wizard"
text: i18n("Discover")
component: topBrowsingComp
objectName: "discover"
shortcut: "Alt+D"
}
]
TopLevelPageData {
id: installedAction
text: TransactionModel.count == 0 ? i18n("Installed") : i18n("Installing...")
component: topInstalledComp
objectName: "installed"
shortcut: "Alt+I"
}
TopLevelPageData {
id: updateAction
iconName: enabled ? "update-low" : "update-none"
text: !enabled ? i18n("No Updates") : i18nc("Update section name", "Update (%1)", ResourcesModel.updatesCount)
enabled: ResourcesModel.updatesCount>0
component: topUpdateComp
objectName: "update"
shortcut: "Alt+U"
}
TopLevelPageData {
id: settingsAction
iconName: "settings"
text: i18n("Settings")
component: topSourcesComp
objectName: "settings"
shortcut: "Alt+S"
}
TopLevelPageData {
id: sources
text: i18n("Configure Sources...")
iconName: "repository"
shortcut: "Alt+S"
component: topSourcesComp
}
Connections {
target: app
onOpenApplicationInternal: {
currentTopLevel = topBrowsingComp;
Navigation.openApplication(app)
}
onListMimeInternal: {
currentTopLevel = topBrowsingComp;
Navigation.openApplicationMime(mime)
}
onListCategoryInternal: {
currentTopLevel = topBrowsingComp;
Navigation.openCategory(cat, "")
}
onPreventedClose: showPassiveNotification(i18n("Could not close the application, there are tasks that need to be done."), 3000)
onUnableToFind: {
showPassiveNotification(i18n("Unable to find resource: %1", resid));
Navigation.openHome()
}
}
globalDrawer: DiscoverDrawer {}
onCurrentTopLevelChanged: {
if(currentTopLevel && currentTopLevel.status==Component.Error) {
console.log("status error: "+currentTopLevel.errorString())
}
var stackView = window.pageStack;
stackView.clear()
if (currentTopLevel)
stackView.push(currentTopLevel, {}, window.status!=Component.Ready)
}
-// ColumnLayout {
-// spacing: 0
-// anchors.fill: parent
-//
-// Repeater {
-// model: MessageActionsModel {
-// filterPriority: QAction.HighPriority
-// }
-// delegate: MessageAction {
-// Layout.fillWidth: true
-// height: Layout.minimumHeight
-// theAction: action
-// }
-// }
-// }
+ Menu {
+ id: actionsMenu
+ }
+
+ Instantiator {
+ model: MessageActionsModel {}
+ delegate: MenuItem {
+ action: ActionBridge { action: model.action }
+ }
+ onObjectAdded: {
+ actionsMenu.insertItem(index, object)
+ }
+ onObjectRemoved: {
+ object.destroy()
+ }
+ }
}
diff --git a/discover/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml
index 0ef9f910..13d1a28c 100644
--- a/discover/qml/SourcesPage.qml
+++ b/discover/qml/SourcesPage.qml
@@ -1,148 +1,159 @@
import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import org.kde.discover 1.0
import org.kde.discover.app 1.0
import org.kde.kquickcontrolsaddons 2.0
import org.kde.kirigami 1.0 as Kirigami
import "navigation.js" as Navigation
DiscoverPage {
id: page
clip: true
title: i18n("Settings")
ListView {
model: SourcesModel
Menu { id: sourcesMenu }
header: PageHeader {
anchors {
left: parent.left
right: parent.right
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.gridUnit
Layout.rightMargin: Kirigami.Units.gridUnit
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
ToolButton {
// iconName: "list-add"
text: i18n("Add Source")
enabled: sourcesMenu.items.count > 0
tooltip: text
menu: sourcesMenu
}
Repeater {
model: SourcesModel.actions
delegate: RowLayout {
QIconItem {
icon: modelData.icon
}
ToolButton {
height: parent.height
action: Action {
readonly property QtObject action: modelData
text: action.text
onTriggered: action.trigger()
enabled: action.enabled
}
}
}
}
+ ToolButton {
+ text: i18n("More...")
+ menu: actionsMenu
+ enabled: actionsMenu.items.length>0
+ }
+
ToolButton {
text: i18n("Help...")
menu: Menu {
MenuItem { action: ActionBridge { action: app.action("help_about_app") } }
MenuItem { action: ActionBridge { action: app.action("help_report_bug") } }
}
}
}
}
delegate: ColumnLayout {
id: sourceDelegate
anchors {
left: parent.left
right: parent.right
leftMargin: Kirigami.Units.largeSpacing
rightMargin: Kirigami.Units.largeSpacing
}
property QtObject sourceBackend: model.sourceBackend
AddSourceDialog {
id: addSourceDialog
source: sourceDelegate.sourceBackend
}
MenuItem {
id: menuItem
text: model.display
onTriggered: {
try {
addSourceDialog.open()
addSourceDialog.visible = true
} catch (e) {
console.log("error loading dialog:", e)
}
}
}
Component.onCompleted: {
sourcesMenu.insertItem(0, menuItem)
}
Kirigami.Heading {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
text: sourceBackend.name
}
spacing: 0
Repeater {
model: sourceBackend.sources
delegate: Kirigami.SwipeListItem {
enabled: display.length>0
onClicked: Navigation.openApplicationListSource(model.display)
actions: [
Kirigami.Action {
enabled: display.length>0
iconName: "view-filter"
tooltip: i18n("Browse the origin's resources")
onTriggered: Navigation.openApplicationListSource(model.display)
},
Kirigami.Action {
iconName: "edit-delete"
tooltip: i18n("Delete the origin")
onTriggered: sourceDelegate.sourceBackend.removeSource(model.display)
}
]
RowLayout {
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
CheckBox {
id: enabledBox
enabled: false //TODO: implement the application of this change
checked: model.checked != Qt.Unchecked
}
Label {
Layout.fillWidth: true
- elide: Text.ElideRight
text: model.display
}
Label {
+ Layout.fillWidth: true
text: model.toolTip
+ elide: Text.ElideRight
}
}
}
}
}
}
}
diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml
index 2bae8b23..f6e2e49a 100644
--- a/discover/qml/UpdatesPage.qml
+++ b/discover/qml/UpdatesPage.qml
@@ -1,251 +1,251 @@
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick 2.1
import org.kde.discover 1.0
import org.kde.discover.app 1.0
import org.kde.kquickcontrolsaddons 2.0
import org.kde.kcoreaddons 1.0
import "navigation.js" as Navigation
import org.kde.kirigami 1.0 as Kirigami
DiscoverPage
{
id: page
title: i18n("Updates")
function start() {
resourcesUpdatesModel.updateAll()
}
property string footerLabel: ""
//TODO: use supportsRefreshing to fetch updates
ListView
{
id: updatesView
ResourcesUpdatesModel {
id: resourcesUpdatesModel
onIsProgressingChanged: {
window.navigationEnabled = !isProgressing
if (!isProgressing) {
resourcesUpdatesModel.prepare()
}
}
Component.onCompleted: {
if (!isProgressing) {
resourcesUpdatesModel.prepare()
}
}
}
UpdateModel {
id: updateModel
backend: resourcesUpdatesModel
}
header: PageHeader {
id: header
anchors {
left: parent.left
right: parent.right
}
background: "qrc:/icons/updatescrop.jpg"
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.gridUnit
Layout.rightMargin: Kirigami.Units.gridUnit
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
- enabled: !resourcesUpdatesModel.isProgressing
visible: resourcesUpdatesModel.isProgressing || updateModel.hasUpdates
LabelBackground {
text: updateModel.toUpdateCount + " (" + updateModel.updateSize+")"
}
Label {
text: i18n("updates selected")
}
LabelBackground {
id: unselectedItem
readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount)
text: unselected
visible: unselected>0
}
Label {
text: i18n("updates not selected")
visible: unselectedItem.visible
}
Item { Layout.fillWidth: true}
Button {
id: startButton
text: unselectedItem.visible ? i18n("Update Selected") : i18n("Update All")
+ enabled: !resourcesUpdatesModel.isProgressing
onClicked: page.start()
}
}
}
footer: ColumnLayout {
anchors.right: parent.right
anchors.left: parent.left
Kirigami.Heading {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
visible: page.footerLabel !== ""
text: page.footerLabel
}
QIconItem {
Layout.alignment: Qt.AlignHCenter
visible: page.footerLabel !== ""
icon: "update-none"
opacity: 0.3
width: 200
height: 200
}
Item {
visible: page.footerLabel === ""
height: Kirigami.Units.gridUnit
width: 1
}
}
model: updateModel
section {
property: "section"
delegate: Kirigami.Heading {
x: Kirigami.Units.gridUnit
level: 2
text: section
}
}
spacing: Kirigami.Units.smallSpacing
delegate: Kirigami.AbstractListItem {
x: Kirigami.Units.gridUnit
width: ListView.view.width - Kirigami.Units.gridUnit * 2
- enabled: !resourcesUpdatesModel.isProgressing
onEnabledChanged: if (!enabled) {
layout.extended = false;
}
ColumnLayout {
id: layout
anchors {
left: parent.left
right: parent.right
}
property bool extended: false
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
CheckBox {
anchors.verticalCenter: parent.verticalCenter
checked: model.checked == Qt.Checked
onClicked: model.checked = (model.checked==Qt.Checked ? Qt.Unchecked : Qt.Checked)
}
QIconItem {
Layout.fillHeight: true
Layout.preferredWidth: height
icon: decoration
}
Label {
Layout.fillWidth: true
text: i18n("%1 (%2)", display, version)
elide: Text.ElideRight
}
LabelBackground {
Layout.minimumWidth: Kirigami.Units.gridUnit * 6
text: size
progress: resourceProgress/100
}
}
ScrollView {
id: view
Layout.fillHeight: true
Layout.fillWidth: true
frameVisible: true
visible: layout.extended && changelog.length>0
Label {
width: view.viewport.width
text: changelog
textFormat: Text.RichText
wrapMode: Text.WordWrap
}
}
Button {
text: i18n("Open")
visible: layout.extended
+ enabled: !resourcesUpdatesModel.isProgressing
onClicked: Navigation.openApplication(resource)
}
}
onClicked: {
updateModel.fetchChangelog(index)
layout.extended = !layout.extended
}
}
}
readonly property var secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate
state: ( ResourcesModel.isFetching ? "fetching"
: updateModel.hasUpdates ? "has-updates"
: resourcesUpdatesModel.isProgressing ? "progressing"
: secSinceUpdate < 0 ? "unknown"
: secSinceUpdate === 0 ? "now-uptodate"
: secSinceUpdate < 1000 * 60 * 60 * 24 ? "uptodate"
: secSinceUpdate < 1000 * 60 * 60 * 24 * 7 ? "medium"
: "low"
)
states: [
State {
name: "fetching"
PropertyChanges { target: page; title: i18nc("@info", "Loading...") }
},
State {
name: "progressing"
PropertyChanges { target: page; title: i18nc("@info", "Updating...") }
PropertyChanges { target: page; footerLabel: resourcesUpdatesModel.progress<=0 ? i18nc("@info", "Fetching updates") : "" }
},
State {
name: "has-updates"
PropertyChanges { target: page; title: i18nc("@info", "Updates") }
},
State {
name: "now-uptodate"
PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") }
PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") }
},
State {
name: "uptodate"
PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") }
},
State {
name: "medium"
PropertyChanges { target: page; title: i18nc("@info", "No updates are available") }
},
State {
name: "low"
PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") }
},
State {
name: "unknown"
PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") }
}
]
}
diff --git a/discover/qml/navigation.js b/discover/qml/navigation.js
index 9f56f343..b4cedcfc 100644
--- a/discover/qml/navigation.js
+++ b/discover/qml/navigation.js
@@ -1,60 +1,62 @@
/*
* 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.
*/
function clearStack()
{
window.currentTopLevel=null
window.stack.clear();
}
function openApplicationListSource(origin) {
openApplicationList({ originFilter: origin, title: origin })
}
function openApplicationMime(mime) {
+ clearStack()
openApplicationList({ mimeTypeFilter: mime , title: i18n("Resources for '%1'", mime) })
}
function openApplicationList(props) {
- clearStack()
var page = window.stack.push(applicationListComp, props)
if (props.search === "")
page.clearSearch()
}
function openCategory(cat, search) {
+ clearStack()
openApplicationList({ category: cat, search: search })
}
function openApplication(app) {
window.stack.push(applicationComp, { application: app })
}
function openReviews(model) {
window.stack.push(reviewsComp, { model: model })
}
function openExtends(ext) {
window.stack.push(applicationListComp, { extend: ext, title: i18n("Extensions...") })
}
function openHome() {
+ window.globalDrawer.resetMenu();
clearStack()
window.stack.push(topBrowsingComp)
}
diff --git a/libdiscover/CMakeLists.txt b/libdiscover/CMakeLists.txt
index 8b9f5a22..c8be46f4 100644
--- a/libdiscover/CMakeLists.txt
+++ b/libdiscover/CMakeLists.txt
@@ -1,61 +1,61 @@
add_definitions(-DTRANSLATION_DOMAIN=\"libdiscover\")
add_subdirectory(backends)
add_subdirectory(declarative)
add_subdirectory(notifiers)
add_subdirectory(tests)
set(discovercommon_SRCS
Category/Category.cpp
Category/CategoryModel.cpp
Category/CategoriesReader.cpp
ReviewsBackend/AbstractReviewsBackend.cpp
ReviewsBackend/Rating.cpp
ReviewsBackend/Review.cpp
ReviewsBackend/AbstractLoginBackend.cpp
ReviewsBackend/ReviewsModel.cpp
ReviewsBackend/PopConParser.cpp
Transaction/AddonList.cpp
Transaction/Transaction.cpp
Transaction/TransactionListener.cpp
Transaction/TransactionModel.cpp
UpdateModel/UpdateItem.cpp
UpdateModel/UpdateModel.cpp
resources/ResourcesModel.cpp
resources/ResourcesProxyModel.cpp
resources/PackageState.cpp
resources/ResourcesUpdatesModel.cpp
resources/StandardBackendUpdater.cpp
resources/SourcesModel.cpp
resources/AbstractResourcesBackend.cpp
resources/AbstractResource.cpp
resources/AbstractBackendUpdater.cpp
resources/AbstractSourcesBackend.cpp
MessageActionsModel
DiscoverBackendsFactory.cpp
ScreenshotsModel.cpp
ApplicationAddonsModel.cpp
)
kconfig_add_kcfg_files(discovercommon_SRCS GENERATE_MOC MuonDataSources.kcfgc)
add_library(DiscoverCommon ${discovercommon_SRCS})
target_link_libraries(DiscoverCommon
LINK_PUBLIC
Qt5::Core
+ Qt5::Qml
Qt5::Widgets
KF5::I18n
LINK_PRIVATE
- Qt5::Qml
Qt5::Xml
KF5::XmlGui
KF5::CoreAddons
)
add_library(Discover::Common ALIAS DiscoverCommon)
generate_export_header(DiscoverCommon)
target_include_directories(DiscoverCommon PRIVATE ${PHONON_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
install(TARGETS DiscoverCommon DESTINATION ${CMAKE_INSTALL_LIBDIR}/plasma-discover)
install(FILES resources/discoverabstractnotifier.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
diff --git a/libdiscover/Category/Category.h b/libdiscover/Category/Category.h
index 5f2ccce3..403df06e 100644
--- a/libdiscover/Category/Category.h
+++ b/libdiscover/Category/Category.h
@@ -1,89 +1,89 @@
/***************************************************************************
* Copyright © 2010 Jonathan Thomas *
* *
* 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 . *
***************************************************************************/
#ifndef CATEGORY_H
#define CATEGORY_H
#include
#include
#include
#include
#include
#include "discovercommon_export.h"
class QDomNode;
enum FilterType {
InvalidFilter,
CategoryFilter,
PkgSectionFilter,
PkgWildcardFilter,
PkgNameFilter
};
class DISCOVERCOMMON_EXPORT Category : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString icon READ icon CONSTANT)
Q_PROPERTY(bool shouldShowTechnical READ shouldShowTechnical CONSTANT)
Q_PROPERTY(QObject* parent READ parent CONSTANT)
Q_PROPERTY(QUrl decoration READ decoration CONSTANT)
Q_PROPERTY(QVariantList subcategories READ subCategoriesVariant CONSTANT)
explicit Category(QSet pluginNames, QObject* parent = nullptr);
~Category() override;
QString name() const;
QString icon() const;
QVector > andFilters() const;
QVector > orFilters() const;
QVector > notFilters() const;
bool shouldShowTechnical() const;
QVector subCategories() const;
QVariantList subCategoriesVariant() const;
static void addSubcategory(QVector& list, Category* cat);
void parseData(const QString& path, const QDomNode& data);
bool blacklistPlugins(const QSet& pluginName);
bool isAddons() const { return m_isAddons; }
QUrl decoration() const;
Q_SCRIPTABLE bool contains(Category* cat) const;
Q_SCRIPTABLE bool contains(const QVariantList &cats) const;
private:
QString m_name;
QString m_iconString;
QUrl m_decoration;
QVector > m_andFilters;
QVector > m_orFilters;
QVector > m_notFilters;
bool m_showTechnical;
QVector m_subCategories;
QVector > parseIncludes(const QDomNode &data);
QSet m_plugins;
bool m_isAddons = false;
};
-Q_DECLARE_METATYPE(QList);
+Q_DECLARE_METATYPE(QList)
#endif
diff --git a/libdiscover/backends/ApplicationBackend/Application.cpp b/libdiscover/backends/ApplicationBackend/Application.cpp
index 2df0bbb6..ae1d95ed 100644
--- a/libdiscover/backends/ApplicationBackend/Application.cpp
+++ b/libdiscover/backends/ApplicationBackend/Application.cpp
@@ -1,545 +1,546 @@
/***************************************************************************
* Copyright © 2010-2011 Jonathan Thomas *
* *
* 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 . *
***************************************************************************/
#include "Application.h"
// Qt includes
#include
#include
#include
#include
#include
+#include
#include
// KDE includes
#include
#include
#include
#include
#include
#include
#include
#include
// QApt includes
#include
#include
#include
#include
#include
#include "ApplicationBackend.h"
#include "resources/PackageState.h"
Application::Application(const Appstream::Component &component, QApt::Backend* backend)
: AbstractResource(nullptr)
, m_data(component)
, m_package(nullptr)
, m_isValid(true)
, m_isTechnical(component.kind() != Appstream::Component::KindDesktop)
, m_isExtrasApp(false)
, m_sourceHasScreenshot(true)
{
static QByteArray currentDesktop = qgetenv("XDG_CURRENT_DESKTOP");
Q_ASSERT(component.packageNames().count() == 1);
if (!component.packageNames().isEmpty())
m_packageName = component.packageNames().at(0);
m_package = backend->package(packageName());
m_isValid = bool(m_package);
}
Application::Application(QApt::Package* package, QApt::Backend* backend)
: AbstractResource(nullptr)
, m_package(package)
, m_packageName(m_package->name())
, m_isValid(true)
, m_isTechnical(true)
, m_isExtrasApp(false)
{
QString arch = m_package->architecture();
if (arch != backend->nativeArchitecture() && arch != QLatin1String("all")) {
m_packageName.append(QLatin1Char(':'));
m_packageName.append(arch);
}
if (m_package->origin() == QLatin1String("LP-PPA-app-review-board")) {
if (!m_package->controlField(QLatin1String("Appname")).isEmpty()) {
m_isExtrasApp = true;
m_isTechnical = false;
}
}
}
QString Application::name()
{
QString name = m_data.isValid() ? m_data.name() : QString();
if (name.isEmpty() && package()) {
// extras.ubuntu.com packages can have this
if (m_isExtrasApp)
name = m_package->controlField(QLatin1String("Appname"));
else
name = m_package->name();
}
if (package() && m_package->isForeignArch())
name = i18n("%1 (%2)", name, m_package->architecture());
return name;
}
QString Application::comment()
{
QString comment = m_data.isValid() ? m_data.summary() : QString();
if (comment.isEmpty()) {
return package()->shortDescription();
}
return i18n(comment.toUtf8().constData());
}
QString Application::packageName() const
{
return m_packageName;
}
QApt::Package *Application::package()
{
if (!m_package && parent()) {
m_package = backend()->package(packageName());
Q_EMIT stateChanged();
}
// Packages removed from archive will remain in app-install-data until the
// next refresh, so we can have valid .desktops with no package
if (!m_package) {
m_isValid = false;
}
return m_package;
}
-QString Application::icon() const
+QVariant Application::icon() const
{
QIcon ret;
const auto icons = m_appdata.iconUrls();
if (icons.isEmpty())
return m_appdata.name();
else {
for (auto it = icons.constBegin(), itEnd = icons.constEnd(); it!=itEnd; ++it) {
if (it->isLocalFile())
ret.addFile(it->toLocalFile(), it.key());
}
}
return ret;
}
QStringList Application::findProvides(Appstream::Provides::Kind kind) const
{
QStringList ret;
Q_FOREACH (Appstream::Provides p, m_data.provides())
if (p.kind() == kind)
ret += p.value();
return ret;
}
QStringList Application::mimetypes() const
{
return findProvides(Appstream::Provides::KindMimetype);
}
QStringList Application::categories()
{
QStringList categories = m_data.isValid() ? m_data.categories() : QStringList();
if (categories.isEmpty()) {
// extras.ubuntu.com packages can have this field
if (m_isExtrasApp)
categories = package()->controlField(QLatin1String("Category")).split(QLatin1Char(';'));
}
return categories;
}
QUrl Application::thumbnailUrl()
{
QUrl url(package()->controlField(QLatin1String("Thumbnail-Url")));
if(m_sourceHasScreenshot) {
url = QUrl(MuonDataSources::screenshotsSource().toString() + QStringLiteral("/thumbnail/") + packageName());
}
return url;
}
QUrl Application::screenshotUrl()
{
QUrl url(package()->controlField(QLatin1String("Screenshot-Url")));
if(m_sourceHasScreenshot) {
url = QUrl(MuonDataSources::screenshotsSource().toString() + QStringLiteral("/screenshot/") + packageName());
}
return url;
}
QString Application::license()
{
QString component = package()->component();
if (component == QLatin1String("main") || component == QLatin1String("universe")) {
return i18nc("@info license", "Open Source");
} else if (component == QLatin1String("restricted")) {
return i18nc("@info license", "Proprietary");
} else {
return i18nc("@info license", "Unknown");
}
}
QApt::PackageList Application::addons()
{
QApt::PackageList addons;
QApt::Package *pkg = package();
if (!pkg) {
return addons;
}
QStringList tempList;
// Only add recommends or suggests to the list if they aren't already going to be
// installed
if (!backend()->config()->readEntry(QStringLiteral("APT::Install-Recommends"), true)) {
tempList << m_package->recommendsList();
}
if (!backend()->config()->readEntry(QStringLiteral("APT::Install-Suggests"), false)) {
tempList << m_package->suggestsList();
}
tempList << m_package->enhancedByList();
QStringList languagePackages;
QFile l10nFilterFile(QStringLiteral("/usr/share/language-selector/data/pkg_depends"));
if (l10nFilterFile.open(QFile::ReadOnly)) {
QString contents = QString::fromLatin1(l10nFilterFile.readAll());
foreach (const QString &line, contents.split(QLatin1Char('\n'))) {
if (line.startsWith(QLatin1Char('#'))) {
continue;
}
languagePackages << line.split(QLatin1Char(':')).last();
}
languagePackages.removeAll(QString());
}
foreach (const QString &addon, tempList) {
bool shouldShow = true;
QApt::Package *package = backend()->package(addon);
if (!package || QString(package->section()).contains(QLatin1String("lib")) || addons.contains(package)) {
continue;
}
foreach (const QString &langpack, languagePackages) {
if (addon.contains(langpack)) {
shouldShow = false;
break;
}
}
if (shouldShow) {
addons << package;
}
}
return addons;
}
QList Application::addonsInformation()
{
QList ret;
QApt::PackageList pkgs = addons();
foreach(QApt::Package* p, pkgs) {
ret += PackageState(p->name(), p->shortDescription(), p->isInstalled());
}
return ret;
}
bool Application::isValid() const
{
return m_isValid;
}
bool Application::isTechnical() const
{
return m_isTechnical;
}
QUrl Application::homepage()
{
if(!m_package) return QUrl();
return QUrl(m_package->homepage());
}
QString Application::origin() const
{
if(!m_package) return QString();
return m_package->origin();
}
QString Application::longDescription()
{
const QString comment = m_data.isValid() ? m_data.description() : QString();
if(!comment.isEmpty()) return comment;
if(m_package) return QString();
return m_package->longDescription();
}
QString Application::availableVersion() const
{
if(!m_package) return QString();
return m_package->availableVersion();
}
QString Application::installedVersion() const
{
if(!m_package) return QString();
return m_package->installedVersion();
}
QString Application::sizeDescription()
{
KFormat f;
if (!isInstalled()) {
return i18nc("@info app size", "%1 to download, %2 on disk",
f.formatByteSize(package()->downloadSize()),
f.formatByteSize(package()->availableInstalledSize()));
} else {
return i18nc("@info app size", "%1 on disk",
f.formatByteSize(package()->currentInstalledSize()));
}
}
int Application::size()
{
return m_package->downloadSize();
}
void Application::clearPackage()
{
m_package = nullptr;
}
QVector Application::findExecutables() const
{
QVector ret;
if (!m_package) {
qWarning() << "trying to find the executables for an uninitialized package!" << packageName();
return ret;
}
QRegExp rx(QStringLiteral(".+\\.desktop$"), Qt::CaseSensitive);
foreach (const QString &desktop, m_package->installedFilesList().filter(rx)) {
// Important to use serviceByStorageId to ensure we get a service even
// if the KSycoca database doesn't have our .desktop file yet.
KService::Ptr service = KService::serviceByStorageId(desktop);
if (service &&
service->isApplication() &&
!service->noDisplay() &&
!service->exec().isEmpty())
{
ret << service;
}
}
return ret;
}
void Application::emitStateChanged()
{
emit stateChanged();
}
void Application::invokeApplication() const
{
QVector< KService::Ptr > execs = findExecutables();
Q_ASSERT(!execs.isEmpty());
KToolInvocation::startServiceByDesktopName(execs.first()->desktopEntryName());
}
bool Application::canExecute() const
{
return !findExecutables().isEmpty();
}
QString Application::section()
{
return package()->section();
}
AbstractResource::State Application::state()
{
if (!package())
return Broken;
int s = package()->state();
if (s & QApt::Package::Upgradeable) {
#if QAPT_VERSION >= QT_VERSION_CHECK(3, 1, 0)
if (package()->isInUpdatePhase())
return Upgradeable;
#else
return Upgradeable;
#endif
}
if (s & QApt::Package::Installed) {
return Installed;
}
return None; // Actually: none of interest to us here in plasma-discover.
}
void Application::fetchScreenshots()
{
if(!m_sourceHasScreenshot)
return;
QString dest = QStandardPaths::locate(QStandardPaths::TempLocation, QStringLiteral("screenshots.")+m_packageName);
const QUrl packageUrl(MuonDataSources::screenshotsSource().toString() + QStringLiteral("/json/package/")+m_packageName);
KIO::StoredTransferJob* job = KIO::storedGet(packageUrl, KIO::NoReload, KIO::HideProgressInfo);
connect(job, &KIO::StoredTransferJob::finished, this, &Application::downloadingScreenshotsFinished);
}
void Application::downloadingScreenshotsFinished(KJob* j)
{
KIO::StoredTransferJob* job = qobject_cast< KIO::StoredTransferJob* >(j);
bool done = false;
if(job) {
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(job->data(), &error);
if(error.error != QJsonParseError::NoError) {
QVariantMap values = doc.toVariant().toMap();
QVariantList screenshots = values[QStringLiteral("screenshots")].toList();
QList thumbnailUrls, screenshotUrls;
foreach(const QVariant& screenshot, screenshots) {
QVariantMap s = screenshot.toMap();
thumbnailUrls += s[QStringLiteral("small_image_url")].toUrl();
screenshotUrls += s[QStringLiteral("large_image_url")].toUrl();
}
emit screenshotsFetched(thumbnailUrls, screenshotUrls);
done = true;
}
}
if(!done) {
QList thumbnails, screenshots;
if(!thumbnailUrl().isEmpty()) {
thumbnails += thumbnailUrl();
screenshots += screenshotUrl();
}
emit screenshotsFetched(thumbnails, screenshots);
}
}
void Application::setHasScreenshot(bool has)
{
m_sourceHasScreenshot = has;
}
QStringList Application::executables() const
{
QStringList ret;
const QVector exes = findExecutables();
for(KService::Ptr exe : exes) {
ret += exe->exec();
}
return ret;
}
bool Application::isFromSecureOrigin() const
{
Q_FOREACH (const QString &archive, m_package->archives()) {
if (archive.contains(QLatin1String("security"))) {
return true;
}
}
return false;
}
void Application::fetchChangelog()
{
KIO::StoredTransferJob* getJob = KIO::storedGet(m_package->changelogUrl(), KIO::NoReload, KIO::HideProgressInfo);
connect(getJob, &KIO::StoredTransferJob::result, this, &Application::processChangelog);
}
void Application::processChangelog(KJob* j)
{
KIO::StoredTransferJob* job = qobject_cast(j);
if (!m_package || !job) {
return;
}
QString changelog;
if(j->error()==0)
changelog = buildDescription(job->data(), m_package->sourcePackage());
if (changelog.isEmpty()) {
if (m_package->origin() == QStringLiteral("Ubuntu")) {
changelog = xi18nc("@info/rich", "The list of changes is not yet available. "
"Please use Launchpad instead.",
QStringLiteral("http://launchpad.net/ubuntu/+source/") + m_package->sourcePackage());
} else {
changelog = xi18nc("@info", "The list of changes is not yet available.");
}
}
emit changelogFetched(changelog);
}
QString Application::buildDescription(const QByteArray& data, const QString& source)
{
QApt::Changelog changelog(QString::fromLatin1(data), source);
QString description;
QApt::ChangelogEntryList entries = changelog.newEntriesSince(m_package->installedVersion());
if (entries.size() < 1) {
return description;
}
foreach(const QApt::ChangelogEntry &entry, entries) {
description += i18nc("@info:label Refers to a software version, Ex: Version 1.2.1:",
"Version %1:", entry.version());
KFormat f;
QString issueDate = entry.issueDateTime().toString(Qt::DefaultLocaleShortDate);
description += QLatin1String("") +
i18nc("@info:label", "This update was issued on %1", issueDate) +
QLatin1String("
");
QString updateText = entry.description();
updateText.replace(QLatin1Char('\n'), QLatin1String("
"));
description += QLatin1String("") + updateText + QLatin1String("
");
}
return description;
}
QApt::Backend *Application::backend() const
{
return qobject_cast(parent())->backend();
}
diff --git a/libdiscover/backends/ApplicationBackend/CMakeLists.txt b/libdiscover/backends/ApplicationBackend/CMakeLists.txt
index 0c0eda63..cc7b35fd 100644
--- a/libdiscover/backends/ApplicationBackend/CMakeLists.txt
+++ b/libdiscover/backends/ApplicationBackend/CMakeLists.txt
@@ -1,51 +1,53 @@
# we will have our own fork of the library now, because they haven't still made their mind out of Qt5
# find_package(QtOAuth REQUIRED)
add_subdirectory(qoauth)
add_subdirectory(libmuonapt)
include_directories(.)
add_subdirectory(tests)
set(appsbackend_SRCS
ApplicationBackend.cpp
Application.cpp
ApplicationUpdates.cpp
ReviewsBackend.cpp #TODO: rename to AptReviewsBackend
UbuntuLoginBackend.cpp
AptSourcesBackend.cpp
)
qt5_add_dbus_interface(appsbackend_SRCS ubuntu_sso_dbus_interface.xml ubuntu_sso OPTIONS -i "LoginMetaTypes.h")
add_library(qapt-backend MODULE ${appsbackend_SRCS})
target_link_libraries(qapt-backend Qt5::Widgets Qt5::DBus Qt5::Concurrent
KF5::Archive KF5::KIOWidgets KF5::XmlGui DebconfKDE::Main KF5::IconThemes AppstreamQt
Muon::QOAuth QApt::Main Discover::Common MuonApt
)
target_include_directories(qapt-backend PRIVATE /usr/include/Qca-qt5/QtCrypto)
install(TARGETS qapt-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
install(FILES qapt-backend.desktop DESTINATION ${DATA_INSTALL_DIR}/libdiscover/backends)
-install(FILES qapt-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
install(FILES distupgradeevent/releasechecker DESTINATION ${DATA_INSTALL_DIR}/libdiscover/applicationsbackend/
PERMISSIONS
OWNER_EXECUTE OWNER_READ OWNER_WRITE
GROUP_EXECUTE GROUP_READ
WORLD_EXECUTE WORLD_READ
)
foreach(testing IN ITEMS ON OFF)
set(name MuonApplicationNotifier)
set(type MODULE)
if(${testing})
set(name MuonApplicationNotifierTestLib)
set(type STATIC)
endif()
add_library(${name} ${type} ApplicationNotifier.cpp)
target_compile_definitions(${name} PRIVATE -DCMAKE_INSTALL_FULL_LIBEXECDIR_KF5=\"${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}\")
target_link_libraries(${name} KF5::CoreAddons KF5::I18n KF5::Notifications KF5::IconThemes Discover::Notifiers)
endforeach()
install(TARGETS MuonApplicationNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
install(FILES muonapplicationnotifier.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
+
+install(FILES ../PackageKitBackend/packagekit-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories/ RENAME qapt-backend-categories.xml)
+# add_subdirectory(${CMAKE_SOURCE_DIR}/libdiscover/backends/PackageKitBackend/categoryimages)
diff --git a/libdiscover/backends/ApplicationBackend/tests/ApplicationBackendTest.cpp b/libdiscover/backends/ApplicationBackend/tests/ApplicationBackendTest.cpp
index 68206c79..b17c15e8 100644
--- a/libdiscover/backends/ApplicationBackend/tests/ApplicationBackendTest.cpp
+++ b/libdiscover/backends/ApplicationBackend/tests/ApplicationBackendTest.cpp
@@ -1,156 +1,154 @@
/***************************************************************************
* Copyright © 2012 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 . *
***************************************************************************/
#include "ApplicationBackendTest.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
QTEST_MAIN( ApplicationBackendTest )
QString getCodename(const QString& value)
{
QString ret;
QFile f(QStringLiteral("/etc/os-release"));
if(f.open(QIODevice::ReadOnly|QIODevice::Text)){
QRegExp rx(QStringLiteral("%1=(.+)\n").arg(value));
while(!f.atEnd()) {
QString line = QString::fromUtf8(f.readLine());
if(rx.exactMatch(line)) {
ret = rx.cap(1);
break;
}
}
}
return ret;
}
AbstractResourcesBackend* backendByName(ResourcesModel* m, const QString& name)
{
QVector backends = m->backends();
foreach(AbstractResourcesBackend* backend, backends) {
if(QString::fromLatin1(backend->metaObject()->className())==name) {
return backend;
}
}
return nullptr;
}
ApplicationBackendTest::ApplicationBackendTest()
{
QString ratingsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+QStringLiteral("/libdiscover/ratings.txt");
QFile testRatings(QStringLiteral("~/.kde-unit-test/share/apps/libdiscover/ratings.txt"));
QFile ratings(ratingsDir);
QString codeName = getCodename(QStringLiteral("ID"));
if(!testRatings.exists()) {
if(ratings.exists()) {
ratings.copy(testRatings.fileName());
} else {
ratings.close();
if(codeName.toLower() == QLatin1String("ubuntu")) {
ratingsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+QStringLiteral("/libdiscover/rnrtestratings.txt");
} else {
ratingsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+QStringLiteral("/libdiscover/popcontestratings.txt");
}
ratings.setFileName(ratingsDir);
if(ratings.exists()) {
ratings.copy(testRatings.fileName());
}
}
testRatings.close();
ratings.close();
}
ResourcesModel* m = new ResourcesModel(QStringLiteral("qapt-backend"), this);
m_window = new KActionCollection(this, QStringLiteral("ApplicationBackendTest"));
m->integrateActions(m_window);
new ModelTest(m,m);
m_appBackend = backendByName(m, QStringLiteral("ApplicationBackend"));
QVERIFY(m_appBackend); //TODO: test all backends
QSignalSpy s(m, SIGNAL(allInitialized()));
QVERIFY(s.wait());
}
ApplicationBackendTest::~ApplicationBackendTest()
{
delete m_window;
}
void ApplicationBackendTest::testReload()
{
ResourcesModel* model = ResourcesModel::global();
QVector apps = m_appBackend->allResources();
QCOMPARE(apps.count(), model->rowCount());
QVector appNames(apps.size());
for(int i=0; irowCount(); ++i) {
AbstractResource* app = apps[i];
appNames[i]=app->property("packageName");
}
bool b = QMetaObject::invokeMethod(m_appBackend, "reload");
QVERIFY(b);
m_appBackend->updatesCount();
QCOMPARE(apps, m_appBackend->allResources() );
QVERIFY(!apps.isEmpty());
QCOMPARE(apps.count(), model->rowCount());
for(int i=0; irowCount(); ++i) {
AbstractResource* app = apps[i];
QCOMPARE(appNames[i], app->property("packageName"));
// QCOMPARE(m_model->data(m_model->index(i), ResourcesModel::NameRole).toString(), app->name());
}
}
void ApplicationBackendTest::testCategories()
{
ResourcesProxyModel* proxy = new ResourcesProxyModel(this);
- CategoryModel* categoryModel = new CategoryModel(proxy);
- categoryModel->setDisplayedCategory(nullptr);
- for(int i=0; irowCount(); ++i) {
- Category* cat = categoryModel->categoryForRow(i);
+ const auto cats = CategoryModel::rootCategories();
+ for(auto cat: cats) {
proxy->setFiltersFromCategory(cat);
}
}
void ApplicationBackendTest::testRefreshUpdates()
{
ResourcesModel* m = ResourcesModel::global();
QSignalSpy spy(m, SIGNAL(fetchingChanged()));
m_window->action(QStringLiteral("update"))->trigger();
// QTest::kWaitForSignal(m, SLOT(fetchingChanged()));
QVERIFY(!m->isFetching());
qDebug() << spy.count();
}
diff --git a/libdiscover/backends/PackageKitBackend/CMakeLists.txt b/libdiscover/backends/PackageKitBackend/CMakeLists.txt
index 43c7541f..e31ea869 100644
--- a/libdiscover/backends/PackageKitBackend/CMakeLists.txt
+++ b/libdiscover/backends/PackageKitBackend/CMakeLists.txt
@@ -1,43 +1,26 @@
find_package(KF5KIO REQUIRED)
find_package(KF5Archive REQUIRED)
add_library(packagekit-backend MODULE PackageKitBackend.cpp
PackageKitResource.cpp
AppPackageKitResource.cpp
PKTransaction.cpp
PackageKitUpdater.cpp
PackageKitMessages.cpp
PackageKitSourcesBackend.cpp
AppstreamReviews.cpp
)
target_link_libraries(packagekit-backend PRIVATE Discover::Common Qt5::Core PK::packagekitqt5 KF5::ConfigGui KF5::Service KF5::KIOWidgets KF5::Archive AppstreamQt)
install(TARGETS packagekit-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
install(FILES packagekit-backend.desktop DESTINATION ${DATA_INSTALL_DIR}/libdiscover/backends)
-install(FILES packagekit-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
#notifier
add_library(DiscoverPackageKitNotifier MODULE PackageKitNotifier.cpp)
target_link_libraries(DiscoverPackageKitNotifier PRIVATE PK::packagekitqt5 Discover::Notifiers)
set_target_properties(DiscoverPackageKitNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
install(TARGETS DiscoverPackageKitNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
-install( FILES
- categoryimages/educationcrop.jpg
- categoryimages/fontcrop.jpg
- categoryimages/gamecrop.jpg
- categoryimages/graphiccrop.jpg
-# categoryimages/multimedia2.jpg
- categoryimages/multimediacrop.jpg
-# categoryimages/music.jpe
- categoryimages/officecrop.jpg
- categoryimages/sciencecrop.jpg
- categoryimages/accesscropped.jpg
- categoryimages/toolcrop.jpg
- categoryimages/accesscropped.jpg
- categoryimages/accessoriescrop.jpg
- categoryimages/applicationcrop.jpg
- categoryimages/internetcrop.jpg
- categoryimages/settingscrop.jpg
- DESTINATION ${KDE_INSTALL_FULL_DATAROOTDIR}/discover/pkcategories)
+install(FILES packagekit-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
+add_subdirectory(categoryimages)
diff --git a/libdiscover/backends/PackageKitBackend/categoryimages/CMakeLists.txt b/libdiscover/backends/PackageKitBackend/categoryimages/CMakeLists.txt
new file mode 100644
index 00000000..aa72a703
--- /dev/null
+++ b/libdiscover/backends/PackageKitBackend/categoryimages/CMakeLists.txt
@@ -0,0 +1,18 @@
+install( FILES
+ educationcrop.jpg
+ fontcrop.jpg
+ gamecrop.jpg
+ graphiccrop.jpg
+# multimedia2.jpg
+ multimediacrop.jpg
+# music.jpe
+ officecrop.jpg
+ sciencecrop.jpg
+ accesscropped.jpg
+ toolcrop.jpg
+ accesscropped.jpg
+ accessoriescrop.jpg
+ applicationcrop.jpg
+ internetcrop.jpg
+ settingscrop.jpg
+ DESTINATION ${KDE_INSTALL_FULL_DATAROOTDIR}/discover/pkcategories)