Warning, /frameworks/knewstuff/src/qtquick/qml/Page.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2019 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003     SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 /**
0009  * @brief A Kirigami.Page component used for managing KNS entries
0010  *
0011  * This component is functionally equivalent to the old DownloadDialog
0012  * @see KNewStuff::DownloadDialog
0013  * @since 5.63
0014  */
0015 
0016 import QtQuick
0017 import QtQuick.Controls as QQC2
0018 import QtQuick.Layouts
0019 
0020 import org.kde.kcmutils as KCM
0021 import org.kde.kirigami 2 as Kirigami
0022 import org.kde.newstuff as NewStuff
0023 
0024 import "private" as Private
0025 import "private/entrygriddelegates" as EntryGridDelegates
0026 
0027 KCM.GridViewKCM {
0028     id: root
0029 
0030     /**
0031      * @brief The configuration file which describes the application (knsrc)
0032      *
0033      * The format and location of this file is found in the documentation for
0034      * KNS3::DownloadDialog
0035      */
0036     property alias configFile: newStuffEngine.configFile
0037 
0038     readonly property alias engine: newStuffEngine
0039 
0040     /**
0041      * Whether or not to show the Upload... context action
0042      * Usually this will be bound to the engine's property which usually defines
0043      * this, but you can override it programmatically by setting it here.
0044      * @since 5.85
0045      * @see KNSCore::Engine::uploadEnabled
0046      */
0047     property alias showUploadAction: uploadAction.visible
0048 
0049     /**
0050      * Show the details page for a specific entry.
0051      * If you call this function before the engine initialisation has been completed,
0052      * the action itself will be postponed until that has happened.
0053      * @param providerId The provider ID for the entry you wish to show details for
0054      * @param entryId The unique ID for the entry you wish to show details for
0055      * @since 5.79
0056      */
0057     function showEntryDetails(providerId, entryId) {
0058         _showEntryDetailsThrottle.enabled = true;
0059         _showEntryDetailsThrottle.entry = newStuffEngine. __createEntry(providerId, entryId);
0060         if (newStuffEngine.busyState === NewStuff.Engine.Initializing) {
0061             _showEntryDetailsThrottle.queryWhenInitialized = true;
0062         } else {
0063             _showEntryDetailsThrottle.requestDetails();
0064         }
0065     }
0066 
0067     // Helper for loading and showing entry details
0068     Connections {
0069         id: _showEntryDetailsThrottle
0070         target: newStuffModel.engine
0071         enabled: false
0072 
0073         property var entry
0074         property bool queryWhenInitialized: false
0075 
0076         function requestDetails() {
0077             newStuffEngine.updateEntryContents(entry);
0078             queryWhenInitialized = false;
0079         }
0080 
0081         function onBusyStateChanged() {
0082            if (queryWhenInitialized && newStuffEngine.busyState !== NewStuff.Engine.Initializing) {
0083                 requestDetails();
0084                 queryWhenInitialized = false;
0085            }
0086         }
0087 
0088         function onSignalEntryEvent(changedEntry, event) {
0089             if (event === NewStuff.Engine.DetailsLoadedEvent && changedEntry === entry) { // only uniqueId and providerId are checked for equality
0090                 enabled = false;
0091                 pageStack.push(detailsPage, {
0092                     newStuffModel,
0093                     providerId: changedEntry.providerId,
0094                     entry: changedEntry,
0095                 });
0096             }
0097         }
0098     }
0099 
0100     Connections {
0101         id: _restoreSearchState
0102 
0103         target: pageStack
0104         enabled: false
0105 
0106         function onCurrentIndexChanged() {
0107             if (pageStack.currentIndex === 0) {
0108                 newStuffEngine.restoreSearch();
0109                 _restoreSearchState.enabled = false;
0110             }
0111         }
0112     }
0113 
0114     property string uninstallLabel: i18ndc("knewstuff6", "Request uninstallation of this item", "Uninstall")
0115     property string useLabel: engine.useLabel
0116 
0117     property int viewMode: Page.ViewMode.Tiles
0118 
0119     enum ViewMode {
0120         Tiles,
0121         Icons,
0122         Preview
0123     }
0124 
0125     // Otherwise the first item will be focused, see BUG: 424894
0126     Component.onCompleted: {
0127         view.currentIndex = -1;
0128     }
0129 
0130     title: newStuffEngine.name
0131 
0132     view.header: Item {
0133         implicitWidth: view.width - Kirigami.Units.gridUnit
0134         implicitHeight: Kirigami.Units.gridUnit * 3
0135         visible: !loadingOverlay.visible
0136 
0137         Kirigami.InlineMessage {
0138             anchors.fill: parent
0139             anchors.margins: Kirigami.Units.smallSpacing
0140             visible: true
0141             text: i18nd("knewstuff6", "The content available here has been uploaded by users like you, and has not been reviewed by your distributor for functionality or stability.")
0142         }
0143     }
0144 
0145     NewStuff.Engine {
0146         id: newStuffEngine
0147     }
0148 
0149     NewStuff.QuestionAsker {}
0150     Private.ErrorDisplayer {
0151         engine: newStuffEngine
0152         active: root.isCurrentPage
0153     }
0154 
0155     QQC2.ActionGroup { id: viewModeActionGroup }
0156     QQC2.ActionGroup { id: viewFilterActionGroup }
0157     QQC2.ActionGroup { id: viewSortingActionGroup }
0158 
0159     actions: [
0160         Kirigami.Action {
0161             visible: newStuffEngine.needsLazyLoadSpinner
0162             displayComponent: QQC2.BusyIndicator {
0163                 implicitWidth: Kirigami.Units.iconSizes.smallMedium
0164                 implicitHeight: Kirigami.Units.iconSizes.smallMedium
0165             }
0166         },
0167 
0168         Kirigami.Action {
0169             text: {
0170                 if (root.viewMode === Page.ViewMode.Tiles) {
0171                     return i18nd("knewstuff6", "Tiles");
0172                 } else if (root.viewMode === Page.ViewMode.Icons) {
0173                     return i18nd("knewstuff6", "Icons");
0174                 } else {
0175                     return i18nd("knewstuff6", "Preview");
0176                 }
0177             }
0178             checkable: false
0179             icon.name: {
0180                 if (root.viewMode === Page.ViewMode.Tiles) {
0181                     return "view-list-details";
0182                 } else if (root.viewMode === Page.ViewMode.Icons) {
0183                     return "view-list-icons";
0184                 } else {
0185                     return "view-preview";
0186                 }
0187             }
0188 
0189             Kirigami.Action {
0190                 icon.name: "view-list-details"
0191                 text: i18nd("knewstuff6", "Detailed Tiles View Mode")
0192                 onTriggered: { root.viewMode = Page.ViewMode.Tiles; }
0193                 checked: root.viewMode === Page.ViewMode.Tiles
0194                 checkable: true
0195                 QQC2.ActionGroup.group: viewModeActionGroup
0196             }
0197 
0198             Kirigami.Action {
0199                 icon.name: "view-list-icons"
0200                 text: i18nd("knewstuff6", "Icons Only View Mode")
0201                 onTriggered: { root.viewMode = Page.ViewMode.Icons; }
0202                 checked: root.viewMode === Page.ViewMode.Icons
0203                 checkable: true
0204                 QQC2.ActionGroup.group: viewModeActionGroup
0205             }
0206 
0207             Kirigami.Action {
0208                 icon.name: "view-preview"
0209                 text: i18nd("knewstuff6", "Large Preview View Mode")
0210                 onTriggered: { root.viewMode = Page.ViewMode.Preview; }
0211                 checked: root.viewMode === Page.ViewMode.Preview
0212                 checkable: true
0213                 QQC2.ActionGroup.group: viewModeActionGroup
0214             }
0215         },
0216 
0217         Kirigami.Action {
0218             text: {
0219                 if (newStuffEngine.filter === 0) {
0220                     return i18nd("knewstuff6", "Everything");
0221                 } else if (newStuffEngine.filter === 1) {
0222                     return i18nd("knewstuff6", "Installed");
0223                 } else if (newStuffEngine.filter === 2) {
0224                     return i18nd("knewstuff6", "Updateable");
0225                 } else {
0226                     // then it's ExactEntryId and we want to probably just ignore that
0227                 }
0228             }
0229             checkable: false
0230             icon.name: {
0231                 if (newStuffEngine.filter === 0) {
0232                     return "package-available"
0233                 } else if (newStuffEngine.filter === 1) {
0234                     return "package-installed-updated"
0235                 } else if (newStuffEngine.filter === 2) {
0236                     return "package-installed-outdated"
0237                 } else {
0238                     // then it's ExactEntryId and we want to probably just ignore that
0239                 }
0240             }
0241 
0242             Kirigami.Action {
0243                 icon.name: "package-available"
0244                 text: i18ndc("knewstuff6", "List option which will set the filter to show everything", "Show All Entries")
0245                 checkable: true
0246                 checked: newStuffEngine.filter === 0
0247                 onTriggered: { newStuffEngine.filter = 0; }
0248                 QQC2.ActionGroup.group: viewFilterActionGroup
0249             }
0250 
0251             Kirigami.Action {
0252                 icon.name: "package-installed-updated"
0253                 text: i18ndc("knewstuff6", "List option which will set the filter so only installed items are shown", "Show Only Installed Entries")
0254                 checkable: true
0255                 checked: newStuffEngine.filter === 1
0256                 onTriggered: { newStuffEngine.filter = 1; }
0257                 QQC2.ActionGroup.group: viewFilterActionGroup
0258             }
0259 
0260             Kirigami.Action {
0261                 icon.name: "package-installed-outdated"
0262                 text: i18ndc("knewstuff6", "List option which will set the filter so only installed items with updates available are shown", "Show Only Updateable Entries")
0263                 checkable: true
0264                 checked: newStuffEngine.filter === 2
0265                 onTriggered: { newStuffEngine.filter = 2; }
0266                 QQC2.ActionGroup.group: viewFilterActionGroup
0267             }
0268         },
0269 
0270         Kirigami.Action {
0271             text: {
0272                 if (newStuffEngine.sortOrder === 0) {
0273                     return i18nd("knewstuff6", "Recent");
0274                 } else if (newStuffEngine.sortOrder === 1) {
0275                     return i18nd("knewstuff6", "Alphabetical");
0276                 } else if (newStuffEngine.sortOrder === 2) {
0277                     return i18nd("knewstuff6", "Rating");
0278                 } else if (newStuffEngine.sortOrder === 3) {
0279                     return i18nd("knewstuff6", "Downloads");
0280                 } else {
0281                 }
0282             }
0283             checkable: false
0284             icon.name: {
0285                 if (newStuffEngine.sortOrder === 0) {
0286                     return "change-date-symbolic";
0287                 } else if (newStuffEngine.sortOrder === 1) {
0288                     return "sort-name";
0289                 } else if (newStuffEngine.sortOrder === 2) {
0290                     return "rating";
0291                 } else if (newStuffEngine.sortOrder === 3) {
0292                     return "download";
0293                 } else {
0294                 }
0295             }
0296 
0297             Kirigami.Action {
0298                 icon.name: "change-date-symbolic"
0299                 text: i18ndc("knewstuff6", "List option which will set the sort order to based on when items were most recently updated", "Show Most Recent First")
0300                 checkable: true
0301                 checked: newStuffEngine.sortOrder === 0
0302                 onTriggered: { newStuffEngine.sortOrder = 0; }
0303                 QQC2.ActionGroup.group: viewSortingActionGroup
0304             }
0305 
0306             Kirigami.Action {
0307                 icon.name: "sort-name"
0308                 text: i18ndc("knewstuff6", "List option which will set the sort order to be alphabetical based on the name", "Sort Alphabetically By Name")
0309                 checkable: true
0310                 checked: newStuffEngine.sortOrder === 1
0311                 onTriggered: { newStuffEngine.sortOrder = 1; }
0312                 QQC2.ActionGroup.group: viewSortingActionGroup
0313             }
0314 
0315             Kirigami.Action {
0316                 icon.name: "rating"
0317                 text: i18ndc("knewstuff6", "List option which will set the sort order to based on user ratings", "Show Highest Rated First")
0318                 checkable: true
0319                 checked: newStuffEngine.sortOrder === 2
0320                 onTriggered: { newStuffEngine.sortOrder = 2; }
0321                 QQC2.ActionGroup.group: viewSortingActionGroup
0322             }
0323 
0324             Kirigami.Action {
0325                 icon.name: "download"
0326                 text: i18ndc("knewstuff6", "List option which will set the sort order to based on number of downloads", "Show Most Downloaded First")
0327                 checkable: true
0328                 checked: newStuffEngine.sortOrder === 3
0329                 onTriggered: { newStuffEngine.sortOrder = 3; }
0330                 QQC2.ActionGroup.group: viewSortingActionGroup
0331             }
0332         },
0333 
0334         Kirigami.Action {
0335             id: uploadAction
0336 
0337             text: i18nd("knewstuff6", "Upload…")
0338             tooltip: i18nd("knewstuff6", "Learn how to add your own hot new stuff to this list")
0339             icon.name: "upload-media"
0340             visible: newStuffEngine.uploadEnabled
0341 
0342             onTriggered: {
0343                 pageStack.push(uploadPage);
0344             }
0345         },
0346 
0347         Kirigami.Action {
0348             text: i18nd("knewstuff6", "Go to…")
0349             icon.name: "go-next"
0350             id: searchModelActions
0351             visible: children.length > 0
0352         },
0353 
0354         Kirigami.Action {
0355             text: i18nd("knewstuff6", "Search…")
0356             icon.name: "system-search"
0357             displayHint: Kirigami.DisplayHint.KeepVisible
0358 
0359             displayComponent: Kirigami.SearchField {
0360                 id: searchField
0361 
0362                 enabled: engine.isValid
0363                 focusSequence: "Ctrl+F"
0364                 placeholderText: i18nd("knewstuff6", "Search…")
0365                 text: newStuffEngine.searchTerm
0366 
0367                 onAccepted: {
0368                     newStuffEngine.searchTerm = searchField.text;
0369                 }
0370 
0371                 Component.onCompleted: {
0372                     if (!Kirigami.InputMethod.willShowOnActive) {
0373                         forceActiveFocus();
0374                     }
0375                 }
0376             }
0377         }
0378     ]
0379 
0380     Instantiator {
0381         id: searchPresetInstatiator
0382 
0383         model: newStuffEngine.searchPresetModel
0384 
0385         Kirigami.Action {
0386             required property int index
0387 
0388             text: model.displayName
0389             icon.name: model.iconName
0390 
0391             onTriggered: {
0392                 const curIndex = newStuffEngine.searchPresetModel.index(index, 0);
0393                 newStuffEngine.searchPresetModel.loadSearch(curIndex);
0394             }
0395         }
0396 
0397         onObjectAdded: (index, object) => {
0398             searchModelActions.children.push(object);
0399         }
0400     }
0401 
0402     Connections {
0403         target: newStuffEngine.searchPresetModel
0404 
0405         function onModelReset() {
0406             searchModelActions.children = [];
0407         }
0408     }
0409 
0410     footer: RowLayout {
0411         spacing: Kirigami.Units.smallSpacing
0412 
0413         visible: visibleChildren.length > 0
0414         height: visible ? implicitHeight : 0
0415 
0416         QQC2.Label {
0417             visible: categoriesCombo.count > 2
0418             text: i18nd("knewstuff6", "Category:")
0419         }
0420 
0421         QQC2.ComboBox {
0422             id: categoriesCombo
0423 
0424             Layout.fillWidth: true
0425 
0426             visible: count > 2
0427             model: newStuffEngine.categories
0428             textRole: "displayName"
0429 
0430             onCurrentIndexChanged: {
0431                 newStuffEngine.categoriesFilter = model.data(model.index(currentIndex, 0), NewStuff.CategoriesModel.NameRole);
0432             }
0433         }
0434 
0435         QQC2.Button {
0436             Layout.alignment: Qt.AlignRight
0437 
0438             text: i18nd("knewstuff6", "Contribute your own…")
0439             icon.name: "upload-media"
0440             visible: newStuffEngine.uploadEnabled && !uploadAction.visible
0441 
0442             onClicked: {
0443                 pageStack.push(uploadPage);
0444             }
0445         }
0446     }
0447 
0448     view.model: NewStuff.ItemsModel {
0449         id: newStuffModel
0450 
0451         engine: newStuffEngine
0452     }
0453 
0454     NewStuff.DownloadItemsSheet {
0455         id: downloadItemsSheet
0456 
0457         onItemPicked: (entry, downloadItemId) => {
0458             newStuffModel.engine.install(entry, downloadItemId);
0459         }
0460     }
0461 
0462     view.implicitCellWidth: switch (root.viewMode) {
0463         case Page.ViewMode.Tiles:
0464             return Kirigami.Units.gridUnit * 30;
0465 
0466         case Page.ViewMode.Preview:
0467             return Kirigami.Units.gridUnit * 25;
0468 
0469         case Page.ViewMode.Icons:
0470         default:
0471             return Kirigami.Units.gridUnit * 10;
0472     }
0473 
0474     view.implicitCellHeight: switch (root.viewMode) {
0475         case Page.ViewMode.Tiles:
0476             return Math.round(view.implicitCellWidth / 3);
0477 
0478         case Page.ViewMode.Preview:
0479             return Kirigami.Units.gridUnit * 25;
0480 
0481         case Page.ViewMode.Icons:
0482         default:
0483             return Math.round(view.implicitCellWidth / 1.6) + Kirigami.Units.gridUnit * 2;
0484     }
0485 
0486     view.delegate: switch (root.viewMode) {
0487         case Page.ViewMode.Tiles:
0488             return tileDelegate;
0489 
0490         case Page.ViewMode.Preview:
0491             return bigPreviewDelegate;
0492 
0493         case Page.ViewMode.Icons:
0494         default:
0495             return thumbDelegate;
0496     }
0497 
0498     Component {
0499         id: bigPreviewDelegate
0500 
0501         EntryGridDelegates.BigPreviewDelegate { }
0502     }
0503 
0504     Component {
0505         id: tileDelegate
0506 
0507         EntryGridDelegates.TileDelegate  {
0508             useLabel: root.useLabel
0509             uninstallLabel: root.uninstallLabel
0510         }
0511     }
0512 
0513     Component {
0514         id: thumbDelegate
0515 
0516         EntryGridDelegates.ThumbDelegate {
0517             useLabel: root.useLabel
0518             uninstallLabel: root.uninstallLabel
0519         }
0520     }
0521 
0522     Component {
0523         id: detailsPage
0524 
0525         NewStuff.EntryDetails { }
0526     }
0527 
0528     Component {
0529         id: uploadPage
0530 
0531         NewStuff.UploadPage {
0532             engine: newStuffEngine
0533         }
0534     }
0535 
0536     Item {
0537         id: loadingOverlay
0538 
0539         anchors.fill: parent
0540         
0541         opacity: newStuffEngine.isLoading && !newStuffEngine.needsLazyLoadSpinner ? 1 : 0
0542         Behavior on opacity {
0543             NumberAnimation {
0544                 duration: Kirigami.Units.longDuration
0545             }
0546         }
0547 
0548         visible: opacity > 0
0549 
0550         Rectangle {
0551             anchors.fill: parent
0552             color: Kirigami.Theme.backgroundColor
0553         }
0554 
0555         Kirigami.LoadingPlaceholder {
0556             anchors.centerIn: parent
0557             text: newStuffEngine.busyMessage
0558         }
0559     }
0560 }