Warning, /multimedia/elisa/src/qml/ContextView.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr> 0003 SPDX-FileCopyrightText: 2019 (c) Nate Graham <nate@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-3.0-or-later 0006 */ 0007 0008 import QtQuick 2.15 0009 import QtQuick.Window 2.2 0010 import QtQuick.Controls 2.15 0011 import QtQml.Models 2.2 0012 import QtQuick.Layouts 1.2 0013 import Qt5Compat.GraphicalEffects 0014 import org.kde.kirigami 2.12 as Kirigami 0015 import org.kde.elisa 1.0 0016 0017 Kirigami.Page { 0018 id: topItem 0019 0020 property int databaseId: 0 0021 property var trackType 0022 property string songTitle: "" 0023 property string albumName: "" 0024 property string artistName: "" 0025 property url albumArtUrl: "" 0026 property url fileUrl: "" 0027 property int albumId 0028 property string albumArtist: "" 0029 0030 signal openArtist() 0031 signal openAlbum() 0032 0033 readonly property bool nothingPlaying: albumName.length === 0 0034 && artistName.length === 0 0035 && albumArtUrl.toString().length === 0 0036 && songTitle.length === 0 0037 && fileUrl.toString().length === 0 0038 0039 title: i18nc("@title:window Title of the context view related to the currently playing track", "Now Playing") 0040 padding: 0 0041 0042 property bool isWidescreen: mainWindow.width >= elisaTheme.viewSelectorSmallSizeThreshold 0043 0044 onAlbumArtUrlChanged: { 0045 background.loadImage(); 0046 } 0047 0048 TrackContextMetaDataModel { 0049 id: metaDataModel 0050 onLyricsChanged: lyricsModel.setLyric(lyrics) 0051 manager: ElisaApplication.musicManager 0052 } 0053 0054 // Header with title and actions 0055 globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None 0056 header: ToolBar { 0057 implicitHeight: Math.round(Kirigami.Units.gridUnit * 2.5) 0058 0059 // Override color to use standard window colors, not header colors 0060 // TODO: remove this if the HeaderBar component is ever removed or moved 0061 // to the bottom of the window such that this toolbar touches the window 0062 // titlebar 0063 Kirigami.Theme.colorSet: Kirigami.Theme.Window 0064 0065 RowLayout { 0066 anchors.fill: parent 0067 spacing: Kirigami.Units.smallSpacing 0068 0069 FlatButtonWithToolTip { 0070 id: showSidebarButton 0071 objectName: 'showSidebarButton' 0072 visible: Kirigami.Settings.isMobile 0073 text: i18nc("@action:button", "Open sidebar") 0074 icon.name: "open-menu-symbolic" 0075 onClicked: mainWindow.globalDrawer.open() 0076 } 0077 0078 Kirigami.Heading { 0079 Layout.fillWidth: true 0080 Layout.leftMargin: Kirigami.Units.largeSpacing 0081 Layout.alignment: Qt.AlignVCenter 0082 0083 text: topItem.title 0084 } 0085 // Invisible; this exists purely to make the toolbar height match that 0086 // of the adjacent one 0087 ToolButton { 0088 icon.name: "edit-paste" 0089 opacity: 0 0090 } 0091 0092 ButtonGroup { 0093 id: nowPlayingButtons 0094 onCheckedButtonChanged: { 0095 persistentSettings.nowPlayingPreferLyric = nowPlayingButtons.checkedButton === showLyricButton 0096 } 0097 } 0098 FlatButtonWithToolTip { 0099 id: showMetaDataButton 0100 ButtonGroup.group: nowPlayingButtons 0101 0102 readonly property alias item: allMetaDataScroll 0103 0104 checkable: true 0105 checked: !persistentSettings.nowPlayingPreferLyric 0106 display: topItem.isWidescreen ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly 0107 icon.name: "documentinfo" 0108 text: i18nc("@option:radio One of the 'now playing' views", "Metadata") 0109 visible: !contentLayout.wideMode 0110 } 0111 FlatButtonWithToolTip { 0112 id: showLyricButton 0113 ButtonGroup.group: nowPlayingButtons 0114 0115 checkable: true 0116 checked: persistentSettings.nowPlayingPreferLyric 0117 display: topItem.isWidescreen ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly 0118 icon.name: "view-media-lyrics" 0119 text: i18nc("@option:radio One of the 'now playing' views", "Lyrics") 0120 visible: !contentLayout.wideMode 0121 } 0122 0123 FlatButtonWithToolTip { 0124 id: showPlaylistButton 0125 visible: Kirigami.Settings.isMobile 0126 text: i18nc("@action:button", "Show Playlist") 0127 icon.name: "view-media-playlist" 0128 display: topItem.isWidescreen ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly 0129 onClicked: { 0130 if (topItem.isWidescreen) { 0131 contentView.showPlaylist = !contentView.showPlaylist; 0132 } else { 0133 playlistDrawer.open(); 0134 } 0135 } 0136 } 0137 } 0138 } 0139 0140 Item { 0141 anchors.fill: parent 0142 0143 // Blurred album art background 0144 StackView { 0145 id: background 0146 anchors.fill: parent 0147 0148 readonly property bool active: ElisaApplication.showNowPlayingBackground && !topItem.nothingPlaying 0149 property Item pendingImage 0150 property bool doesSkipAnimation: true 0151 0152 layer.enabled: true 0153 opacity: 0.2 0154 layer.effect: FastBlur { 0155 radius: 40 0156 } 0157 0158 replaceEnter: Transition { 0159 OpacityAnimator { 0160 id: replaceEnterOpacityAnimator 0161 from: 0 0162 to: 1 0163 // 1 is HACK for https://bugreports.qt.io/browse/QTBUG-106797 to avoid flickering 0164 duration: background.doesSkipAnimation ? 1 : Kirigami.Units.longDuration 0165 } 0166 } 0167 // Keep the old image around till the new one is fully faded in 0168 // If we fade both at the same time you can see the background behind glimpse through 0169 replaceExit: Transition { 0170 PauseAnimation { 0171 duration: replaceEnterOpacityAnimator.duration 0172 } 0173 } 0174 0175 onActiveChanged: loadImage() 0176 0177 function loadImage() { 0178 if (pendingImage) { 0179 pendingImage.statusChanged.disconnect(replaceWhenLoaded); 0180 pendingImage.destroy(); 0181 pendingImage = null; 0182 } 0183 0184 if (!active) { 0185 clear(); 0186 return; 0187 } 0188 0189 doesSkipAnimation = currentItem == undefined; 0190 pendingImage = backgroundComponent.createObject(background, { 0191 "source": topItem.albumArtUrl.toString() === "" ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : topItem.albumArtUrl, 0192 "opacity": 0, 0193 }); 0194 0195 if (pendingImage.status === Image.Loading) { 0196 pendingImage.statusChanged.connect(background.replaceWhenLoaded); 0197 } else { 0198 background.replaceWhenLoaded(); 0199 } 0200 } 0201 0202 function replaceWhenLoaded() { 0203 pendingImage.statusChanged.disconnect(replaceWhenLoaded); 0204 replace(pendingImage, {}, StackView.Transition); 0205 pendingImage = null; 0206 } 0207 0208 Component.onCompleted: { 0209 loadImage(); 0210 } 0211 } 0212 0213 Component { 0214 id: backgroundComponent 0215 0216 Image { 0217 asynchronous: true 0218 fillMode: Image.PreserveAspectCrop 0219 0220 // HACK: set sourceSize to a fixed value to prevent background flickering (BUG431607) 0221 onStatusChanged: { 0222 if (status === Image.Ready && (sourceSize.width > Kirigami.Units.gridUnit * 50 || sourceSize.height > Kirigami.Units.gridUnit * 50)) { 0223 sourceSize = Qt.size(Kirigami.Units.gridUnit * 50, Kirigami.Units.gridUnit * 50); 0224 } 0225 } 0226 0227 StackView.onRemoved: { 0228 destroy(); 0229 } 0230 } 0231 } 0232 0233 RowLayout { 0234 id: contentLayout 0235 0236 property bool wideMode: allMetaDataLoader.width <= width * 0.5 0237 && allMetaDataLoader.height <= height 0238 0239 anchors.fill: parent 0240 visible: !topItem.nothingPlaying 0241 0242 spacing: 0 0243 0244 // Metadata 0245 ScrollView { 0246 id: allMetaDataScroll 0247 0248 implicitWidth: { 0249 if (contentLayout.wideMode) { 0250 return contentLayout.width * 0.5 0251 } else { 0252 return showMetaDataButton.checked ? contentLayout.width : 0 0253 } 0254 } 0255 0256 implicitHeight: Math.min(allMetaDataLoader.height, parent.height) 0257 0258 contentWidth: availableWidth 0259 contentHeight: allMetaDataLoader.height 0260 0261 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890 0262 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 0263 0264 Loader { 0265 id: allMetaDataLoader 0266 0267 sourceComponent: Kirigami.FormLayout { 0268 id: allMetaData 0269 property real margins: Kirigami.Units.largeSpacing + allMetaDataScroll.ScrollBar.vertical.width 0270 width: (implicitWidth + margins <= contentLayout.width * 0.5 ? contentLayout.width * 0.5 : contentLayout.width) - margins 0271 x: wideMode? (allMetaDataScroll.width - width) * 0.5 : Kirigami.Units.largeSpacing 0272 0273 Repeater { 0274 id: trackData 0275 model: metaDataModel 0276 0277 delegate: Item { 0278 Kirigami.FormData.label: "<b>" + model.name + ":</b>" 0279 implicitWidth: childrenRect.width 0280 implicitHeight: childrenRect.height 0281 0282 MediaTrackMetadataDelegate { 0283 maximumWidth: contentLayout.width - allMetaData.margins 0284 index: model.index 0285 name: model.name 0286 display: model.display 0287 type: model.type 0288 readOnly: true 0289 url: topItem.fileUrl 0290 } 0291 } 0292 } 0293 } 0294 0295 // We need unload Kirigami.FormLayout and recreate it 0296 // to avoid lots of warnings in the terminal 0297 Timer { 0298 id: resetTimer 0299 interval: 0 0300 onTriggered: { 0301 allMetaDataLoader.active = true 0302 } 0303 } 0304 Connections { 0305 target: metaDataModel 0306 function onModelAboutToBeReset() { 0307 allMetaDataLoader.active = false 0308 } 0309 function onModelReset() { 0310 resetTimer.restart() 0311 } 0312 } 0313 } 0314 } 0315 0316 // Lyrics 0317 ScrollView { 0318 id: lyricScroll 0319 implicitWidth: { 0320 if (contentLayout.wideMode) { 0321 return contentLayout.width * 0.5 0322 } else { 0323 return showLyricButton.checked ? contentLayout.width : 0 0324 } 0325 } 0326 implicitHeight: Math.min(lyricItem.height, parent.height) 0327 0328 contentWidth: availableWidth 0329 contentHeight: lyricItem.height 0330 0331 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890 0332 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 0333 PropertyAnimation { 0334 id: lyricScrollAnimation 0335 0336 // the target is a flickable 0337 target: lyricScroll.contentItem 0338 property: "contentY" 0339 onToChanged: restart() 0340 } 0341 0342 Item { 0343 id: lyricItem 0344 property real margins: Kirigami.Units.largeSpacing + lyricScroll.ScrollBar.vertical.width 0345 width: lyricScroll.width - margins 0346 height: lyricsView.count === 0 ? lyricPlaceholder.height : lyricsView.height 0347 x: Kirigami.Units.largeSpacing 0348 0349 ListView { 0350 id: lyricsView 0351 height: contentHeight 0352 width: parent.width 0353 model: lyricsModel 0354 delegate: Label { 0355 text: lyric 0356 width: lyricItem.width 0357 wrapMode: Text.WordWrap 0358 font.bold: ListView.isCurrentItem 0359 horizontalAlignment: contentLayout.wideMode? Text.AlignLeft : Text.AlignHCenter 0360 MouseArea { 0361 height: parent.height 0362 width: Math.min(parent.width, parent.contentWidth) 0363 x: contentLayout.wideMode ? 0 : (parent.width - width) / 2 0364 enabled: lyricsModel.isLRC 0365 cursorShape: enabled ? Qt.PointingHandCursor : undefined 0366 onClicked: { 0367 ElisaApplication.audioPlayer.position = timestamp; 0368 } 0369 } 0370 } 0371 currentIndex: lyricsModel.highlightedIndex 0372 onCurrentIndexChanged: { 0373 if (currentIndex === -1) 0374 return 0375 0376 // center aligned 0377 var toPos = Math.round(currentItem.y + currentItem.height * 0.5 - lyricScroll.height * 0.5) 0378 // make sure the first and the last lines are always 0379 // positioned at the beginning and the end of the view 0380 0381 toPos = Math.max(toPos, 0) 0382 toPos = Math.min(toPos, contentHeight - lyricScroll.height) 0383 lyricScrollAnimation.to = toPos 0384 0385 } 0386 } 0387 0388 LyricsModel { 0389 id: lyricsModel 0390 } 0391 Connections { 0392 target: ElisaApplication.audioPlayer 0393 function onPositionChanged(position) { 0394 lyricsModel.setPosition(position) 0395 } 0396 } 0397 0398 Loader { 0399 id: lyricPlaceholder 0400 anchors.centerIn: parent 0401 width: parent.width 0402 0403 active: lyricsView.count === 0 0404 visible: active && status === Loader.Ready 0405 0406 sourceComponent: Kirigami.PlaceholderMessage { 0407 text: i18nc("@info:placeholder", "No lyrics found") 0408 icon.name: "view-media-lyrics" 0409 } 0410 } 0411 } 0412 } 0413 } 0414 0415 // "Nothing Playing" message 0416 Loader { 0417 anchors.centerIn: parent 0418 width: parent.width - (Kirigami.Units.largeSpacing * 4) 0419 0420 active: topItem.nothingPlaying 0421 visible: active && status === Loader.Ready 0422 0423 sourceComponent: Kirigami.PlaceholderMessage { 0424 text: i18nc("@info:placeholder", "Nothing playing") 0425 icon.name: "view-media-track" 0426 } 0427 } 0428 } 0429 0430 // Footer with file path label 0431 footer: ToolBar { 0432 implicitHeight: Math.round(Kirigami.Units.gridUnit * 2) 0433 visible: !topItem.nothingPlaying 0434 0435 RowLayout { 0436 anchors.fill: parent 0437 spacing: Kirigami.Units.smallSpacing 0438 0439 LabelWithToolTip { 0440 id: fileUrlLabel 0441 text: metaDataModel.fileUrl 0442 elide: Text.ElideLeft 0443 Layout.fillWidth: true 0444 } 0445 0446 Kirigami.ActionToolBar { 0447 // because fillWidth is true by default 0448 Layout.fillWidth: false 0449 0450 // when there is not enough space, show the button in the compact mode 0451 // then the file url will be elided if needed 0452 Layout.preferredWidth: parent.width > fileUrlLabel.implicitWidth + spacing + maximumContentWidth ? maximumContentWidth : Kirigami.Units.gridUnit * 2 0453 0454 Layout.fillHeight: true 0455 0456 actions: [ 0457 Kirigami.Action { 0458 text: i18nc("@action:button", "Show In Folder") 0459 icon.name: 'document-open-folder' 0460 visible: metaDataModel.fileUrl.toString() !== "" && !metaDataModel.fileUrl.toString().startsWith("http") && !metaDataModel.fileUrl.toString().startsWith("rtsp") 0461 onTriggered: { 0462 ElisaApplication.showInFolder(metaDataModel.fileUrl) 0463 } 0464 } 0465 ] 0466 } 0467 } 0468 } 0469 0470 onFileUrlChanged: { 0471 if (ElisaApplication.musicManager && trackType !== undefined && fileUrl.toString().length !== 0) { 0472 if (databaseId !== 0) { 0473 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0474 } else { 0475 metaDataModel.initializeByUrl(trackType, fileUrl) 0476 } 0477 } 0478 } 0479 0480 onTrackTypeChanged: { 0481 if (ElisaApplication.musicManager && trackType !== undefined && fileUrl.toString().length !== 0) { 0482 if (databaseId !== 0) { 0483 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0484 } else { 0485 metaDataModel.initializeByUrl(trackType, fileUrl) 0486 } 0487 } 0488 } 0489 0490 Connections { 0491 target: ElisaApplication 0492 0493 function onMusicManagerChanged() { 0494 if (ElisaApplication.musicManager && trackType !== undefined && databaseId !== 0) { 0495 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0496 } 0497 } 0498 } 0499 0500 Component.onCompleted: { 0501 if (ElisaApplication.musicManager && trackType !== undefined) { 0502 if (databaseId !== 0) { 0503 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0504 } else { 0505 metaDataModel.initializeByUrl(trackType, fileUrl) 0506 } 0507 } 0508 } 0509 }