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 }