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 focusPolicy: Qt.NoFocus 0091 } 0092 0093 ButtonGroup { 0094 id: nowPlayingButtons 0095 onCheckedButtonChanged: { 0096 persistentSettings.nowPlayingPreferLyric = nowPlayingButtons.checkedButton === showLyricButton 0097 } 0098 } 0099 FlatButtonWithToolTip { 0100 id: showMetaDataButton 0101 ButtonGroup.group: nowPlayingButtons 0102 0103 readonly property alias item: allMetaDataScroll 0104 0105 checkable: true 0106 checked: !persistentSettings.nowPlayingPreferLyric 0107 display: topItem.isWidescreen ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly 0108 icon.name: "documentinfo" 0109 text: i18nc("@option:radio One of the 'now playing' views", "Metadata") 0110 visible: !contentLayout.wideMode 0111 } 0112 FlatButtonWithToolTip { 0113 id: showLyricButton 0114 ButtonGroup.group: nowPlayingButtons 0115 0116 checkable: true 0117 checked: persistentSettings.nowPlayingPreferLyric 0118 display: topItem.isWidescreen ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly 0119 icon.name: "view-media-lyrics" 0120 text: i18nc("@option:radio One of the 'now playing' views", "Lyrics") 0121 visible: !contentLayout.wideMode 0122 } 0123 0124 FlatButtonWithToolTip { 0125 id: showPlaylistButton 0126 visible: Kirigami.Settings.isMobile 0127 text: i18nc("@action:button", "Show Playlist") 0128 icon.name: "view-media-playlist" 0129 display: topItem.isWidescreen ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly 0130 onClicked: { 0131 if (topItem.isWidescreen) { 0132 contentView.showPlaylist = !contentView.showPlaylist; 0133 } else { 0134 playlistDrawer.open(); 0135 } 0136 } 0137 } 0138 } 0139 } 0140 0141 Item { 0142 anchors.fill: parent 0143 0144 // Blurred album art background 0145 StackView { 0146 id: background 0147 anchors.fill: parent 0148 0149 readonly property bool active: ElisaApplication.showNowPlayingBackground && !topItem.nothingPlaying 0150 property Item pendingImage 0151 property bool doesSkipAnimation: true 0152 0153 layer.enabled: true 0154 opacity: 0.2 0155 layer.effect: FastBlur { 0156 radius: 40 0157 } 0158 0159 replaceEnter: Transition { 0160 OpacityAnimator { 0161 id: replaceEnterOpacityAnimator 0162 from: 0 0163 to: 1 0164 // 1 is HACK for https://bugreports.qt.io/browse/QTBUG-106797 to avoid flickering 0165 duration: background.doesSkipAnimation ? 1 : Kirigami.Units.longDuration 0166 } 0167 } 0168 // Keep the old image around till the new one is fully faded in 0169 // If we fade both at the same time you can see the background behind glimpse through 0170 replaceExit: Transition { 0171 PauseAnimation { 0172 duration: replaceEnterOpacityAnimator.duration 0173 } 0174 } 0175 0176 onActiveChanged: loadImage() 0177 0178 function loadImage() { 0179 if (pendingImage) { 0180 pendingImage.statusChanged.disconnect(replaceWhenLoaded); 0181 pendingImage.destroy(); 0182 pendingImage = null; 0183 } 0184 0185 if (!active) { 0186 clear(); 0187 return; 0188 } 0189 0190 doesSkipAnimation = currentItem == undefined; 0191 pendingImage = backgroundComponent.createObject(background, { 0192 "source": topItem.albumArtUrl.toString() === "" ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : topItem.albumArtUrl, 0193 "opacity": 0, 0194 }); 0195 0196 if (pendingImage.status === Image.Loading) { 0197 pendingImage.statusChanged.connect(background.replaceWhenLoaded); 0198 } else { 0199 background.replaceWhenLoaded(); 0200 } 0201 } 0202 0203 function replaceWhenLoaded() { 0204 pendingImage.statusChanged.disconnect(replaceWhenLoaded); 0205 replace(pendingImage, {}, StackView.Transition); 0206 pendingImage = null; 0207 } 0208 0209 Component.onCompleted: { 0210 loadImage(); 0211 } 0212 } 0213 0214 Component { 0215 id: backgroundComponent 0216 0217 Image { 0218 asynchronous: true 0219 fillMode: Image.PreserveAspectCrop 0220 0221 // HACK: set sourceSize to a fixed value to prevent background flickering (BUG431607) 0222 onStatusChanged: { 0223 if (status === Image.Ready && (sourceSize.width > Kirigami.Units.gridUnit * 50 || sourceSize.height > Kirigami.Units.gridUnit * 50)) { 0224 sourceSize = Qt.size(Kirigami.Units.gridUnit * 50, Kirigami.Units.gridUnit * 50); 0225 } 0226 } 0227 0228 StackView.onRemoved: { 0229 destroy(); 0230 } 0231 } 0232 } 0233 0234 RowLayout { 0235 id: contentLayout 0236 0237 property bool wideMode: allMetaDataLoader.width <= width * 0.5 0238 && allMetaDataLoader.height <= height 0239 0240 anchors.fill: parent 0241 visible: !topItem.nothingPlaying 0242 0243 spacing: 0 0244 0245 // Metadata 0246 ScrollView { 0247 id: allMetaDataScroll 0248 0249 implicitWidth: { 0250 if (contentLayout.wideMode) { 0251 return contentLayout.width * 0.5 0252 } else { 0253 return showMetaDataButton.checked ? contentLayout.width : 0 0254 } 0255 } 0256 0257 implicitHeight: Math.min(allMetaDataLoader.height, parent.height) 0258 0259 contentWidth: availableWidth 0260 contentHeight: allMetaDataLoader.height 0261 0262 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890 0263 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 0264 0265 Loader { 0266 id: allMetaDataLoader 0267 0268 sourceComponent: Kirigami.FormLayout { 0269 id: allMetaData 0270 property real margins: Kirigami.Units.largeSpacing + allMetaDataScroll.ScrollBar.vertical.width 0271 width: (implicitWidth + margins <= contentLayout.width * 0.5 ? contentLayout.width * 0.5 : contentLayout.width) - margins 0272 x: wideMode? (allMetaDataScroll.width - width) * 0.5 : Kirigami.Units.largeSpacing 0273 0274 Repeater { 0275 id: trackData 0276 model: metaDataModel 0277 0278 delegate: Item { 0279 Kirigami.FormData.label: "<b>" + model.name + ":</b>" 0280 implicitWidth: childrenRect.width 0281 implicitHeight: childrenRect.height 0282 0283 MediaTrackMetadataDelegate { 0284 maximumWidth: contentLayout.width - allMetaData.margins 0285 index: model.index 0286 name: model.name 0287 display: model.display 0288 type: model.type 0289 readOnly: true 0290 url: topItem.fileUrl 0291 } 0292 } 0293 } 0294 } 0295 0296 // We need unload Kirigami.FormLayout and recreate it 0297 // to avoid lots of warnings in the terminal 0298 Timer { 0299 id: resetTimer 0300 interval: 0 0301 onTriggered: { 0302 allMetaDataLoader.active = true 0303 } 0304 } 0305 Connections { 0306 target: metaDataModel 0307 function onModelAboutToBeReset() { 0308 allMetaDataLoader.active = false 0309 } 0310 function onModelReset() { 0311 resetTimer.restart() 0312 } 0313 } 0314 } 0315 } 0316 0317 // Lyrics 0318 ScrollView { 0319 id: lyricScroll 0320 implicitWidth: { 0321 if (contentLayout.wideMode) { 0322 return contentLayout.width * 0.5 0323 } else { 0324 return showLyricButton.checked ? contentLayout.width : 0 0325 } 0326 } 0327 implicitHeight: Math.min(lyricItem.height, parent.height) 0328 0329 contentWidth: availableWidth 0330 contentHeight: lyricItem.height 0331 0332 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890 0333 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 0334 PropertyAnimation { 0335 id: lyricScrollAnimation 0336 0337 // the target is a flickable 0338 target: lyricScroll.contentItem 0339 property: "contentY" 0340 onToChanged: restart() 0341 } 0342 0343 Item { 0344 id: lyricItem 0345 property real margins: Kirigami.Units.largeSpacing + lyricScroll.ScrollBar.vertical.width 0346 width: lyricScroll.width - margins 0347 height: lyricsView.count === 0 ? lyricPlaceholder.height : lyricsView.height 0348 x: Kirigami.Units.largeSpacing 0349 0350 ListView { 0351 id: lyricsView 0352 height: contentHeight 0353 width: parent.width 0354 model: lyricsModel 0355 delegate: Label { 0356 text: lyric 0357 width: lyricItem.width 0358 wrapMode: Text.WordWrap 0359 font.bold: ListView.isCurrentItem 0360 horizontalAlignment: contentLayout.wideMode? Text.AlignLeft : Text.AlignHCenter 0361 MouseArea { 0362 height: parent.height 0363 width: Math.min(parent.width, parent.contentWidth) 0364 x: contentLayout.wideMode ? 0 : (parent.width - width) / 2 0365 enabled: lyricsModel.isLRC 0366 cursorShape: enabled ? Qt.PointingHandCursor : undefined 0367 onClicked: { 0368 ElisaApplication.audioPlayer.position = timestamp; 0369 } 0370 } 0371 } 0372 currentIndex: lyricsModel.highlightedIndex 0373 onCurrentIndexChanged: { 0374 if (currentIndex === -1) 0375 return 0376 0377 // center aligned 0378 var toPos = Math.round(currentItem.y + currentItem.height * 0.5 - lyricScroll.height * 0.5) 0379 // make sure the first and the last lines are always 0380 // positioned at the beginning and the end of the view 0381 0382 toPos = Math.max(toPos, 0) 0383 toPos = Math.min(toPos, contentHeight - lyricScroll.height) 0384 lyricScrollAnimation.to = toPos 0385 0386 } 0387 } 0388 0389 LyricsModel { 0390 id: lyricsModel 0391 } 0392 Connections { 0393 target: ElisaApplication.audioPlayer 0394 function onPositionChanged(position) { 0395 lyricsModel.setPosition(position) 0396 } 0397 } 0398 0399 Loader { 0400 id: lyricPlaceholder 0401 anchors.centerIn: parent 0402 width: parent.width 0403 0404 active: lyricsView.count === 0 0405 visible: active && status === Loader.Ready 0406 0407 sourceComponent: Kirigami.PlaceholderMessage { 0408 text: i18nc("@info:placeholder", "No lyrics found") 0409 icon.name: "view-media-lyrics" 0410 } 0411 } 0412 } 0413 } 0414 } 0415 0416 // "Nothing Playing" message 0417 Loader { 0418 anchors.centerIn: parent 0419 width: parent.width - (Kirigami.Units.largeSpacing * 4) 0420 0421 active: topItem.nothingPlaying 0422 visible: active && status === Loader.Ready 0423 0424 sourceComponent: Kirigami.PlaceholderMessage { 0425 text: i18nc("@info:placeholder", "Nothing playing") 0426 icon.name: "view-media-track" 0427 } 0428 } 0429 } 0430 0431 // Footer with file path label 0432 footer: ToolBar { 0433 implicitHeight: Math.round(Kirigami.Units.gridUnit * 2) 0434 visible: !topItem.nothingPlaying 0435 0436 RowLayout { 0437 anchors.fill: parent 0438 spacing: Kirigami.Units.smallSpacing 0439 0440 LabelWithToolTip { 0441 id: fileUrlLabel 0442 text: metaDataModel.fileUrl 0443 elide: Text.ElideLeft 0444 Layout.fillWidth: true 0445 } 0446 0447 Kirigami.ActionToolBar { 0448 // because fillWidth is true by default 0449 Layout.fillWidth: false 0450 0451 // when there is not enough space, show the button in the compact mode 0452 // then the file url will be elided if needed 0453 Layout.preferredWidth: parent.width > fileUrlLabel.implicitWidth + spacing + maximumContentWidth ? maximumContentWidth : Kirigami.Units.gridUnit * 2 0454 0455 Layout.fillHeight: true 0456 0457 actions: [ 0458 Kirigami.Action { 0459 text: i18nc("@action:button", "Show In Folder") 0460 icon.name: 'document-open-folder' 0461 visible: metaDataModel.fileUrl.toString() !== "" && !metaDataModel.fileUrl.toString().startsWith("http") && !metaDataModel.fileUrl.toString().startsWith("rtsp") 0462 onTriggered: { 0463 ElisaApplication.showInFolder(metaDataModel.fileUrl) 0464 } 0465 } 0466 ] 0467 } 0468 } 0469 } 0470 0471 onFileUrlChanged: { 0472 if (ElisaApplication.musicManager && trackType !== undefined && fileUrl.toString().length !== 0) { 0473 if (databaseId !== 0) { 0474 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0475 } else { 0476 metaDataModel.initializeByUrl(trackType, fileUrl) 0477 } 0478 } 0479 } 0480 0481 onTrackTypeChanged: { 0482 if (ElisaApplication.musicManager && trackType !== undefined && fileUrl.toString().length !== 0) { 0483 if (databaseId !== 0) { 0484 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0485 } else { 0486 metaDataModel.initializeByUrl(trackType, fileUrl) 0487 } 0488 } 0489 } 0490 0491 Connections { 0492 target: ElisaApplication 0493 0494 function onMusicManagerChanged() { 0495 if (ElisaApplication.musicManager && trackType !== undefined && databaseId !== 0) { 0496 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0497 } 0498 } 0499 } 0500 0501 Component.onCompleted: { 0502 if (ElisaApplication.musicManager && trackType !== undefined) { 0503 if (databaseId !== 0) { 0504 metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) 0505 } else { 0506 metaDataModel.initializeByUrl(trackType, fileUrl) 0507 } 0508 } 0509 } 0510 }