Warning, /multimedia/elisa/src/qml/MediaPlayListView.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 0009 import QtQuick.Controls 2.3 0010 import QtQuick.Layouts 1.1 0011 import QtQuick.Window 2.2 0012 import QtQml.Models 2.1 0013 import org.kde.kirigami 2.15 as Kirigami 0014 import org.kde.elisa 1.0 0015 0016 import "mobile" 0017 0018 // Not using ScrollablePage because we don't need any of the refresh features 0019 // that it provides 0020 Kirigami.Page { 0021 id: topItem 0022 0023 // set by the respective mobile/desktop view 0024 property var playListNotification 0025 property var playListView 0026 0027 title: i18nc("@title:window Title of the view of the playlist; keep this string as short as possible because horizontal space is quite scarce", "Playlist") 0028 padding: 0 0029 0030 // Use view colors so the background is white 0031 Kirigami.Theme.inherit: false 0032 Kirigami.Theme.colorSet: Kirigami.Theme.View 0033 0034 Accessible.role: Accessible.Pane 0035 Accessible.name: topItem.title 0036 0037 // Header with title and actions 0038 globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None 0039 header: ToolBar { 0040 implicitHeight: Math.round(Kirigami.Units.gridUnit * 2.5) 0041 0042 leftPadding: mirrored ? undefined : Kirigami.Units.largeSpacing 0043 rightPadding: mirrored ? Kirigami.Units.largeSpacing : undefined 0044 0045 // Override color to use standard window colors, not header colors 0046 // TODO: remove this if the HeaderBar component is ever removed or moved 0047 // to the bottom of the window such that this toolbar touches the window 0048 // titlebar 0049 Kirigami.Theme.colorSet: Kirigami.Theme.Window 0050 0051 RowLayout { 0052 anchors.fill: parent 0053 spacing: Kirigami.Units.smallSpacing 0054 0055 Kirigami.Heading { 0056 text: topItem.title 0057 } 0058 Kirigami.ActionToolBar { 0059 Layout.fillWidth: true 0060 alignment: Qt.AlignRight 0061 0062 actions: [ 0063 Kirigami.Action { 0064 id: savePlaylistButton 0065 text: i18nc("@action:button Save a playlist file", "Save…") 0066 icon.name: 'document-save' 0067 displayHint: Kirigami.DisplayHint.KeepVisible 0068 enabled: ElisaApplication.mediaPlayListProxyModel ? ElisaApplication.mediaPlayListProxyModel.tracksCount > 0 : false 0069 onTriggered: { 0070 mainWindow.fileDialog.savePlaylist() 0071 } 0072 }, 0073 Kirigami.Action { 0074 id: loadPlaylistButton 0075 text: i18nc("@action:button Load a playlist file", "Load…") 0076 icon.name: 'document-open' 0077 displayHint: Kirigami.DisplayHint.KeepVisible 0078 onTriggered: { 0079 mainWindow.fileDialog.loadPlaylist() 0080 } 0081 } 0082 ] 0083 } 0084 } 0085 } 0086 0087 ColumnLayout { 0088 anchors.fill: parent 0089 0090 Kirigami.InlineMessage { 0091 id: partiallyLoadedMessage 0092 Layout.fillWidth: true 0093 Layout.margins: Kirigami.Units.smallSpacing 0094 0095 visible: false 0096 showCloseButton: true 0097 0098 type: Kirigami.MessageType.Warning 0099 text: i18nc("@info", "Failed to load some tracks. Make sure that they have not been removed or renamed.") 0100 0101 actions: [ 0102 Kirigami.Action { 0103 id: actionButton 0104 visible: false 0105 text: i18nc("@action:button", "Edit Playlist File") 0106 icon.name: "document-edit" 0107 onTriggered: { 0108 ElisaApplication.mediaPlayListProxyModel.openLoadedPlayList() 0109 partiallyLoadedMessage.visible = false 0110 } 0111 } 0112 ] 0113 0114 onVisibleChanged: { 0115 if (!visible) { 0116 ElisaApplication.mediaPlayListProxyModel.resetPartiallyLoaded() 0117 } 0118 } 0119 0120 Connections { 0121 target: ElisaApplication.mediaPlayListProxyModel 0122 function onPartiallyLoadedChanged() { 0123 partiallyLoadedMessage.visible = ElisaApplication.mediaPlayListProxyModel.partiallyLoaded 0124 } 0125 function onCanOpenLoadedPlaylistChanged() { 0126 actionButton.visible = ElisaApplication.mediaPlayListProxyModel.canOpenLoadedPlaylist 0127 } 0128 } 0129 } 0130 0131 // ========== desktop listview ========== 0132 Component { 0133 id: desktopListView 0134 0135 ScrollView { 0136 property alias list: playListView 0137 0138 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890 0139 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 0140 0141 contentItem: ListView { 0142 id: playListView 0143 0144 signal moveItemRequested(int oldIndex, int newIndex) 0145 property bool draggingEntry: false 0146 0147 reuseItems: true 0148 clip: true 0149 keyNavigationEnabled: true 0150 activeFocusOnTab: count > 0 0151 0152 currentIndex: -1 0153 0154 onActiveFocusChanged: { 0155 if (activeFocus) { 0156 // callLater to make sure this is a Tab focusing 0157 Qt.callLater(function() { 0158 // if the currentItem is not visible, focus on the top fully visible item 0159 if (!itemIsFullyVisible(currentIndex)) { 0160 const topIndex = indexAt(0, contentY) 0161 if (topIndex > -1) { 0162 playListView.currentIndex = Math.min(itemIsFullyVisible(topIndex) ? topIndex : topIndex + 1, playListView.count - 1) 0163 playListView.currentItem.forceActiveFocus() 0164 } 0165 } 0166 }) 0167 } 0168 } 0169 0170 function itemIsFullyVisible(index) { 0171 if (index < 0 || index > count - 1) { 0172 return false 0173 } 0174 const item = itemAtIndex(index) 0175 // `item === null` can be true if the delegate is not loaded, hence check `item` 0176 return item && contentY <= item.y && item.y <= (contentY + height) 0177 } 0178 0179 Accessible.role: Accessible.List 0180 Accessible.name: topItem.title 0181 0182 section.property: 'albumSection' 0183 section.criteria: ViewSection.FullString 0184 0185 model: ElisaApplication.mediaPlayListProxyModel 0186 0187 delegate: ColumnLayout { 0188 id: playListDelegate 0189 0190 property alias entry: entry 0191 property bool nextDelegateHasSection 0192 0193 width: playListView.width 0194 spacing: 0 0195 0196 onFocusChanged: { 0197 if (focus) { 0198 entry.forceActiveFocus() 0199 } 0200 } 0201 0202 Loader { 0203 id: albumSection 0204 active: entry.sectionVisible 0205 visible: active 0206 Layout.fillWidth: true 0207 sourceComponent: BasicPlayListAlbumHeader { 0208 headerData: JSON.parse(playListDelegate.ListView.section) 0209 } 0210 } 0211 0212 // entry's placeholder 0213 // otherwise the layout is broken while dragging 0214 FocusScope { 0215 implicitWidth: entry.width 0216 // the height must be set because entry's parent 0217 // will be changed while dragging 0218 implicitHeight: entry.height 0219 0220 // because this is a FocusScope, clicking on any button inside PlayListEntry 0221 // will change this activeFocus 0222 onActiveFocusChanged: { 0223 if (activeFocus) { 0224 playListView.currentIndex = model.index 0225 entry.forceActiveFocus() 0226 } 0227 } 0228 0229 // ListItemDragHandle requires listItem 0230 // to be a child of delegate 0231 PlayListEntry { 0232 id: entry 0233 0234 width: playListView.width 0235 0236 index: model.index 0237 isSelected: playListView.currentIndex === index 0238 0239 databaseId: model.databaseId ? model.databaseId : 0 0240 entryType: model.entryType ? model.entryType : ElisaUtils.Unknown 0241 title: model.title ? model.title : '' 0242 artist: model.artist ? model.artist : '' 0243 album: model.album ? model.album : '' 0244 albumArtist: model.albumArtist ? model.albumArtist : '' 0245 duration: model.duration ? model.duration : '' 0246 fileName: model.trackResource ? model.trackResource : '' 0247 imageUrl: model.imageUrl ? model.imageUrl : '' 0248 trackNumber: model.trackNumber ? model.trackNumber : -1 0249 discNumber: model.discNumber ? model.discNumber : -1 0250 rating: model.rating ? model.rating : 0 0251 isSingleDiscAlbum: model.isSingleDiscAlbum !== undefined ? model.isSingleDiscAlbum : true 0252 isValid: model.isValid 0253 isPlaying: model.isPlaying 0254 metadataModifiableRole: model && model.metadataModifiableRole ? model.metadataModifiableRole : false 0255 listView: playListView 0256 listDelegate: playListDelegate 0257 showDragHandle: playListView.count > 1 0258 } 0259 } 0260 } 0261 0262 onCountChanged: if (count === 0) { 0263 currentIndex = -1; 0264 } 0265 0266 Loader { 0267 anchors.centerIn: parent 0268 width: parent.width - (Kirigami.Units.largeSpacing * 4) 0269 0270 active: playListView.count === 0 0271 visible: active && status === Loader.Ready 0272 0273 sourceComponent: Kirigami.PlaceholderMessage { 0274 icon.name: "view-media-playlist" 0275 text: i18nc("@info:placeholder", "Playlist is empty") 0276 explanation: i18nc("@info:usagetip", "Add some songs to get started. You can browse your music using the views on the left.") 0277 } 0278 } 0279 0280 /* currently disabled animations due to display corruption 0281 because of https://bugreports.qt.io/browse/QTBUG-49868 0282 causing https://bugs.kde.org/show_bug.cgi?id=406524 0283 and https://bugs.kde.org/show_bug.cgi?id=398093 0284 add: Transition { 0285 NumberAnimation { 0286 property: "opacity"; 0287 from: 0; 0288 to: 1; 0289 duration: Kirigami.Units.shortDuration } 0290 } 0291 0292 populate: Transition { 0293 NumberAnimation { 0294 property: "opacity"; 0295 from: 0; 0296 to: 1; 0297 duration: Kirigami.Units.shortDuration } 0298 } 0299 0300 remove: Transition { 0301 NumberAnimation { 0302 property: "opacity"; 0303 from: 1.0; 0304 to: 0; 0305 duration: Kirigami.Units.shortDuration } 0306 } 0307 0308 displaced: Transition { 0309 NumberAnimation { 0310 properties: "x,y"; 0311 duration: Kirigami.Units.shortDuration 0312 easing.type: Easing.InOutQuad } 0313 } 0314 */ 0315 } 0316 } 0317 } 0318 0319 // ========== mobile listview ========== 0320 Component { 0321 id: mobileListView 0322 0323 ScrollView { 0324 property alias list: playListView 0325 0326 contentItem: ListView { 0327 id: playListView 0328 0329 reuseItems: true 0330 model: ElisaApplication.mediaPlayListProxyModel 0331 0332 moveDisplaced: Transition { 0333 YAnimator { 0334 duration: Kirigami.Units.longDuration 0335 easing.type: Easing.InOutQuad 0336 } 0337 } 0338 0339 Loader { 0340 anchors.centerIn: parent 0341 width: parent.width - (Kirigami.Units.largeSpacing * 4) 0342 0343 active: ElisaApplication.mediaPlayListProxyModel ? ElisaApplication.mediaPlayListProxyModel.tracksCount === 0 : true 0344 visible: active && status === Loader.Ready 0345 0346 sourceComponent: Kirigami.PlaceholderMessage { 0347 anchors.centerIn: parent 0348 0349 icon.name: "view-media-playlist" 0350 text: i18nc("@info:placeholder", "Your playlist is empty.") 0351 } 0352 } 0353 0354 delegate: Item { 0355 width: entry.width 0356 height: entry.height 0357 0358 // ListItemDragHandle requires listItem 0359 // to be a child of delegate 0360 MobilePlayListDelegate { 0361 id: entry 0362 width: playListView.width 0363 0364 index: model ? model.index : 0 0365 isSelected: playListView.currentIndex === index 0366 0367 databaseId: model && model.databaseId ? model.databaseId : 0 0368 entryType: model && model.entryType ? model.entryType : ElisaUtils.Unknown 0369 title: model ? model.title || '' : '' 0370 artist: model ? model.artist || '' : '' 0371 album: model ? model.album || '' : '' 0372 albumArtist: model ? model.albumArtist || '' : '' 0373 duration: model ? model.duration || '' : '' 0374 fileName: model ? model.trackResource || '' : '' 0375 imageUrl: model ? model.imageUrl || '' : '' 0376 trackNumber: model ? model.trackNumber || -1 : -1 0377 discNumber: model ? model.discNumber || -1 : -1 0378 rating: model ? model.rating || 0 : 0 0379 isSingleDiscAlbum: model && model.isSingleDiscAlbum !== undefined ? model.isSingleDiscAlbum : true 0380 isValid: model && model.isValid 0381 isPlaying: model ? model.isPlaying : false 0382 metadataModifiableRole: model && model.metadataModifiableRole ? model.metadataModifiableRole : false 0383 hideDiscNumber: model && model.isSingleDiscAlbum 0384 showDragHandle: playListView.count > 1 0385 0386 listView: playListView 0387 0388 onActiveFocusChanged: { 0389 if (activeFocus && playListView.currentIndex !== index) { 0390 playListView.currentIndex = index 0391 } 0392 } 0393 } 0394 } 0395 } 0396 } 0397 } 0398 0399 Loader { 0400 id: playListLoader 0401 Layout.fillWidth: true 0402 Layout.fillHeight: true 0403 sourceComponent: Kirigami.Settings.isMobile ? mobileListView : desktopListView 0404 onLoaded: playListView = item.list 0405 } 0406 0407 Kirigami.InlineMessage { 0408 id: mobileClearedMessage 0409 Layout.fillWidth: true 0410 visible: false 0411 showCloseButton: true 0412 text: i18nc("@label", "Playlist cleared") 0413 0414 actions: [ 0415 Kirigami.Action { 0416 text: i18nc("@action:button", "Undo") 0417 icon.name: "edit-undo-symbolic" 0418 onTriggered: ElisaApplication.mediaPlayListProxyModel.undoClearPlayList() 0419 } 0420 ] 0421 } 0422 } 0423 0424 footer: ToolBar { 0425 implicitHeight: Math.round(Kirigami.Units.gridUnit * 2) 0426 0427 leftPadding: mirrored ? undefined : Kirigami.Units.largeSpacing 0428 rightPadding: mirrored ? Kirigami.Units.largeSpacing : undefined 0429 0430 RowLayout { 0431 anchors.fill: parent 0432 spacing: Kirigami.Units.smallSpacing 0433 0434 LabelWithToolTip { 0435 text: { 0436 if (ElisaApplication.mediaPlayListProxyModel.remainingTracks === -1) { 0437 return i18ncp("@info:status", "%1 track", "%1 tracks", ElisaApplication.mediaPlayListProxyModel.tracksCount); 0438 } else { 0439 if (ElisaApplication.mediaPlayListProxyModel.tracksCount == 1) { 0440 return i18nc("@info:status", "1 track"); 0441 } else { 0442 var nTracks = i18ncp("@info:status", "%1 track", "%1 tracks", ElisaApplication.mediaPlayListProxyModel.tracksCount); 0443 var nRemaining = i18ncp("@info:status number of tracks remaining", "%1 remaining", "%1 remaining", ElisaApplication.mediaPlayListProxyModel.remainingTracks); 0444 return i18nc("@info:status %1 is the translation of track[s], %2 is the translation of remaining", "%1 (%2)", nTracks, nRemaining); 0445 } 0446 } 0447 } 0448 elide: Text.ElideLeft 0449 } 0450 0451 Kirigami.ActionToolBar { 0452 Layout.fillWidth: true 0453 Layout.fillHeight: true 0454 alignment: Qt.AlignRight 0455 0456 actions: [ 0457 Kirigami.Action { 0458 text: i18nc("@action:button", "Show Current") 0459 icon.name: 'media-track-show-active' 0460 displayHint: Kirigami.DisplayHint.KeepVisible 0461 enabled: ElisaApplication.mediaPlayListProxyModel ? ElisaApplication.mediaPlayListProxyModel.tracksCount > 0 : false 0462 onTriggered: { 0463 playListView.positionViewAtIndex(ElisaApplication.mediaPlayListProxyModel.currentTrackRow, ListView.Contain) 0464 playListView.currentIndex = ElisaApplication.mediaPlayListProxyModel.currentTrackRow 0465 playListView.currentItem.entry.forceActiveFocus() 0466 } 0467 }, 0468 Kirigami.Action { 0469 text: i18nc("@action:button Remove all tracks from play list", "Clear All") 0470 icon.name: 'edit-clear-all' 0471 displayHint: Kirigami.DisplayHint.KeepVisible 0472 enabled: ElisaApplication.mediaPlayListProxyModel ? ElisaApplication.mediaPlayListProxyModel.tracksCount > 0 : false 0473 onTriggered: ElisaApplication.mediaPlayListProxyModel.clearPlayList() 0474 } 0475 ] 0476 } 0477 } 0478 } 0479 } 0480