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 }