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 }