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 }