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