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 }