Warning, /multimedia/elisa/src/qml/ListBrowserDelegate.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: 2017 (c) Alexander Stippich <a.stippich@gmx.net>
0004    SPDX-FileCopyrightText: 2021 (c) Devin Lin <espidev@gmail.com>
0005 
0006    SPDX-License-Identifier: LGPL-3.0-or-later
0007  */
0008 
0009 import QtQuick 2.15
0010 import QtQuick.Layouts 1.2
0011 import QtQuick.Controls 2.3
0012 import QtQuick.Window 2.2
0013 import Qt5Compat.GraphicalEffects
0014 import org.kde.kirigami 2.19 as Kirigami
0015 import org.kde.elisa 1.0
0016 
0017 import "mobile"
0018 
0019 FocusScope {
0020     id: mediaTrack
0021 
0022     property url trackUrl
0023     property var dataType
0024     property string title
0025     property string artist
0026     property string album
0027     property string albumArtist
0028     property string duration
0029     property url imageUrl
0030     property int trackNumber
0031     property int discNumber
0032     property int rating
0033     property bool hideDiscNumber
0034     property bool isSelected
0035     property bool isAlternateColor
0036     property bool detailedView: true
0037     property bool editingRating: false
0038     readonly property bool isFavorite: rating === 10
0039 
0040     signal clicked()
0041     signal enqueue()
0042     signal replaceAndPlay(var url)
0043     signal callOpenMetaDataView(var url, var entryType)
0044     signal trackRatingChanged(var url, var rating)
0045 
0046     Accessible.role: Accessible.ListItem
0047     Accessible.name: title
0048     Accessible.description: title
0049 
0050     Keys.onReturnPressed: enqueue()
0051     Keys.onEnterPressed: enqueue()
0052 
0053     property bool delegateLoaded: true
0054 
0055     ListView.onPooled: delegateLoaded = false
0056     ListView.onReused: delegateLoaded = true
0057 
0058     property int singleLineHeight: {
0059         if (Kirigami.Settings.isMobile) {
0060             return 4 * Kirigami.Units.smallSpacing + Kirigami.Units.gridUnit * 2;
0061         } else {
0062             return 3 * Kirigami.Units.smallSpacing + Kirigami.Units.gridUnit;
0063         }
0064     }
0065     height: singleLineHeight + (!Kirigami.Settings.isMobile && detailedView ? Kirigami.Units.gridUnit : 0)
0066 
0067     property list<Kirigami.Action> actions: [
0068         Kirigami.Action {
0069             text: i18nc("@action:button Show the file for this song in the file manager", "Show in folder")
0070             icon.name: "document-open-folder"
0071             onTriggered: ElisaApplication.showInFolder(mediaTrack.trackUrl)
0072 
0073             visible: mediaTrack.trackUrl.toString().substring(0, 7) === 'file://'
0074         },
0075         Kirigami.Action {
0076             text: i18nc("@action:button Show track metadata", "View details")
0077             icon.name: "help-about"
0078             onTriggered: mediaTrack.callOpenMetaDataView(mediaTrack.trackUrl, mediaTrack.dataType)
0079         },
0080         Kirigami.Action {
0081             text: i18nc("@action:button", "Add to playlist")
0082             icon.name: "list-add"
0083             onTriggered: mediaTrack.enqueue()
0084         },
0085         Kirigami.Action {
0086             text: i18nc("@action:button", "Play from here, replacing current playlist")
0087             icon.name: Qt.application.layoutDirection !== Qt.RightToLeft ? "media-playback-start-symbolic"
0088                                                                          : "media-playback-start-symbolic-rtl"
0089             onTriggered: mediaTrack.replaceAndPlay(mediaTrack.trackUrl)
0090         },
0091         Kirigami.Action {
0092             text: i18nc("@action:button", "Set track rating")
0093             icon.name: "view-media-favorite"
0094             onTriggered: {
0095                 mediaTrack.editingRating = true;
0096             }
0097             visible: !ElisaApplication.useFavoriteStyleRatings && !Kirigami.Settings.isMobile
0098         },
0099         Kirigami.Action {
0100             text: mediaTrack.isFavorite ? i18nc("@action:button", "Un-mark this song as a favorite") : i18nc("@action:button", "Mark this song as a favorite")
0101             icon.name: mediaTrack.isFavorite ? "rating" : "rating-unrated"
0102 
0103             onTriggered: {
0104                 const newRating = mediaTrack.isFavorite ? 0 : 10
0105                 // Change icon immediately in case backend is slow
0106                 icon.name = mediaTrack.isFavorite ? "rating-unrated" : "rating"
0107                 mediaTrack.trackRatingChanged(mediaTrack.trackUrl, newRating);
0108             }
0109 
0110             visible: ElisaApplication.useFavoriteStyleRatings
0111         }
0112     ]
0113 
0114     // open mobile context menu
0115     function openContextMenu() {
0116         contextMenuLoader.active = true;
0117         contextMenuLoader.item.open();
0118     }
0119 
0120     onActiveFocusChanged: {
0121         if (!activeFocus) {
0122             editingRating = false
0123         }
0124     }
0125 
0126 
0127     Rectangle {
0128         id: rowRoot
0129 
0130         anchors.fill: parent
0131         z: 1
0132 
0133         color: (isAlternateColor ? myPalette.alternateBase : myPalette.base)
0134     }
0135 
0136     MouseArea {
0137         id: hoverArea
0138 
0139         anchors.fill: parent
0140         z: 2
0141 
0142         hoverEnabled: true
0143         acceptedButtons: Qt.LeftButton
0144 
0145         onClicked: {
0146             mediaTrack.clicked()
0147             replaceAndPlay(trackUrl);
0148         }
0149 
0150         RowLayout {
0151             anchors {
0152                 fill: parent
0153                 leftMargin: Kirigami.Units.largeSpacing
0154                 rightMargin: Kirigami.Units.largeSpacing
0155             }
0156 
0157             spacing: Kirigami.Units.largeSpacing
0158 
0159             Loader {
0160                 active: mediaTrack.delegateLoaded && (detailedView || Kirigami.Settings.isMobile) // cover is always visible on mobile
0161 
0162                 // mobile delegate needs more margins
0163                 Layout.preferredWidth: mediaTrack.height - Kirigami.Units.smallSpacing * (Kirigami.Settings.isMobile ? 2 : 1)
0164                 Layout.preferredHeight: mediaTrack.height - Kirigami.Units.smallSpacing * (Kirigami.Settings.isMobile ? 2 : 1)
0165 
0166                 Layout.alignment: Qt.AlignCenter
0167 
0168                 sourceComponent: ImageWithFallback {
0169                     id: coverImageElement
0170 
0171                     sourceSize.width: (mediaTrack.height - Kirigami.Units.smallSpacing * (Kirigami.Settings.isMobile ? 2 : 1)) * Screen.devicePixelRatio
0172                     sourceSize.height: (mediaTrack.height - Kirigami.Units.smallSpacing * (Kirigami.Settings.isMobile ? 2 : 1)) * Screen.devicePixelRatio
0173                     fillMode: Image.PreserveAspectFit
0174                     smooth: true
0175 
0176                     source: imageUrl
0177                     fallback: elisaTheme.defaultAlbumImage
0178 
0179                     asynchronous: true
0180 
0181                     layer.enabled: !usingFallback && !Kirigami.Settings.isMobile // disable drop shadow for mobile
0182 
0183                     layer.effect: DropShadow {
0184                         source: coverImageElement
0185                         radius: 8
0186                         samples: (radius * 2) + 1
0187                         cached: true
0188                         color: myPalette.shadow
0189                     }
0190                 }
0191             }
0192 
0193             ColumnLayout {
0194                 id: labels
0195 
0196                 Layout.fillWidth: true
0197                 Layout.fillHeight: true
0198                 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
0199 
0200                 spacing: Kirigami.Units.smallSpacing / 2
0201 
0202                 // first row (main label)
0203                 Label {
0204                     id: mainLabel
0205 
0206                     Layout.fillWidth: true
0207 
0208                     text: {
0209                         if (detailedView) {
0210                             return title;
0211                         } else if (Kirigami.Settings.isMobile) {
0212                             // specific artist/album page (mobile)
0213                             // not detailed view refers to an album page, in which we should put track numbers
0214                             if (trackNumber !== 0 && trackNumber !== -1 && trackNumber !== undefined) {
0215                                 return i18nc("@item:intable %1: track number. %2: track title.",
0216                                         "%1 - %2",
0217                                         trackNumber.toLocaleString(Qt.locale(), 'f', 0),
0218                                         title);
0219                             } else {
0220                                 return title;
0221                             }
0222                         } else {
0223                             // specific artist/album page (desktop)
0224                             if (trackNumber !== 0 && trackNumber !== -1 && trackNumber !== undefined) {
0225                                 if (albumArtist !== undefined && artist !== albumArtist) {
0226                                     return i18nc("@item:intable %1: track number. %2: track title. %3: artist name",
0227                                                 "%1 - %2 - %3",
0228                                                 trackNumber.toLocaleString(Qt.locale(), 'f', 0),
0229                                                 title, artist);
0230                                 } else {
0231                                     return i18nc("@item:intable %1: track number. %2: track title.",
0232                                                 "%1 - %2",
0233                                                 trackNumber.toLocaleString(Qt.locale(), 'f', 0),
0234                                                 title);
0235                                 }
0236                             } else {
0237                                 if (albumArtist !== undefined && artist !== albumArtist) {
0238                                     return i18nc("@item:intable %1: track title. %2: artist name",
0239                                                 "%1 - %2",
0240                                                 title, artist);
0241                                 } else {
0242                                     return title;
0243                                 }
0244                             }
0245                         }
0246                     }
0247                     textFormat: Text.PlainText
0248 
0249                     elide: Text.ElideRight
0250                 }
0251 
0252                 // second row (shown for mobile, and desktop detailed view)
0253                 Label {
0254                     id: artistLabel
0255 
0256                     Layout.fillWidth: true
0257 
0258                     text: {
0259                         var labelText = ""
0260                         if (artist) {
0261                             labelText += artist
0262                         }
0263                         if (album !== '' && detailedView) { // don't show album name on not detailed view
0264                             labelText += ' - ' + album
0265                             if (!hideDiscNumber && discNumber !== -1) {
0266                                 labelText += ' - CD ' + discNumber
0267                             }
0268                         }
0269                         return labelText;
0270                     }
0271                     horizontalAlignment: Text.AlignLeft
0272 
0273                     visible: text.length > 0 && (Kirigami.Settings.isMobile || detailedView)
0274                     opacity: 0.6
0275                     textFormat: Text.PlainText
0276 
0277                     elide: Text.ElideRight
0278                     font: Kirigami.Theme.smallFont
0279                 }
0280             }
0281 
0282             // hover actions (for desktop)
0283             Loader {
0284                 id: hoverLoader
0285                 active: !Kirigami.Settings.isMobile && (hoverArea.containsMouse || mediaTrack.activeFocus) && !mediaTrack.editingRating
0286                 visible: active
0287 
0288                 Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
0289 
0290                 z: 1
0291 
0292                 sourceComponent: Row {
0293                     anchors.centerIn: parent
0294 
0295                     Repeater {
0296                         model: mediaTrack.actions
0297 
0298                         delegate: FlatButtonWithToolTip {
0299                             width: singleLineHeight
0300                             height: singleLineHeight
0301                             action: modelData
0302                             visible: action.visible
0303                         }
0304                     }
0305                 }
0306             }
0307 
0308             // ratings (desktop)
0309             Loader {
0310                 id: cancelRatingLoader
0311                 active: !Kirigami.Settings.isMobile && (hoverArea.containsMouse || mediaTrack.activeFocus)
0312                 visible: active && mediaTrack.editingRating
0313 
0314                 Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
0315 
0316                 z: 1
0317 
0318                 sourceComponent: FlatButtonWithToolTip {
0319                     width: singleLineHeight
0320                     height: singleLineHeight
0321                     text: i18nc("@action:button", "Cancel rating this track")
0322                     icon.name: "dialog-cancel"
0323                     onClicked: { mediaTrack.editingRating = false; }
0324                 }
0325             }
0326             RatingStar {
0327                 id: ratingWidget
0328                 visible: !Kirigami.Settings.isMobile && (mediaTrack.editingRating || (rating > 0 && !hoverArea.containsMouse && !mediaTrack.activeFocus && !ElisaApplication.useFavoriteStyleRatings))
0329 
0330                 readOnly: !mediaTrack.editingRating
0331 
0332                 starRating: rating
0333 
0334                 Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
0335 
0336                 onRatingEdited: {
0337                     mediaTrack.editingRating = false
0338                     trackRatingChanged(trackUrl, starRating);
0339                 }
0340             }
0341             Loader {
0342                 id: favoriteMark
0343 
0344                 visible: !Kirigami.Settings.isMobile && ElisaApplication.useFavoriteStyleRatings && !hoverLoader.active && mediaTrack.isFavorite
0345 
0346                 sourceComponent: FlatButtonWithToolTip {
0347                     width: singleLineHeight
0348                     height: singleLineHeight
0349                     icon.name: mediaTrack.isFavorite ? "rating" : "rating-unrated"
0350                 }
0351             }
0352 
0353             LabelWithToolTip {
0354                 id: durationLabel
0355 
0356                 text: duration
0357 
0358                 horizontalAlignment: Text.AlignRight
0359 
0360                 Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
0361             }
0362 
0363             // mobile context actions menu button
0364             FlatButtonWithToolTip {
0365                 id: contextMenuButton
0366                 visible: Kirigami.Settings.isMobile
0367                 scale: LayoutMirroring.enabled ? -1 : 1
0368                 Layout.alignment: Qt.AlignVCenter
0369                 Layout.maximumHeight: parent.height
0370                 Layout.preferredWidth: height
0371                 Layout.preferredHeight: singleLineHeight - Kirigami.Units.smallSpacing * 2
0372 
0373                 text: i18nc("@action:button", "Song Options")
0374                 icon.name: "view-more-symbolic"
0375                 onClicked: openContextMenu()
0376             }
0377         }
0378     }
0379 
0380     // mobile context menu
0381     Loader {
0382         id: contextMenuLoader
0383         active: false
0384 
0385         // context menu sheet
0386         sourceComponent: Kirigami.MenuDialog {
0387             id: contextMenu
0388             title: mediaTrack.title
0389             preferredWidth: Kirigami.Units.gridUnit * 20
0390 
0391             actions: mediaTrack.actions
0392         }
0393     }
0394 
0395     states: [
0396         State {
0397             name: 'notSelected'
0398             when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected
0399             PropertyChanges {
0400                 target: rowRoot
0401                 color: (isAlternateColor ? myPalette.alternateBase : myPalette.base)
0402             }
0403             PropertyChanges {
0404                 target: rowRoot
0405                 opacity: 1
0406             }
0407         },
0408         State {
0409             name: 'hovered'
0410             when: !mediaTrack.activeFocus && hoverArea.containsMouse
0411             PropertyChanges {
0412                 target: rowRoot
0413                 color: myPalette.highlight
0414             }
0415             PropertyChanges {
0416                 target: rowRoot
0417                 opacity: 0.2
0418             }
0419         },
0420         State {
0421             name: 'selected'
0422             when: !mediaTrack.activeFocus && mediaTrack.isSelected
0423             PropertyChanges {
0424                 target: rowRoot
0425                 color: myPalette.mid
0426             }
0427             PropertyChanges {
0428                 target: rowRoot
0429                 opacity: 1.
0430             }
0431         },
0432         State {
0433             name: 'focused'
0434             when: mediaTrack.activeFocus
0435             PropertyChanges {
0436                 target: rowRoot
0437                 color: myPalette.highlight
0438             }
0439             PropertyChanges {
0440                 target: rowRoot
0441                 opacity: 0.6
0442             }
0443         }
0444     ]
0445 }