// 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() } property var pendingDisableImportApply: null property string pendingDisableImportName: "" function requestDisableImport(sourceName, applyFn) { pendingDisableImportName = sourceName pendingDisableImportApply = applyFn disableImportConfirmDialog.open() } 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 } FormCard.FormDelegateSeparator {} FormCard.FormButtonDelegate { id: uiModeDelegate text: i18n("UI mode") description: { if (App.config.uiMode === Config.Desktop) return i18n("Desktop") if (App.config.uiMode === Config.Handheld) return i18n("Handheld") return i18n("Automatic") } icon.name: "view-fullscreen" onClicked: uiModeMenu.open() QQC2.Menu { id: uiModeMenu QQC2.MenuItem { text: i18n("Automatic") checkable: true checked: App.config.uiMode === Config.Auto onTriggered: App.config.uiMode = Config.Auto } QQC2.MenuItem { text: i18n("Desktop") checkable: true checked: App.config.uiMode === Config.Desktop onTriggered: App.config.uiMode = Config.Desktop } QQC2.MenuItem { text: i18n("Handheld") checkable: true checked: App.config.uiMode === Config.Handheld onTriggered: App.config.uiMode = Config.Handheld } } } } FormCard.FormHeader { Layout.topMargin: Kirigami.Units.mediumSpacing Layout.fillWidth: true title: i18n("Import Sources") } 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") } property bool restoring: false checked: App.config.importSteam 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 settingsPage.requestDisableImport(i18n("Steam"), function() { App.config.importSteam = false }) return } App.config.importSteam = checked restoring = true checked = Qt.binding(function() { return App.config.importSteam }) restoring = false } } FormCard.FormDelegateSeparator {} FormCard.FormCheckDelegate { text: i18n("Lutris") icon.name: "" leading: IconWithResourceFallback { primary: "lutris" secondary: "applications-games" } property bool restoring: false checked: App.config.importLutris 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 settingsPage.requestDisableImport(i18n("Lutris"), function() { App.config.importLutris = false }) return } App.config.importLutris = checked restoring = true checked = Qt.binding(function() { return App.config.importLutris }) restoring = false } } 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" } property bool restoring: false checked: App.config.importHeroic 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 settingsPage.requestDisableImport(i18n("Heroic Games Launcher"), function() { App.config.importHeroic = false }) return } App.config.importHeroic = checked restoring = true checked = Qt.binding(function() { return App.config.importHeroic }) restoring = false } } FormCard.FormDelegateSeparator {} FormCard.FormCheckDelegate { text: i18n("Desktop Entries") description: i18n("Games from .desktop files") icon.name: "" leading: IconWithResourceFallback { primary: "user-desktop" secondary: "computer" } property bool restoring: false checked: App.config.importDesktop 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 settingsPage.requestDisableImport(i18n("Desktop Entries"), function() { App.config.importDesktop = false }) return } App.config.importDesktop = checked restoring = true checked = Qt.binding(function() { return App.config.importDesktop }) restoring = false } } FormCard.FormDelegateSeparator {} FormCard.FormCheckDelegate { text: i18n("Bottles") description: i18n("Wine applications") icon.name: "" leading: IconWithResourceFallback { primary: "com.usebottles.bottles" secondary: "application-x-executable" } property bool restoring: false checked: App.config.importBottles 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 settingsPage.requestDisableImport(i18n("Bottles"), function() { App.config.importBottles = false }) return } App.config.importBottles = checked restoring = true checked = Qt.binding(function() { return App.config.importBottles }) restoring = false } } FormCard.FormDelegateSeparator {} FormCard.FormCheckDelegate { text: i18n("Flatpak") description: i18n("Flatpak game applications") icon.name: "" leading: IconWithResourceFallback { primary: "flatpak-discover" secondary: "applications-games" } property bool restoring: false checked: App.config.importFlatpak 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 settingsPage.requestDisableImport(i18n("Flatpak"), function() { App.config.importFlatpak = false }) return } App.config.importFlatpak = checked restoring = true checked = Qt.binding(function() { return App.config.importFlatpak }) restoring = false } } 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") } property bool restoring: false checked: App.config.importItch 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 settingsPage.requestDisableImport(i18n("itch.io"), function() { App.config.importItch = false }) return } App.config.importItch = checked restoring = true checked = Qt.binding(function() { return App.config.importItch }) restoring = false } } FormCard.FormDelegateSeparator {} FormCard.FormCheckDelegate { text: i18n("Legendary") description: i18n("Epic Games via Legendary CLI") icon.name: "" leading: IconWithResourceFallback { primary: "legendary" secondary: "applications-games" } property bool restoring: false checked: App.config.importLegendary 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 settingsPage.requestDisableImport(i18n("Legendary"), function() { App.config.importLegendary = false }) return } App.config.importLegendary = checked restoring = true checked = Qt.binding(function() { return App.config.importLegendary }) restoring = false } } 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") } property bool restoring: false checked: App.config.importRetroArch 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 settingsPage.requestDisableImport(i18n("RetroArch"), function() { App.config.importRetroArch = false }) return } App.config.importRetroArch = checked restoring = true checked = Qt.binding(function() { return App.config.importRetroArch }) restoring = false } } 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 { Layout.topMargin: Kirigami.Units.mediumSpacing 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 { Layout.topMargin: Kirigami.Units.mediumSpacing 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 { Layout.topMargin: Kirigami.Units.mediumSpacing 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 onClicked: fetchAllCoversConfirmDialog.open() } 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 { Layout.topMargin: Kirigami.Units.mediumSpacing 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" enabled: !App.importing && App.gameModel.count >= 0 && ((App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam")) || (App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris")) || (App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic")) || (App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop")) || (App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles")) || (App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak")) || (App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io")) || (App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary")) || (App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch"))) 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" onClicked: removeMissingConfirmDialog.open() } 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() } } 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 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 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 onAccepted: App.removeMissingGames() } 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 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 onAccepted: { App.clearLibrary() App.config.resetToDefaults() } } }