diff --git a/discover/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml index 9b911536..f4cae7f6 100644 --- a/discover/qml/SourcesPage.qml +++ b/discover/qml/SourcesPage.qml @@ -1,246 +1,246 @@ import QtQuick 2.4 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import org.kde.kirigami 2.10 as Kirigami import "navigation.js" as Navigation DiscoverPage { id: page clip: true title: i18n("Sources") property string search: "" background: Rectangle { color: Kirigami.Theme.backgroundColor Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.inherit: false } mainItem: ListView { id: sourcesView model: QSortFilterProxyModel { filterRegExp: new RegExp(page.search, 'i') dynamicSortFilter: false //We don't want to sort, as sorting can have some semantics on some backends sourceModel: SourcesModel } currentIndex: -1 section.property: "sourceName" section.delegate: Kirigami.ListSectionHeader { id: backendItem readonly property QtObject backend: SourcesModel.sourcesBackendByName(section) readonly property QtObject resourcesBackend: backend.resourcesBackend readonly property bool isDefault: ResourcesModel.currentApplicationBackend === resourcesBackend width: sourcesView.width label: backendItem.isDefault ? i18n("%1 (Default)", resourcesBackend.displayName) : resourcesBackend.displayName Connections { target: backendItem.backend onPassiveMessage: window.showPassiveNotification(message) onProceedRequest: { var dialog = sourceProceedDialog.createObject(window, {sourcesBackend: backendItem.backend, title: title, description: description}) dialog.open() } } Repeater { id: backendActionsInst model: ActionsModel { actions: backendItem.backend ? backendItem.backend.actions : undefined } delegate: Button { Layout.column: 1 text: modelData.text icon.name: app.iconName(modelData.icon) ToolTip.visible: hovered ToolTip.text: modelData.toolTip onClicked: modelData.trigger() } } Button { text: i18n("Add Source...") icon.name: "list-add" visible: backendItem.backend && backendItem.backend.supportsAdding Component { id: dialogComponent AddSourceDialog { source: backendItem.backend onVisibleChanged: if (!visible) { destroy() } } } onClicked: { var addSourceDialog = dialogComponent.createObject(null, {displayName: backendItem.backend.resourcesBackend.displayName }) addSourceDialog.open() } } Button { visible: resourcesBackend && resourcesBackend.hasApplications enabled: !backendItem.isDefault text: i18n("Make default") icon.name: "favorite" onClicked: ResourcesModel.currentApplicationBackend = backendItem.backend.resourcesBackend } } Component { id: sourceProceedDialog Kirigami.OverlaySheet { id: sheet showCloseButton: false property QtObject sourcesBackend property alias title: heading.text property alias description: desc.text property bool acted: false ColumnLayout { Kirigami.Heading { id: heading } Label { id: desc Layout.fillWidth: true textFormat: Text.StyledText wrapMode: Text.WordWrap } RowLayout { Layout.alignment: Qt.AlignRight Button { text: i18n("Proceed") icon.name: "dialog-ok" onClicked: { sourcesBackend.proceed() sheet.acted = true sheet.close() } } Button { Layout.alignment: Qt.AlignRight text: i18n("Cancel") icon.name: "dialog-cancel" onClicked: { sourcesBackend.cancel() sheet.acted = true sheet.close() } } } } onSheetOpenChanged: if(!sheetOpen) { sheet.destroy(1000) if (!sheet.acted) sourcesBackend.cancel() } } } delegate: Kirigami.SwipeListItem { enabled: model.display.length>0 && model.enabled highlighted: ListView.isCurrentItem supportsMouseEvents: false Keys.onReturnPressed: clicked() actions: [ Kirigami.Action { iconName: "go-up" enabled: sourcesBackend.firstSourceId !== sourceId visible: sourcesBackend.canMoveSources onTriggered: { var ret = sourcesBackend.moveSource(sourceId, -1) if (!ret) window.showPassiveNotification(i18n("Failed to increase '%1' preference", model.display)) } }, Kirigami.Action { iconName: "go-down" enabled: sourcesBackend.lastSourceId !== sourceId visible: sourcesBackend.canMoveSources onTriggered: { var ret = sourcesBackend.moveSource(sourceId, +1) if (!ret) window.showPassiveNotification(i18n("Failed to decrease '%1' preference", model.display)) } }, Kirigami.Action { iconName: "edit-delete" tooltip: i18n("Delete the origin") visible: sourcesBackend.supportsAdding onTriggered: { var backend = sourcesBackend if (!backend.removeSource(sourceId)) { window.showPassiveNotification(i18n("Failed to remove the source '%1'", model.display)) } } }, Kirigami.Action { iconName: "view-filter" tooltip: i18n("Show contents") visible: sourcesBackend.canFilterSources onTriggered: { Navigation.openApplicationListSource(sourceId) } } ] RowLayout { CheckBox { id: enabledBox readonly property variant idx: sourcesView.model.index(index, 0) readonly property variant modelChecked: sourcesView.model.data(idx, Qt.CheckStateRole) checked: modelChecked !== Qt.Unchecked enabled: modelChecked !== undefined onClicked: { sourcesView.model.setData(idx, checkState, Qt.CheckStateRole) checked = Qt.binding(function() { return modelChecked !== Qt.Unchecked; }) } } Label { - text: model.display + (toolTip ? " - " + toolTip + "" : "") + text: model.display + (model.toolTip ? " - " + model.toolTip + "" : "") elide: Text.ElideRight textFormat: Text.StyledText Layout.fillWidth: true } } } footer: ColumnLayout { id: foot anchors { right: parent.right left: parent.left margins: Kirigami.Units.smallSpacing } Kirigami.Heading { Layout.fillWidth: true text: i18n("Missing Backends") visible: back.count>0 } spacing: 0 Repeater { id: back model: ResourcesProxyModel { extending: "org.kde.discover.desktop" filterMinimumState: false } delegate: Kirigami.BasicListItem { supportsMouseEvents: false label: name icon: model.icon InstallApplicationButton { application: model.application } } } } } } diff --git a/libdiscover/resources/SourcesModel.cpp b/libdiscover/resources/SourcesModel.cpp index 18d58948..930c6fe9 100644 --- a/libdiscover/resources/SourcesModel.cpp +++ b/libdiscover/resources/SourcesModel.cpp @@ -1,98 +1,100 @@ /*************************************************************************** * Copyright © 2014 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 "SourcesModel.h" #include #include "libdiscover_debug.h" #include #include "resources/AbstractResourcesBackend.h" #include "resources/AbstractSourcesBackend.h" Q_GLOBAL_STATIC(SourcesModel, s_sources) const auto DisplayName = "DisplayName"; const auto SourcesBackendId = "SourcesBackend"; SourcesModel::SourcesModel(QObject* parent) : KConcatenateRowsProxyModel(parent) {} SourcesModel::~SourcesModel() = default; SourcesModel* SourcesModel::global() { return s_sources; } QHash SourcesModel::roleNames() const { QHash roles = KConcatenateRowsProxyModel::roleNames(); roles.insert(AbstractSourcesBackend::IdRole, "sourceId"); + roles.insert(Qt::DisplayRole, "display"); + roles.insert(Qt::ToolTipRole, "toolTip"); roles.insert(SourceNameRole, "sourceName"); roles.insert(SourcesBackend, "sourcesBackend"); roles.insert(ResourcesBackend, "resourcesBackend"); roles.insert(EnabledRole, "enabled"); return roles; } void SourcesModel::addSourcesBackend(AbstractSourcesBackend* sources) { auto backend = qobject_cast(sources->parent()); auto m = sources->sources(); m->setProperty(DisplayName, backend->displayName()); m->setProperty(SourcesBackendId, QVariant::fromValue(sources)); addSourceModel(m); if (!m->rowCount()) qWarning() << "adding empty sources model" << m; } const QAbstractItemModel * SourcesModel::modelAt(const QModelIndex& index) const { const auto sidx = mapToSource(index); return sidx.model(); } QVariant SourcesModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return {}; switch (role) { case SourceNameRole: return modelAt(index)->property(DisplayName); case SourcesBackend: return modelAt(index)->property(SourcesBackendId); case EnabledRole: return QVariant(flags(index) & Qt::ItemIsEnabled); default: return KConcatenateRowsProxyModel::data(index, role); } } AbstractSourcesBackend * SourcesModel::sourcesBackendByName(const QString& id) const { for(int i = 0, c = rowCount(); i < c; ++i) { const auto idx = index(i, 0); if (idx.data(SourceNameRole) == id) { return qobject_cast(idx.data(SourcesBackend).value()); } } return nullptr; }