feat: rebuild the interface after Haruna

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
triandClaude Opus 5.5 committed 2026-10-28 09:26:47 +07:00
1 parent 54ad4eecf3
commit 614a31db1b
45 files changed
+3818 -798

No files matched your search

+502
View File
@@ -0,0 +1,502 @@
// SPDX-FileCopyrightText: 2020 George Florea Bănuș <georgefb899@gmail.com>
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import org.kde.ki18n
// Every command of the player, shared by the menus, the toolbar buttons and the
// keyboard shortcuts. Shortcuts follow Haruna's defaults.
Item {
id: root
required property MpvItem mpv
required property PlaylistModel playlist
required property TranslationService translator
required property Osd osd
// Off while a dialog has the keyboard, so typing there does not seek or pause.
property bool shortcutsEnabled: true
property bool fullscreen: false
readonly property bool hasFile: mpv.fileLoaded
signal openFileRequested()
// Open a known file, such as one from Recent Files.
signal fileOpenRequested(url: url)
signal openUrlRequested()
signal addSubtitleRequested()
signal settingsRequested(page: string)
signal fullscreenToggleRequested()
signal fullscreenExitRequested()
signal playlistToggleRequested()
signal menuBarToggleRequested()
signal headerToggleRequested()
signal contextMenuRequested()
signal translateRequested()
function seekBy(seconds: real) {
mpv.seekBy(seconds)
}
function changeVolume(steps: int) {
mpv.volume = mpv.volume + steps * QtPotatoSettings.volumeStep
osd.message(KI18n.i18nc("@info:osd", "Volume: %1", mpv.volume))
}
function changeSpeed(delta: real) {
mpv.speed = delta === 0 ? 1.0 : mpv.speed + delta
osd.message(KI18n.i18nc("@info:osd", "Playback speed: %1", Number(mpv.speed).toFixed(2)))
}
function changeSubtitleDelay(seconds: real) {
mpv.subtitleDelay = mpv.subtitleDelay + seconds
osd.message(KI18n.i18nc("@info:osd", "Subtitle delay: %1 ms", Math.round(mpv.subtitleDelay * 1000)))
}
function cycleTrack(tracks: var, currentId: int, step: int): int {
// Track ids with 0 ("off") first, as mpv's cycle commands do.
const ids = [0].concat(tracks.map(track => track.id))
const index = Math.max(0, ids.indexOf(currentId))
return ids[(index + step + ids.length) % ids.length]
}
// The next or previous file, following the playback behavior setting.
function playNext(): bool {
return playlist.playNext(QtPotatoSettings.playbackBehavior === "RepeatPlaylist")
}
function playPrevious(): bool {
return playlist.playPrevious(QtPotatoSettings.playbackBehavior === "RepeatPlaylist")
}
property Action openFile: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Open File…")
icon.name: "folder-videos"
shortcut: StandardKey.Open
onTriggered: root.openFileRequested()
}
property Action openUrl: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Open URL…")
icon.name: "internet-services"
shortcut: "Ctrl+Shift+O"
onTriggered: root.openUrlRequested()
}
property Action quit: Action {
text: KI18n.i18nc("@action", "Quit")
icon.name: "application-exit"
shortcut: StandardKey.Quit
onTriggered: Qt.quit()
}
property Action playPause: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Play/Pause")
icon.name: root.mpv.paused ? "media-playback-start" : "media-playback-pause"
shortcut: "Space"
onTriggered: root.mpv.togglePause()
}
property Action stop: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Stop")
icon.name: "media-playback-stop"
shortcut: "Media Stop"
onTriggered: root.mpv.stop()
}
property Action restart: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Restart Playback")
icon.name: "edit-reset"
shortcut: "F5"
onTriggered: root.mpv.seek(0)
}
property Action playNextFile: Action {
enabled: root.shortcutsEnabled && root.playlist.count > 1
text: KI18n.i18nc("@action", "Play Next")
icon.name: "media-skip-forward"
shortcut: "Shift+."
onTriggered: root.playNext()
}
property Action playPreviousFile: Action {
enabled: root.shortcutsEnabled && root.playlist.count > 1
text: KI18n.i18nc("@action", "Play Previous")
icon.name: "media-skip-backward"
shortcut: "Shift+,"
onTriggered: root.playPrevious()
}
property Action seekForwardSmall: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek Small Step Forward")
shortcut: "Right"
onTriggered: root.seekBy(QtPotatoSettings.seekSmallStep)
}
property Action seekBackwardSmall: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek Small Step Backward")
shortcut: "Left"
onTriggered: root.seekBy(-QtPotatoSettings.seekSmallStep)
}
property Action seekForwardMedium: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek Medium Step Forward")
shortcut: "Shift+Right"
onTriggered: root.seekBy(QtPotatoSettings.seekMediumStep)
}
property Action seekBackwardMedium: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek Medium Step Backward")
shortcut: "Shift+Left"
onTriggered: root.seekBy(-QtPotatoSettings.seekMediumStep)
}
property Action seekForwardBig: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek Big Step Forward")
shortcut: "Up"
onTriggered: root.seekBy(QtPotatoSettings.seekBigStep)
}
property Action seekBackwardBig: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek Big Step Backward")
shortcut: "Down"
onTriggered: root.seekBy(-QtPotatoSettings.seekBigStep)
}
property Action seekNextSubtitle: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek To Next Subtitle")
shortcut: "Ctrl+Right"
onTriggered: root.mpv.commandAsync(["sub-seek", "1"])
}
property Action seekPreviousSubtitle: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Seek To Previous Subtitle")
shortcut: "Ctrl+Left"
onTriggered: root.mpv.commandAsync(["sub-seek", "-1"])
}
property Action seekNextChapter: Action {
enabled: root.shortcutsEnabled && root.mpv.chapters.length > 0
text: KI18n.i18nc("@action", "Seek To Next Chapter")
shortcut: "PgUp"
onTriggered: root.mpv.commandAsync(["add", "chapter", "1"])
}
property Action seekPreviousChapter: Action {
enabled: root.shortcutsEnabled && root.mpv.chapters.length > 0
text: KI18n.i18nc("@action", "Seek To Previous Chapter")
shortcut: "PgDown"
onTriggered: root.mpv.commandAsync(["add", "chapter", "-1"])
}
property Action frameStepForward: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Step Frame Forward")
shortcut: "."
onTriggered: root.mpv.frameStep(true)
}
property Action frameStepBackward: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Step Frame Backward")
shortcut: ","
onTriggered: root.mpv.frameStep(false)
}
property Action speedUp: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Increase Playback Speed")
shortcut: "]"
onTriggered: root.changeSpeed(0.1)
}
property Action speedDown: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Decrease Playback Speed")
shortcut: "["
onTriggered: root.changeSpeed(-0.1)
}
property Action speedReset: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Reset Playback Speed")
shortcut: "Backspace"
onTriggered: root.changeSpeed(0)
}
property Action mute: Action {
enabled: root.shortcutsEnabled
text: root.mpv.mute ? KI18n.i18nc("@action", "Unmute") : KI18n.i18nc("@action", "Mute")
icon.name: root.mpv.mute ? "player-volume" : "player-volume-muted"
shortcut: "M"
onTriggered: {
root.mpv.mute = !root.mpv.mute
root.osd.message(root.mpv.mute ? KI18n.i18nc("@info:osd", "Audio muted") : KI18n.i18nc("@info:osd", "Audio unmuted"))
}
}
property Action volumeUp: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Volume Up")
icon.name: "audio-volume-high"
shortcut: "9"
onTriggered: root.changeVolume(1)
}
property Action volumeDown: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Volume Down")
icon.name: "audio-volume-low"
shortcut: "0"
onTriggered: root.changeVolume(-1)
}
property Action audioCycleUp: Action {
enabled: root.shortcutsEnabled && root.mpv.audioTracks.length > 1
text: KI18n.i18nc("@action", "Cycle Up Audio Track")
shortcut: "Shift+3"
onTriggered: root.mpv.audioId = root.cycleTrack(root.mpv.audioTracks, root.mpv.audioId, 1)
}
property Action audioCycleDown: Action {
enabled: root.shortcutsEnabled && root.mpv.audioTracks.length > 1
text: KI18n.i18nc("@action", "Cycle Down Audio Track")
shortcut: "Shift+2"
onTriggered: root.mpv.audioId = root.cycleTrack(root.mpv.audioTracks, root.mpv.audioId, -1)
}
property Action addSubtitle: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Add External Subtitle…")
icon.name: "add-subtitle"
shortcut: "Alt+S"
onTriggered: root.addSubtitleRequested()
}
property Action subtitleToggle: Action {
enabled: root.shortcutsEnabled && root.mpv.subtitleTracks.length > 0
text: KI18n.i18nc("@action", "Toggle Subtitles")
shortcut: "Ctrl+S"
onTriggered: {
const visible = root.mpv.getProperty("sub-visibility")
root.mpv.setPropertyAsync("sub-visibility", !visible)
root.mpv.setPropertyAsync("secondary-sub-visibility", !visible)
root.osd.message(visible ? KI18n.i18nc("@info:osd", "Subtitles hidden") : KI18n.i18nc("@info:osd", "Subtitles shown"))
}
}
property Action subtitleCycleUp: Action {
enabled: root.shortcutsEnabled && root.mpv.subtitleTracks.length > 0
text: KI18n.i18nc("@action", "Cycle Up Subtitle")
shortcut: "J"
onTriggered: root.mpv.subtitleId = root.cycleTrack(root.mpv.subtitleTracks, root.mpv.subtitleId, 1)
}
property Action subtitleCycleDown: Action {
enabled: root.shortcutsEnabled && root.mpv.subtitleTracks.length > 0
text: KI18n.i18nc("@action", "Cycle Down Subtitle")
shortcut: "Shift+J"
onTriggered: root.mpv.subtitleId = root.cycleTrack(root.mpv.subtitleTracks, root.mpv.subtitleId, -1)
}
property Action subtitleEarlier: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Show Subtitles Earlier")
shortcut: "Z"
onTriggered: root.changeSubtitleDelay(-0.1)
}
property Action subtitleLater: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Show Subtitles Later")
shortcut: "Shift+Z"
onTriggered: root.changeSubtitleDelay(0.1)
}
property Action subtitleDelayReset: Action {
enabled: root.shortcutsEnabled && root.hasFile && root.mpv.subtitleDelay !== 0
text: KI18n.i18nc("@action", "Reset Subtitle Delay")
onTriggered: root.changeSubtitleDelay(-root.mpv.subtitleDelay)
}
property Action subtitleBigger: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Increase Subtitle Font Size")
shortcut: "Ctrl+Z"
onTriggered: {
QtPotatoSettings.subtitleFontSize = Math.min(150, QtPotatoSettings.subtitleFontSize + 2)
QtPotatoSettings.save()
root.osd.message(KI18n.i18nc("@info:osd", "Subtitle font size: %1", QtPotatoSettings.subtitleFontSize))
}
}
property Action subtitleSmaller: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Decrease Subtitle Font Size")
shortcut: "Ctrl+X"
onTriggered: {
QtPotatoSettings.subtitleFontSize = Math.max(20, QtPotatoSettings.subtitleFontSize - 2)
QtPotatoSettings.save()
root.osd.message(KI18n.i18nc("@info:osd", "Subtitle font size: %1", QtPotatoSettings.subtitleFontSize))
}
}
property Action translateSubtitle: Action {
enabled: root.shortcutsEnabled && root.hasFile && root.mpv.subtitleId > 0 && !root.translator.busy
text: KI18n.i18nc("@action", "Translate Subtitle into %1", QtPotatoSettings.targetLanguage)
icon.name: "translate"
shortcut: "Ctrl+T"
onTriggered: root.translateRequested()
}
property Action cancelTranslation: Action {
enabled: root.translator.busy
text: KI18n.i18nc("@action", "Cancel Translation")
icon.name: "dialog-cancel"
onTriggered: root.translator.cancel()
}
property Action screenshot: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Screenshot")
icon.name: "image-x-generic"
shortcut: "S"
onTriggered: {
root.mpv.screenshot()
root.osd.message(KI18n.i18nc("@info:osd", "Screenshot saved"))
}
}
property Action toggleDeinterlacing: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Toggle Deinterlacing")
shortcut: "D"
onTriggered: {
const enabled = root.mpv.getProperty("deinterlace") === "yes"
root.mpv.setPropertyAsync("deinterlace", enabled ? "no" : "yes")
root.osd.message(enabled ? KI18n.i18nc("@info:osd", "Deinterlacing off") : KI18n.i18nc("@info:osd", "Deinterlacing on"))
}
}
property Action zoomIn: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Zoom In")
icon.name: "zoom-in"
shortcut: StandardKey.ZoomIn
onTriggered: root.mpv.commandAsync(["add", "video-zoom", "0.1"])
}
property Action zoomOut: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Zoom Out")
icon.name: "zoom-out"
shortcut: StandardKey.ZoomOut
onTriggered: root.mpv.commandAsync(["add", "video-zoom", "-0.1"])
}
property Action zoomReset: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Reset Zoom")
icon.name: "zoom-original"
shortcut: "Ctrl+0"
onTriggered: root.mpv.setPropertyAsync("video-zoom", 0)
}
property Action rotateClockwise: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Rotate Clockwise")
icon.name: "object-rotate-right"
shortcut: "Ctrl+R"
onTriggered: root.mpv.setPropertyAsync("video-rotate", (root.mpv.getProperty("video-rotate") + 90) % 360)
}
property Action rotateCounterClockwise: Action {
enabled: root.shortcutsEnabled && root.hasFile
text: KI18n.i18nc("@action", "Rotate Counter Clockwise")
icon.name: "object-rotate-left"
shortcut: "Ctrl+E"
onTriggered: root.mpv.setPropertyAsync("video-rotate", (root.mpv.getProperty("video-rotate") + 270) % 360)
}
property Action toggleFullscreen: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Toggle Fullscreen")
icon.name: "view-fullscreen"
shortcut: "F"
onTriggered: root.fullscreenToggleRequested()
}
property Action exitFullscreen: Action {
// Only while full screen, so Esc still reaches everything else.
enabled: root.shortcutsEnabled && root.fullscreen
text: KI18n.i18nc("@action", "Exit Fullscreen")
shortcut: "Escape"
onTriggered: root.fullscreenExitRequested()
}
property Action togglePlaylist: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Playlist")
icon.name: "view-media-playlist"
shortcut: "P"
onTriggered: root.playlistToggleRequested()
}
property Action toggleMenuBar: Action {
enabled: root.shortcutsEnabled
text: QtPotatoSettings.showMenuBar ? KI18n.i18nc("@action", "Hide Menubar") : KI18n.i18nc("@action", "Show Menubar")
icon.name: "show-menu"
shortcut: "Ctrl+M"
onTriggered: root.menuBarToggleRequested()
}
property Action toggleHeader: Action {
enabled: root.shortcutsEnabled
text: QtPotatoSettings.showHeader ? KI18n.i18nc("@action", "Hide Toolbar") : KI18n.i18nc("@action", "Show Toolbar")
shortcut: "Ctrl+H"
onTriggered: root.headerToggleRequested()
}
property Action openContextMenu: Action {
enabled: root.shortcutsEnabled
text: KI18n.i18nc("@action", "Open Context Menu")
icon.name: "application-menu"
shortcut: "Menu"
onTriggered: root.contextMenuRequested()
}
property Action configure: Action {
text: KI18n.i18nc("@action", "Settings")
icon.name: "configure"
shortcut: "Ctrl+Shift+,"
onTriggered: root.settingsRequested("general")
}
property Action about: Action {
text: KI18n.i18nc("@action", "About QtPotato")
icon.name: "help-about"
shortcut: "F1"
onTriggered: root.settingsRequested("about")
}
property Action reportBug: Action {
text: KI18n.i18nc("@action", "Report Bug")
icon.name: "tools-report-bug"
onTriggered: Qt.openUrlExternally("https://github.com/vokhactri/QtPotato/issues")
}
}