diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c7217a9..12a90d45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,98 +1,98 @@ project(discover) set(PROJECT_VERSION "5.16.80") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12) set(QT_MIN_VERSION "5.12.0") -set(KF5_MIN_VERSION "5.58.0") +set(KF5_MIN_VERSION "5.62.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} "${CMAKE_SOURCE_DIR}/cmake") find_package(Qt5 ${QT_MIN_VERSION} REQUIRED CONFIG COMPONENTS Widgets Test Network Xml Concurrent DBus Quick) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMAddTests) include(GenerateExportHeader) include(ECMQtDeclareLoggingCategory) find_package(PkgConfig REQUIRED) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons Config Crash DBusAddons I18n Archive XmlGui ItemModels KIO Declarative) find_package(KF5Kirigami2 2.7.0) find_package(packagekitqt5 1.0.1 CONFIG) find_package(AppStreamQt 0.11.1 CONFIG) find_package(KF5Attica 5.23 CONFIG) find_package(KF5NewStuff 5.53 CONFIG) set(CMAKE_AUTORCC ON) pkg_check_modules(Flatpak IMPORTED_TARGET flatpak>=0.11.8) pkg_check_modules(Fwupd IMPORTED_TARGET fwupd>=1.0.6) pkg_check_modules(Markdown IMPORTED_TARGET libmarkdown) if(NOT CMAKE_VERSION VERSION_LESS "3.10.0") # CMake 3.9+ warns about automoc on files without Q_OBJECT, and doesn't know about other macros. # 3.10+ lets us provide more macro names that require automoc. list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "DISCOVER_BACKEND_PLUGIN") endif() configure_file(DiscoverVersion.h.in DiscoverVersion.h) add_subdirectory(libdiscover) add_subdirectory(discover) add_subdirectory(exporter) option(WITH_NOTIFIER "Build and install the notifier plasmoid" ON) if(WITH_NOTIFIER) find_package(KF5 REQUIRED Notifications KIO) add_subdirectory(notifier) endif() set_package_properties(KF5Attica PROPERTIES DESCRIPTION "KDE Framework that implements the Open Collaboration Services API" PURPOSE "Required to build the KNewStuff3 backend" TYPE OPTIONAL) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "KDE's lightweight user interface framework for mobile and convergent applications" URL "https://techbase.kde.org/Kirigami" PURPOSE "Required by discover qml components" TYPE RUNTIME) set_package_properties(KF5NewStuff PROPERTIES DESCRIPTION "Qt library that allows to interact with KNewStuff implementations" PURPOSE "Required to build the KNS backend" TYPE OPTIONAL) set_package_properties(packagekitqt5 PROPERTIES DESCRIPTION "Library that exposes PackageKit resources" URL "https://www.freedesktop.org/software/PackageKit/" PURPOSE "Required to build the PackageKit backend" TYPE OPTIONAL) set_package_properties(AppStreamQt PROPERTIES DESCRIPTION "Library that lists Appstream resources" URL "https://www.freedesktop.org" PURPOSE "Required to build the PackageKit and Flatpak backends" TYPE OPTIONAL) set_package_properties(FLATPAK PROPERTIES DESCRIPTION "Library that exposes flatpak repositories" URL "https://www.freedesktop.org" PURPOSE "Required to build the Flatpak backend" TYPE OPTIONAL) set_package_properties(LIBFWUPD PROPERTIES DESCRIPTION "Library that exposes fwupd" URL "https://www.fwupd.org" PURPOSE "Required to build the Fwupd backend" TYPE OPTIONAL) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES discover.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES discover.categories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() diff --git a/discover/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml index bbc01bb2..21df5211 100644 --- a/discover/qml/SourcesPage.qml +++ b/discover/qml/SourcesPage.qml @@ -1,260 +1,255 @@ 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.2 as Kirigami +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.AbstractListItem { + section.delegate: Kirigami.ListSectionHeader { id: backendItem - hoverEnabled: false - supportsMouseEvents: false + readonly property QtObject backend: SourcesModel.sourcesBackendByName(section) readonly property QtObject resourcesBackend: backend.resourcesBackend readonly property bool isDefault: ResourcesModel.currentApplicationBackend === resourcesBackend - RowLayout { - id: sourceTitleLayout - Layout.fillHeight: true - Connections { - target: backendItem.backend - onPassiveMessage: window.showPassiveNotification(message) - onProceedRequest: { - var dialog = sourceProceedDialog.createObject(window, {sourcesBackend: backendItem.backend, title: title, description: description}) - dialog.open() + width: sourcesView.width + label: backendItem.isDefault ? i18n("%1 (Default)", resourcesBackend.displayName) : resourcesBackend.displayName + + customItems: [ + RowLayout { + id: sourceTitleLayout + Connections { + target: backendItem.backend + onPassiveMessage: window.showPassiveNotification(message) + onProceedRequest: { + var dialog = sourceProceedDialog.createObject(window, {sourcesBackend: backendItem.backend, title: title, description: description}) + dialog.open() + } } - } - Kirigami.Heading { - Layout.fillWidth: true - Layout.leftMargin: Kirigami.Units.smallSpacing - Layout.alignment: Qt.AlignVCenter - text: backendItem.isDefault ? i18n("%1 (Default)", resourcesBackend.displayName) : resourcesBackend.displayName - level: 3 - } - - Instantiator { - id: backendActionsInst - model: ActionsModel { - actions: backendItem.backend ? backendItem.backend.actions : undefined - } - delegate: Button { - parent: sourceTitleLayout - Layout.column: 1 - text: modelData.text - icon.name: app.iconName(modelData.icon) - ToolTip.visible: hovered - ToolTip.text: modelData.toolTip - onClicked: modelData.trigger() - } - onObjectRemoved: { - object.destroy() + Instantiator { + id: backendActionsInst + model: ActionsModel { + actions: backendItem.backend ? backendItem.backend.actions : undefined + } + delegate: Button { + parent: sourceTitleLayout + Layout.column: 1 + text: modelData.text + icon.name: app.iconName(modelData.icon) + ToolTip.visible: hovered + ToolTip.text: modelData.toolTip + onClicked: modelData.trigger() + } + onObjectRemoved: { + object.destroy() + } } - } - 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() + 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() + onClicked: { + var addSourceDialog = dialogComponent.createObject(null, {displayName: backendItem.backend.resourcesBackend.displayName }) + addSourceDialog.open() + } } - } - Button { - visible: resourcesBackend && resourcesBackend.hasApplications + Button { + visible: resourcesBackend && resourcesBackend.hasApplications - enabled: !backendItem.isDefault - text: i18n("Make default") - icon.name: "favorite" - onClicked: ResourcesModel.currentApplicationBackend = backendItem.backend.resourcesBackend + 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: 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", 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", 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'", 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: display + (toolTip ? " - " + 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/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml index d763cc33..1f183eb6 100644 --- a/discover/qml/UpdatesPage.qml +++ b/discover/qml/UpdatesPage.qml @@ -1,402 +1,400 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick 2.4 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import "navigation.js" as Navigation -import org.kde.kirigami 2.3 as Kirigami +import org.kde.kirigami 2.10 as Kirigami DiscoverPage { id: page title: i18n("Updates") property string footerLabel: "" property int footerProgress: 0 property bool isBusy: false ResourcesUpdatesModel { id: resourcesUpdatesModel onPassiveMessage: { desc.text += message + "
\n" sheet.sheetOpen = true } onIsProgressingChanged: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } Component.onCompleted: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } } Kirigami.OverlaySheet { id: sheet ColumnLayout { Label { id: desc Layout.fillWidth: true textFormat: Text.StyledText wrapMode: Text.WordWrap } Button { id: okButton Layout.alignment: Qt.AlignRight text: i18n("Proceed") icon.name: "dialog-ok" onClicked: { sheet.sheetOpen = false } } } onSheetOpenChanged: if(!sheetOpen) { desc.text = "" } else { okButton.focus = true } } 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 && !ResourcesModel.isFetching onTriggered: resourcesUpdatesModel.updateAll() } footer: ScrollView { id: scv width: parent.width height: visible ? Kirigami.Units.gridUnit * 10 : 0 visible: log.contents.length > 0 TextArea { readOnly: true text: log.contents cursorPosition: text.length - 1 font.family: "monospace" ReadFile { id: log filter: ".*ALPM-SCRIPTLET\\] .*" path: "/var/log/pacman.log" } } } 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) header: ToolBar { Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.inherit: false visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates RowLayout { anchors.fill: parent enabled: updateAction.enabled CheckBox { Layout.leftMargin: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing enabled: !resourcesUpdatesModel.isProgressing && !ResourcesModel.isFetching tristate: true checkState: updateModel.toUpdateCount === 0 ? Qt.Unchecked : updateModel.toUpdateCount === updateModel.totalUpdatesCount ? Qt.Checked : Qt.PartiallyChecked onClicked: { if (updateModel.toUpdateCount === 0) updateModel.checkAll() else updateModel.uncheckAll() } } Label { Layout.fillWidth: true text: page.unselected === 0 ? i18n("All updates selected (%1)", updateModel.updateSize) : i18np("%1/%2 update selected (%3)", "%1/%2 updates selected (%3)", updateModel.toUpdateCount, updateModel.totalUpdatesCount, updateModel.updateSize) elide: Text.ElideRight } } } supportsRefreshing: true onRefreshingChanged: { ResourcesModel.updateAction.triggered() refreshing = false } readonly property Item report: ColumnLayout { parent: page anchors.fill: parent Item { Layout.fillHeight: true width: 1 } Kirigami.Heading { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter text: page.footerLabel level: 3 } ProgressBar { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: Kirigami.Units.gridUnit * 20 value: page.footerProgress from: 0 to: 100 visible: page.isBusy } Kirigami.Icon { Layout.alignment: Qt.AlignHCenter visible: page.footerProgress === 0 && page.footerLabel !== "" source: "update-none" opacity: 0.1 width: Kirigami.Units.gridUnit * 8 height: width } Button { Layout.alignment: Qt.AlignHCenter text: i18n("Restart") visible: resourcesUpdatesModel.needsReboot onClicked: app.reboot() } Item { Layout.fillHeight: true width: 1 } } ListView { id: updatesView currentIndex: -1 displaced: Transition { YAnimator { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } model: QSortFilterProxyModel { sourceModel: updateModel sortRole: UpdateModel.SectionResourceProgressRole } section { property: "section" - delegate: Kirigami.Heading { - x: Kirigami.Units.gridUnit - level: 2 - text: section - height: implicitHeight + Kirigami.Units.largeSpacing * 2 + delegate: Kirigami.ListSectionHeader { + width: updatesView.width + label: section } } delegate: Kirigami.AbstractListItem { id: listItem backgroundColor: Kirigami.Theme.backgroundColor highlighted: ListView.isCurrentItem onEnabledChanged: if (!enabled) { layout.extended = false; } visible: resourceState < 3 //3=AbstractBackendUpdater.Done 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.fetchUpdateDetails(index) } RowLayout { Layout.fillWidth: true Layout.fillHeight: true CheckBox { id: itemChecked Layout.leftMargin: Kirigami.Units.gridUnit Layout.alignment: Qt.AlignVCenter checked: model.checked === Qt.Checked onClicked: model.checked = (model.checked===Qt.Checked ? Qt.Unchecked : Qt.Checked) enabled: !resourcesUpdatesModel.isProgressing } Kirigami.Icon { width: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: width source: decoration smooth: true } ColumnLayout { // App name Kirigami.Heading { Layout.fillWidth: true text: i18n("%1", display) level: 3 elide: Text.ElideRight } // Old and new version numbers; show when there's enough room Label { id: oldAndNewVersions Layout.fillWidth: true elide: Text.ElideRight text: i18n("%1 → %2", installedVersion, availableVersion) visible: installedVersion && !truncated opacity: listItem.hovered? 0.8 : 0.6 } // Available version only, for when old+new would be elided. // Use squeezey text to gain more room, and if it's still so // so long that it would be elided, elide from the left so // the most important part on the right is still visible // All of this is mostly for the benefit of KDE Neon users, // since the version strings there are really really long Label { Layout.fillWidth: true elide: Text.ElideLeft text: availableVersion visible: !oldAndNewVersions.visible font.letterSpacing: -0.5 opacity: listItem.hovered? 0.8 : 0.6 } } LabelBackground { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 text: resourceState == 2 ? i18n("Installing") : size progress: resourceProgress/100 } } Frame { Layout.fillWidth: true implicitHeight: view.contentHeight visible: layout.extended && changelog.length>0 LinkLabel { id: view anchors { right: parent.right left: parent.left } text: changelog textFormat: Text.StyledText wrapMode: Text.WordWrap onLinkActivated: Qt.openUrlExternally(link) } //This saves a binding loop on implictHeight, as the Label //height is updated twice (first time with the wrong value) Behavior on implicitHeight { PropertyAnimation { duration: Kirigami.Units.shortDuration } } } 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: ( resourcesUpdatesModel.isProgressing ? "progressing" : updateModel.hasUpdates ? "has-updates" : ResourcesModel.isFetching ? "fetching" : resourcesUpdatesModel.needsReboot ? "reboot" : 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; footerLabel: i18nc("@info", "Fetching Updates...") } PropertyChanges { target: page; footerProgress: ResourcesModel.fetchingUpdatesProgress } PropertyChanges { target: page; isBusy: true } }, State { name: "progressing" PropertyChanges { target: page; supportsRefreshing: false } PropertyChanges { target: page.actions; main: cancelUpdateAction } }, State { name: "has-updates" PropertyChanges { target: page; title: i18nc("@info", "Updates") } PropertyChanges { target: page.actions; main: updateAction } PropertyChanges { target: page.actions; left: refreshAction } }, State { name: "reboot" PropertyChanges { target: page; footerLabel: i18nc("@info", "The system requires a restart to apply updates") } }, State { name: "now-uptodate" PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to Date") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "uptodate" PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to Date") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "medium" PropertyChanges { target: page; title: i18nc("@info", "Up to Date") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "low" PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "unknown" PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") } PropertyChanges { target: page.actions; main: refreshAction } } ] }