diff --git a/discover/qml/DiscoverWindow.qml b/discover/qml/DiscoverWindow.qml
index d2b44b16..e56b1fe8 100644
--- a/discover/qml/DiscoverWindow.qml
+++ b/discover/qml/DiscoverWindow.qml
@@ -1,207 +1,220 @@
-import QtQuick 2.1
+import QtQuick 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import QtQuick.Controls 2.1 as QQC2
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.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 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 : ""
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: {
if (app.isRoot)
showPassiveNotification(i18n("Running as root is discouraged and unnecessary."));
}
TopLevelPageData {
iconName: "tools-wizard"
text: i18n("Discover")
component: topBrowsingComp
objectName: "discover"
shortcut: "Alt+D"
}
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"
shortcut: "Alt+I"
}
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"
shortcut: "Alt+U"
}
TopLevelPageData {
id: settingsAction
iconName: "settings"
text: i18n("Settings")
component: topSourcesComp
objectName: "settings"
shortcut: "Alt+S"
}
+ Action {
+ id: refreshAction
+ readonly property QtObject action: ResourcesModel.updateAction
+ text: action.text
+ onTriggered: action.trigger()
+ enabled: action.enabled
+ tooltip: seq.nativeText
+ shortcut: Shortcut {
+ id: seq
+ sequence: "Ctrl+R"
+ }
+ }
+
Connections {
target: app
onOpenApplicationInternal: {
Navigation.clearStack()
Navigation.openApplication(app)
}
onOpenUrl: {
currentTopLevel = topBrowsingComp;
Navigation.openUrlResources(url)
}
onListMimeInternal: {
currentTopLevel = topBrowsingComp;
Navigation.openApplicationMime(mime)
}
onListCategoryInternal: {
currentTopLevel = topBrowsingComp;
Navigation.openCategory(cat, "")
}
onOpenSearch: {
Navigation.clearStack()
Navigation.openApplicationList({search: search})
}
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()
}
}
Connections {
target: ResourcesModel
onPassiveMessage: {
showPassiveNotification(message, 3000)
console.log("message:", message)
}
}
Component {
id: proceedDialog
Kirigami.OverlaySheet {
id: sheet
property QtObject transaction
property alias title: heading.text
property alias description: desc.text
property bool acted: false
ColumnLayout {
Kirigami.Heading {
id: heading
}
QQC2.Label {
id: desc
Layout.fillWidth: true
textFormat: Text.StyledText
wrapMode: Text.WordWrap
}
RowLayout {
Layout.alignment: Qt.AlignRight
Button {
text: i18n("Proceed")
iconName: "dialog-ok"
onClicked: {
transaction.proceed()
sheet.acted = true
sheet.close()
}
}
Button {
Layout.alignment: Qt.AlignRight
text: i18n("Cancel")
iconName: "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)
}
}
}
globalDrawer: DiscoverDrawer {
wideScreen: window.wideScreen
focus: true
}
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/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml
index 947af891..9df9dec1 100644
--- a/discover/qml/SourcesPage.qml
+++ b/discover/qml/SourcesPage.qml
@@ -1,228 +1,209 @@
import QtQuick 2.4
import QtQuick.Controls 1.1
import QtQuick.Controls 2.1 as QQC2
import QtQuick.Layouts 1.1
import org.kde.discover 2.0
import org.kde.discover.app 1.0
import org.kde.kquickcontrolsaddons 2.0
import org.kde.kirigami 2.1 as Kirigami
import "navigation.js" as Navigation
DiscoverPage {
id: page
clip: true
title: i18n("Settings")
property string search: ""
readonly property var fu: Instantiator {
model: SourcesModel
delegate: QtObject {
readonly property var sourcesModel: sourceBackend.sources
readonly property var a: Connections {
target: sourceBackend
onPassiveMessage: window.showPassiveNotification(message)
}
}
onObjectAdded: {
everySourceModel.addSourceModel(object.sourcesModel)
}
onObjectRemoved: {
everySourceModel.removeSourceModel(object.sourcesModel)
}
}
header: QQC2.ToolBar {
anchors {
right: parent.right
left: parent.left
}
contentItem: RowLayout {
anchors {
topMargin: Kirigami.Units.smallSpacing
bottomMargin: Kirigami.Units.smallSpacing
}
Item {
Layout.fillWidth: true
}
- Repeater {
- model: ActionsModel {
- actions: ResourcesModel.actions
- }
-
- delegate: RowLayout {
- Kirigami.Icon {
- source: modelData.icon
- }
- visible: theAction.action && theAction.action.visible
- ToolButton {
- height: parent.height
- action: Action {
- id: theAction
- readonly property QtObject action: modelData
- text: action.text
- onTriggered: action.trigger()
- enabled: action.enabled
- }
- }
- }
+ ToolButton {
+ action: refreshAction
}
ToolButton {
text: i18n("Help...")
menu: Menu {
MenuItem { action: ActionBridge { action: app.action("help_about_app") } }
MenuItem { action: ActionBridge { action: app.action("help_report_bug") } }
}
}
}
}
mainItem: ListView {
id: sourcesView
model: QSortFilterProxyModel{
filterRegExp: new RegExp(page.search, 'i')
sourceModel: KConcatenateRowsProxyModel {
id: everySourceModel
}
}
currentIndex: -1
section {
property: "statusTip"
delegate: RowLayout {
anchors {
right: parent.right
left: parent.left
}
Kirigami.Heading {
Layout.fillWidth: true
leftPadding: Kirigami.Units.largeSpacing
text: settingsButton.isDefault ? i18n("%1 (Default)", section) : section
}
Button {
id: settingsButton
Layout.rightMargin: Kirigami.Units.smallSpacing
iconName: "preferences-other"
readonly property QtObject backend: SourcesModel.backendForSection(section)
readonly property bool isDefault: ResourcesModel.currentApplicationBackend == settingsButton.backend.resourcesBackend
visible: backend
AddSourceDialog {
id: addSourceDialog
source: settingsButton.backend
}
menu: Menu {
id: settingsMenu
MenuItem {
enabled: !settingsButton.isDefault
text: i18n("Make default")
onTriggered: ResourcesModel.currentApplicationBackend = settingsButton.backend.resourcesBackend
}
MenuItem {
text: i18n("Add Source")
onTriggered: addSourceDialog.open()
}
MenuSeparator {
visible: backendActionsInst.count>0
}
Instantiator {
id: backendActionsInst
model: ActionsModel {
actions: settingsButton.backend ? settingsButton.backend.actions : null
}
delegate: MenuItem {
action: ActionBridge { action: model.action }
}
onObjectAdded: {
settingsMenu.insertItem(index, object)
}
onObjectRemoved: {
object.destroy()
}
}
}
}
}
}
delegate: Kirigami.SwipeListItem {
Layout.fillWidth: true
enabled: display.length>0
highlighted: ListView.isCurrentItem
onClicked: Navigation.openApplicationListSource(model.display)
readonly property string backendName: model.statusTip
readonly property variant modelIndex: sourcesView.model.index(model.index, 0)
Keys.onReturnPressed: clicked()
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: {
var backend = sourcesView.model.data(modelIndex, AbstractSourcesBackend.SourcesBackend)
if (!backend.removeSource(model.display)) {
window.showPassiveNotification(i18n("Failed to remove the source '%1'", model.display))
}
}
}
]
RowLayout {
CheckBox {
id: enabledBox
readonly property variant modelChecked: sourcesView.model.data(modelIndex, Qt.CheckStateRole)
checked: modelChecked != Qt.Unchecked
enabled: modelChecked !== undefined
onClicked: {
model.checked = checkedState
}
}
QQC2.Label {
text: model.display + " - " + model.toolTip + ""
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
footer: ColumnLayout {
id: foot
anchors {
right: parent.right
left: parent.left
margins: Kirigami.Units.smallSpacing
}
Repeater {
id: back
model: ResourcesProxyModel {
extending: "org.kde.discover.desktop"
}
delegate: RowLayout {
visible: !model.application.isInstalled
QQC2.Label {
Layout.fillWidth: true
text: name
}
InstallApplicationButton {
application: model.application
}
}
}
}
}
}
diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml
index acf9ca2c..65b6854e 100644
--- a/discover/qml/UpdatesPage.qml
+++ b/discover/qml/UpdatesPage.qml
@@ -1,276 +1,280 @@
import QtQuick.Controls 1.2
import QtQuick.Controls 2.1 as QQC2
import QtQuick.Layouts 1.1
import QtQuick 2.4
import org.kde.discover 2.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 2.1 as Kirigami
DiscoverPage
{
id: page
title: i18n("Updates")
property string footerLabel: ""
ResourcesUpdatesModel {
id: resourcesUpdatesModel
onPassiveMessage: window.showPassiveNotification(message)
onIsProgressingChanged: {
if (!isProgressing) {
resourcesUpdatesModel.prepare()
}
}
Component.onCompleted: {
if (!isProgressing) {
resourcesUpdatesModel.prepare()
}
}
}
UpdateModel {
id: updateModel
backend: resourcesUpdatesModel
}
Kirigami.Action
{
id: updateAction
text: page.unselected>0 ? i18n("Update Selected") : i18n("Update All")
visible: updateModel.toUpdateCount
iconName: "update-none"
enabled: !resourcesUpdatesModel.isProgressing
onTriggered: resourcesUpdatesModel.updateAll()
}
Kirigami.Action
{
id: cancelUpdateAction
iconName: "dialog-cancel"
text: i18n("Cancel")
enabled: resourcesUpdatesModel.transaction && resourcesUpdatesModel.transaction.isCancellable
onTriggered: resourcesUpdatesModel.transaction.cancel()
}
readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount)
readonly property QtObject currentAction: resourcesUpdatesModel.isProgressing ? cancelUpdateAction : updateAction
actions.main: applicationWindow().wideScreen ? null : currentAction
header: QQC2.ToolBar {
visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates
RowLayout {
anchors.fill: parent
LabelBackground {
Layout.leftMargin: Kirigami.Units.gridUnit
text: updateModel.toUpdateCount + " (" + updateModel.updateSize+")"
}
QQC2.Label {
text: i18n("updates selected")
}
LabelBackground {
id: unselectedItem
text: page.unselected
visible: page.unselected>0
}
QQC2.Label {
text: i18n("updates not selected")
visible: unselectedItem.visible
}
Item {
Layout.fillWidth: true
}
Button {
Layout.minimumWidth: Kirigami.Units.gridUnit * 6
Layout.rightMargin: Kirigami.Units.gridUnit
text: page.currentAction.text
visible: !page.actions.main
onClicked: page.currentAction.trigger()
}
}
}
- //TODO: use supportsRefreshing to fetch updates
+ supportsRefreshing: true
+ onRefreshingChanged: {
+ showPassiveNotification("Fetching updates...")
+ ResourcesModel.updateAction.triggered()
+ }
ListView
{
id: updatesView
currentIndex: -1
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
}
Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter
visible: page.footerLabel !== ""
source: "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 {
backgroundColor: Kirigami.Theme.viewBackgroundColor
x: Kirigami.Units.gridUnit
width: ListView.view.width - Kirigami.Units.gridUnit * 2
highlighted: ListView.isCurrentItem
onEnabledChanged: if (!enabled) {
layout.extended = false;
}
Keys.onReturnPressed: {
itemChecked.clicked()
}
Keys.onPressed: if (event.key===Qt.Key_Alt) layout.extended = true
Keys.onReleased: if (event.key===Qt.Key_Alt) layout.extended = false
ColumnLayout {
id: layout
property bool extended: false
onExtendedChanged: if (extended) {
updateModel.fetchChangelog(index)
}
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
CheckBox {
id: itemChecked
anchors.verticalCenter: parent.verticalCenter
checked: model.checked == Qt.Checked
onClicked: model.checked = (model.checked==Qt.Checked ? Qt.Unchecked : Qt.Checked)
}
Kirigami.Icon {
Layout.fillHeight: true
Layout.preferredWidth: height
source: decoration
}
QQC2.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
}
}
QQC2.Frame {
Layout.fillWidth: true
Layout.minimumHeight: view.contentHeight
visible: layout.extended && changelog.length>0
QQC2.Label {
id: view
anchors {
right: parent.right
left: parent.left
}
text: changelog
textFormat: Text.StyledText
wrapMode: Text.WordWrap
onLinkActivated: Qt.openUrlExternally(link)
}
}
Button {
Layout.alignment: Qt.AlignRight
text: i18n("More Information...")
visible: layout.extended
enabled: !resourcesUpdatesModel.isProgressing
onClicked: Navigation.openApplication(resource)
}
}
onClicked: {
layout.extended = !layout.extended
}
}
}
readonly property alias secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate
state: ( updateModel.hasUpdates ? "has-updates"
: ResourcesModel.isFetching ? "fetching"
: 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", "Fetching...") }
PropertyChanges { target: page; footerLabel: i18nc("@info", "Looking for updates") }
},
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") }
PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") }
},
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/libdiscover/resources/ResourcesModel.cpp b/libdiscover/resources/ResourcesModel.cpp
index ba877681..a72eb835 100644
--- a/libdiscover/resources/ResourcesModel.cpp
+++ b/libdiscover/resources/ResourcesModel.cpp
@@ -1,395 +1,393 @@
/***************************************************************************
* 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 "ResourcesModel.h"
#include "AbstractResource.h"
#include "resources/AbstractResourcesBackend.h"
#include "resources/AbstractBackendUpdater.h"
#include
#include
#include
#include
#include "Transaction/TransactionModel.h"
#include "Category/CategoryModel.h"
#include "utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
ResourcesModel *ResourcesModel::s_self = nullptr;
ResourcesModel *ResourcesModel::global()
{
if(!s_self)
s_self = new ResourcesModel;
return s_self;
}
ResourcesModel::ResourcesModel(QObject* parent, bool load)
: QObject(parent)
, m_isFetching(false)
, m_initializingBackends(0)
, m_currentApplicationBackend(nullptr)
{
init(load);
connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching);
connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend);
}
void ResourcesModel::init(bool load)
{
Q_ASSERT(!s_self);
Q_ASSERT(QCoreApplication::instance()->thread()==QThread::currentThread());
if(load)
QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection);
- QAction* updateAction = new QAction(this);
- updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update")));
- updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates"));
- updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
- connect(this, &ResourcesModel::fetchingChanged, updateAction, [updateAction](bool fetching) {
- updateAction->setEnabled(!fetching);
+ m_updateAction = new QAction(this);
+ m_updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update")));
+ m_updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates"));
+ m_updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
+ connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) {
+ m_updateAction->setEnabled(!fetching);
});
- connect(updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates);
-
- m_ownActions += updateAction;
+ connect(m_updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates);
}
ResourcesModel::ResourcesModel(const QString& backendName, QObject* parent)
: ResourcesModel(parent, false)
{
s_self = this;
registerBackendByName(backendName);
}
ResourcesModel::~ResourcesModel()
{
qDeleteAll(m_backends);
}
void ResourcesModel::addResourcesBackend(AbstractResourcesBackend* backend)
{
Q_ASSERT(!m_backends.contains(backend));
if(!backend->isValid()) {
qWarning() << "Discarding invalid backend" << backend->name();
CategoryModel::global()->blacklistPlugin(backend->name());
backend->deleteLater();
return;
}
m_backends += backend;
if(!backend->isFetching()) {
if (backend->updatesCount() > 0)
emit updatesCountChanged();
} else {
m_initializingBackends++;
}
connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged);
connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller);
connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged);
connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, &ResourcesModel::updatesCountChanged);
connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved);
connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage);
connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching);
if(m_initializingBackends==0)
emit allInitialized();
else
slotFetching();
}
void ResourcesModel::callerFetchingChanged()
{
AbstractResourcesBackend* backend = qobject_cast(sender());
if (!backend->isValid()) {
qWarning() << "Discarding invalid backend" << backend->name();
int idx = m_backends.indexOf(backend);
Q_ASSERT(idx>=0);
m_backends.removeAt(idx);
// Q_EMIT backendsChanged();
CategoryModel::global()->blacklistPlugin(backend->name());
backend->deleteLater();
return;
}
if(backend->isFetching()) {
m_initializingBackends++;
slotFetching();
} else {
m_initializingBackends--;
if(m_initializingBackends==0)
emit allInitialized();
else
slotFetching();
}
}
void ResourcesModel::updateCaller(const QVector& properties)
{
AbstractResourcesBackend* backend = qobject_cast(sender());
Q_EMIT backendDataChanged(backend, properties);
}
QVector< AbstractResourcesBackend* > ResourcesModel::backends() const
{
return m_backends;
}
int ResourcesModel::updatesCount() const
{
int ret = 0;
foreach(AbstractResourcesBackend* backend, m_backends) {
ret += backend->updatesCount();
}
return ret;
}
bool ResourcesModel::hasSecurityUpdates() const
{
bool ret = false;
foreach(AbstractResourcesBackend* backend, m_backends) {
ret |= backend->hasSecurityUpdates();
}
return ret;
}
void ResourcesModel::installApplication(AbstractResource* app)
{
TransactionModel::global()->addTransaction(app->backend()->installApplication(app));
}
void ResourcesModel::installApplication(AbstractResource* app, const AddonList& addons)
{
TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons));
}
void ResourcesModel::removeApplication(AbstractResource* app)
{
TransactionModel::global()->addTransaction(app->backend()->removeApplication(app));
}
void ResourcesModel::registerAllBackends()
{
DiscoverBackendsFactory f;
const auto backends = f.allBackends();
if(m_initializingBackends==0 && backends.isEmpty()) {
qWarning() << "Couldn't find any backends";
emit allInitialized();
} else {
foreach(AbstractResourcesBackend* b, backends) {
addResourcesBackend(b);
}
emit backendsChanged();
}
}
void ResourcesModel::registerBackendByName(const QString& name)
{
DiscoverBackendsFactory f;
for(auto b : f.backend(name))
addResourcesBackend(b);
emit backendsChanged();
}
bool ResourcesModel::isFetching() const
{
return m_isFetching;
}
void ResourcesModel::slotFetching()
{
bool newFetching = false;
foreach(AbstractResourcesBackend* b, m_backends) {
// isFetching should sort of be enough. However, sometimes the backend itself
// will still be operating on things, which from a model point of view would
// still mean something going on. So, interpret that as fetching as well, for
// the purposes of this property.
if(b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) {
newFetching = true;
break;
}
}
if (newFetching != m_isFetching) {
m_isFetching = newFetching;
Q_EMIT fetchingChanged(m_isFetching);
}
}
bool ResourcesModel::isBusy() const
{
return TransactionModel::global()->rowCount() > 0;
}
bool ResourcesModel::isExtended(const QString& id)
{
bool ret = true;
foreach (AbstractResourcesBackend* backend, m_backends) {
ret = backend->extends().contains(id);
if (ret)
break;
}
return ret;
}
AggregatedResultsStream::AggregatedResultsStream(const QSet& streams)
: ResultsStream(QStringLiteral("AggregatedResultsStream"))
{
Q_ASSERT(!streams.contains(nullptr));
if (streams.isEmpty()) {
qWarning() << "no streams to aggregate!!";
QTimer::singleShot(0, this, &AggregatedResultsStream::clear);
}
for (auto stream: streams) {
connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults);
connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::destruction);
m_streams << stream;
}
m_delayedEmission.setInterval(0);
connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults);
}
void AggregatedResultsStream::addResults(const QVector& res)
{
m_results += res;
m_delayedEmission.start();
}
void AggregatedResultsStream::emitResults()
{
if (!m_results.isEmpty()) {
Q_EMIT resourcesFound(m_results);
m_results.clear();
}
m_delayedEmission.setInterval(m_delayedEmission.interval() + 100);
m_delayedEmission.stop();
}
void AggregatedResultsStream::destruction(QObject* obj)
{
m_streams.remove(obj);
clear();
}
void AggregatedResultsStream::clear()
{
if (m_streams.isEmpty()) {
emitResults();
Q_EMIT finished();
deleteLater();
}
}
AggregatedResultsStream * ResourcesModel::findResourceByPackageName(const QUrl& search)
{
auto streams = kTransform>(m_backends, [search](AbstractResourcesBackend* backend){ return backend->findResourceByPackageName(search); });
return new AggregatedResultsStream(streams);
}
AggregatedResultsStream* ResourcesModel::search(const AbstractResourcesBackend::Filters& search)
{
if (search.isEmpty()) {
return new AggregatedResultsStream ({new ResultsStream(QStringLiteral("emptysearch"), {})});
}
auto streams = kTransform>(m_backends, [search](AbstractResourcesBackend* backend){ return backend->search(search); });
return new AggregatedResultsStream(streams);
}
AbstractResource* ResourcesModel::resourceForFile(const QUrl& file)
{
AbstractResource* ret = nullptr;
foreach(auto backend, m_backends) {
ret = backend->resourceForFile(file);
if (ret)
break;
}
return ret;
}
void ResourcesModel::checkForUpdates()
{
for(auto backend: qAsConst(m_backends))
backend->checkForUpdates();
}
QVector ResourcesModel::applicationBackends() const
{
return kFilter>(m_backends, [](AbstractResourcesBackend* b){ return b->hasApplications(); });
}
QVariantList ResourcesModel::applicationBackendsVariant() const
{
return kTransform(applicationBackends(), [](AbstractResourcesBackend* b) {return QVariant::fromValue(b);});
}
AbstractResourcesBackend* ResourcesModel::currentApplicationBackend() const
{
return m_currentApplicationBackend;
}
void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool write)
{
if (backend != m_currentApplicationBackend) {
if (write) {
KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel");
if (backend)
settings.writeEntry("currentApplicationBackend", backend->name());
else
settings.deleteEntry("currentApplicationBackend");
}
qDebug() << "setting currentApplicationBackend" << backend;
m_currentApplicationBackend = backend;
Q_EMIT currentApplicationBackendChanged(backend);
}
}
void ResourcesModel::initApplicationsBackend()
{
KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel");
const QString name = settings.readEntry("currentApplicationBackend", QStringLiteral("packagekit-backend"));
const auto backends = applicationBackends();
auto idx = kIndexOf(backends, [name](AbstractResourcesBackend* b) { return b->name() == name; });
if (idx<0) {
idx = kIndexOf(backends, [](AbstractResourcesBackend* b) { return b->hasApplications(); });
qDebug() << "falling back applications backend to" << idx;
}
setCurrentApplicationBackend(backends.value(idx, nullptr), false);
}
diff --git a/libdiscover/resources/ResourcesModel.h b/libdiscover/resources/ResourcesModel.h
index e9b3c9f0..71befca4 100644
--- a/libdiscover/resources/ResourcesModel.h
+++ b/libdiscover/resources/ResourcesModel.h
@@ -1,130 +1,130 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#ifndef RESOURCESMODEL_H
#define RESOURCESMODEL_H
#include
#include
#include
#include "discovercommon_export.h"
#include "AbstractResourcesBackend.h"
class QAction;
class DISCOVERCOMMON_EXPORT AggregatedResultsStream : public ResultsStream
{
Q_OBJECT
public:
AggregatedResultsStream(const QSet& streams);
Q_SIGNALS:
void finished();
private:
void addResults(const QVector& res);
void emitResults();
void destruction(QObject* obj);
void clear();
QSet m_streams;
QVector m_results;
QTimer m_delayedEmission;
};
class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject
{
Q_OBJECT
Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged)
Q_PROPERTY(bool hasSecurityUpdates READ hasSecurityUpdates NOTIFY updatesCountChanged)
Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged)
Q_PROPERTY(QVariantList applicationBackends READ applicationBackendsVariant NOTIFY backendsChanged)
Q_PROPERTY(AbstractResourcesBackend* currentApplicationBackend READ currentApplicationBackend WRITE setCurrentApplicationBackend NOTIFY currentApplicationBackendChanged)
- Q_PROPERTY(QList actions READ actions CONSTANT)
+ Q_PROPERTY(QAction* updateAction READ updateAction CONSTANT)
public:
/** This constructor should be only used by unit tests.
* @p backendName defines what backend will be loaded when the backend is constructed.
*/
explicit ResourcesModel(const QString& backendName, QObject* parent = nullptr);
static ResourcesModel* global();
~ResourcesModel() override;
QVector< AbstractResourcesBackend* > backends() const;
int updatesCount() const;
bool hasSecurityUpdates() const;
bool isBusy() const;
bool isFetching() const;
Q_SCRIPTABLE bool isExtended(const QString &id);
AggregatedResultsStream* findResourceByPackageName(const QUrl& search);
AggregatedResultsStream* search(const AbstractResourcesBackend::Filters &search);
AbstractResource* resourceForFile(const QUrl &/*url*/);
void checkForUpdates();
QVariantList applicationBackendsVariant() const;
QVector applicationBackends() const;
void setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool writeConfig = true);
AbstractResourcesBackend* currentApplicationBackend() const;
- QList actions() const { return m_ownActions; }
+ QAction* updateAction() const { return m_updateAction; }
public Q_SLOTS:
void installApplication(AbstractResource* app, const AddonList& addons);
void installApplication(AbstractResource* app);
void removeApplication(AbstractResource* app);
Q_SIGNALS:
void fetchingChanged(bool isFetching);
void allInitialized();
void backendsChanged();
void updatesCountChanged();
void backendDataChanged(AbstractResourcesBackend* backend, const QVector& properties);
void resourceDataChanged(AbstractResource* resource, const QVector& properties);
void resourceRemoved(AbstractResource* resource);
void passiveMessage(const QString &message);
void currentApplicationBackendChanged(AbstractResourcesBackend* currentApplicationBackend);
private Q_SLOTS:
void callerFetchingChanged();
void updateCaller(const QVector& properties);
void registerAllBackends();
private:
///@p initialize tells if all backends load will be triggered on construction
explicit ResourcesModel(QObject* parent=nullptr, bool load = true);
void init(bool load);
void addResourcesBackend(AbstractResourcesBackend* backend);
void registerBackendByName(const QString& name);
void initApplicationsBackend();
void slotFetching();
bool m_isFetching;
QVector< AbstractResourcesBackend* > m_backends;
int m_initializingBackends;
- QList m_ownActions;
+ QAction* m_updateAction = nullptr;
AbstractResourcesBackend* m_currentApplicationBackend;
static ResourcesModel* s_self;
};
#endif // RESOURCESMODEL_H