feat(ui): style app menus and extract glossary dialog

- Add AppMenu and AppMenuItem so menu labels, mnemonics and shortcuts render consistently under org.kde.union
- Move the translation glossary into GlossaryDialog with add, remove and clear-all actions
- Regroup the translation preset and instructions settings layout
This commit was merged in pull request #3.
This commit is contained in:
tri committed 2026-11-07 15:03:37 +07:00
1 parent 9e55dba687
commit de9e6484ee
24 files changed
+519 -152

No files matched your search

+3
View File
@@ -62,11 +62,14 @@ ecm_add_qml_module(qtpotato
ecm_target_qml_sources(qtpotato SOURCES ecm_target_qml_sources(qtpotato SOURCES
qml/Actions.qml qml/Actions.qml
qml/AppMenu.qml
qml/AppMenuItem.qml
qml/AudioMenu.qml qml/AudioMenu.qml
qml/AudioTracksMenu.qml qml/AudioTracksMenu.qml
qml/FileMenu.qml qml/FileMenu.qml
qml/Footer.qml qml/Footer.qml
qml/GeneralSettings.qml qml/GeneralSettings.qml
qml/GlossaryDialog.qml
qml/HamburgerMenu.qml qml/HamburgerMenu.qml
qml/Header.qml qml/Header.qml
qml/HelpMenu.qml qml/HelpMenu.qml
+8
View File
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick.Controls
Menu {
delegate: AppMenuItem {}
}
+93
View File
@@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
MenuItem {
id: root
// org.kde.desktop sets these on its MenuItem, but other Plasma styles such
// as org.kde.union do not, which would leave the label below empty.
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: text
// The content includes the submenu arrow slot, so do not add it again as
// the desktop style does. Keep the style's background and indicator sizing.
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
contentItem: Item {
implicitWidth: row.implicitWidth
implicitHeight: row.implicitHeight
baselineOffset: row.y + label.y + label.baselineOffset
RowLayout {
id: row
width: parent.width
anchors.verticalCenter: parent.verticalCenter
spacing: Kirigami.Units.smallSpacing
layoutDirection: root.mirrored ? Qt.RightToLeft : Qt.LeftToRight
Item {
Layout.preferredWidth: (root.ListView.view && root.ListView.view.hasCheckables) || root.checkable
? root.indicator.width : Kirigami.Units.smallSpacing
}
// Reserve the icon column even for rows without an icon.
Item {
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
Kirigami.Icon {
anchors.fill: parent
visible: root.icon.name !== "" || root.icon.source.toString() !== ""
source: root.icon.name !== "" ? root.icon.name : root.icon.source
color: root.icon.color
selected: root.pressed
}
}
Label {
id: label
Layout.fillWidth: true
Layout.alignment: Qt.AlignBaseline
text: root.Kirigami.MnemonicData.richTextLabel
// StyledText keeps mnemonic underlines without RichText's
// different line metrics relative to the shortcut label.
textFormat: Text.StyledText
font: root.font
color: root.pressed ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
elide: Text.ElideRight
horizontalAlignment: root.mirrored ? Text.AlignRight : Text.AlignLeft
}
Label {
id: shortcut
Layout.alignment: Qt.AlignBaseline
visible: !root.subMenu && root.action && root.action.shortcut !== undefined
text: visible ? itemShortcut.nativeText : ""
textFormat: Text.PlainText
font: root.font
color: label.color
horizontalAlignment: Text.AlignRight
Shortcut {
id: itemShortcut
sequence: shortcut.visible && root.action ? root.action.shortcut : ""
}
}
// The inherited arrow sits over this slot. Without a submenu,
// shortcut text reaches the very same trailing edge.
Item {
visible: root.subMenu !== null
Layout.preferredWidth: root.arrow ? root.arrow.width : 0
}
}
}
}
+6 -6
View File
@@ -6,7 +6,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
@@ -19,9 +19,9 @@ Menu {
title: KI18n.i18nc("@title:menu", "&Track") title: KI18n.i18nc("@title:menu", "&Track")
} }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.mute } AppMenuItem { action: root.actions.mute }
MenuItem { action: root.actions.volumeUp } AppMenuItem { action: root.actions.volumeUp }
MenuItem { action: root.actions.volumeDown } AppMenuItem { action: root.actions.volumeDown }
MenuItem { action: root.actions.audioCycleUp } AppMenuItem { action: root.actions.audioCycleUp }
MenuItem { action: root.actions.audioCycleDown } AppMenuItem { action: root.actions.audioCycleDown }
} }
+2 -2
View File
@@ -9,7 +9,7 @@ import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
// Checkable list of audio tracks. // Checkable list of audio tracks.
Menu { AppMenu {
id: root id: root
required property MpvItem mpv required property MpvItem mpv
@@ -25,7 +25,7 @@ Menu {
Instantiator { Instantiator {
model: root.mpv.audioTracks model: root.mpv.audioTracks
delegate: MenuItem { delegate: AppMenuItem {
required property var modelData required property var modelData
text: root.trackLabel(modelData) text: root.trackLabel(modelData)
checkable: true checkable: true
+4 -4
View File
@@ -6,16 +6,16 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
title: KI18n.i18nc("@title:menu", "&File") title: KI18n.i18nc("@title:menu", "&File")
MenuItem { action: root.actions.openFile } AppMenuItem { action: root.actions.openFile }
MenuItem { action: root.actions.openUrl } AppMenuItem { action: root.actions.openUrl }
RecentFilesMenu { actions: root.actions } RecentFilesMenu { actions: root.actions }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.quit } AppMenuItem { action: root.actions.quit }
} }
+287
View File
@@ -0,0 +1,287 @@
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.ki18n
import org.kde.kirigami as Kirigami
import org.kde.kirigami.dialogs as KDialogs
Kirigami.Dialog {
id: root
required property var translator
readonly property real removeButtonWidth: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
// One inset for the header, content and footer so every edge lines up.
readonly property real inset: Kirigami.Units.largeSpacing
title: KI18n.i18nc("@title:window", "Translation Glossary")
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(parent.width * 0.9, Kirigami.Units.gridUnit * 38)
height: Math.min(parent.height - Kirigami.Units.largeSpacing * 2, Kirigami.Units.gridUnit * 30)
leftPadding: inset
rightPadding: inset
topPadding: 0
bottomPadding: 0
header: KDialogs.DialogHeader {
dialog: root
leftPadding: root.inset
rightPadding: root.inset
topPadding: root.inset
contentItem: KDialogs.DialogHeaderTopContent {
dialog: root
}
}
function addTerm() {
glossaryModel.append({
source: "",
target: ""
})
glossaryList.currentIndex = glossaryModel.count - 1
Qt.callLater(() => {
glossaryList.forceLayout()
glossaryList.positionViewAtIndex(glossaryList.currentIndex, ListView.Contain)
glossaryList.forceLayout()
const row = glossaryList.itemAtIndex(glossaryList.currentIndex)
if (row) {
row.focusOriginal()
}
})
}
onOpened: {
glossaryModel.clear()
for (const entry of translator.glossaryEntries) {
glossaryModel.append(entry)
}
glossaryList.positionViewAtBeginning()
}
onAccepted: {
const entries = []
for (let i = 0; i < glossaryModel.count; ++i) {
const entry = glossaryModel.get(i)
entries.push({
source: entry.source,
target: entry.target
})
}
translator.saveGlossary(entries)
}
ListModel {
id: glossaryModel
}
// Union's DialogButtonBox adds its own padding on top of the dialog's, so
// lay the footer out ourselves with the same inset as the header and content.
footer: Control {
leftPadding: root.inset
rightPadding: root.inset
topPadding: Kirigami.Units.largeSpacing
bottomPadding: root.inset
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Item {
Layout.fillWidth: true
}
Button {
text: KI18n.i18nc("@action:button", "Save")
icon.name: "document-save"
onClicked: root.accept()
}
Button {
text: KI18n.i18nc("@action:button", "Cancel")
icon.name: "dialog-cancel"
onClicked: root.reject()
}
}
}
Action {
id: addTermAction
text: KI18n.i18nc("@action:button", "Add Term")
icon.name: "list-add"
onTriggered: root.addTerm()
}
contentItem: ColumnLayout {
spacing: Kirigami.Units.largeSpacing
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Label {
Layout.fillWidth: true
text: KI18n.i18ncp("@label glossary term count", "%1 term", "%1 terms", glossaryModel.count)
color: Kirigami.Theme.disabledTextColor
}
Button {
action: addTermAction
}
Button {
text: KI18n.i18nc("@action:button", "Clear All")
icon.name: "edit-clear-all"
enabled: glossaryModel.count > 0
onClicked: glossaryModel.clear()
}
}
RowLayout {
visible: glossaryModel.count > 0
Layout.fillWidth: true
Layout.rightMargin: listScroll.rightPadding
spacing: Kirigami.Units.smallSpacing
Label {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.minimumWidth: 0
text: KI18n.i18nc("@title:column glossary source term", "Original")
font: Kirigami.Theme.smallFont
color: Kirigami.Theme.disabledTextColor
}
Label {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.minimumWidth: 0
text: KI18n.i18nc("@title:column glossary translated term", "Translation")
font: Kirigami.Theme.smallFont
color: Kirigami.Theme.disabledTextColor
}
Item {
Layout.preferredWidth: root.removeButtonWidth
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ScrollView {
id: listScroll
anchors.fill: parent
clip: true
contentWidth: availableWidth
leftPadding: 0
topPadding: 0
bottomPadding: 0
// Room for the scrollbar plus a gap, so its right edge lines up
// with the buttons above and below.
rightPadding: ScrollBar.vertical.visible ? ScrollBar.vertical.width + Kirigami.Units.smallSpacing : 0
// The style's frame and focus ring would stretch into the margin.
background: null
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
// The style's scrollbar is wide and draws a separator line; a slim
// one keeps the right side tidy.
ScrollBar.vertical: ScrollBar {
parent: listScroll
policy: ScrollBar.AsNeeded
active: true
// Union sets each side explicitly, so `padding: 0` alone is not enough.
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
leftInset: 0
rightInset: 0
topInset: 0
bottomInset: 0
background: null
contentItem: Rectangle {
implicitWidth: Kirigami.Units.smallSpacing * 1.5
radius: width / 2
color: Kirigami.Theme.textColor
opacity: parent.pressed ? 0.6 : parent.hovered ? 0.45 : 0.25
}
width: implicitWidth
x: listScroll.width - width
y: 0
height: listScroll.height
}
contentItem: ListView {
id: glossaryList
clip: true
spacing: Kirigami.Units.smallSpacing
boundsBehavior: Flickable.StopAtBounds
model: glossaryModel
delegate: RowLayout {
id: termRow
required property int index
required property string source
required property string target
width: glossaryList.width
height: implicitHeight
spacing: Kirigami.Units.smallSpacing
function focusOriginal() {
originalField.forceActiveFocus(Qt.TabFocusReason)
}
TextField {
id: originalField
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.minimumWidth: 0
text: termRow.source
placeholderText: KI18n.i18nc("@info:placeholder glossary source term", "Original term")
Accessible.name: KI18n.i18nc("@label:textbox glossary source term", "Original term")
onTextEdited: glossaryModel.setProperty(termRow.index, "source", text)
}
TextField {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.minimumWidth: 0
text: termRow.target
placeholderText: KI18n.i18nc("@info:placeholder glossary translated term", "Translation")
Accessible.name: KI18n.i18nc("@label:textbox glossary translated term", "Translation")
onTextEdited: glossaryModel.setProperty(termRow.index, "target", text)
}
ToolButton {
Layout.preferredWidth: root.removeButtonWidth
Layout.minimumWidth: root.removeButtonWidth
Layout.maximumWidth: root.removeButtonWidth
Layout.alignment: Qt.AlignVCenter
flat: true
icon.name: "edit-delete"
icon.color: Kirigami.Theme.textColor
display: AbstractButton.IconOnly
text: KI18n.i18nc("@action:button remove glossary term", "Remove")
ToolTip.text: text
ToolTip.visible: hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: glossaryModel.remove(termRow.index)
}
}
}
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.largeSpacing * 2
visible: glossaryModel.count === 0
text: KI18n.i18nc("@info:placeholder", "No glossary terms yet")
explanation: KI18n.i18nc("@info:placeholder", "Terms are extracted automatically before translation, or you can add them manually.")
helpfulAction: addTermAction
}
}
}
}
+11 -11
View File
@@ -29,19 +29,19 @@ ToolButton {
ToolTip.visible: hovered && !menu.visible ToolTip.visible: hovered && !menu.visible
ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.delay: Kirigami.Units.toolTipDelay
Menu { AppMenu {
id: menu id: menu
y: root.inFooter ? -height - Kirigami.Units.largeSpacing : root.height + Kirigami.Units.largeSpacing y: root.inFooter ? -height - Kirigami.Units.largeSpacing : root.height + Kirigami.Units.largeSpacing
modal: true modal: true
dim: false dim: false
MenuItem { AppMenuItem {
action: root.actions.openFile action: root.actions.openFile
visible: root.inFooter visible: root.inFooter
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
} }
MenuItem { AppMenuItem {
action: root.actions.openUrl action: root.actions.openUrl
visible: root.inFooter visible: root.inFooter
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
@@ -62,20 +62,20 @@ ToolButton {
title: KI18n.i18nc("@title:menu", "Audio Track") title: KI18n.i18nc("@title:menu", "Audio Track")
} }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.translateSubtitle } AppMenuItem { action: root.actions.translateSubtitle }
MenuItem { action: root.actions.toggleFullscreen } AppMenuItem { action: root.actions.toggleFullscreen }
MenuItem { action: root.actions.togglePlaylist } AppMenuItem { action: root.actions.togglePlaylist }
MenuItem { action: root.actions.screenshot } AppMenuItem { action: root.actions.screenshot }
MenuSeparator {} MenuSeparator {}
MenuItem { AppMenuItem {
action: root.actions.configure action: root.actions.configure
visible: root.inFooter visible: root.inFooter
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
} }
MenuItem { action: root.actions.toggleMenuBar } AppMenuItem { action: root.actions.toggleMenuBar }
MenuItem { action: root.actions.about } AppMenuItem { action: root.actions.about }
MenuSeparator {} MenuSeparator {}
Menu { AppMenu {
title: KI18n.i18nc("@title:menu", "&More") title: KI18n.i18nc("@title:menu", "&More")
FileMenu { actions: root.actions } FileMenu { actions: root.actions }
ViewMenu { actions: root.actions } ViewMenu { actions: root.actions }
+2 -2
View File
@@ -79,8 +79,8 @@ ToolBar {
enabled: true enabled: true
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.translateSubtitle } AppMenuItem { action: root.actions.translateSubtitle }
MenuItem { action: root.actions.addSubtitle } AppMenuItem { action: root.actions.addSubtitle }
} }
} }
+3 -3
View File
@@ -6,13 +6,13 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
title: KI18n.i18nc("@title:menu", "&Help") title: KI18n.i18nc("@title:menu", "&Help")
MenuItem { action: root.actions.about } AppMenuItem { action: root.actions.about }
MenuItem { action: root.actions.reportBug } AppMenuItem { action: root.actions.reportBug }
} }
+2 -33
View File
@@ -318,39 +318,8 @@ ApplicationWindow {
onAccepted: video.addSubtitleFile(selectedFile) onAccepted: video.addSubtitleFile(selectedFile)
} }
Kirigami.Dialog { GlossaryDialog {
id: glossaryDialog id: glossaryDialog
title: KI18n.i18nc("@title:window", "Translation Glossary") translator: translationService
standardButtons: Kirigami.Dialog.Save | Kirigami.Dialog.Cancel
width: Math.min(appWindow.width * 0.9, 650)
contentItem: ColumnLayout {
ListView {
id: glossaryList
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 15
clip: true
model: ListModel { id: glossaryModel }
delegate: RowLayout {
width: glossaryList.width
TextField { text: source; Layout.fillWidth: true; onTextChanged: model.source = text }
TextField { text: target; Layout.fillWidth: true; onTextChanged: model.target = text }
ToolButton { icon.name: "edit-delete-remove"; onClicked: glossaryModel.remove(index) }
}
}
RowLayout {
Button { text: KI18n.i18nc("@action:button", "Add"); onClicked: glossaryModel.append({source: "", target: ""}) }
Button { text: KI18n.i18nc("@action:button", "Clear"); onClicked: glossaryModel.clear() }
}
}
onOpened: {
glossaryModel.clear()
for (const entry of translationService.glossaryEntries) glossaryModel.append(entry)
}
onAccepted: {
const entries = []
for (let i = 0; i < glossaryModel.count; ++i) entries.push(glossaryModel.get(i))
translationService.saveGlossary(entries)
}
} }
} }
+16 -16
View File
@@ -7,24 +7,24 @@ import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
// Right click menu of the video. // Right click menu of the video.
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
required property MpvItem mpv required property MpvItem mpv
MenuItem { action: root.actions.playPause } AppMenuItem { action: root.actions.playPause }
MenuItem { action: root.actions.playNextFile } AppMenuItem { action: root.actions.playNextFile }
MenuItem { action: root.actions.playPreviousFile } AppMenuItem { action: root.actions.playPreviousFile }
Menu { AppMenu {
title: KI18n.i18nc("@title:menu", "Playback Speed") title: KI18n.i18nc("@title:menu", "Playback Speed")
MenuItem { action: root.actions.speedUp } AppMenuItem { action: root.actions.speedUp }
MenuItem { action: root.actions.speedDown } AppMenuItem { action: root.actions.speedDown }
MenuItem { action: root.actions.speedReset } AppMenuItem { action: root.actions.speedReset }
} }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.openFile } AppMenuItem { action: root.actions.openFile }
MenuItem { action: root.actions.openUrl } AppMenuItem { action: root.actions.openUrl }
MenuSeparator {} MenuSeparator {}
SubtitleTracksMenu { SubtitleTracksMenu {
mpv: root.mpv mpv: root.mpv
@@ -40,7 +40,7 @@ Menu {
mpv: root.mpv mpv: root.mpv
title: KI18n.i18nc("@title:menu", "Audio Track") title: KI18n.i18nc("@title:menu", "Audio Track")
} }
MenuItem { action: root.actions.translateSubtitle } AppMenuItem { action: root.actions.translateSubtitle }
MenuSeparator {} MenuSeparator {}
VideoMenu { actions: root.actions } VideoMenu { actions: root.actions }
SubtitlesMenu { SubtitlesMenu {
@@ -52,10 +52,10 @@ Menu {
mpv: root.mpv mpv: root.mpv
} }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.toggleFullscreen } AppMenuItem { action: root.actions.toggleFullscreen }
MenuItem { action: root.actions.togglePlaylist } AppMenuItem { action: root.actions.togglePlaylist }
MenuItem { action: root.actions.toggleMenuBar } AppMenuItem { action: root.actions.toggleMenuBar }
MenuItem { action: root.actions.toggleHeader } AppMenuItem { action: root.actions.toggleHeader }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.configure } AppMenuItem { action: root.actions.configure }
} }
+22 -22
View File
@@ -6,41 +6,41 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
title: KI18n.i18nc("@title:menu", "&Playback") title: KI18n.i18nc("@title:menu", "&Playback")
MenuItem { action: root.actions.playPause } AppMenuItem { action: root.actions.playPause }
MenuItem { action: root.actions.stop } AppMenuItem { action: root.actions.stop }
MenuItem { action: root.actions.playNextFile } AppMenuItem { action: root.actions.playNextFile }
MenuItem { action: root.actions.playPreviousFile } AppMenuItem { action: root.actions.playPreviousFile }
MenuItem { action: root.actions.restart } AppMenuItem { action: root.actions.restart }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.speedUp } AppMenuItem { action: root.actions.speedUp }
MenuItem { action: root.actions.speedDown } AppMenuItem { action: root.actions.speedDown }
MenuItem { action: root.actions.speedReset } AppMenuItem { action: root.actions.speedReset }
MenuSeparator {} MenuSeparator {}
Menu { AppMenu {
title: KI18n.i18nc("@title:menu", "Seek") title: KI18n.i18nc("@title:menu", "Seek")
MenuItem { action: root.actions.seekForwardSmall } AppMenuItem { action: root.actions.seekForwardSmall }
MenuItem { action: root.actions.seekBackwardSmall } AppMenuItem { action: root.actions.seekBackwardSmall }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.seekForwardMedium } AppMenuItem { action: root.actions.seekForwardMedium }
MenuItem { action: root.actions.seekBackwardMedium } AppMenuItem { action: root.actions.seekBackwardMedium }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.seekForwardBig } AppMenuItem { action: root.actions.seekForwardBig }
MenuItem { action: root.actions.seekBackwardBig } AppMenuItem { action: root.actions.seekBackwardBig }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.seekNextSubtitle } AppMenuItem { action: root.actions.seekNextSubtitle }
MenuItem { action: root.actions.seekPreviousSubtitle } AppMenuItem { action: root.actions.seekPreviousSubtitle }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.seekNextChapter } AppMenuItem { action: root.actions.seekNextChapter }
MenuItem { action: root.actions.seekPreviousChapter } AppMenuItem { action: root.actions.seekPreviousChapter }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.frameStepForward } AppMenuItem { action: root.actions.frameStepForward }
MenuItem { action: root.actions.frameStepBackward } AppMenuItem { action: root.actions.frameStepBackward }
} }
} }
+6 -6
View File
@@ -113,15 +113,15 @@ Item {
} }
} }
Menu { AppMenu {
id: contextMenu id: contextMenu
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Play") text: KI18n.i18nc("@action:inmenu", "Play")
icon.name: "media-playback-start" icon.name: "media-playback-start"
onTriggered: root.playlist.play(root.index) onTriggered: root.playlist.play(root.index)
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Open Containing Folder") text: KI18n.i18nc("@action:inmenu", "Open Containing Folder")
icon.name: "folder-open" icon.name: "folder-open"
enabled: root.url.toString().startsWith("file://") enabled: root.url.toString().startsWith("file://")
@@ -131,19 +131,19 @@ Item {
} }
} }
MenuSeparator {} MenuSeparator {}
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Move Up") text: KI18n.i18nc("@action:inmenu", "Move Up")
icon.name: "go-up" icon.name: "go-up"
enabled: root.index > 0 enabled: root.index > 0
onTriggered: root.playlist.move(root.index, root.index - 1) onTriggered: root.playlist.move(root.index, root.index - 1)
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Move Down") text: KI18n.i18nc("@action:inmenu", "Move Down")
icon.name: "go-down" icon.name: "go-down"
enabled: root.index < root.playlist.count - 1 enabled: root.index < root.playlist.count - 1
onTriggered: root.playlist.move(root.index, root.index + 1) onTriggered: root.playlist.move(root.index, root.index + 1)
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Remove from Playlist") text: KI18n.i18nc("@action:inmenu", "Remove from Playlist")
icon.name: "list-remove" icon.name: "list-remove"
onTriggered: root.playlist.remove(root.index) onTriggered: root.playlist.remove(root.index)
+3 -3
View File
@@ -8,7 +8,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
@@ -18,7 +18,7 @@ Menu {
Instantiator { Instantiator {
model: RecentFilesModel model: RecentFilesModel
delegate: MenuItem { delegate: AppMenuItem {
required property url url required property url url
required property string name required property string name
required property string path required property string path
@@ -35,7 +35,7 @@ Menu {
MenuSeparator {} MenuSeparator {}
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Clear List") text: KI18n.i18nc("@action:inmenu", "Clear List")
icon.name: "edit-clear-history" icon.name: "edit-clear-history"
onTriggered: RecentFilesModel.clear() onTriggered: RecentFilesModel.clear()
+7 -7
View File
@@ -6,34 +6,34 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
title: KI18n.i18nc("@title:menu", "&Settings") title: KI18n.i18nc("@title:menu", "&Settings")
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "General") text: KI18n.i18nc("@action:inmenu", "General")
onTriggered: root.actions.settingsRequested("general") onTriggered: root.actions.settingsRequested("general")
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Playback") text: KI18n.i18nc("@action:inmenu", "Playback")
onTriggered: root.actions.settingsRequested("playback") onTriggered: root.actions.settingsRequested("playback")
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Subtitles") text: KI18n.i18nc("@action:inmenu", "Subtitles")
onTriggered: root.actions.settingsRequested("subtitles") onTriggered: root.actions.settingsRequested("subtitles")
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Translation") text: KI18n.i18nc("@action:inmenu", "Translation")
onTriggered: root.actions.settingsRequested("translation") onTriggered: root.actions.settingsRequested("translation")
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "Playlist") text: KI18n.i18nc("@action:inmenu", "Playlist")
onTriggered: root.actions.settingsRequested("playlist") onTriggered: root.actions.settingsRequested("playlist")
} }
MenuItem { AppMenuItem {
text: KI18n.i18nc("@action:inmenu", "About") text: KI18n.i18nc("@action:inmenu", "About")
onTriggered: root.actions.settingsRequested("about") onTriggered: root.actions.settingsRequested("about")
} }
+3 -3
View File
@@ -9,7 +9,7 @@ import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
// Checkable list of subtitle tracks, for the primary or the secondary subtitle. // Checkable list of subtitle tracks, for the primary or the secondary subtitle.
Menu { AppMenu {
id: root id: root
required property MpvItem mpv required property MpvItem mpv
@@ -36,7 +36,7 @@ Menu {
enabled: mpv.subtitleTracks.length > 0 enabled: mpv.subtitleTracks.length > 0
MenuItem { AppMenuItem {
text: KI18n.i18nc("@item:inmenu no subtitle", "None") text: KI18n.i18nc("@item:inmenu no subtitle", "None")
checkable: true checkable: true
checked: root.selectedId === 0 checked: root.selectedId === 0
@@ -49,7 +49,7 @@ Menu {
Instantiator { Instantiator {
model: root.mpv.subtitleTracks model: root.mpv.subtitleTracks
delegate: MenuItem { delegate: AppMenuItem {
required property var modelData required property var modelData
text: root.trackLabel(modelData) text: root.trackLabel(modelData)
checkable: true checkable: true
+13 -13
View File
@@ -6,7 +6,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
@@ -25,21 +25,21 @@ Menu {
title: KI18n.i18nc("@title:menu", "Secondary Subtitle") title: KI18n.i18nc("@title:menu", "Secondary Subtitle")
} }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.translateSubtitle } AppMenuItem { action: root.actions.translateSubtitle }
MenuItem { action: root.actions.editGlossary } AppMenuItem { action: root.actions.editGlossary }
MenuItem { AppMenuItem {
action: root.actions.cancelTranslation action: root.actions.cancelTranslation
visible: root.actions.cancelTranslation.enabled visible: root.actions.cancelTranslation.enabled
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
} }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.addSubtitle } AppMenuItem { action: root.actions.addSubtitle }
MenuItem { action: root.actions.subtitleBigger } AppMenuItem { action: root.actions.subtitleBigger }
MenuItem { action: root.actions.subtitleSmaller } AppMenuItem { action: root.actions.subtitleSmaller }
MenuItem { action: root.actions.subtitleEarlier } AppMenuItem { action: root.actions.subtitleEarlier }
MenuItem { action: root.actions.subtitleLater } AppMenuItem { action: root.actions.subtitleLater }
MenuItem { action: root.actions.subtitleDelayReset } AppMenuItem { action: root.actions.subtitleDelayReset }
MenuItem { action: root.actions.subtitleToggle } AppMenuItem { action: root.actions.subtitleToggle }
MenuItem { action: root.actions.subtitleCycleUp } AppMenuItem { action: root.actions.subtitleCycleUp }
MenuItem { action: root.actions.subtitleCycleDown } AppMenuItem { action: root.actions.subtitleCycleDown }
} }
+7 -7
View File
@@ -147,12 +147,9 @@ SettingsBasePage {
Label { Label {
text: KI18n.i18nc("@label:listbox", "Preset") text: KI18n.i18nc("@label:listbox", "Preset")
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
} }
ColumnLayout {
Layout.fillWidth: true
ComboBox { ComboBox {
id: presetCombo id: presetCombo
Layout.fillWidth: true Layout.fillWidth: true
@@ -166,17 +163,19 @@ SettingsBasePage {
} }
} }
Item { Layout.preferredWidth: 1 }
Label { Label {
text: presetCombo.currentIndex >= 0 ? presetCombo.model[presetCombo.currentIndex].guidance : "" 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
Layout.fillWidth: true Layout.fillWidth: true
} }
}
Label { Label {
text: KI18n.i18nc("@label:textbox", "Additional instructions") text: KI18n.i18nc("@label:textbox", "Additional instructions")
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignBaseline
} }
TextArea { TextArea {
@@ -184,6 +183,7 @@ SettingsBasePage {
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.preferredHeight: Kirigami.Units.gridUnit * 4 Layout.preferredHeight: 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
+8 -8
View File
@@ -6,18 +6,18 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
title: KI18n.i18nc("@title:menu", "&Video") title: KI18n.i18nc("@title:menu", "&Video")
MenuItem { action: root.actions.toggleDeinterlacing } AppMenuItem { action: root.actions.toggleDeinterlacing }
MenuItem { action: root.actions.zoomIn } AppMenuItem { action: root.actions.zoomIn }
MenuItem { action: root.actions.zoomOut } AppMenuItem { action: root.actions.zoomOut }
MenuItem { action: root.actions.zoomReset } AppMenuItem { action: root.actions.zoomReset }
MenuItem { action: root.actions.screenshot } AppMenuItem { action: root.actions.screenshot }
MenuItem { action: root.actions.rotateClockwise } AppMenuItem { action: root.actions.rotateClockwise }
MenuItem { action: root.actions.rotateCounterClockwise } AppMenuItem { action: root.actions.rotateCounterClockwise }
} }
+5 -5
View File
@@ -6,16 +6,16 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import org.kde.ki18n import org.kde.ki18n
Menu { AppMenu {
id: root id: root
required property Actions actions required property Actions actions
title: KI18n.i18nc("@title:menu", "&View") title: KI18n.i18nc("@title:menu", "&View")
MenuItem { action: root.actions.toggleFullscreen } AppMenuItem { action: root.actions.toggleFullscreen }
MenuItem { action: root.actions.togglePlaylist } AppMenuItem { action: root.actions.togglePlaylist }
MenuSeparator {} MenuSeparator {}
MenuItem { action: root.actions.toggleMenuBar } AppMenuItem { action: root.actions.toggleMenuBar }
MenuItem { action: root.actions.toggleHeader } AppMenuItem { action: root.actions.toggleHeader }
} }
+6
View File
@@ -15,31 +15,37 @@ const QList<Preset> &all()
{ {
u"general"_s, u"general"_s,
i18nc("@item:inlistbox translation preset", "General"), i18nc("@item:inlistbox translation preset", "General"),
i18nc("@info translation preset description", "Natural dialogue with conventional names and a tone suited to the scene."),
u"Keep names as they are conventionally rendered and use a natural, conversational tone appropriate to the scene."_s, u"Keep names as they are conventionally rendered and use a natural, conversational tone appropriate to the scene."_s,
}, },
{ {
u"chinese-donghua"_s, u"chinese-donghua"_s,
i18nc("@item:inlistbox translation preset", "Chinese animation / wuxia / xianxia"), i18nc("@item:inlistbox translation preset", "Chinese animation / wuxia / xianxia"),
i18nc("@info translation preset description", "Traditional names, cultivation terms, and period-appropriate forms of address."),
u"For Chinese animation, wuxia, xianxia, and historical settings, the source is often an English subtitle with pinyin names. When translating into Vietnamese, convert Chinese personal names, place names, sect and clan names, techniques, and cultivation terms into their Sino-Vietnamese (Hán-Việt) forms (for example, Xiao Yan → Tiêu Viêm, Dou Qi → Đấu Khí, Sect Master → Tông chủ). For other target languages, use that language's conventional rendering. Be consistent and prefer well-known established renderings from famous works. When translating into Vietnamese, use period-appropriate forms of address such as ta/ngươi, huynh/đệ, sư phụ, and tiền bối."_s, u"For Chinese animation, wuxia, xianxia, and historical settings, the source is often an English subtitle with pinyin names. When translating into Vietnamese, convert Chinese personal names, place names, sect and clan names, techniques, and cultivation terms into their Sino-Vietnamese (Hán-Việt) forms (for example, Xiao Yan → Tiêu Viêm, Dou Qi → Đấu Khí, Sect Master → Tông chủ). For other target languages, use that language's conventional rendering. Be consistent and prefer well-known established renderings from famous works. When translating into Vietnamese, use period-appropriate forms of address such as ta/ngươi, huynh/đệ, sư phụ, and tiền bối."_s,
}, },
{ {
u"japanese-anime"_s, u"japanese-anime"_s,
i18nc("@item:inlistbox translation preset", "Japanese anime"), i18nc("@item:inlistbox translation preset", "Japanese anime"),
i18nc("@info translation preset description", "Casual dialogue with Japanese names and honorifics preserved."),
u"Keep Japanese names in romaji. Keep honorifics such as -san, -kun, and -senpai as-is. Use a natural, casual tone."_s, u"Keep Japanese names in romaji. Keep honorifics such as -san, -kun, and -senpai as-is. Use a natural, casual tone."_s,
}, },
{ {
u"korean-drama"_s, u"korean-drama"_s,
i18nc("@item:inlistbox translation preset", "Korean drama"), i18nc("@item:inlistbox translation preset", "Korean drama"),
i18nc("@info translation preset description", "Modern dialogue with Korean names and relationship-aware forms of address."),
u"Keep Korean names. Use modern forms of address that fit the relationships; when translating into Vietnamese, use forms such as anh/em, and oppa may be kept when appropriate."_s, u"Keep Korean names. Use modern forms of address that fit the relationships; when translating into Vietnamese, use forms such as anh/em, and oppa may be kept when appropriate."_s,
}, },
{ {
u"western"_s, u"western"_s,
i18nc("@item:inlistbox translation preset", "Western"), i18nc("@item:inlistbox translation preset", "Western"),
i18nc("@info translation preset description", "A neutral modern tone with naturally adapted idioms."),
u"Keep names. Use a neutral modern tone and adapt idioms naturally for the target language."_s, u"Keep names. Use a neutral modern tone and adapt idioms naturally for the target language."_s,
}, },
{ {
u"documentary"_s, u"documentary"_s,
i18nc("@item:inlistbox translation preset", "Documentary"), i18nc("@item:inlistbox translation preset", "Documentary"),
i18nc("@info translation preset description", "Formal, precise language with accurate terminology."),
u"Keep names. Use a formal, precise tone and translate terminology accurately."_s, u"Keep names. Use a formal, precise tone and translate terminology accurately."_s,
}, },
}; };
+1
View File
@@ -11,6 +11,7 @@ namespace TranslationPresets
struct Preset { struct Preset {
QString id; QString id;
QString name; QString name;
QString description;
QString guidance; QString guidance;
}; };
+1 -1
View File
@@ -117,7 +117,7 @@ QVariantList TranslationService::translationPresets() const
{ {
QVariantList result; QVariantList result;
for (const auto &preset : TranslationPresets::all()) { for (const auto &preset : TranslationPresets::all()) {
result.append(QVariantMap{{u"id"_s, preset.id}, {u"name"_s, preset.name}, {u"guidance"_s, preset.guidance}}); result.append(QVariantMap{{u"id"_s, preset.id}, {u"name"_s, preset.name}, {u"description"_s, preset.description}, {u"guidance"_s, preset.guidance}});
} }
return result; return result;
} }