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
23import org.kde.coreaddons as KCoreAddons
24
25import "private" as Private
26import "private/entrygriddelegates" as EntryGridDelegates
27
28KCMUtils.GridViewKCM {
29 id: root
30
31 /**
32 * @brief The configuration file which describes the application (knsrc)
33 *
34 * The format and location of this file is found in the documentation for
35 * KNS3::DownloadDialog
36 */
37 property alias configFile: newStuffEngine.configFile
38
39 readonly property alias engine: newStuffEngine
40
41 /**
42 * Whether or not to show the Upload... context action
43 * Usually this will be bound to the engine's property which usually defines
44 * this, but you can override it programmatically by setting it here.
45 * @since 5.85
46 * @see KNSCore::Engine::uploadEnabled
47 */
48 property alias showUploadAction: uploadAction.visible
49
50 /**
51 * Show the details page for a specific entry.
52 * If you call this function before the engine initialisation has been completed,
53 * the action itself will be postponed until that has happened.
54 * @param providerId The provider ID for the entry you wish to show details for
55 * @param entryId The unique ID for the entry you wish to show details for
56 * @since 5.79
57 */
58 function showEntryDetails(providerId, entryId) {
59 _showEntryDetailsThrottle.enabled = true;
60 _showEntryDetailsThrottle.entry = newStuffEngine. __createEntry(providerId, entryId);
61 if (newStuffEngine.busyState === NewStuff.Engine.Initializing) {
62 _showEntryDetailsThrottle.queryWhenInitialized = true;
63 } else {
64 _showEntryDetailsThrottle.requestDetails();
65 }
66 }
67
68 // Helper for loading and showing entry details
69 Connections {
70 id: _showEntryDetailsThrottle
71 target: newStuffModel.engine
72 enabled: false
73
74 property var entry
75 property bool queryWhenInitialized: false
76
77 function requestDetails() {
78 newStuffEngine.updateEntryContents(entry);
79 queryWhenInitialized = false;
80 }
81
82 function onBusyStateChanged() {
83 if (queryWhenInitialized && newStuffEngine.busyState !== NewStuff.Engine.Initializing) {
84 requestDetails();
85 queryWhenInitialized = false;
86 }
87 }
88
89 function onSignalEntryEvent(changedEntry, event) {
90 if (event === NewStuff.Engine.DetailsLoadedEvent && changedEntry === entry) { // only uniqueId and providerId are checked for equality
91 enabled = false;
92 pageStack.push(detailsPage, {
93 newStuffModel,
94 providerId: changedEntry.providerId,
95 entry: changedEntry,
96 });
97 }
98 }
99 }
100
101 Connections {
102 id: _restoreSearchState
103
104 target: pageStack
105 enabled: false
106
107 function onCurrentIndexChanged() {
108 if (pageStack.currentIndex === 0) {
109 newStuffEngine.restoreSearch();
110 _restoreSearchState.enabled = false;
111 }
112 }
113 }
114
115 property string uninstallLabel: i18ndc("knewstuff6", "Request uninstallation of this item", "Uninstall")
116 property string useLabel: engine.useLabel
117
118 property int viewMode: Page.ViewMode.Tiles
119
120 // TODO KF7: remove Icons
121 enum ViewMode {
122 Tiles,
123 Icons,
124 Preview
125 }
126
127 // Otherwise the first item will be focused, see BUG: 424894
128 Component.onCompleted: {
129 view.currentIndex = -1;
130 }
131
132 title: newStuffEngine.name
133
134 headerPaddingEnabled: false
135 header: Kirigami.InlineMessage {
136 readonly property bool riskyContent: newStuffEngine.contentWarningType === NewStuff.Engine.Executables
137 visible: !loadingOverlay.visible
138 type: riskyContent ? Kirigami.MessageType.Warning : Kirigami.MessageType.Information
139 position: Kirigami.InlineMessage.Position.Header
140 text: riskyContent
141 ? 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)
142 : 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)
143 }
144
145 NewStuff.Engine {
146 id: newStuffEngine
147 }
148
149 NewStuff.QuestionAsker {
150 parent: root.QQC2.Overlay.overlay
151 }
152
153 Private.ErrorDisplayer {
154 engine: newStuffEngine
155 active: root.isCurrentPage
156 }
157
158 QQC2.ActionGroup { id: viewFilterActionGroup }
159 QQC2.ActionGroup { id: viewSortingActionGroup }
160
161 actions: [
163 visible: newStuffEngine.needsLazyLoadSpinner
164 displayComponent: QQC2.BusyIndicator {
165 implicitWidth: Kirigami.Units.iconSizes.smallMedium
166 implicitHeight: Kirigami.Units.iconSizes.smallMedium
167 }
168 },
169
171 text: {
172 if (newStuffEngine.filter === 0) {
173 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "All");
174 } else if (newStuffEngine.filter === 1) {
175 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Installed");
176 } else if (newStuffEngine.filter === 2) {
177 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Updateable");
178 } else {
179 // then it's ExactEntryId and we want to probably just ignore that
180 }
181 }
182 checkable: false
183 icon.name: "view-filter"
184
185 Kirigami.Action {
186 icon.name: "package-available"
187 text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the filter to show everything", "All")
188 checkable: true
189 checked: newStuffEngine.filter === 0
190 onTriggered: source => {
191 newStuffEngine.filter = 0;
192 }
193 QQC2.ActionGroup.group: viewFilterActionGroup
194 }
195
196 Kirigami.Action {
197 icon.name: "package-installed-updated"
198 text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the filter so only installed items are shown", "Installed")
199 checkable: true
200 checked: newStuffEngine.filter === 1
201 onTriggered: source => {
202 newStuffEngine.filter = 1;
203 }
204 QQC2.ActionGroup.group: viewFilterActionGroup
205 }
206
207 Kirigami.Action {
208 icon.name: "package-installed-outdated"
209 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")
210 checkable: true
211 checked: newStuffEngine.filter === 2
212 onTriggered: source => {
213 newStuffEngine.filter = 2;
214 }
215 QQC2.ActionGroup.group: viewFilterActionGroup
216 }
217 },
218
219 Kirigami.Action {
220 text: {
221 if (newStuffEngine.sortOrder === 0) {
222 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Release date");
223 } else if (newStuffEngine.sortOrder === 1) {
224 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Name");
225 } else if (newStuffEngine.sortOrder === 2) {
226 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Rating");
227 } else if (newStuffEngine.sortOrder === 3) {
228 return i18ndc("knewstuff6", "@action:button opening menu similar to combobox, filter list", "Sort: Downloads");
229 } else {
230 }
231 }
232 checkable: false
233 icon.name: "view-sort"
234
235 Kirigami.Action {
236 icon.name: "sort-name"
237 text: i18ndc("knewstuff6", "@option:radio in menu, List option which will set the sort order to be alphabetical based on the name", "Name")
238 checkable: true
239 checked: newStuffEngine.sortOrder === 1
240 onTriggered: source => {
241 newStuffEngine.sortOrder = 1;
242 }
243 QQC2.ActionGroup.group: viewSortingActionGroup
244 }
245
246 Kirigami.Action {
247 icon.name: "rating"
248 text: i18ndc("knewstuff6", "@option:radio in menu, List option which will set the sort order to based on user ratings", "Rating")
249 checkable: true
250 checked: newStuffEngine.sortOrder === 2
251 onTriggered: source => {
252 newStuffEngine.sortOrder = 2;
253 }
254 QQC2.ActionGroup.group: viewSortingActionGroup
255 }
256
257 Kirigami.Action {
258 icon.name: "download"
259 text: i18ndc("knewstuff6", "@option:radio similar to combobox item, List option which will set the sort order to based on number of downloads", "Downloads")
260 checkable: true
261 checked: newStuffEngine.sortOrder === 3
262 onTriggered: source => {
263 newStuffEngine.sortOrder = 3;
264 }
265 QQC2.ActionGroup.group: viewSortingActionGroup
266 }
267
268 Kirigami.Action {
269 icon.name: "change-date-symbolic"
270 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")
271 checkable: true
272 checked: newStuffEngine.sortOrder === 0
273 onTriggered: source => {
274 newStuffEngine.sortOrder = 0;
275 }
276 QQC2.ActionGroup.group: viewSortingActionGroup
277 }
278 },
279
280 Kirigami.Action {
281 id: uploadAction
282
283 text: i18nd("knewstuff6", "Upload…")
284 tooltip: i18nd("knewstuff6", "Learn how to add your own hot new stuff to this list")
285 icon.name: "upload-media"
286 visible: newStuffEngine.uploadEnabled
287
288 onTriggered: source => {
289 pageStack.push(uploadPage);
290 }
291 },
292
293 Kirigami.Action {
294 text: i18nd("knewstuff6", "Go to…")
295 icon.name: "go-next"
296 id: searchModelActions
297 visible: children.length > 0
298 },
299
301 text: i18nd("knewstuff6", "Search…")
302 icon.name: "system-search"
303 displayHint: Kirigami.DisplayHint.KeepVisible
304
305 displayComponent: Kirigami.SearchField {
306 id: searchField
307
308 enabled: engine.isValid
309 focusSequence: "Ctrl+F"
310 placeholderText: i18nd("knewstuff6", "Search…")
311 text: newStuffEngine.searchTerm
312
313 onAccepted: {
314 newStuffEngine.searchTerm = searchField.text;
315 }
316
317 Component.onCompleted: {
318 if (!Kirigami.InputMethod.willShowOnActive) {
319 forceActiveFocus();
320 }
321 }
322 }
323 }
324 ]
325
326 Instantiator {
327 id: searchPresetInstatiator
328
329 model: newStuffEngine.searchPresetModel
330
331 Kirigami.Action {
332 required property int index
333
334 text: model.displayName
335 icon.name: model.iconName
336
337 onTriggered: source => {
338 const curIndex = newStuffEngine.searchPresetModel.index(index, 0);
339 newStuffEngine.searchPresetModel.loadSearch(curIndex);
340 }
341 }
342
343 onObjectAdded: (index, object) => {
344 searchModelActions.children.push(object);
345 }
346 }
347
348 Connections {
349 target: newStuffEngine.searchPresetModel
350
351 function onModelReset() {
352 searchModelActions.children = [];
353 }
354 }
355
356 footer: RowLayout {
357 spacing: Kirigami.Units.smallSpacing
358
359 visible: visibleChildren.length > 0
360 height: visible ? implicitHeight : 0
361
362 QQC2.Label {
363 visible: categoriesCombo.count > 2
364 text: i18nd("knewstuff6", "Category:")
365 }
366
367 QQC2.ComboBox {
368 id: categoriesCombo
369
370 Layout.fillWidth: true
371
372 visible: count > 2
373 model: newStuffEngine.categories
374 textRole: "displayName"
375
376 onCurrentIndexChanged: {
377 newStuffEngine.categoriesFilter = model.data(model.index(currentIndex, 0), NewStuff.CategoriesModel.NameRole);
378 }
379 }
380
381 QQC2.Button {
382 Layout.alignment: Qt.AlignRight
383
384 text: i18nd("knewstuff6", "Contribute Your Own…")
385 icon.name: "upload-media"
386 visible: newStuffEngine.uploadEnabled && !uploadAction.visible
387
388 onClicked: {
389 pageStack.push(uploadPage);
390 }
391 }
392 }
393
394 view.model: NewStuff.ItemsModel {
395 id: newStuffModel
396
397 engine: newStuffEngine
398 }
399
400 NewStuff.DownloadItemsSheet {
401 id: downloadItemsSheet
402
403 parent: root.QQC2.Overlay.overlay
404
405 onItemPicked: (entry, downloadItemId) => {
406 newStuffModel.engine.installLinkId(entry, downloadItemId);
407 }
408 }
409
410 view.implicitCellWidth: switch (root.viewMode) {
411 case Page.ViewMode.Preview:
412 return Kirigami.Units.gridUnit * 25;
413
414 case Page.ViewMode.Tiles:
415 case Page.ViewMode.Icons:
416 default:
417 return Kirigami.Units.gridUnit * 30;
418 }
419
420 view.implicitCellHeight: switch (root.viewMode) {
421 case Page.ViewMode.Preview:
422 return Kirigami.Units.gridUnit * 25;
423
424 case Page.ViewMode.Tiles:
425 case Page.ViewMode.Icons:
426 default:
427 return Math.round(view.implicitCellWidth / 3);
428 }
429
430 view.delegate: switch (root.viewMode) {
431 case Page.ViewMode.Preview:
432 return bigPreviewDelegate;
433
434 case Page.ViewMode.Tiles:
435 case Page.ViewMode.Icons:
436 default:
437 return tileDelegate;
438 }
439
440 Component {
441 id: bigPreviewDelegate
442
443 EntryGridDelegates.BigPreviewDelegate { }
444 }
445
446 Component {
447 id: tileDelegate
448
449 EntryGridDelegates.TileDelegate {
450 useLabel: root.useLabel
451 uninstallLabel: root.uninstallLabel
452 }
453 }
454
455 Component {
456 id: detailsPage
457
458 NewStuff.EntryDetails { }
459 }
460
461 Component {
462 id: uploadPage
463
464 NewStuff.UploadPage {
465 engine: newStuffEngine
466 }
467 }
468
469 Item {
470 id: loadingOverlay
471
472 anchors.fill: parent
473
474 opacity: newStuffEngine.isLoading && !newStuffEngine.needsLazyLoadSpinner ? 1 : 0
475 Behavior on opacity {
476 NumberAnimation {
477 duration: Kirigami.Units.longDuration
478 }
479 }
480
481 visible: opacity > 0
482
483 Rectangle {
484 anchors.fill: parent
485 color: Kirigami.Theme.backgroundColor
486 }
487
488 Kirigami.LoadingPlaceholder {
489 anchors.centerIn: parent
490 text: newStuffEngine.busyMessage
491 }
492 }
493}
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 i18nd(const char *domain, const char *text, const TYPE &arg...)
QString xi18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
QString name(StandardAction id)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 12:06:23 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.