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