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 }