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