mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
2045 lines
76 KiB
QML
2045 lines
76 KiB
QML
// 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
|
|
|
|
property string runnerInstallUrl: ""
|
|
property string runnerInstallSha256: ""
|
|
property string runnerInstallName: ""
|
|
|
|
property string compatibilityGameId: ""
|
|
property string compatibilityLoadedRunnerId: ""
|
|
property string compatibilityLoadedRunner: ""
|
|
property string compatibilityLastError: ""
|
|
|
|
property int compatibilitySpecRevision: 0
|
|
readonly property bool compatibilityHasGame: (compatibilityGameId || "").trim() !== ""
|
|
|
|
property int compatibilityRunnerIndex: 0
|
|
property string compatibilityRunnerPath: ""
|
|
property string compatibilityPrefixPath: ""
|
|
|
|
ListModel {
|
|
id: compatibilityGameChoicesModel
|
|
}
|
|
|
|
ListModel {
|
|
id: compatibilityRunnerChoicesModel
|
|
}
|
|
|
|
ListModel {
|
|
id: compatibilityEnvModel
|
|
}
|
|
|
|
ListModel {
|
|
id: compatibilityExtraArgsModel
|
|
}
|
|
|
|
ListModel {
|
|
id: compatibilityDllOverridesModel
|
|
}
|
|
|
|
property string lastControllerInput: ""
|
|
property string lastControllerInputTime: ""
|
|
|
|
property string profileMenuControllerId: ""
|
|
property string newProfileName: ""
|
|
property string pendingDeleteProfileId: ""
|
|
property string pendingDeleteProfileName: ""
|
|
|
|
function focusFirstControl() {
|
|
showPlatformBadgesDelegate.forceActiveFocus()
|
|
}
|
|
|
|
property var pendingDisableImportApply: null
|
|
property string pendingDisableImportName: ""
|
|
|
|
readonly property bool anyConfirmOpen: disableImportConfirmDialog.opened
|
|
|| fetchAllCoversConfirmDialog.opened
|
|
|| removeMissingConfirmDialog.opened
|
|
|| clearConfirmDialog.opened
|
|
|| resetConfirmDialog.opened
|
|
|| deleteProfileConfirmDialog.opened
|
|
|
|
readonly property bool anyMenuOpen: uiModeMenu.visible || profileMenu.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
|
|
if (deleteProfileConfirmDialog.opened) return deleteProfileConfirmDialog
|
|
return null
|
|
}
|
|
|
|
function closeCurrentConfirmDialog() {
|
|
let d = currentConfirmDialog()
|
|
if (!d) return
|
|
if (typeof d.reject === "function") {
|
|
d.reject()
|
|
} else {
|
|
d.close()
|
|
}
|
|
}
|
|
|
|
function closeCurrentMenu() {
|
|
let m = currentMenu()
|
|
if (m && m.close) m.close()
|
|
}
|
|
|
|
function currentMenu() {
|
|
if (profileMenu.visible) return profileMenu
|
|
if (uiModeMenu.visible) return uiModeMenu
|
|
return null
|
|
}
|
|
|
|
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 menu = currentMenu()
|
|
if (!menu) return
|
|
let next = w.activeFocusItem
|
|
for (let i = 0; i < 50; i++) {
|
|
next = next.nextItemInFocusChain(forward)
|
|
if (!next) return
|
|
if (settingsPage.isDescendant(next, menu)) {
|
|
next.forceActiveFocus()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function rebuildCompatibilityGameChoices() {
|
|
compatibilityGameChoicesModel.clear()
|
|
if (!App || !App.gameModel) return
|
|
for (let i = 0; i < App.gameModel.count; i++) {
|
|
let g = App.gameModel.gameAt(i)
|
|
if (!g) continue
|
|
compatibilityGameChoicesModel.append({ gameId: String(g.id || ""), label: String(g.name || g.id || "") })
|
|
}
|
|
|
|
if (compatibilityGameId === "") {
|
|
if (compatibilityGameChoicesModel.count > 0) {
|
|
compatibilityGameId = String(compatibilityGameChoicesModel.get(0).gameId || "")
|
|
}
|
|
}
|
|
}
|
|
|
|
function rebuildCompatibilityRunnerChoices() {
|
|
compatibilityRunnerChoicesModel.clear()
|
|
|
|
compatibilityRunnerChoicesModel.append({ kind: "native", label: i18n("Native"), runner: "", runnerId: "", runnerType: "" })
|
|
compatibilityRunnerChoicesModel.append({ kind: "wine", label: i18n("Wine (system)"), runner: "wine", runnerId: "", runnerType: "wine" })
|
|
compatibilityRunnerChoicesModel.append({ kind: "proton", label: i18n("Proton (auto)"), runner: "proton", runnerId: "", runnerType: "proton" })
|
|
|
|
let runners = (App.runnerManager && App.runnerManager.runners) ? App.runnerManager.runners : []
|
|
for (let i = 0; i < runners.length; i++) {
|
|
let r = runners[i]
|
|
if (!r) continue
|
|
let name = r.name ? String(r.name) : ""
|
|
let type = r.type ? String(r.type) : ""
|
|
let source = r.source ? String(r.source) : ""
|
|
let label = name
|
|
if (type !== "" || source !== "") {
|
|
label = name + " (" + type + (source !== "" ? (" · " + source) : "") + ")"
|
|
}
|
|
compatibilityRunnerChoicesModel.append({ kind: "inventory", label: label, runner: "", runnerId: String(r.id || ""), runnerType: type })
|
|
}
|
|
|
|
compatibilityRunnerChoicesModel.append({ kind: "custom", label: i18n("Custom runner path"), runner: "custom", runnerId: "", runnerType: "custom" })
|
|
}
|
|
|
|
function compatibilityIndexForGameId(gameId) {
|
|
let id = (gameId || "").trim()
|
|
if (id === "") return 0
|
|
for (let i = 0; i < compatibilityGameChoicesModel.count; i++) {
|
|
let row = compatibilityGameChoicesModel.get(i)
|
|
if (row && String(row.gameId || "") === id) return i
|
|
}
|
|
return 0
|
|
}
|
|
|
|
function compatibilityCurrentRunnerChoice() {
|
|
if (!compatibilityRunnerChoicesModel || compatibilityRunnerChoicesModel.count <= 0) return null
|
|
let idx = settingsPage.compatibilityRunnerIndex
|
|
if (idx < 0 || idx >= compatibilityRunnerChoicesModel.count) idx = 0
|
|
return compatibilityRunnerChoicesModel.get(idx)
|
|
}
|
|
|
|
function compatibilityCurrentRunnerChoiceKind() {
|
|
let c = settingsPage.compatibilityCurrentRunnerChoice()
|
|
if (!c || !c.kind) return "native"
|
|
return String(c.kind)
|
|
}
|
|
|
|
function compatibilityCurrentRunnerChoiceRunnerType() {
|
|
let c = settingsPage.compatibilityCurrentRunnerChoice()
|
|
if (!c) return ""
|
|
if (c.runnerType) return String(c.runnerType)
|
|
if (c.runner) return String(c.runner)
|
|
return ""
|
|
}
|
|
|
|
function compatibilityIndexForRunnerId(runnerId) {
|
|
let id = (runnerId || "").trim()
|
|
if (id === "") return 0
|
|
for (let i = 0; i < compatibilityRunnerChoicesModel.count; i++) {
|
|
let row = compatibilityRunnerChoicesModel.get(i)
|
|
if (row.kind === "inventory" && String(row.runnerId || "") === id) return i
|
|
}
|
|
return 0
|
|
}
|
|
|
|
function compatibilityIndexForRunner(runner) {
|
|
let r = (runner || "").trim().toLowerCase()
|
|
if (r === "wine") return 1
|
|
if (r === "proton") return 2
|
|
if (r === "custom") return Math.max(0, compatibilityRunnerChoicesModel.count - 1)
|
|
return 0
|
|
}
|
|
|
|
function compatibilitySyncRunnerIndexFromLoaded() {
|
|
if ((settingsPage.compatibilityLoadedRunnerId || "") !== "") {
|
|
settingsPage.compatibilityRunnerIndex = settingsPage.compatibilityIndexForRunnerId(settingsPage.compatibilityLoadedRunnerId)
|
|
return
|
|
}
|
|
if ((settingsPage.compatibilityLoadedRunner || "") !== "") {
|
|
settingsPage.compatibilityRunnerIndex = settingsPage.compatibilityIndexForRunner(settingsPage.compatibilityLoadedRunner)
|
|
return
|
|
}
|
|
settingsPage.compatibilityRunnerIndex = 0
|
|
}
|
|
|
|
function compatibilitySetEnvFromMap(envMap) {
|
|
compatibilityEnvModel.clear()
|
|
if (!envMap) return
|
|
let keys = Object.keys(envMap)
|
|
keys.sort()
|
|
for (let i = 0; i < keys.length; i++) {
|
|
let k = keys[i]
|
|
compatibilityEnvModel.append({ key: String(k), value: String(envMap[k] || "") })
|
|
}
|
|
}
|
|
|
|
function compatibilityEnvModelToMap() {
|
|
let result = ({})
|
|
for (let i = 0; i < compatibilityEnvModel.count; i++) {
|
|
let row = compatibilityEnvModel.get(i)
|
|
let k = (row.key || "").trim()
|
|
if (k === "") continue
|
|
if (k.indexOf("=") !== -1) continue
|
|
result[k] = String(row.value || "")
|
|
}
|
|
return result
|
|
}
|
|
|
|
function compatibilitySetExtraArgsFromList(list) {
|
|
compatibilityExtraArgsModel.clear()
|
|
if (!list || !Array.isArray(list)) return
|
|
for (let i = 0; i < list.length; i++) {
|
|
let a = String(list[i] || "").trim()
|
|
if (a === "") continue
|
|
compatibilityExtraArgsModel.append({ arg: a })
|
|
}
|
|
}
|
|
|
|
function compatibilityExtraArgsModelToList() {
|
|
let result = []
|
|
for (let i = 0; i < compatibilityExtraArgsModel.count; i++) {
|
|
let row = compatibilityExtraArgsModel.get(i)
|
|
let a = (row.arg || "").trim()
|
|
if (a === "") continue
|
|
result.push(a)
|
|
}
|
|
return result
|
|
}
|
|
|
|
function compatibilitySetDllOverridesFromMap(dllMap) {
|
|
compatibilityDllOverridesModel.clear()
|
|
if (!dllMap) return
|
|
let keys = Object.keys(dllMap)
|
|
keys.sort()
|
|
for (let i = 0; i < keys.length; i++) {
|
|
let k = String(keys[i] || "").trim()
|
|
if (k === "") continue
|
|
compatibilityDllOverridesModel.append({ key: k, value: String(dllMap[k] || "") })
|
|
}
|
|
}
|
|
|
|
function compatibilityDllOverridesModelToMap() {
|
|
let result = ({})
|
|
for (let i = 0; i < compatibilityDllOverridesModel.count; i++) {
|
|
let row = compatibilityDllOverridesModel.get(i)
|
|
let k = (row.key || "").trim()
|
|
if (k === "") continue
|
|
if (k.indexOf("=") !== -1) continue
|
|
if (k.indexOf(";") !== -1) continue
|
|
let v = (row.value || "").trim()
|
|
if (v === "") continue
|
|
result[k] = String(v)
|
|
}
|
|
return result
|
|
}
|
|
|
|
function compatibilitySetEnvVar(key, value) {
|
|
let k = (key || "").trim()
|
|
if (k === "") return
|
|
for (let i = 0; i < compatibilityEnvModel.count; i++) {
|
|
let row = compatibilityEnvModel.get(i)
|
|
if ((row.key || "").trim() === k) {
|
|
compatibilityEnvModel.setProperty(i, "value", String(value || ""))
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
return
|
|
}
|
|
}
|
|
compatibilityEnvModel.append({ key: k, value: String(value || "") })
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
|
|
function compatibilityBumpSpecRevision() {
|
|
settingsPage.compatibilitySpecRevision = settingsPage.compatibilitySpecRevision + 1
|
|
}
|
|
|
|
function compatibilityResetEditor() {
|
|
settingsPage.compatibilityLastError = ""
|
|
settingsPage.compatibilityLoadedRunnerId = ""
|
|
settingsPage.compatibilityLoadedRunner = ""
|
|
settingsPage.compatibilityRunnerPath = ""
|
|
settingsPage.compatibilityPrefixPath = ""
|
|
settingsPage.compatibilityRunnerIndex = 0
|
|
compatibilityEnvModel.clear()
|
|
compatibilityExtraArgsModel.clear()
|
|
compatibilityDllOverridesModel.clear()
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
|
|
function compatibilityApplyProfile(profile) {
|
|
let p = profile || ({})
|
|
settingsPage.compatibilityLastError = ""
|
|
|
|
settingsPage.compatibilityLoadedRunnerId = String(p.runnerId || "")
|
|
settingsPage.compatibilityLoadedRunner = String(p.runner || "")
|
|
settingsPage.compatibilityRunnerPath = String(p.runnerPath || "")
|
|
settingsPage.compatibilityPrefixPath = String(p.prefixPath || "")
|
|
|
|
settingsPage.compatibilitySyncRunnerIndexFromLoaded()
|
|
settingsPage.compatibilitySetEnvFromMap(p.envOverrides || ({}))
|
|
settingsPage.compatibilitySetExtraArgsFromList(p.extraArgs || ([]))
|
|
settingsPage.compatibilitySetDllOverridesFromMap(p.dllOverrides || ({}))
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
|
|
function compatibilityRequestProfile() {
|
|
let id = (settingsPage.compatibilityGameId || "").trim()
|
|
if (id === "") return
|
|
if (!App || !App.runnerManager) return
|
|
App.runnerManager.requestGameProfile(id)
|
|
}
|
|
|
|
function compatibilityBuildProfileSpec() {
|
|
let id = (settingsPage.compatibilityGameId || "").trim()
|
|
if (id === "") return ({})
|
|
|
|
let choice = settingsPage.compatibilityCurrentRunnerChoice()
|
|
let kind = choice && choice.kind ? String(choice.kind) : "native"
|
|
let runnerType = settingsPage.compatibilityCurrentRunnerChoiceRunnerType()
|
|
let runnerId = choice && choice.runnerId ? String(choice.runnerId) : ""
|
|
let runner = choice && choice.runner ? String(choice.runner) : ""
|
|
|
|
let spec = ({ gameId: id })
|
|
if (runnerId !== "") spec.runnerId = runnerId
|
|
if (runner !== "") spec.runner = runner
|
|
if ((kind === "custom" || kind === "wine" || kind === "proton") && (settingsPage.compatibilityRunnerPath || "").trim() !== "") {
|
|
spec.runnerPath = settingsPage.compatibilityRunnerPath.trim()
|
|
}
|
|
if ((runnerType === "wine" || runnerType === "proton") && (settingsPage.compatibilityPrefixPath || "").trim() !== "") {
|
|
spec.prefixPath = settingsPage.compatibilityPrefixPath.trim()
|
|
}
|
|
|
|
let env = settingsPage.compatibilityEnvModelToMap()
|
|
if (Object.keys(env).length > 0) spec.envOverrides = env
|
|
|
|
let extraArgs = settingsPage.compatibilityExtraArgsModelToList()
|
|
if (extraArgs.length > 0) spec.extraArgs = extraArgs
|
|
|
|
let dllOverrides = settingsPage.compatibilityDllOverridesModelToMap()
|
|
if (Object.keys(dllOverrides).length > 0) spec.dllOverrides = dllOverrides
|
|
|
|
return spec
|
|
}
|
|
|
|
function compatibilityCanSaveProfile() {
|
|
let spec = settingsPage.compatibilityBuildProfileSpec()
|
|
if (!spec || Object.keys(spec).length <= 1) return false
|
|
return true
|
|
}
|
|
|
|
function compatibilitySaveProfile() {
|
|
let id = (settingsPage.compatibilityGameId || "").trim()
|
|
if (id === "") return
|
|
if (!App || !App.runnerManager) return
|
|
|
|
let spec = settingsPage.compatibilityBuildProfileSpec()
|
|
if (!spec || Object.keys(spec).length <= 1) return
|
|
App.runnerManager.setGameProfile(spec)
|
|
}
|
|
|
|
function compatibilityClearProfile() {
|
|
let id = (settingsPage.compatibilityGameId || "").trim()
|
|
if (id === "") return
|
|
if (!App || !App.runnerManager) return
|
|
App.runnerManager.clearGameProfile(id)
|
|
}
|
|
|
|
onCompatibilityGameIdChanged: {
|
|
settingsPage.compatibilityResetEditor()
|
|
settingsPage.compatibilityRequestProfile()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
settingsPage.rebuildCompatibilityGameChoices()
|
|
settingsPage.rebuildCompatibilityRunnerChoices()
|
|
settingsPage.compatibilityRequestProfile()
|
|
}
|
|
|
|
Connections {
|
|
target: App && App.gameModel ? App.gameModel : null
|
|
function onCountChanged() {
|
|
settingsPage.rebuildCompatibilityGameChoices()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: App && App.runnerManager ? App.runnerManager : null
|
|
|
|
function onRunnersChanged() {
|
|
settingsPage.rebuildCompatibilityRunnerChoices()
|
|
settingsPage.compatibilitySyncRunnerIndexFromLoaded()
|
|
}
|
|
|
|
function onPrefixEnsured(gameId, prefixPath) {
|
|
let id = String(gameId || "")
|
|
if (id !== settingsPage.compatibilityGameId) return
|
|
settingsPage.compatibilityLastError = ""
|
|
if ((settingsPage.compatibilityPrefixPath || "").trim() !== "") {
|
|
settingsPage.compatibilityPrefixPath = String(prefixPath || "")
|
|
}
|
|
}
|
|
|
|
function onPrefixDeleted(gameId, prefixPath) {
|
|
let id = String(gameId || "")
|
|
if (id !== settingsPage.compatibilityGameId) return
|
|
settingsPage.compatibilityLastError = ""
|
|
}
|
|
|
|
function onGameProfileFetched(gameId, result) {
|
|
let id = String(gameId || "")
|
|
if (id !== settingsPage.compatibilityGameId) return
|
|
if (!result || result.ok !== true) {
|
|
settingsPage.compatibilityLastError = result && result.error ? String(result.error) : i18n("Failed to load profile")
|
|
settingsPage.compatibilityApplyProfile({})
|
|
return
|
|
}
|
|
settingsPage.compatibilityApplyProfile(result.profile || ({}))
|
|
}
|
|
|
|
function onGameProfileSaved(gameId, result) {
|
|
let id = String(gameId || "")
|
|
if (id !== settingsPage.compatibilityGameId) return
|
|
if (!result || result.ok !== true) {
|
|
settingsPage.compatibilityLastError = result && result.error ? String(result.error) : i18n("Failed to save profile")
|
|
return
|
|
}
|
|
settingsPage.compatibilityApplyProfile(result.profile || ({}))
|
|
}
|
|
|
|
function onGameProfileCleared(gameId, result) {
|
|
let id = String(gameId || "")
|
|
if (id !== settingsPage.compatibilityGameId) return
|
|
if (!result || result.ok !== true) {
|
|
settingsPage.compatibilityLastError = result && result.error ? String(result.error) : i18n("Failed to clear profile")
|
|
return
|
|
}
|
|
settingsPage.compatibilityResetEditor()
|
|
}
|
|
}
|
|
|
|
FormCard.FormHeader {
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
Layout.fillWidth: true
|
|
title: i18n("Controller")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: GamepadManager.connected ? i18n("Connected") : i18n("Not connected")
|
|
description: GamepadManager.connected ? (GamepadManager.name !== "" ? GamepadManager.name : i18n("Gamepad")) : i18n("Connect a controller to use couch navigation")
|
|
textItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("Device")
|
|
description: {
|
|
if (!GamepadManager.connected) return i18n("-")
|
|
let parts = []
|
|
if (GamepadManager.vendorId > 0) parts.push(i18n("Vendor: %1").arg(GamepadManager.vendorId))
|
|
if (GamepadManager.productId > 0) parts.push(i18n("Product: %1").arg(GamepadManager.productId))
|
|
return parts.length > 0 ? parts.join(" · ") : i18n("-")
|
|
}
|
|
textItem.wrapMode: Text.WordWrap
|
|
descriptionItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("Buttons")
|
|
description: {
|
|
if (!GamepadManager.connected) return i18n("-")
|
|
return i18n("Confirm: %1 · Back: %2 · Details: %3 · Search: %4")
|
|
.arg(GamepadManager.confirmButtonLabel)
|
|
.arg(GamepadManager.backButtonLabel)
|
|
.arg(GamepadManager.detailsButtonLabel)
|
|
.arg(GamepadManager.searchButtonLabel)
|
|
}
|
|
textItem.wrapMode: Text.WordWrap
|
|
descriptionItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("Active input")
|
|
description: {
|
|
if (InputManager.activeInput === InputManager.Gamepad) return i18n("Gamepad")
|
|
return i18n("Keyboard & Mouse")
|
|
}
|
|
textItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("Input service")
|
|
description: {
|
|
if (App.inputService.available) return i18n("Available")
|
|
if (App.inputService.lastError !== "") return App.inputService.lastError
|
|
return i18n("Unavailable")
|
|
}
|
|
descriptionItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Refresh controllers")
|
|
icon.name: "view-refresh"
|
|
onClicked: App.inputService.refreshControllers()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Refresh profiles")
|
|
icon.name: "view-refresh"
|
|
onClicked: App.inputService.refreshProfiles()
|
|
}
|
|
}
|
|
|
|
FormCard.FormHeader {
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
Layout.fillWidth: true
|
|
title: i18n("Controllers")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
Repeater {
|
|
model: App.inputService.controllers
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
|
|
readonly property var controller: modelData
|
|
readonly property string controllerId: controller && controller.controllerId ? controller.controllerId : ""
|
|
readonly property string controllerName: controller && controller.name ? controller.name : controllerId
|
|
readonly property string connectionState: controller && controller.connectionState ? controller.connectionState : ""
|
|
readonly property int batteryPercent: controller && controller.batteryPercent !== undefined ? controller.batteryPercent : -1
|
|
readonly property string batteryState: controller && controller.batteryStateName ? controller.batteryStateName : ""
|
|
readonly property string activeProfileName: controller && controller.activeProfileName ? controller.activeProfileName : ""
|
|
readonly property string activeProfileId: controller && controller.activeProfileId ? controller.activeProfileId : "default"
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: controllerName
|
|
description: {
|
|
let parts = []
|
|
if (connectionState !== "") parts.push(connectionState)
|
|
if (batteryPercent >= 0) {
|
|
parts.push(i18n("Battery: %1%2").arg(batteryPercent).arg("%"))
|
|
} else if (batteryState !== "") {
|
|
parts.push(i18n("Battery: %1").arg(batteryState))
|
|
}
|
|
return parts.length > 0 ? parts.join(" · ") : ""
|
|
}
|
|
textItem.wrapMode: Text.WordWrap
|
|
descriptionItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Profile")
|
|
description: activeProfileName !== "" ? activeProfileName : i18n("Default")
|
|
icon.name: "preferences-desktop-gaming"
|
|
enabled: controllerId !== "" && App.inputService.profiles.length > 0
|
|
onClicked: {
|
|
settingsPage.profileMenuControllerId = controllerId
|
|
profileMenu.open()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
}
|
|
}
|
|
|
|
FormCard.FormTextDelegate {
|
|
visible: App.inputService.controllers.length === 0
|
|
text: i18n("No controllers detected")
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
}
|
|
|
|
QQC2.Menu {
|
|
id: profileMenu
|
|
focus: true
|
|
|
|
onOpened: Qt.callLater(function() {
|
|
if (profileMenu.contentChildren.length > 0 && profileMenu.contentChildren[0].forceActiveFocus) {
|
|
profileMenu.contentChildren[0].forceActiveFocus()
|
|
}
|
|
})
|
|
|
|
Repeater {
|
|
model: App.inputService.profiles
|
|
delegate: QQC2.MenuItem {
|
|
readonly property var profile: modelData
|
|
readonly property string profileId: profile && profile.id ? profile.id : ""
|
|
text: profile && profile.name ? profile.name : profileId
|
|
checkable: true
|
|
checked: profileId !== "" && profileId === settingsPage.currentProfileIdForMenu()
|
|
onTriggered: {
|
|
if (profileId === "" || settingsPage.profileMenuControllerId === "") return
|
|
App.inputService.setActiveProfile(settingsPage.profileMenuControllerId, profileId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function currentProfileIdForMenu() {
|
|
for (let i = 0; i < App.inputService.controllers.length; i++) {
|
|
let c = App.inputService.controllers[i]
|
|
if (c && c.controllerId === settingsPage.profileMenuControllerId) {
|
|
return c.activeProfileId ? c.activeProfileId : "default"
|
|
}
|
|
}
|
|
return "default"
|
|
}
|
|
|
|
FormCard.FormHeader {
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
Layout.fillWidth: true
|
|
title: i18n("Controller Profiles")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
label: i18n("New profile name")
|
|
text: settingsPage.newProfileName
|
|
placeholderText: i18n("e.g. Couch")
|
|
onTextChanged: settingsPage.newProfileName = text
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Create profile")
|
|
icon.name: "list-add"
|
|
enabled: settingsPage.newProfileName.trim() !== "" && App.inputService.available
|
|
onClicked: {
|
|
App.inputService.createProfile(settingsPage.newProfileName.trim())
|
|
settingsPage.newProfileName = ""
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
Repeater {
|
|
model: App.inputService.profiles
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
readonly property var profile: modelData
|
|
readonly property string profileId: profile && profile.id ? profile.id : ""
|
|
readonly property string profileName: profile && profile.name ? profile.name : profileId
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: profileName
|
|
description: profileId
|
|
descriptionItem.wrapMode: Text.WrapAnywhere
|
|
descriptionItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Delete")
|
|
icon.name: "edit-delete"
|
|
enabled: profileId !== "" && profileId !== "default" && App.inputService.available
|
|
onClicked: {
|
|
settingsPage.pendingDeleteProfileId = profileId
|
|
settingsPage.pendingDeleteProfileName = profileName
|
|
deleteProfileConfirmDialog.open()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormHeader {
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
Layout.fillWidth: true
|
|
title: i18n("Input Test")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: settingsPage.lastControllerInput !== "" ? settingsPage.lastControllerInput : i18n("No input yet")
|
|
description: settingsPage.lastControllerInputTime
|
|
textItem.wrapMode: Text.WordWrap
|
|
descriptionItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Clear")
|
|
icon.name: "edit-clear"
|
|
enabled: settingsPage.lastControllerInput !== ""
|
|
onClicked: {
|
|
settingsPage.lastControllerInput = ""
|
|
settingsPage.lastControllerInputTime = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
function activateFocusedInMenu() {
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
let menu = currentMenu()
|
|
if (!menu) 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()
|
|
})
|
|
}
|
|
|
|
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.Couch) return i18n("Couch")
|
|
return i18n("Automatic")
|
|
}
|
|
icon.name: "view-fullscreen"
|
|
onClicked: uiModeMenu.open()
|
|
|
|
QQC2.Menu {
|
|
id: uiModeMenu
|
|
focus: true
|
|
|
|
onOpened: Qt.callLater(function() { uiModeAuto.forceActiveFocus() })
|
|
|
|
QQC2.MenuItem {
|
|
id: uiModeAuto
|
|
text: i18n("Automatic")
|
|
checkable: true
|
|
checked: App.config.uiMode === Config.Auto
|
|
onTriggered: App.config.uiMode = Config.Auto
|
|
}
|
|
|
|
QQC2.MenuItem {
|
|
id: uiModeDesktop
|
|
text: i18n("Desktop")
|
|
checkable: true
|
|
checked: App.config.uiMode === Config.Desktop
|
|
onTriggered: App.config.uiMode = Config.Desktop
|
|
}
|
|
|
|
QQC2.MenuItem {
|
|
id: uiModeCouch
|
|
text: i18n("Couch")
|
|
checkable: true
|
|
checked: App.config.uiMode === Config.Couch
|
|
onTriggered: App.config.uiMode = Config.Couch
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onNavigateUp() {
|
|
if (!settingsPage.anyMenuOpen) return
|
|
let menu = settingsPage.currentMenu()
|
|
if (!menu) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, menu)) return
|
|
settingsPage.focusNextInMenu(false)
|
|
}
|
|
function onNavigateDown() {
|
|
if (!settingsPage.anyMenuOpen) return
|
|
let menu = settingsPage.currentMenu()
|
|
if (!menu) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, menu)) return
|
|
settingsPage.focusNextInMenu(true)
|
|
}
|
|
function onNavigateLeft() {
|
|
if (!settingsPage.anyMenuOpen) return
|
|
let menu = settingsPage.currentMenu()
|
|
if (!menu) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, menu)) return
|
|
settingsPage.focusNextInMenu(false)
|
|
}
|
|
function onNavigateRight() {
|
|
if (!settingsPage.anyMenuOpen) return
|
|
let menu = settingsPage.currentMenu()
|
|
if (!menu) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, menu)) return
|
|
settingsPage.focusNextInMenu(true)
|
|
}
|
|
function onSelectPressed() {
|
|
if (!settingsPage.anyMenuOpen) return
|
|
let menu = settingsPage.currentMenu()
|
|
if (!menu) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!settingsPage.isDescendant(w.activeFocusItem, menu)) return
|
|
settingsPage.activateFocusedInMenu()
|
|
}
|
|
|
|
function onBackPressed() {
|
|
settingsPage.lastControllerInput = i18n("Back")
|
|
settingsPage.lastControllerInputTime = new Date().toLocaleTimeString()
|
|
}
|
|
function onDetailsPressed() {
|
|
settingsPage.lastControllerInput = i18n("Details")
|
|
settingsPage.lastControllerInputTime = new Date().toLocaleTimeString()
|
|
}
|
|
function onMenuPressed() {
|
|
settingsPage.lastControllerInput = i18n("Menu")
|
|
settingsPage.lastControllerInputTime = new Date().toLocaleTimeString()
|
|
}
|
|
function onSearchPressed() {
|
|
settingsPage.lastControllerInput = i18n("Search")
|
|
settingsPage.lastControllerInputTime = new Date().toLocaleTimeString()
|
|
}
|
|
function onLeftBumperPressed() {
|
|
settingsPage.lastControllerInput = i18n("Left bumper")
|
|
settingsPage.lastControllerInputTime = new Date().toLocaleTimeString()
|
|
}
|
|
function onRightBumperPressed() {
|
|
settingsPage.lastControllerInput = i18n("Right bumper")
|
|
settingsPage.lastControllerInputTime = new Date().toLocaleTimeString()
|
|
}
|
|
}
|
|
|
|
Kirigami.PromptDialog {
|
|
id: deleteProfileConfirmDialog
|
|
title: i18n("Delete Profile")
|
|
subtitle: settingsPage.pendingDeleteProfileName !== ""
|
|
? i18n("Delete '%1'?", settingsPage.pendingDeleteProfileName)
|
|
: i18n("Delete this profile?")
|
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
|
onOpened: settingsPage.focusNoButton(deleteProfileConfirmDialog)
|
|
onAccepted: {
|
|
if (settingsPage.pendingDeleteProfileId !== "") {
|
|
App.inputService.deleteProfile(settingsPage.pendingDeleteProfileId)
|
|
}
|
|
settingsPage.pendingDeleteProfileId = ""
|
|
settingsPage.pendingDeleteProfileName = ""
|
|
}
|
|
onRejected: {
|
|
settingsPage.pendingDeleteProfileId = ""
|
|
settingsPage.pendingDeleteProfileName = ""
|
|
}
|
|
}
|
|
|
|
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("Runners")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
label: i18n("Archive URL or local path")
|
|
text: settingsPage.runnerInstallUrl
|
|
placeholderText: i18n("https://... or /home/.../file.tar.gz")
|
|
enabled: !App.runnerManager.busy
|
|
onTextChanged: settingsPage.runnerInstallUrl = text
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
label: i18n("SHA256/SHA512 (optional)")
|
|
text: settingsPage.runnerInstallSha256
|
|
placeholderText: i18n("Leave empty to skip verification")
|
|
enabled: !App.runnerManager.busy
|
|
onTextChanged: settingsPage.runnerInstallSha256 = text
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
label: i18n("Name (optional)")
|
|
text: settingsPage.runnerInstallName
|
|
placeholderText: i18n("Proton-GE")
|
|
enabled: !App.runnerManager.busy
|
|
onTextChanged: settingsPage.runnerInstallName = text
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Install from URL")
|
|
description: App.runnerManager.busy ? App.runnerManager.status : i18n("Download and install runner")
|
|
icon.name: "download"
|
|
enabled: settingsPage.runnerInstallUrl.trim() !== "" && !App.runnerManager.busy
|
|
onClicked: App.runnerManager.installRunnerFromUrl(settingsPage.runnerInstallUrl, settingsPage.runnerInstallSha256, settingsPage.runnerInstallName)
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Cancel install")
|
|
icon.name: "dialog-cancel"
|
|
enabled: App.runnerManager.busy
|
|
onClicked: App.runnerManager.cancelCurrentInstall()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
|
|
ColumnLayout {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: App.runnerManager.status
|
|
wrapMode: Text.WordWrap
|
|
visible: App.runnerManager.status !== ""
|
|
}
|
|
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: App.runnerManager.lastError
|
|
wrapMode: Text.WordWrap
|
|
visible: App.runnerManager.lastError !== ""
|
|
color: Kirigami.Theme.negativeTextColor
|
|
}
|
|
|
|
QQC2.ProgressBar {
|
|
Layout.fillWidth: true
|
|
from: 0
|
|
to: App.runnerManager.totalBytes > 0 ? App.runnerManager.totalBytes : 1
|
|
value: App.runnerManager.receivedBytes
|
|
indeterminate: App.runnerManager.busy && App.runnerManager.totalBytes <= 0
|
|
visible: App.runnerManager.busy
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormHeader {
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
Layout.fillWidth: true
|
|
title: i18n("Installed Runners")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Refresh")
|
|
icon.name: "view-refresh"
|
|
enabled: !App.runnerManager.busy
|
|
onClicked: App.runnerManager.refreshRunners()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
Repeater {
|
|
model: App.runnerManager.runners
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
|
|
readonly property var runner: modelData
|
|
readonly property string runnerId: (runner && runner.id) ? runner.id : ""
|
|
readonly property string runnerName: (runner && runner.name) ? runner.name : runnerId
|
|
readonly property string runnerType: (runner && runner.type) ? runner.type : ""
|
|
readonly property string runnerSource: (runner && runner.source) ? runner.source : ""
|
|
readonly property string runnerPath: (runner && runner.path) ? runner.path : ""
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: runnerName
|
|
description: (runnerType !== "" ? (runnerType + " · ") : "") + (runnerSource !== "" ? runnerSource : "")
|
|
textItem.wrapMode: Text.WordWrap
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: runnerPath
|
|
textItem.wrapMode: Text.WrapAnywhere
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Uninstall")
|
|
icon.name: "edit-delete"
|
|
enabled: runnerSource === "installed" && runnerId !== "" && !App.runnerManager.busy
|
|
onClicked: App.runnerManager.uninstallRunner(runnerId)
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
}
|
|
}
|
|
|
|
FormCard.FormTextDelegate {
|
|
visible: App.runnerManager.runners.length === 0
|
|
text: i18n("No runners found")
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
}
|
|
|
|
FormCard.FormHeader {
|
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
|
Layout.fillWidth: true
|
|
title: i18n("Compatibility")
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormComboBoxDelegate {
|
|
text: i18n("Game")
|
|
model: compatibilityGameChoicesModel
|
|
textRole: "label"
|
|
currentIndex: settingsPage.compatibilityIndexForGameId(settingsPage.compatibilityGameId)
|
|
onActivated: {
|
|
if (currentIndex < 0 || currentIndex >= compatibilityGameChoicesModel.count) return
|
|
settingsPage.compatibilityGameId = String(compatibilityGameChoicesModel.get(currentIndex).gameId || "")
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Reload profile")
|
|
icon.name: "view-refresh"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: settingsPage.compatibilityRequestProfile()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextDelegate {
|
|
visible: settingsPage.compatibilityLastError !== ""
|
|
text: settingsPage.compatibilityLastError
|
|
textItem.wrapMode: Text.WordWrap
|
|
textItem.color: Kirigami.Theme.negativeTextColor
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormComboBoxDelegate {
|
|
text: i18n("Runner")
|
|
model: compatibilityRunnerChoicesModel
|
|
textRole: "label"
|
|
currentIndex: settingsPage.compatibilityRunnerIndex
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onActivated: {
|
|
settingsPage.compatibilityRunnerIndex = currentIndex
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
label: i18n("Runner Path")
|
|
text: settingsPage.compatibilityRunnerPath
|
|
placeholderText: settingsPage.compatibilityCurrentRunnerChoiceKind() === "proton" ? i18n("Auto-detect Proton") : (settingsPage.compatibilityCurrentRunnerChoiceKind() === "wine" ? i18n("Use system Wine") : i18n("Required for Custom"))
|
|
enabled: settingsPage.compatibilityHasGame && (settingsPage.compatibilityCurrentRunnerChoiceKind() === "custom" || settingsPage.compatibilityCurrentRunnerChoiceKind() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceKind() === "proton")
|
|
onTextChanged: {
|
|
settingsPage.compatibilityRunnerPath = text
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
label: i18n("Prefix Path")
|
|
text: settingsPage.compatibilityPrefixPath
|
|
placeholderText: i18n("Default (AppDataLocation/prefixes/<gameId>)")
|
|
enabled: settingsPage.compatibilityHasGame && (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
onTextChanged: {
|
|
settingsPage.compatibilityPrefixPath = text
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Create Prefix")
|
|
icon.name: "folder-new"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
&& (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
onClicked: App.runnerManager.ensurePrefix(settingsPage.compatibilityGameId, settingsPage.compatibilityCurrentRunnerChoiceRunnerType(), settingsPage.compatibilityPrefixPath.trim())
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Delete Prefix")
|
|
icon.name: "edit-delete"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
&& (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
&& (settingsPage.compatibilityPrefixPath.trim() === "")
|
|
onClicked: App.runnerManager.deletePrefix(settingsPage.compatibilityGameId, settingsPage.compatibilityPrefixPath.trim())
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Reset Prefix to Default")
|
|
icon.name: "edit-clear"
|
|
enabled: settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton"
|
|
onClicked: settingsPage.compatibilityPrefixPath = ""
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Save Profile")
|
|
icon.name: "document-save"
|
|
enabled: settingsPage.compatibilityHasGame && settingsPage.compatibilitySpecRevision >= 0 && settingsPage.compatibilityCanSaveProfile()
|
|
onClicked: settingsPage.compatibilitySaveProfile()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Clear Profile")
|
|
icon.name: "edit-delete"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: settingsPage.compatibilityClearProfile()
|
|
}
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
|
|
FormCard.FormHeader {
|
|
title: i18n("Environment Variables")
|
|
}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("These variables are applied only when launching this game from A-La-Karte.")
|
|
textItem.wrapMode: Text.WordWrap
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Add Variable")
|
|
icon.name: "list-add"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: {
|
|
compatibilityEnvModel.append({ key: "", value: "" })
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Preset: MangoHud")
|
|
description: i18n("Sets MANGOHUD=1")
|
|
icon.name: "games-hint"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: settingsPage.compatibilitySetEnvVar("MANGOHUD", "1")
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Preset: GameMode")
|
|
description: i18n("Sets GAMEMODERUN=1")
|
|
icon.name: "speedometer"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: settingsPage.compatibilitySetEnvVar("GAMEMODERUN", "1")
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Clear Variables")
|
|
icon.name: "edit-clear"
|
|
enabled: settingsPage.compatibilityHasGame && compatibilityEnvModel.count > 0
|
|
onClicked: {
|
|
compatibilityEnvModel.clear()
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
Repeater {
|
|
model: compatibilityEnvModel
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: 1
|
|
placeholderText: i18n("KEY")
|
|
text: key
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onTextChanged: {
|
|
compatibilityEnvModel.setProperty(index, "key", text)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: 2
|
|
placeholderText: i18n("VALUE")
|
|
text: value
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onTextChanged: {
|
|
compatibilityEnvModel.setProperty(index, "value", text)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
QQC2.ToolButton {
|
|
icon.name: "list-remove"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: {
|
|
compatibilityEnvModel.remove(index)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
|
|
FormCard.FormHeader {
|
|
title: i18n("Extra Launch Arguments")
|
|
}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("These arguments are appended when launching this game from A-La-Karte.")
|
|
textItem.wrapMode: Text.WordWrap
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Add Argument")
|
|
icon.name: "list-add"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: {
|
|
compatibilityExtraArgsModel.append({ arg: "" })
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Clear Arguments")
|
|
icon.name: "edit-clear"
|
|
enabled: settingsPage.compatibilityHasGame && compatibilityExtraArgsModel.count > 0
|
|
onClicked: {
|
|
compatibilityExtraArgsModel.clear()
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
Repeater {
|
|
model: compatibilityExtraArgsModel
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
placeholderText: i18n("ARG")
|
|
text: arg
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onTextChanged: {
|
|
compatibilityExtraArgsModel.setProperty(index, "arg", text)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
QQC2.ToolButton {
|
|
icon.name: "list-remove"
|
|
enabled: settingsPage.compatibilityHasGame
|
|
onClicked: {
|
|
compatibilityExtraArgsModel.remove(index)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
|
|
FormCard.FormHeader {
|
|
title: i18n("Wine DLL Overrides")
|
|
}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("These overrides are applied to Wine/Proton launches via WINEDLLOVERRIDES.")
|
|
textItem.wrapMode: Text.WordWrap
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Add Override")
|
|
icon.name: "list-add"
|
|
enabled: settingsPage.compatibilityHasGame && (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
onClicked: {
|
|
compatibilityDllOverridesModel.append({ key: "", value: "" })
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Clear Overrides")
|
|
icon.name: "edit-clear"
|
|
enabled: settingsPage.compatibilityHasGame && compatibilityDllOverridesModel.count > 0
|
|
onClicked: {
|
|
compatibilityDllOverridesModel.clear()
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
Repeater {
|
|
model: compatibilityDllOverridesModel
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: 1
|
|
placeholderText: i18n("DLL")
|
|
text: key
|
|
enabled: settingsPage.compatibilityHasGame && (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
onTextChanged: {
|
|
compatibilityDllOverridesModel.setProperty(index, "key", text)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: 2
|
|
placeholderText: i18n("OVERRIDE")
|
|
text: value
|
|
enabled: settingsPage.compatibilityHasGame && (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
onTextChanged: {
|
|
compatibilityDllOverridesModel.setProperty(index, "value", text)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
|
|
QQC2.ToolButton {
|
|
icon.name: "list-remove"
|
|
enabled: settingsPage.compatibilityHasGame && (settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "wine" || settingsPage.compatibilityCurrentRunnerChoiceRunnerType() === "proton")
|
|
onClicked: {
|
|
compatibilityDllOverridesModel.remove(index)
|
|
settingsPage.compatibilityBumpSpecRevision()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
onOpened: settingsPage.focusNoButton(disableImportConfirmDialog)
|
|
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
|
|
onOpened: settingsPage.focusNoButton(fetchAllCoversConfirmDialog)
|
|
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
|
|
onOpened: settingsPage.focusNoButton(removeMissingConfirmDialog)
|
|
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
|
|
onOpened: settingsPage.focusNoButton(clearConfirmDialog)
|
|
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
|
|
onOpened: settingsPage.focusNoButton(resetConfirmDialog)
|
|
onAccepted: {
|
|
App.clearLibrary()
|
|
App.config.resetToDefaults()
|
|
}
|
|
}
|
|
}
|