KNewStuff

Page.qml
1/*
2 SPDX-FileCopyrightText: 2019 Dan Leinir Turthra Jensen <admin@leinir.dk>
3 SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8/**
9 * @brief A Kirigami.Page component used for managing KNS entries
10 *
11 * This component is functionally equivalent to the old DownloadDialog
12 * @see KNewStuff::DownloadDialog
13 * @since 5.63
14 */
15
16import QtQuick
17import QtQuick.Controls as QQC2
18import QtQuick.Layouts
19
20import org.kde.kcmutils as KCMUtils
21import org.kde.kirigami as Kirigami
22import org.kde.newstuff as NewStuff
23
24import "private" as Private
25import "private/entrygriddelegates" as EntryGridDelegates
26
27KCMUtils.GridViewKCM {
28 id: root
29
30 /**
31 * @brief The configuration file which describes the application (knsrc)
32 *
33 * The format and location of this file is found in the documentation for
34 * KNS3::DownloadDialog
35 */
36 property alias configFile: newStuffEngine.configFile
37
38 readonly property alias engine: newStuffEngine
39
40 /**
41 * Whether or not to show the Upload... context action
42 * Usually this will be bound to the engine's property which usually defines
43 * this, but you can override it programmatically by setting it here.
44 * @since 5.85
45 * @see KNSCore::Engine::uploadEnabled
46 */
47 property alias showUploadAction: uploadAction.visible
48
49 /**
50 * Show the details page for a specific entry.
51 * If you call this function before the engine initialisation has been completed,
52 * the action itself will be postponed until that has happened.
53 * @param providerId The provider ID for the entry you wish to show details for
54 * @param entryId The unique ID for the entry you wish to show details for
55 * @since 5.79
56 */
57 function showEntryDetails(providerId, entryId) {
58 _showEntryDetailsThrottle.enabled = true;
59 _showEntryDetailsThrottle.entry = newStuffEngine. __createEntry(providerId, entryId);
60 if (newStuffEngine.busyState === NewStuff.Engine.Initializing) {
61 _showEntryDetailsThrottle.queryWhenInitialized = true;
62 } else {
63 _showEntryDetailsThrottle.requestDetails();
64 }
65 }
66
67 // Helper for loading and showing entry details
68 Connections {
69 id: _showEntryDetailsThrottle
70 target: newStuffModel.engine
71 enabled: false
72
73 property var entry
74 property bool queryWhenInitialized: false
75
76 function requestDetails() {
77 newStuffEngine.updateEntryContents(entry);
79 }
80
81 function onBusyStateChanged() {
82 if (queryWhenInitialized && newStuffEngine.busyState !== NewStuff.Engine.Initializing) {
85 }
86 }
87
88 function onSignalEntryEvent(changedEntry, event) {
89 if (event === NewStuff.Engine.DetailsLoadedEvent && changedEntry === entry) { // only uniqueId and providerId are checked for equality
90 enabled = false;
91 pageStack.push(detailsPage, {
92 newStuffModel,
93 providerId: changedEntry.providerId,
94 entry: changedEntry,
95 });
96 }
97 }
98 }
99
100 Connections {
101 id: _restoreSearchState
102
103 target: pageStack
104 enabled: false
105
106 function onCurrentIndexChanged() {
107 if (pageStack.currentIndex === 0) {
108 newStuffEngine.restoreSearch();
109 _restoreSearchState.enabled = false;
110 }
111 }
112 }
113
114 property string uninstallLabel: i18ndc("knewstuff6", "Request uninstallation of this item", "Uninstall")
115 property string useLabel: engine.useLabel
116
117 property int viewMode: Page.ViewMode.Tiles
118
119 enum ViewMode {
120 Tiles,
121 Icons,
122 Preview
123 }
124
125 // Otherwise the first item will be focused, see BUG: 424894
126 Component.onCompleted: {
127 view.currentIndex = -1;
128 }
129
130 title: newStuffEngine.name
131
132 headerPaddingEnabled: false
133 header: Kirigami.InlineMessage {
134 readonly property bool riskyContent: newStuffEngine.contentWarningType === NewStuff.Engine.Executables
135 visible: !loadingOverlay.visible
136 type: riskyContent ? Kirigami.MessageType.Warning : Kirigami.MessageType.Information
137 position: Kirigami.InlineMessage.Position.Header
138 text: riskyContent
139 ? xi18nd("knewstuff6", "Use caution when accessing user-created content shown here, as it may contain executable code that hasn't been tested by KDE or your distributor for safety, stability, or quality")
140 : i18nd("knewstuff6", "User-created content shown here hasn't been tested by KDE or your distributor for functionality or quality.")
141 }
142
144 id: newStuffEngine
145 }
146
148 parent: root.QQC2.Overlay.overlay
149 }
150
151 Private.ErrorDisplayer {
152 engine: newStuffEngine
153 active: root.isCurrentPage
154 }
155
156 QQC2.ActionGroup { id: viewModeActionGroup }
157 QQC2.ActionGroup { id: viewFilterActionGroup }
158 QQC2.ActionGroup { id: viewSortingActionGroup }
159
160 actions: [
162 visible: newStuffEngine.needsLazyLoadSpinner
163 displayComponent: QQC2.BusyIndicator {
164 implicitWidth: Kirigami.Units.iconSizes.smallMedium
165 implicitHeight: Kirigami.Units.iconSizes.smallMedium
166 }
167 },
168
170 text: {
171 if (root.viewMode === Page.ViewMode.Tiles) {
172 return i18nd("knewstuff6", "Tiles");
173 } else if (root.viewMode === Page.ViewMode.Icons) {
174 return i18nd("knewstuff6", "Icons");
175 } else {
176 return i18nd("knewstuff6", "Preview");
177 }
178 }
179 checkable: false
180 icon.name: {
181 if (root.viewMode === Page.ViewMode.Tiles) {
182 return "view-list-details";
183 } else if (root.viewMode === Page.ViewMode.Icons) {
184 return "view-list-icons";
185 } else {
186 return "view-preview";
187 }
188 }
189
190 Kirigami.Action {
191 icon.name: "view-list-details"
192 text: i18nd("knewstuff6", "Detailed Tiles View Mode")
193 onTriggered: source => {
194 root.viewMode = Page.ViewMode.Tiles;
195 }
196 checked: root.viewMode === Page.ViewMode.Tiles
197 checkable: true
198 QQC2.ActionGroup.group: viewModeActionGroup
199 }
200
201 Kirigami.Action {
202 icon.name: "view-list-icons"
203 text: i18nd("knewstuff6", "Icons Only View Mode")
204 onTriggered: source => {
205 root.viewMode = Page.ViewMode.Icons;
206 }
207 checked: root.viewMode === Page.ViewMode.Icons
208 checkable: true
209 QQC2.ActionGroup.group: viewModeActionGroup
210 }
211
212 Kirigami.Action {
213 icon.name: "view-preview"
214 text: i18nd("knewstuff6", "Large Preview View Mode")
215 onTriggered: source => {
216 root.viewMode = Page.ViewMode.Preview;
217 }
218 checked: root.viewMode === Page.ViewMode.Preview
219 checkable: true
220 QQC2.ActionGroup.group: viewModeActionGroup
221 }
222 },
223
224 Kirigami.Action {
225 text: {
226 if (newStuffEngine.filter === 0) {
227 return i18nd("knewstuff6", "Everything");
228 } else if (newStuffEngine.filter === 1) {
229 return i18nd("knewstuff6", "Installed");
230 } else if (newStuffEngine.filter === 2) {
231 return i18nd("knewstuff6", "Updateable");
232 } else {
233 // then it's ExactEntryId and we want to probably just ignore that
234 }
235 }
236 checkable: false
237 icon.name: {
238 if (newStuffEngine.filter === 0) {
239 return "package-available"
240 } else if (newStuffEngine.filter === 1) {
241 return "package-installed-updated"
242 } else if (newStuffEngine.filter === 2) {
243 return "package-installed-outdated"
244 } else {
245 // then it's ExactEntryId and we want to probably just ignore that
246 }
247 }
248
249 Kirigami.Action {
250 icon.name: "package-available"
251 text: i18ndc("knewstuff6", "List option which will set the filter to show everything", "Show All Entries")
252 checkable: true
253 checked: newStuffEngine.filter === 0
254 onTriggered: source => {
255 newStuffEngine.filter = 0;
256 }
257 QQC2.ActionGroup.group: viewFilterActionGroup
258 }
259
260 Kirigami.Action {
261 icon.name: "package-installed-updated"
262 text: i18ndc("knewstuff6", "List option which will set the filter so only installed items are shown", "Show Only Installed Entries")
263 checkable: true
264 checked: newStuffEngine.filter === 1
265 onTriggered: source => {
266 newStuffEngine.filter = 1;
267 }
268 QQC2.ActionGroup.group: viewFilterActionGroup
269 }
270
271 Kirigami.Action {
272 icon.name: "package-installed-outdated"
273 text: i18ndc("knewstuff6", "List option which will set the filter so only installed items with updates available are shown", "Show Only Updateable Entries")
274 checkable: true
275 checked: newStuffEngine.filter === 2
276 onTriggered: source => {
277 newStuffEngine.filter = 2;
278 }
279 QQC2.ActionGroup.group: viewFilterActionGroup
280 }
281 },
282
283 Kirigami.Action {
284 text: {
285 if (newStuffEngine.sortOrder === 0) {
286 return i18nd("knewstuff6", "Recent");
287 } else if (newStuffEngine.sortOrder === 1) {
288 return i18nd("knewstuff6", "Alphabetical");
289 } else if (newStuffEngine.sortOrder === 2) {
290 return i18nd("knewstuff6", "Rating");
291 } else if (newStuffEngine.sortOrder === 3) {
292 return i18nd("knewstuff6", "Downloads");
293 } else {
294 }
295 }
296 checkable: false
297 icon.name: {
298 if (newStuffEngine.sortOrder === 0) {
299 return "change-date-symbolic";
300 } else if (newStuffEngine.sortOrder === 1) {
301 return "sort-name";
302 } else if (newStuffEngine.sortOrder === 2) {
303 return "rating";
304 } else if (newStuffEngine.sortOrder === 3) {
305 return "download";
306 } else {
307 }
308 }
309
310 Kirigami.Action {
311 icon.name: "change-date-symbolic"
312 text: i18ndc("knewstuff6", "List option which will set the sort order to based on when items were most recently updated", "Show Most Recent First")
313 checkable: true
314 checked: newStuffEngine.sortOrder === 0
315 onTriggered: source => {
316 newStuffEngine.sortOrder = 0;
317 }
318 QQC2.ActionGroup.group: viewSortingActionGroup
319 }
320
321 Kirigami.Action {
322 icon.name: "sort-name"
323 text: i18ndc("knewstuff6", "List option which will set the sort order to be alphabetical based on the name", "Sort Alphabetically By Name")
324 checkable: true
325 checked: newStuffEngine.sortOrder === 1
326 onTriggered: source => {
327 newStuffEngine.sortOrder = 1;
328 }
329 QQC2.ActionGroup.group: viewSortingActionGroup
330 }
331
332 Kirigami.Action {
333 icon.name: "rating"
334 text: i18ndc("knewstuff6", "List option which will set the sort order to based on user ratings", "Show Highest Rated First")
335 checkable: true
336 checked: newStuffEngine.sortOrder === 2
337 onTriggered: source => {
338 newStuffEngine.sortOrder = 2;
339 }
340 QQC2.ActionGroup.group: viewSortingActionGroup
341 }
342
343 Kirigami.Action {
344 icon.name: "download"
345 text: i18ndc("knewstuff6", "List option which will set the sort order to based on number of downloads", "Show Most Downloaded First")
346 checkable: true
347 checked: newStuffEngine.sortOrder === 3
348 onTriggered: source => {
349 newStuffEngine.sortOrder = 3;
350 }
351 QQC2.ActionGroup.group: viewSortingActionGroup
352 }
353 },
354
355 Kirigami.Action {
356 id: uploadAction
357
358 text: i18nd("knewstuff6", "Upload…")
359 tooltip: i18nd("knewstuff6", "Learn how to add your own hot new stuff to this list")
360 icon.name: "upload-media"
361 visible: newStuffEngine.uploadEnabled
362
363 onTriggered: source => {
364 pageStack.push(uploadPage);
365 }
366 },
367
368 Kirigami.Action {
369 text: i18nd("knewstuff6", "Go to…")
370 icon.name: "go-next"
372 visible: children.length > 0
373 },
374
376 text: i18nd("knewstuff6", "Search…")
377 icon.name: "system-search"
378 displayHint: Kirigami.DisplayHint.KeepVisible
379
380 displayComponent: Kirigami.SearchField {
381 id: searchField
382
383 enabled: engine.isValid
384 focusSequence: "Ctrl+F"
385 placeholderText: i18nd("knewstuff6", "Search…")
386 text: newStuffEngine.searchTerm
387
388 onAccepted: {
389 newStuffEngine.searchTerm = searchField.text;
390 }
391
392 Component.onCompleted: {
393 if (!Kirigami.InputMethod.willShowOnActive) {
394 forceActiveFocus();
395 }
396 }
397 }
398 }
399 ]
400
403
404 model: newStuffEngine.searchPresetModel
405
406 Kirigami.Action {
407 required property int index
408
409 text: model.displayName
410 icon.name: model.iconName
411
412 onTriggered: source => {
413 const curIndex = newStuffEngine.searchPresetModel.index(index, 0);
414 newStuffEngine.searchPresetModel.loadSearch(curIndex);
415 }
416 }
417
418 onObjectAdded: (index, object) => {
419 searchModelActions.children.push(object);
420 }
421 }
422
423 Connections {
424 target: newStuffEngine.searchPresetModel
425
426 function onModelReset() {
427 searchModelActions.children = [];
428 }
429 }
430
431 footer: RowLayout {
432 spacing: Kirigami.Units.smallSpacing
433
434 visible: visibleChildren.length > 0
435 height: visible ? implicitHeight : 0
436
437 QQC2.Label {
438 visible: categoriesCombo.count > 2
439 text: i18nd("knewstuff6", "Category:")
440 }
441
442 QQC2.ComboBox {
444
445 Layout.fillWidth: true
446
447 visible: count > 2
448 model: newStuffEngine.categories
449 textRole: "displayName"
450
452 newStuffEngine.categoriesFilter = model.data(model.index(currentIndex, 0), NewStuff.CategoriesModel.NameRole);
453 }
454 }
455
456 QQC2.Button {
457 Layout.alignment: Qt.AlignRight
458
459 text: i18nd("knewstuff6", "Contribute your own…")
460 icon.name: "upload-media"
461 visible: newStuffEngine.uploadEnabled && !uploadAction.visible
462
463 onClicked: {
464 pageStack.push(uploadPage);
465 }
466 }
467 }
468
469 view.model: NewStuff.ItemsModel {
470 id: newStuffModel
471
472 engine: newStuffEngine
473 }
474
475 NewStuff.DownloadItemsSheet {
476 id: downloadItemsSheet
477
478 parent: root.QQC2.Overlay.overlay
479
480 onItemPicked: (entry, downloadItemId) => {
481 newStuffModel.engine.install(entry, downloadItemId);
482 }
483 }
484
485 view.implicitCellWidth: switch (root.viewMode) {
486 case Page.ViewMode.Tiles:
487 return Kirigami.Units.gridUnit * 30;
488
489 case Page.ViewMode.Preview:
490 return Kirigami.Units.gridUnit * 25;
491
492 case Page.ViewMode.Icons:
493 default:
494 return Kirigami.Units.gridUnit * 10;
495 }
496
497 view.implicitCellHeight: switch (root.viewMode) {
498 case Page.ViewMode.Tiles:
499 return Math.round(view.implicitCellWidth / 3);
500
501 case Page.ViewMode.Preview:
502 return Kirigami.Units.gridUnit * 25;
503
504 case Page.ViewMode.Icons:
505 default:
506 return Math.round(view.implicitCellWidth / 1.6) + Kirigami.Units.gridUnit * 2;
507 }
508
509 view.delegate: switch (root.viewMode) {
510 case Page.ViewMode.Tiles:
511 return tileDelegate;
512
513 case Page.ViewMode.Preview:
514 return bigPreviewDelegate;
515
516 case Page.ViewMode.Icons:
517 default:
518 return thumbDelegate;
519 }
520
521 Component {
523
524 EntryGridDelegates.BigPreviewDelegate { }
525 }
526
527 Component {
528 id: tileDelegate
529
530 EntryGridDelegates.TileDelegate {
531 useLabel: root.useLabel
532 uninstallLabel: root.uninstallLabel
533 }
534 }
535
536 Component {
537 id: thumbDelegate
538
539 EntryGridDelegates.ThumbDelegate {
540 useLabel: root.useLabel
541 uninstallLabel: root.uninstallLabel
542 }
543 }
544
545 Component {
546 id: detailsPage
547
548 NewStuff.EntryDetails { }
549 }
550
551 Component {
552 id: uploadPage
553
554 NewStuff.UploadPage {
555 engine: newStuffEngine
556 }
557 }
558
559 Item {
561
562 anchors.fill: parent
563
564 opacity: newStuffEngine.isLoading && !newStuffEngine.needsLazyLoadSpinner ? 1 : 0
565 Behavior on opacity {
567 duration: Kirigami.Units.longDuration
568 }
569 }
570
571 visible: opacity > 0
572
573 Rectangle {
574 anchors.fill: parent
575 color: Kirigami.Theme.backgroundColor
576 }
577
578 Kirigami.LoadingPlaceholder {
579 anchors.centerIn: parent
580 text: newStuffEngine.busyMessage
581 }
582 }
583}
KNSCore::EngineBase for interfacing with QML.
Definition quickengine.h:29
A component used to forward questions from KNewStuff's engine to the UI.
QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...)
QString xi18nd(const char *domain, const char *text, const TYPE &arg...)
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
T * data() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 24 2024 11:54:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.