fix(settings): align settings pages with KDE HIG
This commit is contained in:
1 parent
de9e6484ee
commit
3d8f644d8d
10 files changed
+200
-186
No files matched your search
+20
-30
@@ -13,23 +13,19 @@ SettingsBasePage {
|
|||||||
|
|
||||||
required property MpvItem mpv
|
required property MpvItem mpv
|
||||||
|
|
||||||
GridLayout {
|
Kirigami.FormLayout {
|
||||||
columns: 2
|
|
||||||
|
|
||||||
SettingsHeader {
|
SettingsHeader {
|
||||||
text: KI18n.i18nc("@title", "Interface")
|
text: KI18n.i18nc("@title", "Interface")
|
||||||
topMargin: 0
|
|
||||||
Layout.columnSpan: 2
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:listbox", "Footer style")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "Footer style")
|
||||||
|
Kirigami.FormData.buddyFor: footerStyleCombo
|
||||||
ComboBox {
|
ComboBox {
|
||||||
|
id: footerStyleCombo
|
||||||
textRole: "text"
|
textRole: "text"
|
||||||
valueRole: "value"
|
valueRole: "value"
|
||||||
model: [
|
model: [
|
||||||
@@ -48,9 +44,7 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
Switch {
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
text: KI18n.i18nc("@option:check", "Show menu bar")
|
text: KI18n.i18nc("@option:check", "Show menu bar")
|
||||||
checked: QtPotatoSettings.showMenuBar
|
checked: QtPotatoSettings.showMenuBar
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -59,9 +53,7 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
Switch {
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
text: KI18n.i18nc("@option:check", "Show toolbar")
|
text: KI18n.i18nc("@option:check", "Show toolbar")
|
||||||
checked: QtPotatoSettings.showHeader
|
checked: QtPotatoSettings.showHeader
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -70,13 +62,12 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Maximum recent files")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Maximum recent files")
|
||||||
|
Kirigami.FormData.buddyFor: maxRecentSpin
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
id: maxRecentSpin
|
||||||
from: 0
|
from: 0
|
||||||
to: 50
|
to: 50
|
||||||
value: QtPotatoSettings.maxRecentFiles
|
value: QtPotatoSettings.maxRecentFiles
|
||||||
@@ -93,14 +84,12 @@ SettingsBasePage {
|
|||||||
|
|
||||||
SettingsHeader {
|
SettingsHeader {
|
||||||
text: KI18n.i18nc("@title", "mpv")
|
text: KI18n.i18nc("@title", "mpv")
|
||||||
Layout.columnSpan: 2
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
CheckBox {
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Switch {
|
||||||
text: KI18n.i18nc("@option:check", "Use mpv.conf")
|
text: KI18n.i18nc("@option:check", "Use mpv.conf")
|
||||||
checked: QtPotatoSettings.loadMpvConfig
|
checked: QtPotatoSettings.loadMpvConfig
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -114,7 +103,13 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
Label {
|
||||||
|
text: KI18n.i18nc("@info", "Restart QtPotato to apply changes to this setting or to mpv.conf.")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: root.mpv.loadedUserConfigFiles.length > 0
|
text: root.mpv.loadedUserConfigFiles.length > 0
|
||||||
@@ -127,11 +122,6 @@ SettingsBasePage {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 1
|
|
||||||
visible: root.mpv.skippedUserOptions.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
visible: root.mpv.skippedUserOptions.length > 0
|
visible: root.mpv.skippedUserOptions.length > 0
|
||||||
text: KI18n.i18nc("@info", "Ignored because they would break the built-in player: %1", root.mpv.skippedUserOptions.join(", "))
|
text: KI18n.i18nc("@info", "Ignored because they would break the built-in player: %1", root.mpv.skippedUserOptions.join(", "))
|
||||||
|
|||||||
@@ -5,27 +5,21 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.ki18n
|
import org.kde.ki18n
|
||||||
|
|
||||||
SettingsBasePage {
|
SettingsBasePage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
GridLayout {
|
Kirigami.FormLayout {
|
||||||
columns: 2
|
|
||||||
|
|
||||||
SettingsHeader {
|
SettingsHeader {
|
||||||
text: KI18n.i18nc("@title", "Seeking")
|
text: KI18n.i18nc("@title", "Seeking")
|
||||||
topMargin: 0
|
|
||||||
Layout.columnSpan: 2
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Small step")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Small step")
|
||||||
from: 1
|
from: 1
|
||||||
to: 600
|
to: 600
|
||||||
value: QtPotatoSettings.seekSmallStep
|
value: QtPotatoSettings.seekSmallStep
|
||||||
@@ -37,12 +31,8 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Medium step")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Medium step")
|
||||||
from: 1
|
from: 1
|
||||||
to: 600
|
to: 600
|
||||||
value: QtPotatoSettings.seekMediumStep
|
value: QtPotatoSettings.seekMediumStep
|
||||||
@@ -54,12 +44,8 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Big step")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Big step")
|
||||||
from: 1
|
from: 1
|
||||||
to: 600
|
to: 600
|
||||||
value: QtPotatoSettings.seekBigStep
|
value: QtPotatoSettings.seekBigStep
|
||||||
@@ -73,16 +59,11 @@ SettingsBasePage {
|
|||||||
|
|
||||||
SettingsHeader {
|
SettingsHeader {
|
||||||
text: KI18n.i18nc("@title", "Audio")
|
text: KI18n.i18nc("@title", "Audio")
|
||||||
Layout.columnSpan: 2
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Volume step")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Volume step")
|
||||||
from: 1
|
from: 1
|
||||||
to: 100
|
to: 100
|
||||||
value: QtPotatoSettings.volumeStep
|
value: QtPotatoSettings.volumeStep
|
||||||
|
|||||||
@@ -5,20 +5,18 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.ki18n
|
import org.kde.ki18n
|
||||||
|
|
||||||
SettingsBasePage {
|
SettingsBasePage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
GridLayout {
|
Kirigami.FormLayout {
|
||||||
columns: 2
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:listbox", "Position")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: 0
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "Position")
|
||||||
textRole: "text"
|
textRole: "text"
|
||||||
valueRole: "value"
|
valueRole: "value"
|
||||||
model: [
|
model: [
|
||||||
@@ -32,12 +30,10 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:listbox", "When a file ends")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: 0
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "When a file ends")
|
||||||
textRole: "text"
|
textRole: "text"
|
||||||
valueRole: "value"
|
valueRole: "value"
|
||||||
model: [
|
model: [
|
||||||
@@ -53,11 +49,10 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
CheckBox {
|
spacing: Kirigami.Units.smallSpacing
|
||||||
text: KI18n.i18nc("@option:check", "Add the other files of the folder")
|
Switch {
|
||||||
|
text: KI18n.i18nc("@option:check", "Add other files from the folder")
|
||||||
checked: QtPotatoSettings.playlistLoadSiblings
|
checked: QtPotatoSettings.playlistLoadSiblings
|
||||||
onClicked: {
|
onClicked: {
|
||||||
QtPotatoSettings.playlistLoadSiblings = checked
|
QtPotatoSettings.playlistLoadSiblings = checked
|
||||||
@@ -70,9 +65,7 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
Switch {
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
text: KI18n.i18nc("@option:check", "Overlay video")
|
text: KI18n.i18nc("@option:check", "Overlay video")
|
||||||
checked: QtPotatoSettings.playlistOverlayVideo
|
checked: QtPotatoSettings.playlistOverlayVideo
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -81,10 +74,9 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
CheckBox {
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Switch {
|
||||||
text: KI18n.i18nc("@option:check", "Open with the mouse")
|
text: KI18n.i18nc("@option:check", "Open with the mouse")
|
||||||
checked: QtPotatoSettings.playlistToggleWithMouse
|
checked: QtPotatoSettings.playlistToggleWithMouse
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -98,9 +90,7 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
Switch {
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
text: KI18n.i18nc("@option:check", "Show row numbers")
|
text: KI18n.i18nc("@option:check", "Show row numbers")
|
||||||
checked: QtPotatoSettings.playlistShowRowNumber
|
checked: QtPotatoSettings.playlistShowRowNumber
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.kde.kirigami as Kirigami
|
|||||||
|
|
||||||
// Page of the settings window. Changes are saved as they are made.
|
// Page of the settings window. Changes are saved as they are made.
|
||||||
Kirigami.ScrollablePage {
|
Kirigami.ScrollablePage {
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
|
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
|
||||||
Component.onDestruction: QtPotatoSettings.save()
|
Component.onDestruction: QtPotatoSettings.save()
|
||||||
}
|
}
|
||||||
@@ -2,36 +2,12 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
// Section title of a settings page, between two lines.
|
// FormLayout supplies the standard section heading and group spacing.
|
||||||
ColumnLayout {
|
Item {
|
||||||
id: root
|
|
||||||
|
|
||||||
property string text: ""
|
property string text: ""
|
||||||
property int topMargin: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
spacing: 0
|
Kirigami.FormData.label: text
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
RowLayout {
|
|
||||||
Layout.topMargin: root.topMargin
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: Kirigami.Theme.alternateBackgroundColor
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Heading {
|
|
||||||
text: root.text
|
|
||||||
level: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: Kirigami.Theme.alternateBackgroundColor
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -109,10 +109,10 @@ Loader {
|
|||||||
|
|
||||||
property Item currentPage: null
|
property Item currentPage: null
|
||||||
|
|
||||||
width: 800
|
width: Kirigami.Units.gridUnit * 45
|
||||||
height: 600
|
height: Kirigami.Units.gridUnit * 34
|
||||||
minimumWidth: 750
|
minimumWidth: Kirigami.Units.gridUnit * 41
|
||||||
minimumHeight: 400
|
minimumHeight: Kirigami.Units.gridUnit * 22
|
||||||
title: KI18n.i18nc("@title:window", "Settings")
|
title: KI18n.i18nc("@title:window", "Settings")
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
@@ -132,6 +132,7 @@ Loader {
|
|||||||
text: modelData.text
|
text: modelData.text
|
||||||
icon.name: modelData.iconName
|
icon.name: modelData.iconName
|
||||||
highlighted: index === ListView.view.currentIndex
|
highlighted: index === ListView.view.currentIndex
|
||||||
|
font.bold: highlighted
|
||||||
onClicked: settingsWindow.showPage(modelData.name)
|
onClicked: settingsWindow.showPage(modelData.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,20 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.ki18n
|
import org.kde.ki18n
|
||||||
|
|
||||||
SettingsBasePage {
|
SettingsBasePage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
GridLayout {
|
Kirigami.FormLayout {
|
||||||
columns: 2
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Font size")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Font size")
|
||||||
|
Kirigami.FormData.buddyFor: fontSizeSpin
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
id: fontSizeSpin
|
||||||
from: 20
|
from: 20
|
||||||
to: 150
|
to: 150
|
||||||
stepSize: 2
|
stepSize: 2
|
||||||
@@ -35,12 +34,8 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:spinbox", "Outline size")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Outline size")
|
||||||
from: 0
|
from: 0
|
||||||
to: 10
|
to: 10
|
||||||
value: QtPotatoSettings.subtitleOutlineSize
|
value: QtPotatoSettings.subtitleOutlineSize
|
||||||
|
|||||||
@@ -13,44 +13,73 @@ SettingsBasePage {
|
|||||||
required property TranslationService translator
|
required property TranslationService translator
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: KI18n.i18nc("@info", "Use an OpenAI-compatible API to translate subtitles. Tracks are translated in batches and saved for reuse.")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.preferredWidth: form.implicitWidth
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.InlineMessage {
|
Kirigami.InlineMessage {
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: form.implicitWidth
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
type: Kirigami.MessageType.Error
|
type: Kirigami.MessageType.Error
|
||||||
text: root.translator.errorText || ApiKeyStore.error
|
text: root.translator.errorText
|
||||||
visible: text.length > 0
|
visible: text.length > 0
|
||||||
showCloseButton: true
|
showCloseButton: true
|
||||||
onVisibleChanged: if (!visible) root.translator.clearError()
|
onVisibleChanged: if (!visible) root.translator.clearError()
|
||||||
}
|
}
|
||||||
|
|
||||||
GridLayout {
|
Kirigami.FormLayout {
|
||||||
columns: 2
|
id: form
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:textbox", "API base URL")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:textbox", "API base URL")
|
||||||
|
id: apiUrlField
|
||||||
|
// Keep an invalid draft in the field; only valid URLs reach KConfig.
|
||||||
|
property bool edited: false
|
||||||
|
readonly property bool valid: /^https?:\/\/(?:\[[0-9a-f:.]+\]|[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.?)(?::[0-9]+)?(?:[/?#][^\s]*)?$/i.test(text.trim())
|
||||||
|
|
||||||
|
function saveUrl() {
|
||||||
|
if (valid) {
|
||||||
|
QtPotatoSettings.apiBaseUrl = text.trim()
|
||||||
|
QtPotatoSettings.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
placeholderText: "https://example.com/v1"
|
placeholderText: "https://example.com/v1"
|
||||||
text: QtPotatoSettings.apiBaseUrl
|
text: QtPotatoSettings.apiBaseUrl
|
||||||
inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase
|
inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onTextEdited: QtPotatoSettings.apiBaseUrl = text.trim()
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
onEditingFinished: QtPotatoSettings.save()
|
onTextEdited: edited = true
|
||||||
|
onEditingFinished: saveUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Kirigami.InlineMessage {
|
||||||
text: KI18n.i18nc("@label:textbox", "API key")
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
|
type: Kirigami.MessageType.Error
|
||||||
|
text: KI18n.i18nc("@info", "Enter a valid API base URL with http:// or https:// and a host name.")
|
||||||
|
visible: !apiUrlField.valid && (apiUrlField.edited || apiUrlField.text.length > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:textbox", "API key")
|
||||||
|
Kirigami.FormData.buddyFor: apiKeyField
|
||||||
Kirigami.PasswordField {
|
Kirigami.PasswordField {
|
||||||
id: apiKeyField
|
id: apiKeyField
|
||||||
|
enabled: !ApiKeyStore.busy
|
||||||
placeholderText: ApiKeyStore.hasKey
|
placeholderText: ApiKeyStore.hasKey
|
||||||
? KI18n.i18nc("@info:placeholder", "Saved in the wallet. Type a new key to replace it.")
|
? KI18n.i18nc("@info:placeholder", "Saved in the wallet. Type a new key to replace it.")
|
||||||
: KI18n.i18nc("@info:placeholder", "Not set")
|
: KI18n.i18nc("@info:placeholder", "Not set")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
if (text.trim().length > 0) {
|
if (text.trim().length > 0) {
|
||||||
ApiKeyStore.setKey(text)
|
ApiKeyStore.setKey(text)
|
||||||
@@ -61,6 +90,7 @@ SettingsBasePage {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
visible: ApiKeyStore.hasKey
|
visible: ApiKeyStore.hasKey
|
||||||
|
enabled: !ApiKeyStore.busy
|
||||||
icon.name: "edit-delete-remove"
|
icon.name: "edit-delete-remove"
|
||||||
text: KI18n.i18nc("@action:button", "Remove")
|
text: KI18n.i18nc("@action:button", "Remove")
|
||||||
onClicked: ApiKeyStore.setKey("")
|
onClicked: ApiKeyStore.setKey("")
|
||||||
@@ -71,12 +101,26 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Kirigami.InlineMessage {
|
||||||
text: KI18n.i18nc("@label:listbox", "Model")
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
|
type: Kirigami.MessageType.Error
|
||||||
|
text: ApiKeyStore.error
|
||||||
|
visible: text.length > 0
|
||||||
|
actions: Kirigami.Action {
|
||||||
|
text: KI18n.i18nc("@action:button", "Retry")
|
||||||
|
icon.name: "view-refresh"
|
||||||
|
enabled: !ApiKeyStore.busy
|
||||||
|
onTriggered: ApiKeyStore.retry()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "Model")
|
||||||
|
Kirigami.FormData.buddyFor: modelCombo
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: modelCombo
|
id: modelCombo
|
||||||
|
|
||||||
@@ -95,6 +139,7 @@ SettingsBasePage {
|
|||||||
|
|
||||||
editable: true
|
editable: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
onEditTextChanged: if (!updatingList) QtPotatoSettings.model = editText.trim()
|
onEditTextChanged: if (!updatingList) QtPotatoSettings.model = editText.trim()
|
||||||
onActivated: QtPotatoSettings.save()
|
onActivated: QtPotatoSettings.save()
|
||||||
Component.onCompleted: showModels()
|
Component.onCompleted: showModels()
|
||||||
@@ -112,8 +157,11 @@ SettingsBasePage {
|
|||||||
text: root.translator.fetchingModels
|
text: root.translator.fetchingModels
|
||||||
? KI18n.i18nc("@action:button", "Loading…")
|
? KI18n.i18nc("@action:button", "Loading…")
|
||||||
: KI18n.i18nc("@action:button", "Load Models")
|
: KI18n.i18nc("@action:button", "Load Models")
|
||||||
enabled: !root.translator.fetchingModels
|
enabled: apiUrlField.valid && !root.translator.fetchingModels
|
||||||
onClicked: root.translator.fetchModels()
|
onClicked: {
|
||||||
|
apiUrlField.saveUrl()
|
||||||
|
root.translator.fetchModels()
|
||||||
|
}
|
||||||
|
|
||||||
ToolTip.text: KI18n.i18nc("@info:tooltip", "Ask the API which models it offers")
|
ToolTip.text: KI18n.i18nc("@info:tooltip", "Ask the API which models it offers")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -121,11 +169,6 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 1
|
|
||||||
visible: modelCountLabel.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: modelCountLabel
|
id: modelCountLabel
|
||||||
visible: root.translator.availableModels.length > 0
|
visible: root.translator.availableModels.length > 0
|
||||||
@@ -133,26 +176,20 @@ SettingsBasePage {
|
|||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:textbox", "Translate into")
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:textbox", "Translate into")
|
||||||
text: QtPotatoSettings.targetLanguage
|
text: QtPotatoSettings.targetLanguage
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
onTextEdited: QtPotatoSettings.targetLanguage = text.trim()
|
onTextEdited: QtPotatoSettings.targetLanguage = text.trim()
|
||||||
onEditingFinished: QtPotatoSettings.save()
|
onEditingFinished: QtPotatoSettings.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:listbox", "Preset")
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "Preset")
|
||||||
id: presetCombo
|
id: presetCombo
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
model: root.translator.translationPresets
|
model: root.translator.translationPresets
|
||||||
textRole: "name"
|
textRole: "name"
|
||||||
valueRole: "id"
|
valueRole: "id"
|
||||||
@@ -163,46 +200,49 @@ SettingsBasePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: presetCombo.currentIndex >= 0 ? presetCombo.model[presetCombo.currentIndex].description : ""
|
text: presetCombo.currentIndex >= 0 ? presetCombo.model[presetCombo.currentIndex].description : ""
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
font: Kirigami.Theme.smallFont
|
font: Kirigami.Theme.smallFont
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@label:textbox", "Additional instructions")
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignBaseline
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:textbox", "Additional instructions")
|
||||||
text: QtPotatoSettings.customInstructions
|
text: QtPotatoSettings.customInstructions
|
||||||
placeholderText: KI18n.i18nc("@info:placeholder", "e.g. Translate 'Young Master' as 'Thiếu gia'. Use 'ta/ngươi' between enemies.")
|
placeholderText: KI18n.i18nc("@info:placeholder", "e.g. Translate 'Young Master' as 'Thiếu gia'. Use 'ta/ngươi' between enemies.")
|
||||||
wrapMode: TextArea.Wrap
|
wrapMode: TextArea.Wrap
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignBaseline
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 4
|
Kirigami.FormData.labelAlignment: Qt.AlignTop
|
||||||
|
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding, Kirigami.Units.gridUnit * 4)
|
||||||
// TextArea has no textEdited before Qt 6.9.
|
// TextArea has no textEdited before Qt 6.9.
|
||||||
onTextChanged: QtPotatoSettings.customInstructions = text
|
onTextChanged: QtPotatoSettings.customInstructions = text
|
||||||
onEditingFinished: QtPotatoSettings.save()
|
onEditingFinished: QtPotatoSettings.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
Switch {
|
||||||
text: KI18n.i18nc("@option:check", "Build a glossary automatically before translating")
|
text: KI18n.i18nc("@option:check", "Build glossary automatically")
|
||||||
checked: QtPotatoSettings.autoBuildGlossary
|
checked: QtPotatoSettings.autoBuildGlossary
|
||||||
Layout.columnSpan: 2
|
onClicked: {
|
||||||
onToggled: { QtPotatoSettings.autoBuildGlossary = checked; QtPotatoSettings.save() }
|
QtPotatoSettings.autoBuildGlossary = checked
|
||||||
|
QtPotatoSettings.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: KI18n.i18nc("@label:spinbox", "Lines per request")
|
text: KI18n.i18nc("@info", "Collect names and terms before translating.")
|
||||||
Layout.alignment: Qt.AlignRight
|
wrapMode: Text.WordWrap
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 19
|
||||||
}
|
}
|
||||||
|
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Lines per request")
|
||||||
from: 5
|
from: 5
|
||||||
to: 200
|
to: 200
|
||||||
stepSize: 5
|
stepSize: 5
|
||||||
@@ -212,16 +252,6 @@ SettingsBasePage {
|
|||||||
QtPotatoSettings.save()
|
QtPotatoSettings.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.preferredWidth: 1 }
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: KI18n.i18nc("@info", "Works with any OpenAI-compatible API. The whole subtitle track is translated in batches before it is shown, and the result is kept so the same track is never translated twice.")
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
opacity: 0.7
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <QCoroSignal>
|
#include <QCoroSignal>
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
||||||
#include <qt6keychain/keychain.h>
|
#include <qt6keychain/keychain.h>
|
||||||
@@ -48,9 +50,31 @@ QString ApiKeyStore::error() const
|
|||||||
return m_error;
|
return m_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ApiKeyStore::busy() const
|
||||||
|
{
|
||||||
|
return m_busy;
|
||||||
|
}
|
||||||
|
|
||||||
void ApiKeyStore::setKey(const QString &key)
|
void ApiKeyStore::setKey(const QString &key)
|
||||||
{
|
{
|
||||||
store(key.trimmed());
|
if (m_busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_pendingKey = key.trimmed();
|
||||||
|
m_retryStore = true;
|
||||||
|
store(m_pendingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiKeyStore::retry()
|
||||||
|
{
|
||||||
|
if (m_busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_retryStore) {
|
||||||
|
store(m_pendingKey);
|
||||||
|
} else {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoro::Task<ApiKeyStore::ReadResult> ApiKeyStore::readKey()
|
QCoro::Task<ApiKeyStore::ReadResult> ApiKeyStore::readKey()
|
||||||
@@ -63,13 +87,14 @@ QCoro::Task<ApiKeyStore::ReadResult> ApiKeyStore::readKey()
|
|||||||
co_return ReadResult{};
|
co_return ReadResult{};
|
||||||
}
|
}
|
||||||
if (job->error() != QKeychain::NoError) {
|
if (job->error() != QKeychain::NoError) {
|
||||||
co_return ReadResult{{}, job->errorString()};
|
co_return ReadResult{{}, i18nc("@info", "Could not read the API key from the system wallet.\nMake sure KWallet or Secret Service is running, then retry.")};
|
||||||
}
|
}
|
||||||
co_return ReadResult{job->textData(), {}};
|
co_return ReadResult{job->textData(), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoro::Task<> ApiKeyStore::refresh()
|
QCoro::Task<> ApiKeyStore::refresh()
|
||||||
{
|
{
|
||||||
|
setBusy(true);
|
||||||
QPointer<ApiKeyStore> self(this);
|
QPointer<ApiKeyStore> self(this);
|
||||||
const ReadResult result = co_await readKey();
|
const ReadResult result = co_await readKey();
|
||||||
if (!self) {
|
if (!self) {
|
||||||
@@ -77,10 +102,12 @@ QCoro::Task<> ApiKeyStore::refresh()
|
|||||||
}
|
}
|
||||||
setHasKey(!result.key.isEmpty());
|
setHasKey(!result.key.isEmpty());
|
||||||
setError(result.error);
|
setError(result.error);
|
||||||
|
setBusy(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoro::Task<> ApiKeyStore::store(QString key)
|
QCoro::Task<> ApiKeyStore::store(QString key)
|
||||||
{
|
{
|
||||||
|
setBusy(true);
|
||||||
QPointer<ApiKeyStore> self(this);
|
QPointer<ApiKeyStore> self(this);
|
||||||
std::unique_ptr<QKeychain::Job, JobDeleter> job;
|
std::unique_ptr<QKeychain::Job, JobDeleter> job;
|
||||||
if (key.isEmpty()) {
|
if (key.isEmpty()) {
|
||||||
@@ -97,11 +124,17 @@ QCoro::Task<> ApiKeyStore::store(QString key)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (job->error() != QKeychain::NoError && job->error() != QKeychain::EntryNotFound) {
|
if (job->error() != QKeychain::NoError && job->error() != QKeychain::EntryNotFound) {
|
||||||
setError(job->errorString());
|
setError(key.isEmpty()
|
||||||
|
? i18nc("@info", "Could not remove the API key from the system wallet.\nMake sure KWallet or Secret Service is running, then retry.")
|
||||||
|
: i18nc("@info", "Could not store the API key in the system wallet.\nMake sure KWallet or Secret Service is running, then retry."));
|
||||||
|
setBusy(false);
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
setError({});
|
setError({});
|
||||||
setHasKey(!key.isEmpty());
|
setHasKey(!key.isEmpty());
|
||||||
|
m_retryStore = false;
|
||||||
|
m_pendingKey.clear();
|
||||||
|
setBusy(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiKeyStore::setHasKey(bool hasKey)
|
void ApiKeyStore::setHasKey(bool hasKey)
|
||||||
@@ -119,3 +152,11 @@ void ApiKeyStore::setError(const QString &error)
|
|||||||
Q_EMIT errorChanged();
|
Q_EMIT errorChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiKeyStore::setBusy(bool busy)
|
||||||
|
{
|
||||||
|
if (busy != m_busy) {
|
||||||
|
m_busy = busy;
|
||||||
|
Q_EMIT busyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ class ApiKeyStore : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(bool hasKey READ hasKey NOTIFY hasKeyChanged)
|
Q_PROPERTY(bool hasKey READ hasKey NOTIFY hasKeyChanged)
|
||||||
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
|
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
|
||||||
|
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct ReadResult {
|
struct ReadResult {
|
||||||
@@ -26,9 +27,12 @@ public:
|
|||||||
|
|
||||||
bool hasKey() const;
|
bool hasKey() const;
|
||||||
QString error() const;
|
QString error() const;
|
||||||
|
bool busy() const;
|
||||||
|
|
||||||
// Stores \a key, or removes the stored key when it is empty.
|
// Stores \a key, or removes the stored key when it is empty.
|
||||||
Q_INVOKABLE void setKey(const QString &key);
|
Q_INVOKABLE void setKey(const QString &key);
|
||||||
|
// Repeats the failed read, write, or removal without asking for the key again.
|
||||||
|
Q_INVOKABLE void retry();
|
||||||
|
|
||||||
// An empty key without error means none is stored.
|
// An empty key without error means none is stored.
|
||||||
static QCoro::Task<ReadResult> readKey();
|
static QCoro::Task<ReadResult> readKey();
|
||||||
@@ -36,13 +40,18 @@ public:
|
|||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void hasKeyChanged();
|
void hasKeyChanged();
|
||||||
void errorChanged();
|
void errorChanged();
|
||||||
|
void busyChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QCoro::Task<> refresh();
|
QCoro::Task<> refresh();
|
||||||
QCoro::Task<> store(QString key);
|
QCoro::Task<> store(QString key);
|
||||||
void setHasKey(bool hasKey);
|
void setHasKey(bool hasKey);
|
||||||
void setError(const QString &error);
|
void setError(const QString &error);
|
||||||
|
void setBusy(bool busy);
|
||||||
|
|
||||||
bool m_hasKey = false;
|
bool m_hasKey = false;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
|
bool m_busy = false;
|
||||||
|
bool m_retryStore = false;
|
||||||
|
QString m_pendingKey;
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user