diff --git a/discover/qml/ApplicationPage.qml b/discover/qml/ApplicationPage.qml index 657d5279..bfdcd271 100644 --- a/discover/qml/ApplicationPage.qml +++ b/discover/qml/ApplicationPage.qml @@ -1,391 +1,405 @@ /* * 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. */ import QtQuick 2.5 import QtQuick.Controls 1.1 import QtQuick.Controls 2.3 as QQC2 import QtQuick.Window 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.1 as Kirigami import "navigation.js" as Navigation DiscoverPage { id: appInfo property QtObject application: null clip: true background: Rectangle { color: Kirigami.Theme.viewBackgroundColor } ReviewsPage { id: reviewsSheet model: ReviewsModel { id: reviewsModel resource: appInfo.application } } contextualActions: [originsMenuAction] QQC2.ActionGroup { id: sourcesGroup exclusive: true } Kirigami.Action { id: originsMenuAction text: i18n("Sources") visible: children.length>1 readonly property var r0: Instantiator { model: ResourcesProxyModel { id: alternativeResourcesModel allBackends: true resourcesUrl: appInfo.application.url } delegate: QQC2.Action { QQC2.ActionGroup.group: sourcesGroup text: displayOrigin icon.name: sourceIcon checked: appInfo.application == model.application onTriggered: if(index>=0) { var res = model.application console.assert(res) window.stack.pop() Navigation.openApplication(res) } } onObjectAdded: originsMenuAction.children.push(object) } } actions { main: appbutton.action right: Kirigami.Action { visible: application.isInstalled && application.canExecute text: application.executeLabel icon.name: "media-playback-start" onTriggered: application.invokeApplication() } } InstallApplicationButton { id: appbutton Layout.rightMargin: Kirigami.Units.smallSpacing application: appInfo.application visible: false } leftPadding: Kirigami.Units.largeSpacing * (applicationWindow().wideScreen ? 2 : 1) rightPadding: Kirigami.Units.largeSpacing * (applicationWindow().wideScreen ? 2 : 1) // Icon, name, caption, screenshots, description and reviews ColumnLayout { spacing: 0 RowLayout { Kirigami.Icon { Layout.preferredHeight: 80 Layout.preferredWidth: 80 source: appInfo.application.icon Layout.rightMargin: Kirigami.Units.smallSpacing * 2 } ColumnLayout { spacing: 0 Kirigami.Heading { level: 1 text: appInfo.application.name lineHeight: 1.0 maximumLineCount: 1 elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Text.AlignBottom } RowLayout { spacing: Kirigami.Units.largeSpacing Rating { rating: appInfo.application.rating ? appInfo.application.rating.sortableRating : 0 starSize: summary.font.pointSize } QQC2.Label { text: appInfo.application.rating ? i18n("%1 ratings", appInfo.application.rating.ratingCount) : i18n("No ratings yet") opacity: 0.5 } } Kirigami.Heading { id: summary level: 4 text: appInfo.application.comment maximumLineCount: 2 lineHeight: lineCount > 1 ? 0.75 : 1.2 elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Qt.AlignTop } } Layout.bottomMargin: Kirigami.Units.largeSpacing } ApplicationScreenshots { id: screenshots Layout.fillWidth: true visible: count > 0 resource: appInfo.application QQC2.ScrollBar.horizontal: screenshotsScrollbar } QQC2.ScrollBar { id: screenshotsScrollbar Layout.fillWidth: true } QQC2.Label { Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true wrapMode: Text.WordWrap text: appInfo.application.longDescription } Kirigami.Heading { Layout.topMargin: Kirigami.Units.largeSpacing text: i18n("What's New") level: 2 visible: changelogLabel.text.length > 0 } Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 visible: changelogLabel.text.length > 0 } QQC2.Label { id: changelogLabel Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true wrapMode: Text.WordWrap Component.onCompleted: appInfo.application.fetchChangelog() Connections { target: appInfo.application onChangelogFetched: { changelogLabel.text = changelog } } } LinkButton { id: addonsButton text: i18n("Addons") visible: addonsView.containsAddons onClicked: addonsView.sheetOpen = true } Kirigami.Heading { Layout.topMargin: Kirigami.Units.largeSpacing text: i18n("Reviews") level: 2 visible: rep.count > 0 } Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 visible: rep.count > 0 } Repeater { id: rep model: PaginateModel { sourceModel: reviewsSheet.model pageSize: 3 } delegate: ReviewDelegate { Layout.topMargin: Kirigami.Units.largeSpacing separator: false compact: true Layout.bottomMargin: Kirigami.Units.largeSpacing } } LinkButton { text: appInfo.application.isInstalled? i18n("Be the first to write a review!") : i18n("Install this app and be the first to write a review!") onClicked: reviewsSheet.openReviewDialog() enabled: appInfo.application.isInstalled visible: !commentsButton.visible && reviewsModel.backend && reviewsModel.backend.isResourceSupported(appInfo.application) } LinkButton { id: commentsButton visible: reviewsModel.count > 0 text: i18n("Show all %1 reviews...", reviewsModel.count) onClicked: { reviewsSheet.open() } Layout.bottomMargin: Kirigami.Units.largeSpacing } + Repeater { + model: application.objects + delegate: Loader { + property QtObject resource: appInfo.application + source: modelData + } + } + + Item { + height: addonsButton.height + width: 5 + } + + // Details/metadata Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 Layout.bottomMargin: Kirigami.Units.largeSpacing } GridLayout { rowSpacing: 0 columns: 2 // Category row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("Category:") } QQC2.Label { Layout.fillWidth: true elide: Text.ElideRight text: appInfo.application.categoryDisplay } // Version row QQC2.Label { visible: versionLabel.visible Layout.alignment: Qt.AlignRight text: i18n("Version:") } QQC2.Label { readonly property string version: appInfo.application.isInstalled ? appInfo.application.installedVersion : appInfo.application.availableVersion id: versionLabel visible: text.length > 0 Layout.fillWidth: true elide: Text.ElideRight text: version ? version : "" } // Size row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("Size:") } QQC2.Label { Layout.fillWidth: true elide: Text.ElideRight text: appInfo.application.sizeDescription } // Source row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("Source:") } QQC2.Label { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft text: appInfo.application.displayOrigin elide: Text.ElideRight } // License row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("License:") visible: appInfo.application.license.length>0 } LinkButton { elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft visible: text.length>0 text: appInfo.application.license // tooltip: i18n("See full license terms") onClicked: Qt.openUrlExternally("https://spdx.org/licenses/" + appInfo.application.license + ".html#licenseText") } // Homepage row QQC2.Label { visible: homepageLink.visible Layout.alignment: Qt.AlignRight text: i18n("Homepage:") } LinkButton { id: homepageLink visible: text.length > 0 text: application.homepage onClicked: Qt.openUrlExternally(application.homepage) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // "User Guide" row QQC2.Label { visible: docsLink.visible Layout.alignment: Qt.AlignRight text: i18n("User Guide:") } LinkButton { id: docsLink visible: text.length > 0 text: application.helpURL onClicked: Qt.openUrlExternally(helpURL) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // Donate row QQC2.Label { visible: donationLink.visible Layout.alignment: Qt.AlignRight text: i18n("Donate:") } LinkButton { id: donationLink visible: text.length > 0 text: application.donationURL onClicked: Qt.openUrlExternally(donationURL) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // "Report a Droblem" row QQC2.Label { visible: bugLink.visible Layout.alignment: Qt.AlignRight text: i18n("Report a Problem:") } LinkButton { id: bugLink visible: text.length > 0 text: application.bugURL onClicked: Qt.openUrlExternally(bugURL) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } } } readonly property var addons: AddonsView { id: addonsView application: appInfo.application parent: overlay } } diff --git a/libdiscover/backends/CMakeLists.txt b/libdiscover/backends/CMakeLists.txt index f959ef98..5b1e0eb7 100644 --- a/libdiscover/backends/CMakeLists.txt +++ b/libdiscover/backends/CMakeLists.txt @@ -1,41 +1,41 @@ function(add_unit_test name) add_executable(${name} ${CMAKE_SOURCE_DIR}/libdiscover/tests/modeltest.cpp ${ARGN}) add_test(${name} ${name}) ecm_mark_as_test(${name}) target_link_libraries(${name} Discover::Common Qt5::Test Qt5::Core ${EXTRA_LIBS}) endfunction() if(KF5Attica_FOUND AND KF5NewStuff_FOUND) add_subdirectory(KNSBackend) endif() if(packagekitqt5_FOUND AND AppStreamQt_FOUND) add_subdirectory(PackageKitBackend) endif() option(BUILD_DummyBackend "Build the DummyBackend" "OFF") if(BUILD_DummyBackend) add_subdirectory(DummyBackend) endif() option(BUILD_FlatpakBackend "Build Flatpak support" "ON") if(FLATPAK_FOUND AND AppStreamQt_FOUND AND BUILD_FlatpakBackend) add_subdirectory(FlatpakBackend) elseif(BUILD_FlatpakBackend) message(WARNING "BUILD_FlatpakBackend enabled but Flatpak=${FLATPAK_FOUND} or AppStreamQt=${AppStreamQt_FOUND} not found") endif() -option(BUILD_SnapBackend "Build Snap support. Still a proof of concept" "OFF") +option(BUILD_SnapBackend "Build Snap support." "ON") if(BUILD_SnapBackend) find_package(Snapd) set_package_properties(Snapd PROPERTIES DESCRIPTION "Library that exposes Snapd" URL "http://www.snapcraft.io" PURPOSE "Required to build the Snap backend" TYPE OPTIONAL) add_subdirectory(SnapBackend) endif() diff --git a/libdiscover/backends/SnapBackend/CMakeLists.txt b/libdiscover/backends/SnapBackend/CMakeLists.txt index 2c8e0675..353205fd 100644 --- a/libdiscover/backends/SnapBackend/CMakeLists.txt +++ b/libdiscover/backends/SnapBackend/CMakeLists.txt @@ -1,8 +1,8 @@ add_subdirectory(libsnapclient) -add_library(snap-backend MODULE SnapResource.cpp SnapBackend.cpp SnapReviewsBackend.cpp SnapTransaction.cpp) +add_library(snap-backend MODULE SnapResource.cpp SnapBackend.cpp SnapReviewsBackend.cpp SnapTransaction.cpp snapui.qrc) target_link_libraries(snap-backend Qt5::Core KF5::CoreAddons KF5::ConfigCore Discover::Common Snapd::Core) install(TARGETS snap-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover) install(FILES snap-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories) install( FILES org.kde.discover.snap.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) diff --git a/libdiscover/backends/SnapBackend/SnapResource.cpp b/libdiscover/backends/SnapBackend/SnapResource.cpp index 56caca4e..df0fbe94 100644 --- a/libdiscover/backends/SnapBackend/SnapResource.cpp +++ b/libdiscover/backends/SnapBackend/SnapResource.cpp @@ -1,194 +1,327 @@ /*************************************************************************** * Copyright © 2013 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 "SnapResource.h" #include "SnapBackend.h" #include #include #include #include +#include +#include +#include +#include -SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* parent) - : AbstractResource(parent) +QDebug operator<<(QDebug debug, const QSnapdPlug& plug) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "QSnapdPlug("; + debug.nospace() << "name:" << plug.name() << ','; + debug.nospace() << "snap:" << plug.snap() << ','; + debug.nospace() << "label:" << plug.label() << ','; + debug.nospace() << "interface:" << plug.interface() << ','; + debug.nospace() << "connectionCount:" << plug.connectionCount(); + debug.nospace() << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const QSnapdSlot& slot) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "QSnapdSlot("; + debug.nospace() << "name:" << slot.name() << ','; + debug.nospace() << "label:" << slot.label() << ','; + debug.nospace() << "snap:" << slot.snap() << ','; + debug.nospace() << "interface:" << slot.interface() << ','; + debug.nospace() << "connectionCount:" << slot.connectionCount(); + debug.nospace() << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const QSnapdPlug* plug) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "*" << *plug; + return debug; +} + +QDebug operator<<(QDebug debug, const QSnapdSlot* slot) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "*" << *slot; + return debug; +} + +SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* backend) + : AbstractResource(backend) , m_state(state) , m_snap(snap) + , m_objects({ QStringLiteral("qrc:/snapui/PermissionsButton.qml") }) { setObjectName(snap->name()); } QString SnapResource::availableVersion() const { return installedVersion(); } QStringList SnapResource::categories() { return { QStringLiteral("Application") }; } QString SnapResource::comment() { return m_snap->summary(); } int SnapResource::size() { // return isInstalled() ? m_snap->installedSize() : m_snap->downloadSize(); return m_snap->downloadSize(); } QVariant SnapResource::icon() const { if (m_icon.isNull()) { m_icon = [this]() -> QVariant { const auto iconPath = m_snap->icon(); if (iconPath.isEmpty()) return QStringLiteral("package-x-generic"); if (!iconPath.startsWith(QLatin1Char('/'))) return QUrl(iconPath); auto backend = qobject_cast(parent()); auto req = backend->client()->getIcon(packageName()); connect(req, &QSnapdGetIconRequest::complete, this, &SnapResource::gotIcon); req->runAsync(); return {}; }(); } return m_icon; } void SnapResource::gotIcon() { auto req = qobject_cast(sender()); if (req->error()) { qWarning() << "icon error" << req->errorString(); return; } auto icon = req->icon(); QBuffer buffer; buffer.setData(icon->data()); QImageReader reader(&buffer); auto theIcon = QVariant::fromValue(reader.read()); if (theIcon != m_icon) { m_icon = theIcon; iconChanged(); } } QString SnapResource::installedVersion() const { return m_snap->version(); } QString SnapResource::license() { return m_snap->license(); } QString SnapResource::longDescription() { return m_snap->description(); } QString SnapResource::name() const { return m_snap->title().isEmpty() ? m_snap->name() : m_snap->title(); } QString SnapResource::origin() const { return QStringLiteral("snappy:") + m_snap->channel(); } QString SnapResource::packageName() const { return m_snap->name(); } QString SnapResource::section() { return QStringLiteral("snap"); } AbstractResource::State SnapResource::state() { return m_state; } void SnapResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(); } } void SnapResource::fetchChangelog() { QString log; emit changelogFetched(log); } void SnapResource::fetchScreenshots() { QList screenshots; for(int i = 0, c = m_snap->screenshotCount(); i screenshot(m_snap->screenshot(i)); screenshots << QUrl(screenshot->url()); } Q_EMIT screenshotsFetched(screenshots, screenshots); } void SnapResource::invokeApplication() const { // QProcess::startDetached(m_snap->price()); } bool SnapResource::isTechnical() const { return m_snap->snapType() != QLatin1String("app"); } QUrl SnapResource::url() const { //FIXME interim, until it has an appstreamId return QUrl(QStringLiteral("snap://") + packageName()); } void SnapResource::setSnap(const QSharedPointer& snap) { Q_ASSERT(snap->name() == m_snap->name()); if (m_snap == snap) return; const bool newSize = m_snap->installedSize() != snap->installedSize() || m_snap->downloadSize() != snap->downloadSize(); m_snap = snap; if (newSize) Q_EMIT sizeChanged(); } QDate SnapResource::releaseDate() const { return {}; } + +class PlugsModel : public QStandardItemModel +{ +public: + enum Roles { + PlugNameRole = Qt::UserRole + 1, + SlotSnapRole, + SlotNameRole + }; + + PlugsModel(QSnapdSnap* snap, SnapBackend* backend, QObject* parent) + : QStandardItemModel(parent) + , m_snap(snap) + , m_backend(backend) + { + setItemRoleNames(roleNames().unite( + { {Qt::CheckStateRole, "checked"} } + )); + + auto req = backend->client()->getInterfaces(); + req->runSync(); + + QHash> slotsForInterface; + for (int i = 0; islotCount(); ++i) { + const auto slot = req->slot(i); + slot->setParent(this); + slotsForInterface[slot->interface()].append(slot); + + } + + for (int i = 0; iplugCount(); ++i) { + const QScopedPointer plug(req->plug(i)); + if (plug->snap() == m_snap->name()) { + for (auto slot: slotsForInterface[plug->interface()]) { + auto item = new QStandardItem; + if (plug->label().isEmpty()) + item->setText(plug->name()); + else + item->setText(i18n("%1 - %2", plug->name(), plug->label())); + + item->setCheckable(true); + item->setCheckState(plug->connectionCount()>0 ? Qt::Checked : Qt::Unchecked); + item->setData(plug->name(), PlugNameRole); + item->setData(slot->snap(), SlotSnapRole); + item->setData(slot->name(), SlotNameRole); + appendRow(item); + } + } + } + } + +private: + bool setData(const QModelIndex & index, const QVariant & value, int role) override { + if (role != Qt::CheckStateRole) + return QStandardItemModel::setData(index, value, role); + + auto item = itemFromIndex(index); + Q_ASSERT(item); + const QString plugName = item->data(PlugNameRole).toString(); + const QString slotSnap = item->data(SlotSnapRole).toString(); + const QString slotName = item->data(SlotNameRole).toString(); + + QSnapdRequest* req; + + if (item->checkState() == Qt::Checked) { + req = m_backend->client()->connectInterface(m_snap->name(), plugName, slotSnap, slotName); + } else { + req = m_backend->client()->disconnectInterface(m_snap->name(), plugName, slotSnap, slotName); + } + req->runSync(); + if (req->error()) { + qWarning() << "snapd error" << req->errorString(); + } + return req->error() == QSnapdRequest::NoError; + } + + QSnapdSnap* const m_snap; + SnapBackend* const m_backend; +}; + +QAbstractItemModel* SnapResource::plugs(QObject* p) +{ + if (!isInstalled()) + return new QStandardItemModel(p); + + + return new PlugsModel(m_snap.data(), qobject_cast(parent()), p); +} diff --git a/libdiscover/backends/SnapBackend/SnapResource.h b/libdiscover/backends/SnapBackend/SnapResource.h index b2f6be18..abe8dc3e 100644 --- a/libdiscover/backends/SnapBackend/SnapResource.h +++ b/libdiscover/backends/SnapBackend/SnapResource.h @@ -1,73 +1,78 @@ /*************************************************************************** * Copyright © 2013 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 SNAPRESOURCE_H #define SNAPRESOURCE_H #include #include #include #include class SnapBackend; +class QAbstractItemModel; class SnapResource : public AbstractResource { Q_OBJECT +Q_PROPERTY(QStringList objects MEMBER m_objects CONSTANT) public: explicit SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* parent); ~SnapResource() override = default; QString section() override; QString origin() const override; QString longDescription() override; QString availableVersion() const override; QString installedVersion() const override; QString license() override; int size() override; QStringList categories() override; AbstractResource::State state() override; QVariant icon() const override; QString comment() override; QString name() const override; QString packageName() const override; bool isTechnical() const override; bool canExecute() const override { return true; } void invokeApplication() const override; void fetchChangelog() override; void fetchScreenshots() override; QList addonsInformation() override { return {}; } QUrl url() const override; void setSnap(const QSharedPointer &snap); void setState(AbstractResource::State state); QString sourceIcon() const override { return QStringLiteral("snap"); } QDate releaseDate() const override; + Q_SCRIPTABLE QAbstractItemModel* plugs(QObject* parents); + public: void gotIcon(); AbstractResource::State m_state; QSharedPointer m_snap; mutable QVariant m_icon; + const QStringList m_objects; }; #endif // SNAPRESOURCE_H diff --git a/libdiscover/backends/SnapBackend/snapui.qrc b/libdiscover/backends/SnapBackend/snapui.qrc new file mode 100644 index 00000000..dd68340a --- /dev/null +++ b/libdiscover/backends/SnapBackend/snapui.qrc @@ -0,0 +1,6 @@ + + + + snapui/PermissionsButton.qml + + diff --git a/libdiscover/backends/SnapBackend/snapui/PermissionsButton.qml b/libdiscover/backends/SnapBackend/snapui/PermissionsButton.qml new file mode 100644 index 00000000..b99a0d8c --- /dev/null +++ b/libdiscover/backends/SnapBackend/snapui/PermissionsButton.qml @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library/Lesser General Public License + * version 2, or (at your option) any later version, as published by the + * Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library/Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.1 +import QtQuick.Controls 2.1 +import org.kde.kirigami 2.1 as Kirigami + +Button +{ + id: root + text: i18n("Configure permissions...") + + visible: resource.isInstalled + onClicked: overlay.open() + Popup { + id: overlay + parent: applicationWindow().overlay + bottomPadding: Kirigami.Units.largeSpacing + topPadding: Kirigami.Units.largeSpacing + + x: (parent.width - width)/2 + y: (parent.height - height)/2 + width: parent.width * 1/3 + height: Math.min(view.contentHeight + bottomPadding + topPadding, parent.height * 4/5) + + ListView { + id: view + anchors.fill: parent + header: Kirigami.Heading { + text: i18n ("Permissions for %1", resource.name) + } + model: resource.plugs(root) + delegate: CheckDelegate { + id: delegate + width: parent.width + text: model.display + checked: model.checked + onClicked: { + model.checked = delegate.checked + } + } + } + } +}