Warning, /multimedia/elisa/src/qml/ElisaMainWindow.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr> 0003 0004 SPDX-License-Identifier: LGPL-3.0-or-later 0005 */ 0006 0007 import QtQuick 2.15 0008 import QtQuick.Controls 2.13 0009 import QtQuick.Layouts 1.1 0010 import QtQuick.Window 2.2 0011 import org.kde.kirigami 2.5 as Kirigami 0012 import org.kde.elisa 1.0 0013 import Qt.labs.settings 1.0 0014 import Qt.labs.platform 1.1 0015 0016 import "mobile" 0017 0018 Kirigami.ApplicationWindow { 0019 id: mainWindow 0020 0021 visible: true 0022 0023 Connections { 0024 target: ElisaApplication.mediaPlayListProxyModel 0025 function onPlayListLoadFailed() { 0026 showPassiveNotification(i18nc("@label", "Loading failed"), 7000, i18nc("@action:button", "Retry"), () => loadPlaylistButton.clicked()) 0027 } 0028 0029 function onDisplayUndoNotification() { 0030 showPassiveNotification(i18nc("@label", "Playlist cleared"), 7000, i18nc("@action:button", "Undo"), () => ElisaApplication.mediaPlayListProxyModel.undoClearPlayList()) 0031 } 0032 } 0033 0034 contextDrawer: Kirigami.ContextDrawer { 0035 id: playlistDrawer 0036 handleClosedIcon.source: "view-media-playlist" 0037 handleOpenIcon.source: "view-right-close" 0038 0039 handleVisible: !Kirigami.Settings.isMobile && (drawerOpen || mainWindow.spaceForPlayListIconInHeader) 0040 0041 // Don't allow dragging it on non-mobile as the UX is not so great; see 0042 // https://bugs.kde.org/show_bug.cgi?id=468211 and 0043 // https://bugs.kde.org/show_bug.cgi?id=478121 0044 interactive: Kirigami.Settings.isMobile 0045 0046 // without this drawer button is never shown 0047 enabled: true 0048 MediaPlayListView { 0049 anchors.fill: parent 0050 } 0051 0052 StateGroup { 0053 states: [ 0054 State { 0055 name: "inactive" 0056 when: mainWindow.isWideScreen && !mainWindow.inPartyMode 0057 PropertyChanges { 0058 target: playlistDrawer 0059 collapsed: true 0060 visible: false 0061 drawerOpen: false 0062 handleVisible: false 0063 } 0064 } 0065 ] 0066 } 0067 } 0068 0069 DropArea { 0070 anchors.fill: parent 0071 onDropped: { 0072 if (drop.hasUrls) { 0073 if (!ElisaApplication.openFiles(drop.urls)) { 0074 showPassiveNotification(i18nc("@info:status", "Could not load some files. Elisa can only open audio and playlist files."), 7000, "", function() {}) 0075 } 0076 } 0077 } 0078 } 0079 0080 // HACK: since elisa's main view hasn't been ported to a page, but page layers are used for mobile settings 0081 // lower the main view and mobile footer's z to be behind the layer when there are layers added (normally it is in front) 0082 property bool layerOnTop: pageStack.layers.depth > 1 0083 0084 // disable certain transitions at startup 0085 property bool transitionsEnabled: false 0086 Timer { 0087 interval: 10 0088 running: true 0089 repeat: false 0090 onTriggered: { transitionsEnabled = true } 0091 } 0092 0093 minimumWidth: Kirigami.Units.gridUnit * (Kirigami.Settings.isMobile ? 17 : 34) 0094 property int minHeight: Kirigami.Units.gridUnit * 17 0095 0096 property string previousStateBeforeFullScreen: "windowed" 0097 function restorePreviousStateBeforeFullScreen() { 0098 if (previousStateBeforeFullScreen === "windowed") { 0099 showNormal() 0100 } else if (previousStateBeforeFullScreen === "maximized") { 0101 // Need to make it windowed before showMaximized will work, apparently 0102 showNormal() 0103 showMaximized() 0104 } else if (previousStateBeforeFullScreen === "minimized") { 0105 showMinimized() 0106 } else if (previousStateBeforeFullScreen === "hidden") { 0107 show() 0108 } else if (previousStateBeforeFullScreen === "automatic") { 0109 show() 0110 } 0111 } 0112 function goFullScreen() { 0113 if (visibility === Window.Windowed) { 0114 previousStateBeforeFullScreen = "windowed" 0115 } else if (visibility === Window.Maximized) { 0116 previousStateBeforeFullScreen = "maximized" 0117 } else if (visibility === Window.Minimized) { 0118 previousStateBeforeFullScreen = "minimized" 0119 } else if (visibility === Window.Hidden) { 0120 previousStateBeforeFullScreen = "hidden" 0121 } else if (visibility === Window.Automatic){ 0122 previousStateBeforeFullScreen = "automatic" 0123 } 0124 showFullScreen() 0125 } 0126 0127 LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft 0128 LayoutMirroring.childrenInherit: true 0129 0130 x: persistentSettings.x 0131 y: persistentSettings.y 0132 width: persistentSettings.width 0133 height: persistentSettings.height 0134 0135 title: ElisaApplication.manageHeaderBar.title ? i18nc("@title:window", "%1 — Elisa", ElisaApplication.manageHeaderBar.title) : i18nc("@title:window", "Elisa") 0136 0137 readonly property int initialViewIndex: 3 0138 0139 readonly property var goBackAction: ElisaApplication.action("go_back") 0140 readonly property var seekAction: ElisaApplication.action("Seek") 0141 readonly property var scrubAction: ElisaApplication.action("Scrub") 0142 readonly property var nextTrackAction : ElisaApplication.action("NextTrack") 0143 readonly property var previousTrackAction: ElisaApplication.action("PreviousTrack") 0144 readonly property var playPauseAction: ElisaApplication.action("Play-Pause") 0145 readonly property var findAction: ElisaApplication.action("edit_find") 0146 readonly property var togglePartyModeAction: ElisaApplication.action("togglePartyMode") 0147 0148 readonly property var mediaPlayerControl: Kirigami.Settings.isMobile ? mobileFooterBarLoader.item : headerBarLoader.item 0149 readonly property alias fileDialog: fileDialog 0150 0151 readonly property bool inPartyMode: headerBarLoader.item?.isMaximized ?? false 0152 readonly property bool isWideScreen: mainWindow.width >= elisaTheme.viewSelectorSmallSizeThreshold 0153 readonly property bool spaceForPlayListIconInHeader: headerBarLoader.active && headerBarLoader.height > elisaTheme.mediaPlayerControlHeight * 2 0154 0155 function toggleDrawer() { 0156 contentView.showPlaylist = !contentView.showPlaylist 0157 } 0158 0159 Action { 0160 shortcut: ElisaApplication.actionShortcut(goBackAction) 0161 onTriggered: contentView.goBack() 0162 } 0163 0164 Action { 0165 shortcut: ElisaApplication.actionShortcut(seekAction) 0166 onTriggered: ElisaApplication.audioControl.seek(mediaPlayerControl.playerControl.position + 10000) 0167 } 0168 0169 Action { 0170 shortcut: ElisaApplication.actionShortcut(scrubAction) 0171 onTriggered: ElisaApplication.audioControl.seek(mediaPlayerControl.playerControl.position - 10000) 0172 } 0173 0174 Action { 0175 shortcut: ElisaApplication.actionShortcut(nextTrackAction) 0176 onTriggered: ElisaApplication.mediaPlayListProxyModel.skipNextTrack(ElisaApplication.audioPlayer.position) 0177 } 0178 0179 Action { 0180 shortcut: ElisaApplication.actionShortcut(previousTrackAction) 0181 onTriggered: ElisaApplication.mediaPlayListProxyModel.skipPreviousTrack(ElisaApplication.audioPlayer.position) 0182 } 0183 0184 Action { 0185 shortcut: ElisaApplication.actionShortcut(playPauseAction) 0186 onTriggered: ElisaApplication.audioControl.playPause() 0187 } 0188 0189 Action { 0190 shortcut: ElisaApplication.actionShortcut(findAction) 0191 onTriggered: persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView 0192 } 0193 0194 Action { 0195 shortcut: ElisaApplication.actionShortcut(togglePartyModeAction) 0196 onTriggered: mediaPlayerControl.isMaximized = !mediaPlayerControl.isMaximized 0197 } 0198 0199 SystemPalette { 0200 id: myPalette 0201 colorGroup: SystemPalette.Active 0202 } 0203 0204 Theme { 0205 id: elisaTheme 0206 } 0207 0208 FileDialog { 0209 id: fileDialog 0210 0211 function savePlaylist() { 0212 fileDialog.nameFilters = [i18nc("@option file type (mime type) for m3u, m3u8 playlist file formats; do not translate *.m3u*", "m3u8, m3u Playlist File (*.m3u*)"), i18nc("@option file type (mime type) for pls playlist file formats; do not translate *.pls", "pls Playlist File (*.pls)")] 0213 fileDialog.defaultSuffix = 'm3u8' 0214 fileDialog.fileMode = FileDialog.SaveFile 0215 fileDialog.file = '' 0216 fileDialog.open() 0217 } 0218 function loadPlaylist() { 0219 fileDialog.nameFilters = [i18nc("@option file type (mime type) for m3u, m3u8 and pls playlist file formats; do not translate *.m3u8 *.m3u *.pls", "m3u8, m3u, pls Playlist File (*.m3u8 *.m3u *.pls)")] 0220 fileDialog.fileMode = FileDialog.OpenFile 0221 fileDialog.file = '' 0222 fileDialog.open() 0223 } 0224 0225 folder: StandardPaths.writableLocation(StandardPaths.MusicLocation) 0226 0227 onAccepted: { 0228 if (fileMode === FileDialog.SaveFile) { 0229 if (!ElisaApplication.mediaPlayListProxyModel.savePlayList(fileDialog.file)) { 0230 showPassiveNotification(i18nc("@label", "Saving failed"), 7000, i18nc("@action:button", "Retry"), () => savePlaylistButton.clicked()) 0231 } 0232 } else { 0233 ElisaApplication.mediaPlayListProxyModel.loadPlayList(fileDialog.file) 0234 } 0235 } 0236 } 0237 0238 Settings { 0239 id: persistentSettings 0240 0241 property int x 0242 property int y 0243 property int width: Kirigami.Units.gridUnit * 50 0244 property int height: Kirigami.Units.gridUnit * 36 0245 0246 property var playListState 0247 0248 property var audioPlayerState 0249 0250 property double playControlItemVolume : 100.0 0251 property bool playControlItemMuted : false 0252 0253 property bool expandedFilterView: false 0254 0255 property bool showPlaylist: true 0256 0257 property bool headerBarIsMaximized: false 0258 property real headerBarHeight : 100000.0 0259 0260 property bool nowPlayingPreferLyric: false 0261 } 0262 0263 Connections { 0264 target: Qt.application 0265 function onAboutToQuit() { 0266 persistentSettings.x = mainWindow.x; 0267 persistentSettings.y = mainWindow.y; 0268 persistentSettings.width = mainWindow.width; 0269 persistentSettings.height = mainWindow.height; 0270 0271 persistentSettings.playListState = ElisaApplication.mediaPlayListProxyModel.persistentState; 0272 persistentSettings.audioPlayerState = ElisaApplication.audioControl.persistentState 0273 0274 persistentSettings.playControlItemVolume = mediaPlayerControl.playerControl.volume 0275 persistentSettings.playControlItemMuted = mediaPlayerControl.playerControl.muted 0276 0277 persistentSettings.showPlaylist = contentView.showPlaylist 0278 0279 if (Kirigami.Settings.isMobile) { 0280 persistentSettings.headerBarIsMaximized = mobileFooterBarLoader.item.isMaximized 0281 } else { 0282 persistentSettings.headerBarIsMaximized = headerBarLoader.item.isMaximized 0283 } 0284 } 0285 } 0286 0287 Loader { 0288 id: mprisloader 0289 active: false 0290 0291 sourceComponent: PlatformIntegration { 0292 id: platformInterface 0293 0294 playListModel: ElisaApplication.mediaPlayListProxyModel 0295 audioPlayerManager: ElisaApplication.audioControl 0296 player: ElisaApplication.audioPlayer 0297 headerBarManager: ElisaApplication.manageHeaderBar 0298 manageMediaPlayerControl: ElisaApplication.playerControl 0299 showProgressOnTaskBar: ElisaApplication.showProgressOnTaskBar 0300 showSystemTrayIcon: ElisaApplication.showSystemTrayIcon 0301 elisaMainWindow: mainWindow 0302 0303 onRaisePlayer: function() { 0304 mainWindow.visible = true 0305 mainWindow.raise() 0306 mainWindow.requestActivate() 0307 } 0308 } 0309 } 0310 0311 Connections { 0312 target: ElisaApplication.audioPlayer 0313 function onVolumeChanged() { 0314 if (mediaPlayerControl !== null) { 0315 mediaPlayerControl.playerControl.volume = ElisaApplication.audioPlayer.volume 0316 } 0317 } 0318 function onMutedChanged() { 0319 if (mediaPlayerControl !== null) { 0320 mediaPlayerControl.playerControl.muted = ElisaApplication.audioPlayer.muted 0321 } 0322 } 0323 } 0324 0325 // mobile footer bar 0326 Loader { 0327 id: mobileFooterBarLoader 0328 anchors.fill: parent 0329 0330 active: Kirigami.Settings.isMobile 0331 visible: active 0332 0333 // footer bar fills the whole page, so only be in front of the main view when it is opened 0334 // otherwise, it captures all mouse/touch events on the main view 0335 z: (!item || item.contentY === 0) ? (mainWindow.layerOnTop ? -1 : 0) : 999 0336 0337 sourceComponent: MobileFooterBar { 0338 id: mobileFooterBar 0339 contentHeight: mainWindow.height * 2 0340 0341 focus: true 0342 0343 album: (ElisaApplication.manageHeaderBar.album !== undefined ? ElisaApplication.manageHeaderBar.album : '') 0344 title: ElisaApplication.manageHeaderBar.title 0345 artist: (ElisaApplication.manageHeaderBar.artist !== undefined ? ElisaApplication.manageHeaderBar.artist : '') 0346 albumArtist: (ElisaApplication.manageHeaderBar.albumArtist !== undefined ? ElisaApplication.manageHeaderBar.albumArtist : '') 0347 image: ElisaApplication.manageHeaderBar.image 0348 albumID: ElisaApplication.manageHeaderBar.albumId 0349 0350 ratingVisible: false 0351 0352 // since we have multiple volume bars, and only one is linked directly to audio, sync the other one (trackControl) 0353 Binding on playerControl.volume { 0354 when: mobileFooterBar.trackControl.volumeSlider.moved 0355 value: mobileFooterBar.trackControl.volume 0356 } 0357 Component.onCompleted: { 0358 trackControl.volume = Qt.binding(() => mobileFooterBar.playerControl.volume); 0359 } 0360 0361 onOpenArtist: { contentView.openArtist(artist) } 0362 onOpenNowPlaying: { contentView.openNowPlaying() } 0363 onOpenAlbum: { contentView.openAlbum(album, albumArtist, image, albumID) } 0364 } 0365 } 0366 0367 Rectangle { 0368 id: mainContent 0369 0370 visible: !mainWindow.layerOnTop 0371 0372 color: myPalette.base 0373 anchors.fill: parent 0374 anchors.bottomMargin: Kirigami.Settings.isMobile ? elisaTheme.mediaPlayerControlHeight : 0 0375 0376 0377 ColumnLayout { 0378 anchors.fill: parent 0379 spacing: 0 0380 0381 // desktop header bar 0382 Loader { 0383 id: headerBarLoader 0384 active: !Kirigami.Settings.isMobile 0385 visible: active 0386 0387 Layout.minimumHeight: persistentSettings.isMaximized ? Layout.maximumHeight : elisaTheme.mediaPlayerControlHeight 0388 Layout.maximumHeight: persistentSettings.isMaximized ? Layout.maximumHeight : Math.round(mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight) 0389 Layout.fillWidth: true 0390 Layout.preferredHeight: status === Loader.Ready ? item.handlePosition : normalHeight 0391 0392 // height when HeaderBar is not maximized 0393 property int normalHeight : persistentSettings.headerBarHeight 0394 0395 Component.onDestruction: { 0396 // saving height in onAboutToQuit() leads to invalid values, so we do it here 0397 if (!Kirigami.Settings.isMobile) { 0398 if (headerBarLoader.item.isMaximized) { 0399 persistentSettings.headerBarHeight = normalHeight 0400 } else { 0401 persistentSettings.headerBarHeight = Layout.preferredHeight 0402 } 0403 } 0404 } 0405 0406 sourceComponent: HeaderBar { 0407 id: headerBar 0408 0409 focus: true 0410 transitionsEnabled: mainWindow.transitionsEnabled 0411 0412 album: (ElisaApplication.manageHeaderBar.album !== undefined ? ElisaApplication.manageHeaderBar.album : '') 0413 title: ElisaApplication.manageHeaderBar.title 0414 artist: (ElisaApplication.manageHeaderBar.artist !== undefined ? ElisaApplication.manageHeaderBar.artist : '') 0415 albumArtist: (ElisaApplication.manageHeaderBar.albumArtist !== undefined ? ElisaApplication.manageHeaderBar.albumArtist : '') 0416 image: ElisaApplication.manageHeaderBar.image 0417 albumID: ElisaApplication.manageHeaderBar.albumId 0418 handlePosition: persistentSettings.headerBarHeight 0419 0420 ratingVisible: false 0421 0422 playerControl.isMaximized: persistentSettings.headerBarIsMaximized 0423 onOpenArtist: contentView.openArtist(artist) 0424 onOpenNowPlaying: contentView.openNowPlaying() 0425 onOpenAlbum: contentView.openAlbum(album, albumArtist, image, albumID) 0426 0427 // animations 0428 StateGroup { 0429 id: mainWindowState 0430 states: [ 0431 State { 0432 name: "headerBarIsNormal" 0433 when: !headerBar.isMaximized 0434 changes: [ 0435 PropertyChanges { 0436 target: mainWindow 0437 minimumHeight: mainWindow.minHeight * 1.5 0438 explicit: true 0439 }, 0440 PropertyChanges { 0441 target: headerBarLoader 0442 Layout.preferredHeight: headerBar.handlePosition 0443 } 0444 ] 0445 }, 0446 State { 0447 name: "headerBarIsMaximized" 0448 // Workaround: only do this when transitions are enabled, or the playlist layout will be messed up 0449 when: headerBar.isMaximized && transitionsEnabled 0450 changes: [ 0451 PropertyChanges { 0452 target: mainWindow 0453 minimumHeight: mainWindow.minHeight 0454 explicit: true 0455 }, 0456 PropertyChanges { 0457 target: headerBarLoader 0458 Layout.minimumHeight: mainWindow.height 0459 Layout.maximumHeight: mainWindow.height 0460 Layout.preferredHeight: Layout.maximumHeight 0461 }, 0462 StateChangeScript { 0463 script: headerBarLoader.normalHeight = headerBarLoader.height 0464 } 0465 ] 0466 } 0467 ] 0468 transitions: Transition { 0469 enabled: mainWindow.transitionsEnabled 0470 NumberAnimation { 0471 properties: "Layout.minimumHeight, Layout.maximumHeight, minimumHeight" 0472 easing.type: Easing.InOutQuad 0473 duration: Kirigami.Units.longDuration 0474 } 0475 } 0476 } 0477 } 0478 } 0479 0480 ContentView { 0481 id: contentView 0482 Layout.fillHeight: true 0483 Layout.fillWidth: true 0484 showPlaylist: persistentSettings.showPlaylist 0485 showExpandedFilterView: persistentSettings.expandedFilterView 0486 playlistDrawer: playlistDrawer 0487 initialIndex: ElisaApplication.initialViewIndex 0488 } 0489 0490 FooterBar { 0491 Layout.fillWidth: true 0492 } 0493 } 0494 } 0495 0496 // capture mouse events behind flickable when it is open 0497 MouseArea { 0498 visible: Kirigami.Settings.isMobile && mobileFooterBarLoader.item.contentY !== 0 // only capture when the mobile footer panel is open 0499 anchors.fill: mobileFooterBarLoader 0500 preventStealing: true 0501 onClicked: mouse => mouse.accepted = true 0502 } 0503 0504 Component.onCompleted: 0505 { 0506 ElisaApplication.initialize() 0507 ElisaApplication.activateColorScheme(ElisaConfigurationDialog.colorScheme) 0508 0509 if (persistentSettings.playListState) { 0510 ElisaApplication.mediaPlayListProxyModel.persistentState = persistentSettings.playListState 0511 } 0512 0513 if (persistentSettings.audioPlayerState) { 0514 ElisaApplication.audioControl.persistentState = persistentSettings.audioPlayerState 0515 } 0516 0517 // it seems the header/footer bars load before settings are loaded, so we need to set their settings here 0518 mediaPlayerControl.playerControl.volume = persistentSettings.playControlItemVolume; 0519 mediaPlayerControl.playerControl.muted = persistentSettings.playControlItemMuted; 0520 0521 ElisaApplication.audioPlayer.muted = Qt.binding(() => mediaPlayerControl.playerControl.muted); 0522 ElisaApplication.audioPlayer.volume = Qt.binding(() => mediaPlayerControl.playerControl.volume); 0523 0524 mprisloader.active = true 0525 0526 if (!ElisaApplication.openFiles(elisaStartupArguments)) { 0527 showPassiveNotification(i18nc("@info:status", "Could not load some files. Elisa can only open audio and playlist files."), 7000, "", function() {}) 0528 } 0529 0530 // use global drawer on mobile 0531 if (Kirigami.Settings.isMobile) { 0532 globalDrawer = contentView.sidebar; 0533 } 0534 } 0535 }