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 }