/* SPDX-FileCopyrightText: 2019 Dan Leinir Turthra Jensen SPDX-FileCopyrightText: 2023 ivan tkachenko SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import org.kde.kcmutils as KCMUtils import org.kde.kirigami as Kirigami import org.kde.newstuff as NewStuff import org.kde.coreaddons as KCoreAddons import "private" as Private import "private/entrygriddelegates" as EntryGridDelegates /*! \qmltype Page \inqmlmodule org.kde.newstuff \brief A Kirigami.Page component used for managing KNS entries. This component is functionally equivalent to the old DownloadDialog. \sa KNewStuff::DownloadDialog \since 5.63 */ KCMUtils.GridViewKCM { id: root /*! \brief The configuration file which describes the application (knsrc) The format and location of this file is found in the documentation for KNS3::DownloadDialog */ property alias configFile: newStuffEngine.configFile readonly property alias engine: newStuffEngine /*! Whether or not to show the Upload... context action Usually this will be bound to the engine's property which usually defines this, but you can override it programmatically by setting it here. \since 5.85 \sa KNSCore::Engine::uploadEnabled */ property alias showUploadAction: uploadAction.visible /*! Show the details page for a specific entry. If you call this function before the engine initialisation has been completed, the action itself will be postponed until that has happened. \a providerId The provider ID for the entry you wish to show details for \a entryId The unique ID for the entry you wish to show details for \since 5.79 */ function showEntryDetails(providerId, entryId) { _showEntryDetailsThrottle.enabled = true; _showEntryDetailsThrottle.entry = newStuffEngine. __createEntry(providerId, entryId); if (newStuffEngine.busyState === NewStuff.Engine.Initializing) { _showEntryDetailsThrottle.queryWhenInitialized = true; } else { _showEntryDetailsThrottle.requestDetails(); } } // Helper for loading and showing entry details Connections { id: _showEntryDetailsThrottle target: newStuffModel.engine enabled: false property var entry property bool queryWhenInitialized: false function requestDetails() { newStuffEngine.updateEntryContents(entry); queryWhenInitialized = false; } function onBusyStateChanged() { if (queryWhenInitialized && newStuffEngine.busyState !== NewStuff.Engine.Initializing) { requestDetails(); queryWhenInitialized = false; } } function onSignalEntryEvent(changedEntry, event) { if (event === NewStuff.Engine.DetailsLoadedEvent && changedEntry === entry) { // only uniqueId and providerId are checked for equality enabled = false; pageStack.push(detailsPage, { newStuffModel, providerId: changedEntry.providerId, entry: changedEntry, }); } } } Connections { id: _restoreSearchState target: pageStack enabled: false function onCurrentIndexChanged() { if (pageStack.currentIndex === 0) { newStuffEngine.restoreSearch(); _restoreSearchState.enabled = false; } } } property string uninstallLabel: i18ndc("knewstuff6", "Request uninstallation of this item", "Uninstall") property string useLabel: engine.useLabel property int viewMode: Page.ViewMode.Tiles // TODO KF7: remove Icons enum ViewMode { Tiles, Icons, Preview } // Otherwise the first item will be focused, see BUG: 424894 Component.onCompleted: { view.currentIndex = -1; } title: newStuffEngine.name headerPaddingEnabled: false header: Kirigami.InlineMessage { readonly property bool riskyContent: newStuffEngine.contentWarningType === NewStuff.Engine.Executables visible: !loadingOverlay.visible type: riskyContent ? Kirigami.MessageType.Warning : Kirigami.MessageType.Information position: Kirigami.InlineMessage.Position.Header text: riskyContent ? xi18ndc("knewstuff6", "@info displayed as InlineMessage", "Use caution when accessing user-created content shown here, as it may contain executable code that hasn't been tested by KDE or %1 for safety, stability, or quality.", KCoreAddons.KOSRelease.name) : i18ndc("knewstuff6", "@info displayed as InlineMessage", "User-created content shown here hasn't been tested by KDE or %1 for functionality or quality.", KCoreAddons.KOSRelease.name) } NewStuff.Engine { id: newStuffEngine } NewStuff.QuestionAsker { parent: root.QQC2.Overlay.overlay } Private.ErrorDisplayer { engine: newStuffEngine active: root.isCurrentPage } QQC2.ActionGroup { id: viewFilterActionGroup } QQC2.ActionGroup { id: viewSortingActionGroup } actions: [ Kirigami.Action { visible: newStuffEngine.needsLazyLoadSpinner displayComponent: QQC2.BusyIndicator { implicitWidth: Kirigami.Units.iconSizes.smallMedium implicitHeight: Kirigami.Units.iconSizes.smallMedium } }, Kirigami.Action { text: { if (newStuffEngine.filter === 0) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "All"); } else if (newStuffEngine.filter === 1) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Installed"); } else if (newStuffEngine.filter === 2) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Updateable"); } else { // then it's ExactEntryId and we want to probably just ignore that } } checkable: false icon.name: "view-filter" Kirigami.Action { icon.name: "package-available" text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the filter to show everything", "All") checkable: true checked: newStuffEngine.filter === 0 onTriggered: source => { newStuffEngine.filter = 0; } QQC2.ActionGroup.group: viewFilterActionGroup } Kirigami.Action { icon.name: "package-installed-updated" text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the filter so only installed items are shown", "Installed") checkable: true checked: newStuffEngine.filter === 1 onTriggered: source => { newStuffEngine.filter = 1; } QQC2.ActionGroup.group: viewFilterActionGroup } Kirigami.Action { icon.name: "package-installed-outdated" text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the filter so only installed items with updates available are shown", "Updateable") checkable: true checked: newStuffEngine.filter === 2 onTriggered: source => { newStuffEngine.filter = 2; } QQC2.ActionGroup.group: viewFilterActionGroup } }, Kirigami.Action { text: { if (newStuffEngine.sortOrder === 0) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Release date"); } else if (newStuffEngine.sortOrder === 1) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Name"); } else if (newStuffEngine.sortOrder === 2) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Rating"); } else if (newStuffEngine.sortOrder === 3) { return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Downloads"); } else { } } checkable: false icon.name: "view-sort" Kirigami.Action { icon.name: "sort-name" text: i18ndc("knewstuff6", "@option:radio in menu, List option which will set the sort order to be alphabetical based on the name", "Name") checkable: true checked: newStuffEngine.sortOrder === 1 onTriggered: source => { newStuffEngine.sortOrder = 1; } QQC2.ActionGroup.group: viewSortingActionGroup } Kirigami.Action { icon.name: "rating" text: i18ndc("knewstuff6", "@option:radio in menu, List option which will set the sort order to based on user ratings", "Rating") checkable: true checked: newStuffEngine.sortOrder === 2 onTriggered: source => { newStuffEngine.sortOrder = 2; } QQC2.ActionGroup.group: viewSortingActionGroup } Kirigami.Action { icon.name: "download" text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the sort order to based on number of downloads", "Downloads") checkable: true checked: newStuffEngine.sortOrder === 3 onTriggered: source => { newStuffEngine.sortOrder = 3; } QQC2.ActionGroup.group: viewSortingActionGroup } Kirigami.Action { icon.name: "change-date-symbolic" text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the sort order to based on when items were most recently updated", "Release date") checkable: true checked: newStuffEngine.sortOrder === 0 onTriggered: source => { newStuffEngine.sortOrder = 0; } QQC2.ActionGroup.group: viewSortingActionGroup } }, Kirigami.Action { id: uploadAction text: i18nd("knewstuff6", "Upload…") tooltip: i18nd("knewstuff6", "Learn how to add your own hot new stuff to this list") icon.name: "upload-media" visible: newStuffEngine.uploadEnabled onTriggered: source => { pageStack.push(uploadPage); } }, Kirigami.Action { text: i18nd("knewstuff6", "Go to…") icon.name: "go-next" id: searchModelActions visible: children.length > 0 }, Kirigami.Action { text: i18nd("knewstuff6", "Search…") icon.name: "system-search" displayHint: Kirigami.DisplayHint.KeepVisible displayComponent: Kirigami.SearchField { id: searchField enabled: engine.isValid focusSequence: "Ctrl+F" placeholderText: i18nd("knewstuff6", "Search…") text: newStuffEngine.searchTerm onAccepted: { newStuffEngine.searchTerm = searchField.text; } Component.onCompleted: { if (!Kirigami.InputMethod.willShowOnActive) { forceActiveFocus(); } } } } ] Instantiator { id: searchPresetInstatiator model: newStuffEngine.searchPresetModel Kirigami.Action { required property int index text: model.displayName icon.name: model.iconName onTriggered: source => { const curIndex = newStuffEngine.searchPresetModel.index(index, 0); newStuffEngine.searchPresetModel.loadSearch(curIndex); } } onObjectAdded: (index, object) => { searchModelActions.children.push(object); } } Connections { target: newStuffEngine.searchPresetModel function onModelReset() { searchModelActions.children = []; } } footer: RowLayout { spacing: Kirigami.Units.smallSpacing visible: visibleChildren.length > 0 height: visible ? implicitHeight : 0 QQC2.Label { visible: categoriesCombo.count > 2 text: i18nd("knewstuff6", "Category:") } QQC2.ComboBox { id: categoriesCombo Layout.fillWidth: true visible: count > 2 model: newStuffEngine.categories textRole: "displayName" onCurrentIndexChanged: { newStuffEngine.categoriesFilter = model.data(model.index(currentIndex, 0), NewStuff.CategoriesModel.NameRole); } } QQC2.Button { Layout.alignment: Qt.AlignRight text: i18nd("knewstuff6", "Contribute Your Own…") icon.name: "upload-media" visible: newStuffEngine.uploadEnabled && !uploadAction.visible onClicked: { pageStack.push(uploadPage); } } } view.model: NewStuff.ItemsModel { id: newStuffModel engine: newStuffEngine } NewStuff.DownloadItemsSheet { id: downloadItemsSheet parent: root.QQC2.Overlay.overlay onItemPicked: (entry, downloadItemId) => { newStuffModel.engine.installLinkId(entry, downloadItemId); } } view.implicitCellWidth: Kirigami.Units.gridUnit * 20 view.implicitCellHeight: switch (root.viewMode) { case Page.ViewMode.Preview: return Kirigami.Units.gridUnit * 20; case Page.ViewMode.Tiles: case Page.ViewMode.Icons: default: return Kirigami.Units.gridUnit * 8; } view.delegate: switch (root.viewMode) { case Page.ViewMode.Preview: return bigPreviewDelegate; case Page.ViewMode.Tiles: case Page.ViewMode.Icons: default: return tileDelegate; } Component { id: bigPreviewDelegate EntryGridDelegates.BigPreviewDelegate { } } Component { id: tileDelegate EntryGridDelegates.TileDelegate { useLabel: root.useLabel uninstallLabel: root.uninstallLabel } } Component { id: detailsPage NewStuff.EntryDetails { } } Component { id: uploadPage NewStuff.UploadPage { engine: newStuffEngine } } Item { id: loadingOverlay anchors.fill: parent opacity: newStuffEngine.isLoading && !newStuffEngine.needsLazyLoadSpinner ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } visible: opacity > 0 Rectangle { anchors.fill: parent color: Kirigami.Theme.backgroundColor } Kirigami.LoadingPlaceholder { anchors.centerIn: parent text: newStuffEngine.busyMessage } } }