mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-02-09 21:13:08 +00:00
Select the couch launch profile based on effective UI mode so profile choice is consistent across games. Update details and edit UI to toggle and reflect couch mode globally, and fix duplicate QML property declarations.
722 lines
26 KiB
QML
722 lines
26 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 QtQuick.Dialogs
|
|
import org.kde.kirigami as Kirigami
|
|
import org.kde.kirigamiaddons.formcard as FormCard
|
|
import org.kde.alakarte
|
|
|
|
Kirigami.Dialog {
|
|
id: dialog
|
|
|
|
property var game: null
|
|
property bool isEditing: game !== null
|
|
property string editProfileId: "default"
|
|
|
|
ListModel {
|
|
id: envModel
|
|
}
|
|
|
|
signal gameCreated(var game)
|
|
signal gameUpdated(var game)
|
|
|
|
title: isEditing ? i18n("Edit Game") : i18n("Add New Game")
|
|
standardButtons: Kirigami.Dialog.NoButton
|
|
|
|
width: Math.min(parent.width - Kirigami.Units.gridUnit * 4, Kirigami.Units.gridUnit * 30)
|
|
height: Math.min(parent.height - Kirigami.Units.gridUnit * 4, implicitHeight)
|
|
|
|
x: Math.round((parent.width - width) / 2)
|
|
y: Math.round((parent.height - height) / 2)
|
|
|
|
customFooterActions: [
|
|
Kirigami.Action {
|
|
text: isEditing ? i18n("Apply") : i18n("Add")
|
|
icon.name: isEditing ? "dialog-ok-apply" : "list-add"
|
|
enabled: nameField.text.trim() !== "" && executableField.text.trim() !== "" && (runnerCombo.currentIndex !== 3 || runnerPathField.text.trim() !== "")
|
|
onTriggered: {
|
|
let runnerValue = dialog.runnerFromIndex(runnerCombo.currentIndex)
|
|
let runnerPathValue = runnerPathField.text.trim()
|
|
let prefixPathValue = prefixPathField.text.trim()
|
|
if (runnerValue === "") {
|
|
runnerPathValue = ""
|
|
prefixPathValue = ""
|
|
}
|
|
if (isEditing) {
|
|
game.name = nameField.text.trim()
|
|
game.developer = developerField.text.trim()
|
|
game.launchCommand = executableField.text.trim()
|
|
game.workingDirectory = workingDirField.text.trim()
|
|
if (dialog.editProfileId === "couch") {
|
|
game.setLaunchProfileConfig("couch", {
|
|
"runner": runnerValue,
|
|
"runnerPath": runnerPathValue,
|
|
"prefixPath": prefixPathValue,
|
|
"env": dialog.envModelToMap()
|
|
})
|
|
} else {
|
|
game.launchEnv = dialog.envModelToMap()
|
|
game.launchRunner = runnerValue
|
|
game.launchRunnerPath = runnerPathValue
|
|
game.launchPrefixPath = prefixPathValue
|
|
}
|
|
if (selectedCoverPath !== "") {
|
|
App.setCoverFromFile(game, selectedCoverPath)
|
|
}
|
|
App.saveLibrary()
|
|
gameUpdated(game)
|
|
} else {
|
|
let newGame = App.createGame(nameField.text.trim(), executableField.text.trim())
|
|
if (newGame) {
|
|
newGame.developer = developerField.text.trim()
|
|
newGame.workingDirectory = workingDirField.text.trim()
|
|
newGame.launchEnv = dialog.envModelToMap()
|
|
newGame.launchRunner = runnerValue
|
|
newGame.launchRunnerPath = runnerPathValue
|
|
newGame.launchPrefixPath = prefixPathValue
|
|
if (selectedCoverPath !== "") {
|
|
App.setCoverFromFile(newGame, selectedCoverPath)
|
|
}
|
|
App.saveLibrary()
|
|
gameCreated(newGame)
|
|
}
|
|
}
|
|
dialog.close()
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
text: i18n("Cancel")
|
|
icon.name: "dialog-cancel"
|
|
onTriggered: dialog.close()
|
|
}
|
|
]
|
|
|
|
property string selectedCoverPath: ""
|
|
|
|
readonly property bool anyMenuOpen: (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) || (profileCombo && profileCombo.popup && profileCombo.popup.visible)
|
|
|
|
function closeCurrentMenu() {
|
|
if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) {
|
|
runnerCombo.popup.close()
|
|
}
|
|
if (profileCombo && profileCombo.popup && profileCombo.popup.visible) {
|
|
profileCombo.popup.close()
|
|
}
|
|
}
|
|
|
|
function isDescendant(item, ancestor) {
|
|
let p = item
|
|
while (p) {
|
|
if (p === ancestor || (ancestor.contentItem && p === ancestor.contentItem)) return true
|
|
if (p.visualParent !== undefined && p.visualParent !== null) {
|
|
if (dialog.isDescendant(p.visualParent, ancestor)) return true
|
|
} else if (p.popup !== undefined && p.popup !== null && p.popup.visualParent !== undefined && p.popup.visualParent !== null) {
|
|
if (dialog.isDescendant(p.popup.visualParent, ancestor)) return true
|
|
}
|
|
p = p.parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
function focusNextInChain(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 (dialog.isDescendant(next, dialog)) {
|
|
next.forceActiveFocus()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function activateFocused() {
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
let item = w.activeFocusItem
|
|
|
|
if (typeof item.toggle === "function") {
|
|
item.toggle()
|
|
return
|
|
}
|
|
if (item.checkable !== undefined && item.checkable && item.checked !== undefined) {
|
|
item.checked = !item.checked
|
|
return
|
|
}
|
|
if (typeof item.clicked === "function") {
|
|
item.clicked()
|
|
return
|
|
}
|
|
}
|
|
|
|
function envModelToMap() {
|
|
let result = ({})
|
|
for (let i = 0; i < envModel.count; i++) {
|
|
let row = envModel.get(i)
|
|
let k = (row.key || "").trim()
|
|
if (k === "") continue
|
|
if (k.indexOf("=") !== -1) continue
|
|
result[k] = String(row.value || "")
|
|
}
|
|
return result
|
|
}
|
|
|
|
function setEnvVar(key, value) {
|
|
let k = (key || "").trim()
|
|
if (k === "") return
|
|
for (let i = 0; i < envModel.count; i++) {
|
|
let row = envModel.get(i)
|
|
if ((row.key || "").trim() === k) {
|
|
envModel.setProperty(i, "value", String(value || ""))
|
|
return
|
|
}
|
|
}
|
|
envModel.append({ key: k, value: String(value || "") })
|
|
}
|
|
|
|
function runnerFromIndex(idx) {
|
|
if (idx === 1) return "wine"
|
|
if (idx === 2) return "proton"
|
|
if (idx === 3) return "custom"
|
|
return ""
|
|
}
|
|
|
|
function runnerToIndex(runner) {
|
|
let r = (runner || "").trim().toLowerCase()
|
|
if (r === "wine") return 1
|
|
if (r === "proton") return 2
|
|
if (r === "custom") return 3
|
|
return 0
|
|
}
|
|
|
|
function profileIdFromIndex(idx) {
|
|
if (idx === 1) return "couch"
|
|
return "default"
|
|
}
|
|
|
|
function profileIndexFromId(profileId) {
|
|
if ((profileId || "").trim() === "couch") return 1
|
|
return 0
|
|
}
|
|
|
|
function loadProfileFields() {
|
|
envModel.clear()
|
|
|
|
if (!isEditing || !game) {
|
|
runnerCombo.currentIndex = 0
|
|
runnerPathField.text = ""
|
|
prefixPathField.text = ""
|
|
return
|
|
}
|
|
|
|
if (dialog.editProfileId === "couch") {
|
|
let cfg = game.launchProfileConfig("couch") || ({})
|
|
runnerCombo.currentIndex = dialog.runnerToIndex(cfg.runner)
|
|
runnerPathField.text = cfg.runnerPath || ""
|
|
prefixPathField.text = cfg.prefixPath || ""
|
|
let env = cfg.env || ({})
|
|
let keys = Object.keys(env)
|
|
keys.sort()
|
|
for (let i = 0; i < keys.length; i++) {
|
|
let k = keys[i]
|
|
envModel.append({ key: k, value: String(env[k]) })
|
|
}
|
|
} else {
|
|
runnerCombo.currentIndex = dialog.runnerToIndex(game.launchRunner)
|
|
runnerPathField.text = game.launchRunnerPath || ""
|
|
prefixPathField.text = game.launchPrefixPath || ""
|
|
let env = game.launchEnv || ({})
|
|
let keys = Object.keys(env)
|
|
keys.sort()
|
|
for (let i = 0; i < keys.length; i++) {
|
|
let k = keys[i]
|
|
envModel.append({ key: k, value: String(env[k]) })
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadFields() {
|
|
selectedCoverPath = ""
|
|
if (isEditing && game) {
|
|
nameField.text = game.name || ""
|
|
developerField.text = game.developer || ""
|
|
executableField.text = game.launchCommand || ""
|
|
workingDirField.text = game.workingDirectory || ""
|
|
|
|
dialog.editProfileId = (App.config.uiMode === Config.Couch
|
|
|| (App.config.uiMode === Config.Auto
|
|
&& GamepadManager.connected
|
|
&& (InputManager.activeInput === InputManager.Gamepad || !InputManager.hasSeenKeyboardMouse)))
|
|
? "couch"
|
|
: "default"
|
|
profileCombo.currentIndex = dialog.profileIndexFromId(dialog.editProfileId)
|
|
dialog.loadProfileFields()
|
|
} else {
|
|
nameField.text = ""
|
|
developerField.text = ""
|
|
executableField.text = ""
|
|
workingDirField.text = ""
|
|
|
|
dialog.editProfileId = "default"
|
|
profileCombo.currentIndex = 0
|
|
dialog.loadProfileFields()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onNavigateUp() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!dialog.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
|
dialog.focusNextInChain(false)
|
|
}
|
|
function onNavigateDown() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!dialog.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
|
dialog.focusNextInChain(true)
|
|
}
|
|
function onNavigateLeft() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!dialog.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
|
dialog.focusNextInChain(false)
|
|
}
|
|
function onNavigateRight() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!dialog.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
|
dialog.focusNextInChain(true)
|
|
}
|
|
function onSelectPressed() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!dialog.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
|
dialog.activateFocused()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: 0
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
id: nameField
|
|
label: i18n("Name")
|
|
text: ""
|
|
placeholderText: i18n("Game title")
|
|
onAccepted: developerField.forceActiveFocus()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
id: developerField
|
|
label: i18n("Developer")
|
|
text: ""
|
|
placeholderText: i18n("Optional")
|
|
onAccepted: executableField.forceActiveFocus()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
id: executableField
|
|
label: i18n("Executable")
|
|
text: ""
|
|
placeholderText: i18n("/path/to/game or command")
|
|
onAccepted: if (nameField.text.trim() !== "" && text.trim() !== "") {
|
|
dialog.customFooterActions[0].trigger()
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Browse...")
|
|
icon.name: "document-open"
|
|
onClicked: fileDialog.open()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
id: workingDirField
|
|
label: i18n("Working Directory")
|
|
text: ""
|
|
placeholderText: i18n("Optional")
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
|
|
FormCard.FormHeader {
|
|
title: i18n("Compatibility")
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
visible: isEditing
|
|
|
|
RowLayout {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: Kirigami.Units.largeSpacing
|
|
|
|
QQC2.Label {
|
|
text: i18n("Profile")
|
|
Layout.alignment: Qt.AlignVCenter
|
|
}
|
|
|
|
QQC2.ComboBox {
|
|
id: profileCombo
|
|
Layout.fillWidth: true
|
|
model: [i18n("Default"), i18n("Couch")]
|
|
onCurrentIndexChanged: if (dialog.isEditing) {
|
|
dialog.editProfileId = dialog.profileIdFromIndex(currentIndex)
|
|
dialog.loadProfileFields()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator { visible: isEditing }
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
|
|
RowLayout {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: Kirigami.Units.largeSpacing
|
|
|
|
QQC2.Label {
|
|
text: i18n("Runner")
|
|
Layout.alignment: Qt.AlignVCenter
|
|
}
|
|
|
|
QQC2.ComboBox {
|
|
id: runnerCombo
|
|
Layout.fillWidth: true
|
|
model: [i18n("Native"), i18n("Wine"), i18n("Proton"), i18n("Custom")]
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
id: runnerPathField
|
|
label: i18n("Runner Path")
|
|
text: ""
|
|
placeholderText: runnerCombo.currentIndex === 2 ? i18n("Auto-detect Proton") : (runnerCombo.currentIndex === 1 ? i18n("Use system Wine") : i18n("Required for Custom"))
|
|
enabled: runnerCombo.currentIndex !== 0
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Browse Runner...")
|
|
icon.name: "document-open"
|
|
enabled: runnerCombo.currentIndex !== 0
|
|
onClicked: runnerFileDialog.open()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormTextFieldDelegate {
|
|
id: prefixPathField
|
|
label: i18n("Prefix Path")
|
|
text: ""
|
|
placeholderText: i18n("Default (AppDataLocation/prefixes/<gameId>)")
|
|
enabled: runnerCombo.currentIndex === 1 || runnerCombo.currentIndex === 2
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Browse Prefix...")
|
|
icon.name: "document-open-folder"
|
|
enabled: runnerCombo.currentIndex === 1 || runnerCombo.currentIndex === 2
|
|
onClicked: prefixFolderDialog.open()
|
|
}
|
|
}
|
|
|
|
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"
|
|
onClicked: envModel.append({ key: "", value: "" })
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Preset: MangoHud")
|
|
description: i18n("Sets MANGOHUD=1")
|
|
icon.name: "games-hint"
|
|
onClicked: dialog.setEnvVar("MANGOHUD", "1")
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Preset: GameMode")
|
|
description: i18n("Sets GAMEMODERUN=1")
|
|
icon.name: "speedometer"
|
|
onClicked: dialog.setEnvVar("GAMEMODERUN", "1")
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Clear Variables")
|
|
icon.name: "edit-clear"
|
|
enabled: envModel.count > 0
|
|
onClicked: envModel.clear()
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
|
|
ColumnLayout {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
Repeater {
|
|
model: envModel
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: 1
|
|
placeholderText: i18n("KEY")
|
|
text: key
|
|
onTextChanged: envModel.setProperty(index, "key", text)
|
|
}
|
|
|
|
QQC2.TextField {
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: 2
|
|
placeholderText: i18n("VALUE")
|
|
text: value
|
|
onTextChanged: envModel.setProperty(index, "value", text)
|
|
}
|
|
|
|
QQC2.ToolButton {
|
|
icon.name: "list-remove"
|
|
onClicked: envModel.remove(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
|
|
FormCard.FormHeader {
|
|
title: i18n("Cover Art")
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 8
|
|
Layout.margins: Kirigami.Units.largeSpacing
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
spacing: Kirigami.Units.largeSpacing
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 5
|
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 7
|
|
radius: Kirigami.Units.smallSpacing
|
|
color: Kirigami.Theme.alternateBackgroundColor
|
|
border.color: Kirigami.Theme.disabledTextColor
|
|
border.width: 1
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
anchors.margins: 2
|
|
source: selectedCoverPath !== "" ? "file://" + selectedCoverPath :
|
|
(isEditing && game && game.coverUrl.toString() !== "" ? game.coverUrl : "")
|
|
fillMode: Image.PreserveAspectCrop
|
|
|
|
Kirigami.Icon {
|
|
anchors.centerIn: parent
|
|
source: "image-x-generic"
|
|
width: Kirigami.Units.iconSizes.large
|
|
height: width
|
|
visible: parent.status !== Image.Ready
|
|
opacity: 0.5
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.Button {
|
|
text: i18n("Choose Cover...")
|
|
icon.name: "document-open"
|
|
onClicked: coverDialog.open()
|
|
}
|
|
|
|
QQC2.Button {
|
|
text: i18n("Fetch from SteamGridDB")
|
|
icon.name: "download"
|
|
enabled: App.steamGridDB.enabled && App.steamGridDB.apiKey.length > 0 &&
|
|
nameField.text.trim() !== "" && !App.steamGridDB.busy
|
|
visible: App.steamGridDB.enabled
|
|
onClicked: {
|
|
if (isEditing && game) {
|
|
App.steamGridDB.fetchCover(game)
|
|
}
|
|
}
|
|
}
|
|
|
|
QQC2.Button {
|
|
text: i18n("Clear Cover")
|
|
icon.name: "edit-clear"
|
|
visible: selectedCoverPath !== "" || (isEditing && game && game.coverUrl.toString() !== "")
|
|
onClicked: {
|
|
selectedCoverPath = ""
|
|
if (isEditing && game) {
|
|
game.coverUrl = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FormCard.FormCard {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
|
|
FormCard.FormHeader {
|
|
title: i18n("Help")
|
|
}
|
|
|
|
FormCard.FormTextDelegate {
|
|
text: i18n("To launch an executable, enter its full path:\n\"/path/to/game\"\n\nTo open a file with the default application:\nxdg-open \"/path/to/file\"\n\nIf the path contains spaces, wrap it in quotes.")
|
|
textItem.wrapMode: Text.WordWrap
|
|
textItem.font: Kirigami.Theme.smallFont
|
|
}
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: fileDialog
|
|
title: i18n("Select Executable")
|
|
fileMode: FileDialog.OpenFile
|
|
onAccepted: {
|
|
let path = selectedFile.toString().replace("file://", "")
|
|
if (path.includes(" ")) {
|
|
executableField.text = "\"" + path + "\""
|
|
} else {
|
|
executableField.text = path
|
|
}
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: coverDialog
|
|
title: i18n("Select Cover Image")
|
|
fileMode: FileDialog.OpenFile
|
|
nameFilters: [i18n("Image files (*.png *.jpg *.jpeg *.webp)"), i18n("All files (*)")]
|
|
onAccepted: {
|
|
selectedCoverPath = selectedFile.toString().replace("file://", "")
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: runnerFileDialog
|
|
title: i18n("Select Runner Executable")
|
|
fileMode: FileDialog.OpenFile
|
|
onAccepted: {
|
|
runnerPathField.text = selectedFile.toString().replace("file://", "")
|
|
}
|
|
}
|
|
|
|
FolderDialog {
|
|
id: prefixFolderDialog
|
|
title: i18n("Select Prefix Folder")
|
|
onAccepted: {
|
|
prefixPathField.text = selectedFolder.toString().replace("file://", "")
|
|
}
|
|
}
|
|
|
|
function saveCoverImage(game) {
|
|
if (selectedCoverPath === "") return
|
|
|
|
// Copy cover to app data directory
|
|
let coversPath = StandardPaths.writableLocation(StandardPaths.AppDataLocation) + "/covers"
|
|
let fileName = game.id + ".jpg"
|
|
let destPath = coversPath + "/" + fileName
|
|
|
|
// Use App to copy the file and set cover URL
|
|
App.setCoverFromFile(game, selectedCoverPath)
|
|
}
|
|
|
|
onOpened: {
|
|
loadFields()
|
|
nameField.forceActiveFocus()
|
|
}
|
|
|
|
onGameChanged: loadFields()
|
|
}
|