Warning, /multimedia/kasts/src/qml/GenericEntryListView.qml is written in an unsupported language. File is not indexed.

0001 /**
0002  * SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 import QtQuick
0008 import QtQuick.Controls as Controls
0009 import QtQuick.Layouts
0010 import QtQml.Models
0011 
0012 import org.kde.kirigami as Kirigami
0013 
0014 import org.kde.kasts
0015 
0016 ListView {
0017     id: listView
0018     clip: true
0019     property bool isQueue: false
0020     property bool isDownloads: false
0021 
0022     property var selectionForContextMenu: []
0023     property var singleSelectedEntry: undefined
0024     property ItemSelectionModel selectionModel: ItemSelectionModel {
0025         id: selectionModel
0026         model: listView.model
0027         onSelectionChanged: {
0028             selectionForContextMenu = selectedIndexes;
0029         }
0030     }
0031 
0032     topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
0033     currentIndex: -1
0034 
0035     onSelectionForContextMenuChanged: {
0036         if (selectionForContextMenu.length === 1) {
0037             singleSelectedEntry = selectionForContextMenu[0].model.data(selectionForContextMenu[0], AbstractEpisodeModel.EntryRole);
0038         } else {
0039             singleSelectedEntry = undefined;
0040         }
0041     }
0042 
0043     // The selection is not updated when the model is reset, so we have to take
0044     // this into account manually.
0045     // TODO: Fix the fact that the current item is not highlighted after reset
0046     Connections {
0047         target: listView.model
0048         function onModelAboutToBeReset() {
0049             selectionForContextMenu = [];
0050             listView.selectionModel.clear();
0051             listView.selectionModel.setCurrentIndex(model.index(0, 0), ItemSelectionModel.Current); // Only set current item; don't select it
0052             currentIndex = 0;
0053         }
0054     }
0055 
0056     Keys.onPressed: (event) => {
0057         if (event.matches(StandardKey.SelectAll)) {
0058             listView.selectionModel.select(model.index(0, 0), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Columns);
0059             return;
0060         }
0061         switch (event.key) {
0062             case Qt.Key_Up:
0063                 selectRelative(-1, event.modifiers == Qt.ShiftModifier);
0064                 return;
0065             case Qt.Key_Down:
0066                 selectRelative(1, event.modifiers == Qt.ShiftModifier);
0067                 return;
0068             case Qt.Key_PageUp:
0069                 if (!atYBeginning) {
0070                     if ((contentY - listView.height) < 0) {
0071                         contentY = 0
0072                     } else {
0073                         contentY -= listView.height
0074                     }
0075                     returnToBounds()
0076                 }
0077                 return;
0078             case Qt.Key_PageDown:
0079                 if (!atYEnd) {
0080                     if ((contentY + listView.height) > contentHeight - height) {
0081                         contentY = contentHeight - height
0082                     } else {
0083                         contentY += listView.height
0084                     }
0085                     returnToBounds()
0086                 }
0087                 return;
0088             case Qt.Key_Home:
0089                 if (!atYBeginning) {
0090                     contentY = 0
0091                     returnToBounds()
0092                 }
0093                 return;
0094             case Qt.Key_End:
0095                 if (!atYEnd) {
0096                     contentY = contentHeight - height
0097                     returnToBounds()
0098                 }
0099                 return;
0100             default:
0101                 break;
0102         }
0103     }
0104 
0105     onActiveFocusChanged: {
0106         if (activeFocus && !selectionModel.hasSelection) {
0107             selectionModel.clear();
0108             selectionModel.setCurrentIndex(model.index(0, 0), ItemSelectionModel.Current); // Only set current item; don't select it
0109         }
0110     }
0111 
0112     function selectRelative(delta, append) {
0113         var nextRow = listView.currentIndex + delta;
0114         if (nextRow < 0) {
0115             nextRow = 0;
0116         }
0117         if (nextRow >= listView.count) {
0118             nextRow = listView.count - 1;
0119         }
0120         if (append) {
0121             listView.selectionModel.select(listView.model.createSelection(nextRow, listView.selectionModel.currentIndex.row), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
0122         } else {
0123             listView.selectionModel.setCurrentIndex(model.index(nextRow, 0), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
0124         }
0125     }
0126 
0127 
0128     // For lack of a better place, we put generic entry list actions here so
0129     // they can be re-used across the different ListViews.
0130 
0131     readonly property var sortAction: Kirigami.Action {
0132         id: sortActionRoot
0133         visible: !isDownloads
0134         enabled: visible
0135         icon.name: "view-sort"
0136         text: i18nc("@action:intoolbar Open menu with options to sort episodes", "Sort")
0137 
0138         tooltip: i18nc("@info:tooltip", "Select how to sort episodes")
0139 
0140         property Controls.ActionGroup sortGroup: Controls.ActionGroup { }
0141 
0142         property Instantiator repeater: Instantiator {
0143             model: ListModel {
0144                 id: sortModel
0145                 // have to use script because i18n doesn't work within ListElement
0146                 Component.onCompleted: {
0147                     if (sortActionRoot.visible) {
0148                         var sortList = [AbstractEpisodeProxyModel.DateDescending,
0149                                         AbstractEpisodeProxyModel.DateAscending]
0150                         for (var i in sortList) {
0151                             sortModel.append({"name": listView.model.getSortName(sortList[i]),
0152                                             "iconName": listView.model.getSortIconName(sortList[i]),
0153                                             "sortType": sortList[i]});
0154                         }
0155                     }
0156                 }
0157             }
0158 
0159             Kirigami.Action {
0160                 visible: sortActionRoot.visible
0161                 icon.name: model.iconName
0162                 text: model.name
0163                 checkable: !isQueue
0164                 checked: !isQueue && (listView.model.sortType === model.sortType)
0165                 Controls.ActionGroup.group: isQueue ? null : sortActionRoot.sortGroup
0166 
0167                 onTriggered: {
0168                     if (isQueue) {
0169                         DataManager.sortQueue(model.sortType);
0170                     } else {
0171                         listView.model.sortType = model.sortType;
0172                     }
0173                 }
0174             }
0175 
0176             onObjectAdded: (index, object) => {
0177                 sortActionRoot.children.push(object);
0178             }
0179         }
0180     }
0181 
0182     readonly property var filterAction: Kirigami.Action {
0183         id: filterActionRoot
0184         visible: !isDownloads && !isQueue
0185         enabled: visible
0186         icon.name: "view-filter"
0187         text: i18nc("@action:intoolbar Button to open menu to filter episodes based on their status (played, new, etc.)", "Filter")
0188 
0189         tooltip: i18nc("@info:tooltip", "Filter episodes by status")
0190 
0191         property Controls.ActionGroup filterGroup: Controls.ActionGroup { }
0192 
0193 
0194         property Instantiator repeater: Instantiator {
0195             model: ListModel {
0196                 id: filterModel
0197                 // have to use script because i18n doesn't work within ListElement
0198                 Component.onCompleted: {
0199                     if (filterActionRoot.visible) {
0200                         var filterList = [AbstractEpisodeProxyModel.NoFilter,
0201                                         AbstractEpisodeProxyModel.ReadFilter,
0202                                         AbstractEpisodeProxyModel.NotReadFilter,
0203                                         AbstractEpisodeProxyModel.NewFilter,
0204                                         AbstractEpisodeProxyModel.NotNewFilter,
0205                                         AbstractEpisodeProxyModel.FavoriteFilter,
0206                                         AbstractEpisodeProxyModel.NotFavoriteFilter]
0207                         for (var i in filterList) {
0208                             filterModel.append({"name": listView.model.getFilterName(filterList[i]),
0209                                                 "filterType": filterList[i]});
0210                         }
0211                     }
0212                 }
0213             }
0214 
0215             Kirigami.Action {
0216                 visible: filterActionRoot.visible
0217                 text: model.name
0218                 checkable: true
0219                 checked: listView.model.filterType === model.filterType
0220                 Controls.ActionGroup.group: filterActionRoot.filterGroup
0221 
0222                 onTriggered: {
0223                     listView.model.filterType = model.filterType;
0224                 }
0225             }
0226 
0227             onObjectAdded: (index, object) => {
0228                 filterActionRoot.children.push(object);
0229             }
0230         }
0231     }
0232 
0233     readonly property var selectAllAction: Kirigami.Action {
0234         icon.name: "edit-select-all"
0235         text: i18n("Select All")
0236         visible: true
0237         onTriggered: {
0238             listView.selectionModel.select(model.index(0, 0), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Columns);
0239         }
0240     }
0241 
0242     readonly property var selectNoneAction: Kirigami.Action {
0243         icon.name: "edit-select-none"
0244         text: i18n("Deselect All")
0245         visible: listView.selectionModel.hasSelection
0246         onTriggered: {
0247             listView.selectionModel.clearSelection();
0248         }
0249     }
0250 
0251     readonly property var addToQueueAction: Kirigami.Action {
0252         text: i18n("Add to Queue")
0253         icon.name: "media-playlist-append"
0254         visible: listView.selectionModel.hasSelection && !listView.isQueue && (singleSelectedEntry ? !singleSelectedEntry.queueStatus : true)
0255         //visible: listView.selectionModel.hasSelection && !listView.isQueue
0256         onTriggered: {
0257             DataManager.bulkQueueStatusByIndex(true, selectionForContextMenu);
0258         }
0259     }
0260 
0261     readonly property var removeFromQueueAction: Kirigami.Action {
0262         text: i18n("Remove from Queue")
0263         icon.name: "list-remove"
0264         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? singleSelectedEntry.queueStatus : true)
0265         //visible: listView.selectionModel.hasSelection
0266         onTriggered: {
0267             DataManager.bulkQueueStatusByIndex(false, selectionForContextMenu);
0268         }
0269     }
0270 
0271     readonly property var markPlayedAction: Kirigami.Action {
0272         text: i18n("Mark as Played")
0273         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? !singleSelectedEntry.read : true)
0274         onTriggered: {
0275             DataManager.bulkMarkReadByIndex(true, selectionForContextMenu);
0276         }
0277     }
0278 
0279     readonly property var markNotPlayedAction: Kirigami.Action {
0280         text: i18n("Mark as Unplayed")
0281         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? singleSelectedEntry.read : true)
0282         onTriggered: {
0283             DataManager.bulkMarkReadByIndex(false, selectionForContextMenu);
0284         }
0285     }
0286 
0287     readonly property var markNewAction: Kirigami.Action {
0288         text: i18n("Label as \"New\"")
0289         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? !singleSelectedEntry.new : true)
0290         onTriggered: {
0291             DataManager.bulkMarkNewByIndex(true, selectionForContextMenu);
0292         }
0293     }
0294 
0295     readonly property var markNotNewAction: Kirigami.Action {
0296         text: i18n("Remove \"New\" Label")
0297         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? singleSelectedEntry.new : true)
0298         onTriggered: {
0299             DataManager.bulkMarkNewByIndex(false, selectionForContextMenu);
0300         }
0301     }
0302 
0303     readonly property var markFavoriteAction: Kirigami.Action {
0304         text: i18nc("@action:intoolbar Button to add a podcast episode as favorite", "Add to Favorites")
0305         icon.name: "starred-symbolic"
0306         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? !singleSelectedEntry.favorite : true)
0307         onTriggered: {
0308             DataManager.bulkMarkFavoriteByIndex(true, selectionForContextMenu);
0309         }
0310     }
0311 
0312     readonly property var markNotFavoriteAction: Kirigami.Action {
0313         text: i18nc("@action:intoolbar Button to remove the \"favorite\" property of a podcast episode", "Remove from Favorites")
0314         icon.name: "non-starred-symbolic"
0315         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? singleSelectedEntry.favorite : true)
0316         onTriggered: {
0317             DataManager.bulkMarkFavoriteByIndex(false, selectionForContextMenu);
0318         }
0319     }
0320 
0321     readonly property var downloadEnclosureAction: Kirigami.Action {
0322         text: i18n("Download")
0323         icon.name: "download"
0324         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? (singleSelectedEntry.hasEnclosure ? singleSelectedEntry.enclosure.status !== Enclosure.Downloaded : false) : true)
0325         onTriggered: {
0326             downloadOverlay.selection = selectionForContextMenu;
0327             downloadOverlay.run();
0328         }
0329     }
0330 
0331     readonly property var deleteEnclosureAction: Kirigami.Action {
0332         text: i18ncp("context menu action", "Delete Download", "Delete Downloads", selectionForContextMenu.length)
0333         icon.name: "delete"
0334         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? (singleSelectedEntry.hasEnclosure ? singleSelectedEntry.enclosure.status !== Enclosure.Downloadable : false) : true)
0335         onTriggered: {
0336             DataManager.bulkDeleteEnclosuresByIndex(selectionForContextMenu);
0337         }
0338     }
0339 
0340     readonly property var streamAction: Kirigami.Action {
0341         text: i18nc("@action:inmenu Action to start playback by streaming the episode rather than downloading it first", "Stream")
0342         icon.name: "media-playback-cloud"
0343         visible: listView.selectionModel.hasSelection && (singleSelectedEntry ? (singleSelectedEntry.hasEnclosure ? singleSelectedEntry.enclosure.status !== Enclosure.Downloaded : false) : false)
0344         onTriggered: {
0345             if (!singleSelectedEntry.queueStatus) {
0346                 singleSelectedEntry.queueStatus = true;
0347             }
0348             AudioManager.entry = singleSelectedEntry;
0349             AudioManager.play();
0350         }
0351     }
0352 
0353     readonly property var defaultActionList: [sortAction,
0354                                               filterAction,
0355                                               addToQueueAction,
0356                                               removeFromQueueAction,
0357                                               markPlayedAction,
0358                                               markNotPlayedAction,
0359                                               markNewAction,
0360                                               markNotNewAction,
0361                                               markFavoriteAction,
0362                                               markNotFavoriteAction,
0363                                               downloadEnclosureAction,
0364                                               deleteEnclosureAction,
0365                                               streamAction,
0366                                               selectAllAction,
0367                                               selectNoneAction]
0368 
0369     property Controls.Menu contextMenu: Controls.Menu {
0370         id: contextMenu
0371 
0372         Controls.MenuItem {
0373             action: listView.addToQueueAction
0374             visible: !listView.isQueue && (singleSelectedEntry ? !singleSelectedEntry.queueStatus : true)
0375             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0376         }
0377         Controls.MenuItem {
0378             action: listView.removeFromQueueAction
0379             visible: singleSelectedEntry ? singleSelectedEntry.queueStatus : true
0380             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0381         }
0382         Controls.MenuItem {
0383             action: listView.markPlayedAction
0384             visible: singleSelectedEntry ? !singleSelectedEntry.read : true
0385             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0386         }
0387         Controls.MenuItem {
0388             action: listView.markNotPlayedAction
0389             visible: singleSelectedEntry ? singleSelectedEntry.read : true
0390             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0391         }
0392         Controls.MenuItem {
0393             action: listView.markNewAction
0394             visible: singleSelectedEntry ? !singleSelectedEntry.new : true
0395             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0396         }
0397         Controls.MenuItem {
0398             action: listView.markNotNewAction
0399             visible: singleSelectedEntry ? singleSelectedEntry.new : true
0400             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0401          }
0402         Controls.MenuItem {
0403             action: listView.markFavoriteAction
0404             visible: singleSelectedEntry ? !singleSelectedEntry.favorite : true
0405             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0406         }
0407         Controls.MenuItem {
0408             action: listView.markNotFavoriteAction
0409             visible: singleSelectedEntry ? singleSelectedEntry.favorite : true
0410             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0411          }
0412         Controls.MenuItem {
0413             action: listView.downloadEnclosureAction
0414             visible: singleSelectedEntry ? (singleSelectedEntry.hasEnclosure ? singleSelectedEntry.enclosure.status !== Enclosure.Downloaded : false) : true
0415             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0416         }
0417         Controls.MenuItem {
0418             action: listView.deleteEnclosureAction
0419             visible: singleSelectedEntry ? (singleSelectedEntry.hasEnclosure ? singleSelectedEntry.enclosure.status !== Enclosure.Downloadable : false) : true
0420             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0421         }
0422         Controls.MenuItem {
0423             action: listView.streamAction
0424             visible: singleSelectedEntry ? (singleSelectedEntry.hasEnclosure ? (singleSelectedEntry.enclosure.status !== Enclosure.Downloaded && NetworkConnectionManager.streamingAllowed) : false) : false
0425             height: visible ? implicitHeight : 0 // workaround for qqc2-breeze-style
0426          }
0427         onClosed: {
0428             // reset to normal selection if this context menu is closed
0429             listView.selectionForContextMenu = listView.selectionModel.selectedIndexes;
0430         }
0431     }
0432 }