2026-01-18 12:13:07 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
|
|
|
|
|
|
|
|
|
import QtQuick
|
|
|
|
|
import QtQuick.Controls as QQC2
|
|
|
|
|
import QtQuick.Layouts
|
|
|
|
|
import org.kde.kirigami as Kirigami
|
|
|
|
|
import org.kde.kirigamiaddons.formcard as FormCard
|
|
|
|
|
import org.kde.alakarte
|
|
|
|
|
import "components"
|
|
|
|
|
|
|
|
|
|
ColumnLayout {
|
|
|
|
|
id: settingsPage
|
|
|
|
|
spacing: Kirigami.Units.mediumSpacing
|
|
|
|
|
|
|
|
|
|
function focusFirstControl() {
|
|
|
|
|
showPlatformBadgesDelegate.forceActiveFocus()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 09:03:46 +00:00
|
|
|
property var pendingDisableImportApply: null
|
|
|
|
|
property string pendingDisableImportName: ""
|
|
|
|
|
|
2026-01-29 18:49:45 +00:00
|
|
|
readonly property bool anyConfirmOpen: disableImportConfirmDialog.opened
|
|
|
|
|
|| fetchAllCoversConfirmDialog.opened
|
|
|
|
|
|| removeMissingConfirmDialog.opened
|
|
|
|
|
|| clearConfirmDialog.opened
|
|
|
|
|
|| resetConfirmDialog.opened
|
|
|
|
|
|
|
|
|
|
readonly property bool anyMenuOpen: uiModeMenu.visible
|
|
|
|
|
|
|
|
|
|
function currentConfirmDialog() {
|
|
|
|
|
if (disableImportConfirmDialog.opened) return disableImportConfirmDialog
|
|
|
|
|
if (fetchAllCoversConfirmDialog.opened) return fetchAllCoversConfirmDialog
|
|
|
|
|
if (removeMissingConfirmDialog.opened) return removeMissingConfirmDialog
|
|
|
|
|
if (clearConfirmDialog.opened) return clearConfirmDialog
|
|
|
|
|
if (resetConfirmDialog.opened) return resetConfirmDialog
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeCurrentConfirmDialog() {
|
|
|
|
|
let d = currentConfirmDialog()
|
|
|
|
|
if (!d) return
|
|
|
|
|
if (typeof d.reject === "function") {
|
|
|
|
|
d.reject()
|
|
|
|
|
} else {
|
|
|
|
|
d.close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeCurrentMenu() {
|
|
|
|
|
if (uiModeMenu.visible) {
|
|
|
|
|
uiModeMenu.close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isDescendant(item, ancestor) {
|
|
|
|
|
let p = item
|
|
|
|
|
while (p) {
|
|
|
|
|
if (p === ancestor) return true
|
|
|
|
|
if (ancestor.contentItem && p === ancestor.contentItem) return true
|
|
|
|
|
if (p.visualParent !== undefined && p.visualParent !== null) {
|
|
|
|
|
if (settingsPage.isDescendant(p.visualParent, ancestor)) return true
|
|
|
|
|
} else if (p.popup !== undefined && p.popup !== null && p.popup.visualParent !== undefined && p.popup.visualParent !== null) {
|
|
|
|
|
if (settingsPage.isDescendant(p.popup.visualParent, ancestor)) return true
|
|
|
|
|
}
|
|
|
|
|
p = p.parent
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function focusNextInMenu(forward) {
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
let next = w.activeFocusItem
|
|
|
|
|
for (let i = 0; i < 50; i++) {
|
|
|
|
|
next = next.nextItemInFocusChain(forward)
|
|
|
|
|
if (!next) return
|
|
|
|
|
if (settingsPage.isDescendant(next, uiModeMenu)) {
|
|
|
|
|
next.forceActiveFocus()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function activateFocusedInMenu() {
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
let item = w.activeFocusItem
|
|
|
|
|
if (typeof item.triggered === "function") {
|
|
|
|
|
item.triggered()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (typeof item.clicked === "function") {
|
|
|
|
|
item.clicked()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function focusNoButton(dialog) {
|
|
|
|
|
Qt.callLater(function() {
|
|
|
|
|
if (!dialog) return
|
|
|
|
|
if (typeof dialog.standardButton === "function") {
|
|
|
|
|
let noButton = dialog.standardButton(Kirigami.Dialog.No)
|
|
|
|
|
if (noButton) {
|
|
|
|
|
noButton.forceActiveFocus()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dialog.forceActiveFocus()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 13:08:33 +00:00
|
|
|
function requestDisableImport(sourceName, applyFn) {
|
2026-01-25 09:03:46 +00:00
|
|
|
pendingDisableImportName = sourceName
|
|
|
|
|
pendingDisableImportApply = applyFn
|
|
|
|
|
disableImportConfirmDialog.open()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 12:13:07 +00:00
|
|
|
FormCard.FormHeader {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
title: i18n("Appearance")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCard {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
id: showPlatformBadgesDelegate
|
|
|
|
|
text: i18n("Show platform badges")
|
|
|
|
|
description: i18n("Display platform icons on game cards")
|
|
|
|
|
checked: App.config.showPlatformBadges
|
|
|
|
|
onToggled: App.config.showPlatformBadges = checked
|
|
|
|
|
}
|
2026-01-24 12:27:30 +00:00
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormButtonDelegate {
|
|
|
|
|
id: uiModeDelegate
|
|
|
|
|
text: i18n("UI mode")
|
|
|
|
|
description: {
|
|
|
|
|
if (App.config.uiMode === Config.Desktop) return i18n("Desktop")
|
2026-01-29 18:49:45 +00:00
|
|
|
if (App.config.uiMode === Config.Couch) return i18n("Couch")
|
2026-01-24 12:27:30 +00:00
|
|
|
return i18n("Automatic")
|
|
|
|
|
}
|
|
|
|
|
icon.name: "view-fullscreen"
|
|
|
|
|
onClicked: uiModeMenu.open()
|
|
|
|
|
|
|
|
|
|
QQC2.Menu {
|
|
|
|
|
id: uiModeMenu
|
2026-01-29 18:49:45 +00:00
|
|
|
focus: true
|
|
|
|
|
|
|
|
|
|
onOpened: Qt.callLater(function() { uiModeAuto.forceActiveFocus() })
|
2026-01-24 12:27:30 +00:00
|
|
|
|
|
|
|
|
QQC2.MenuItem {
|
2026-01-29 18:49:45 +00:00
|
|
|
id: uiModeAuto
|
2026-01-24 12:27:30 +00:00
|
|
|
text: i18n("Automatic")
|
|
|
|
|
checkable: true
|
|
|
|
|
checked: App.config.uiMode === Config.Auto
|
|
|
|
|
onTriggered: App.config.uiMode = Config.Auto
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QQC2.MenuItem {
|
2026-01-29 18:49:45 +00:00
|
|
|
id: uiModeDesktop
|
2026-01-24 12:27:30 +00:00
|
|
|
text: i18n("Desktop")
|
|
|
|
|
checkable: true
|
|
|
|
|
checked: App.config.uiMode === Config.Desktop
|
|
|
|
|
onTriggered: App.config.uiMode = Config.Desktop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QQC2.MenuItem {
|
2026-01-29 18:49:45 +00:00
|
|
|
id: uiModeCouch
|
|
|
|
|
text: i18n("Couch")
|
2026-01-24 12:27:30 +00:00
|
|
|
checkable: true
|
2026-01-29 18:49:45 +00:00
|
|
|
checked: App.config.uiMode === Config.Couch
|
|
|
|
|
onTriggered: App.config.uiMode = Config.Couch
|
2026-01-24 12:27:30 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 18:49:45 +00:00
|
|
|
Connections {
|
|
|
|
|
target: GamepadManager
|
|
|
|
|
function onNavigateUp() {
|
|
|
|
|
if (!uiModeMenu.visible) return
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
|
|
|
|
|
settingsPage.focusNextInMenu(false)
|
|
|
|
|
}
|
|
|
|
|
function onNavigateDown() {
|
|
|
|
|
if (!uiModeMenu.visible) return
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
|
|
|
|
|
settingsPage.focusNextInMenu(true)
|
|
|
|
|
}
|
|
|
|
|
function onNavigateLeft() {
|
|
|
|
|
if (!uiModeMenu.visible) return
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
|
|
|
|
|
settingsPage.focusNextInMenu(false)
|
|
|
|
|
}
|
|
|
|
|
function onNavigateRight() {
|
|
|
|
|
if (!uiModeMenu.visible) return
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
|
|
|
|
|
settingsPage.focusNextInMenu(true)
|
|
|
|
|
}
|
|
|
|
|
function onSelectPressed() {
|
|
|
|
|
if (!uiModeMenu.visible) return
|
|
|
|
|
let w = applicationWindow()
|
|
|
|
|
if (!w || !w.activeFocusItem) return
|
|
|
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
|
|
|
|
|
settingsPage.activateFocusedInMenu()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 19:50:20 +00:00
|
|
|
FormCard.FormHeader {
|
|
|
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
title: i18n("Import Sources")
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
|
|
|
|
|
FormCard.FormCard {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Steam")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "com.valvesoftware.Steam"
|
|
|
|
|
secondary: "steam"
|
|
|
|
|
resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg")
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importSteam
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importSteam) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Steam")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importSteam })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Steam"), function() { App.config.importSteam = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importSteam = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importSteam })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Lutris")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "lutris"
|
|
|
|
|
secondary: "applications-games"
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importLutris
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importLutris) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Lutris")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importLutris })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Lutris"), function() { App.config.importLutris = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importLutris = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importLutris })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Heroic Games Launcher")
|
|
|
|
|
description: i18n("Epic, GOG, and Amazon games")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "com.heroicgameslauncher.hgl"
|
|
|
|
|
secondary: "applications-games"
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importHeroic
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importHeroic) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Heroic")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importHeroic })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Heroic Games Launcher"), function() { App.config.importHeroic = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importHeroic = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importHeroic })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Desktop Entries")
|
|
|
|
|
description: i18n("Games from .desktop files")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "user-desktop"
|
|
|
|
|
secondary: "computer"
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importDesktop
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importDesktop) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Desktop")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importDesktop })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Desktop Entries"), function() { App.config.importDesktop = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importDesktop = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importDesktop })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Bottles")
|
|
|
|
|
description: i18n("Wine applications")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "com.usebottles.bottles"
|
|
|
|
|
secondary: "application-x-executable"
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importBottles
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importBottles) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Bottles")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importBottles })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Bottles"), function() { App.config.importBottles = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importBottles = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importBottles })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Flatpak")
|
|
|
|
|
description: i18n("Flatpak game applications")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "flatpak-discover"
|
|
|
|
|
secondary: "applications-games"
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importFlatpak
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importFlatpak) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Flatpak")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importFlatpak })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Flatpak"), function() { App.config.importFlatpak = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importFlatpak = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importFlatpak })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("itch.io")
|
|
|
|
|
description: i18n("Games from itch.io app")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "io.itch.itch"
|
|
|
|
|
secondary: "itch"
|
|
|
|
|
resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg")
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importItch
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importItch) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("itch.io")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importItch })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("itch.io"), function() { App.config.importItch = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importItch = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importItch })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Legendary")
|
|
|
|
|
description: i18n("Epic Games via Legendary CLI")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "legendary"
|
|
|
|
|
secondary: "applications-games"
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importLegendary
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importLegendary) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("Legendary")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importLegendary })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("Legendary"), function() { App.config.importLegendary = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importLegendary = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importLegendary })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("RetroArch")
|
|
|
|
|
description: i18n("Games from RetroArch playlists")
|
|
|
|
|
icon.name: ""
|
|
|
|
|
leading: IconWithResourceFallback {
|
|
|
|
|
primary: "org.libretro.RetroArch"
|
|
|
|
|
secondary: "retroarch"
|
|
|
|
|
resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg")
|
|
|
|
|
}
|
2026-01-25 09:03:46 +00:00
|
|
|
property bool restoring: false
|
2026-01-18 12:13:07 +00:00
|
|
|
checked: App.config.importRetroArch
|
2026-01-25 09:03:46 +00:00
|
|
|
onToggled: {
|
|
|
|
|
if (restoring) return
|
|
|
|
|
|
|
|
|
|
if (checked === App.config.importRetroArch) return
|
|
|
|
|
|
|
|
|
|
if (!checked && App.gameModel.hasPlatformPrefix("RetroArch")) {
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importRetroArch })
|
|
|
|
|
restoring = false
|
2026-01-25 13:08:33 +00:00
|
|
|
settingsPage.requestDisableImport(i18n("RetroArch"), function() { App.config.importRetroArch = false })
|
2026-01-25 09:03:46 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
App.config.importRetroArch = checked
|
|
|
|
|
restoring = true
|
|
|
|
|
checked = Qt.binding(function() { return App.config.importRetroArch })
|
|
|
|
|
restoring = false
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Auto-import on startup")
|
|
|
|
|
description: i18n("Scan for new games when launching")
|
|
|
|
|
checked: App.config.autoImportOnStartup
|
|
|
|
|
onToggled: App.config.autoImportOnStartup = checked
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormHeader {
|
2026-01-24 19:50:20 +00:00
|
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
2026-01-18 12:13:07 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
|
title: i18n("Behavior")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCard {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Exit after launching game")
|
|
|
|
|
description: i18n("Close A-La-Karte when a game starts")
|
|
|
|
|
checked: App.config.exitAfterLaunch
|
|
|
|
|
onToggled: App.config.exitAfterLaunch = checked
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Click cover to launch")
|
|
|
|
|
description: i18n("Clicking the cover launches the game instead of showing details")
|
|
|
|
|
checked: App.config.coverLaunchesGame
|
|
|
|
|
onToggled: App.config.coverLaunchesGame = checked
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormHeader {
|
2026-01-24 19:50:20 +00:00
|
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
2026-01-18 12:13:07 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
|
title: i18n("Cover Art")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCard {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("High quality images")
|
|
|
|
|
description: i18n("Sharper covers at the cost of memory")
|
|
|
|
|
checked: App.config.highQualityImages
|
|
|
|
|
onToggled: App.config.highQualityImages = checked
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Animated covers")
|
|
|
|
|
description: i18n("Play animated cover art when available")
|
|
|
|
|
checked: App.config.animatedCovers
|
|
|
|
|
onToggled: App.config.animatedCovers = checked
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormHeader {
|
2026-01-24 19:50:20 +00:00
|
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
2026-01-18 12:13:07 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
|
title: i18n("SteamGridDB")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCard {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Enable SteamGridDB")
|
|
|
|
|
description: i18n("Automatically download cover art")
|
|
|
|
|
checked: App.steamGridDB.enabled
|
|
|
|
|
onToggled: App.steamGridDB.enabled = checked
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
|
|
|
label: i18n("SteamGridDB API Key")
|
|
|
|
|
text: App.steamGridDB.apiKey
|
|
|
|
|
placeholderText: i18n("Enter your API key")
|
|
|
|
|
echoMode: TextInput.Password
|
|
|
|
|
enabled: App.steamGridDB.enabled
|
|
|
|
|
onTextChanged: App.steamGridDB.apiKey = text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCheckDelegate {
|
|
|
|
|
text: i18n("Prefer SteamGridDB covers")
|
|
|
|
|
description: i18n("Replace existing covers with SteamGridDB art")
|
|
|
|
|
checked: App.steamGridDB.preferSteamGridDB
|
|
|
|
|
enabled: App.steamGridDB.enabled
|
|
|
|
|
onToggled: App.steamGridDB.preferSteamGridDB = checked
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormButtonDelegate {
|
|
|
|
|
text: i18n("Fetch All Covers")
|
|
|
|
|
description: App.steamGridDB.busy ? i18n("Fetching...") : i18n("Download covers for all games")
|
|
|
|
|
icon.name: "download"
|
|
|
|
|
enabled: App.steamGridDB.enabled && App.steamGridDB.apiKey.length > 0 && !App.steamGridDB.busy
|
2026-01-25 09:03:46 +00:00
|
|
|
onClicked: fetchAllCoversConfirmDialog.open()
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormTextDelegate {
|
|
|
|
|
text: i18n("Get a free API key at steamgriddb.com")
|
|
|
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
|
|
|
textItem.color: Kirigami.Theme.linkColor
|
|
|
|
|
|
|
|
|
|
TapHandler {
|
|
|
|
|
onTapped: Qt.openUrlExternally("https://www.steamgriddb.com/profile/preferences/api")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormHeader {
|
2026-01-24 19:50:20 +00:00
|
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
2026-01-18 12:13:07 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
|
title: i18n("Library")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormCard {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
FormCard.FormButtonDelegate {
|
|
|
|
|
text: i18n("Import All Games")
|
|
|
|
|
description: i18n("Scan all enabled sources")
|
|
|
|
|
icon.name: "document-import"
|
2026-02-03 09:41:29 +00:00
|
|
|
enabled: !App.importing && App.gameModel.count >= 0 && (App.config.importSteam
|
|
|
|
|
|| App.config.importLutris
|
|
|
|
|
|| App.config.importHeroic
|
|
|
|
|
|| App.config.importDesktop
|
|
|
|
|
|| App.config.importBottles
|
|
|
|
|
|| App.config.importFlatpak
|
|
|
|
|
|| App.config.importItch
|
|
|
|
|
|| App.config.importLegendary
|
|
|
|
|
|| App.config.importRetroArch)
|
2026-01-18 12:13:07 +00:00
|
|
|
onClicked: App.importAllGames()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormButtonDelegate {
|
|
|
|
|
text: i18n("Remove Missing Games")
|
|
|
|
|
description: i18n("Remove games whose executables no longer exist")
|
|
|
|
|
icon.name: "edit-delete"
|
2026-01-25 09:03:46 +00:00
|
|
|
onClicked: removeMissingConfirmDialog.open()
|
2026-01-18 12:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormButtonDelegate {
|
|
|
|
|
text: i18n("Clear Library")
|
|
|
|
|
description: i18n("Remove all games")
|
|
|
|
|
icon.name: "edit-clear-all"
|
|
|
|
|
onClicked: clearConfirmDialog.open()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
|
|
|
|
|
|
FormCard.FormButtonDelegate {
|
|
|
|
|
text: i18n("Reset Application")
|
|
|
|
|
description: i18n("Clear library and reset all settings to defaults")
|
|
|
|
|
icon.name: "edit-reset"
|
|
|
|
|
onClicked: resetConfirmDialog.open()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 09:03:46 +00:00
|
|
|
Kirigami.PromptDialog {
|
|
|
|
|
id: disableImportConfirmDialog
|
|
|
|
|
title: i18n("Disable Import Source")
|
|
|
|
|
subtitle: i18n("Disabling %1 will remove all games imported from that source. Are you sure?", settingsPage.pendingDisableImportName)
|
|
|
|
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
2026-01-29 18:49:45 +00:00
|
|
|
onOpened: settingsPage.focusNoButton(disableImportConfirmDialog)
|
2026-01-25 09:03:46 +00:00
|
|
|
onAccepted: {
|
|
|
|
|
if (settingsPage.pendingDisableImportApply) {
|
|
|
|
|
settingsPage.pendingDisableImportApply()
|
|
|
|
|
}
|
|
|
|
|
settingsPage.pendingDisableImportApply = null
|
|
|
|
|
settingsPage.pendingDisableImportName = ""
|
|
|
|
|
}
|
|
|
|
|
onRejected: {
|
|
|
|
|
settingsPage.pendingDisableImportApply = null
|
|
|
|
|
settingsPage.pendingDisableImportName = ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Kirigami.PromptDialog {
|
|
|
|
|
id: fetchAllCoversConfirmDialog
|
|
|
|
|
title: i18n("Fetch All Covers")
|
|
|
|
|
subtitle: App.steamGridDB.preferSteamGridDB
|
|
|
|
|
? i18n("This will download cover art for all games and may replace existing covers. Continue?")
|
|
|
|
|
: i18n("This will download cover art for games that are missing covers. Continue?")
|
|
|
|
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
2026-01-29 18:49:45 +00:00
|
|
|
onOpened: settingsPage.focusNoButton(fetchAllCoversConfirmDialog)
|
2026-01-25 09:03:46 +00:00
|
|
|
onAccepted: App.steamGridDB.fetchAllCovers()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Kirigami.PromptDialog {
|
|
|
|
|
id: removeMissingConfirmDialog
|
|
|
|
|
title: i18n("Remove Missing Games")
|
|
|
|
|
subtitle: i18n("This will remove games whose executables cannot be found. This cannot be undone. Continue?")
|
|
|
|
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
2026-01-29 18:49:45 +00:00
|
|
|
onOpened: settingsPage.focusNoButton(removeMissingConfirmDialog)
|
2026-01-25 09:03:46 +00:00
|
|
|
onAccepted: App.removeMissingGames()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 12:13:07 +00:00
|
|
|
Kirigami.PromptDialog {
|
|
|
|
|
id: clearConfirmDialog
|
|
|
|
|
title: i18n("Clear Library")
|
|
|
|
|
subtitle: i18n("Are you sure you want to remove all games?")
|
|
|
|
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
2026-01-29 18:49:45 +00:00
|
|
|
onOpened: settingsPage.focusNoButton(clearConfirmDialog)
|
2026-01-18 12:13:07 +00:00
|
|
|
onAccepted: App.clearLibrary()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Kirigami.PromptDialog {
|
|
|
|
|
id: resetConfirmDialog
|
|
|
|
|
title: i18n("Reset Application")
|
|
|
|
|
subtitle: i18n("This will remove all games and reset all settings to defaults. Are you sure?")
|
|
|
|
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
2026-01-29 18:49:45 +00:00
|
|
|
onOpened: settingsPage.focusNoButton(resetConfirmDialog)
|
2026-01-18 12:13:07 +00:00
|
|
|
onAccepted: {
|
|
|
|
|
App.clearLibrary()
|
|
|
|
|
App.config.resetToDefaults()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|