From bd1cf2c5f0c3d55859400676f7a72d838423a257 Mon Sep 17 00:00:00 2001 From: Bart De Vries Date: Mon, 20 Sep 2021 16:57:43 +0200 Subject: [PATCH] Add FeedListPage sorting The feeds will be sorted by (1) descending number of unread/unplayed entries and (2) alphabetically by name. The current item and current selection are maintained after re-sorting when the number of unread entries has changed. --- src/CMakeLists.txt | 1 + src/main.cpp | 5 +++-- src/models/episodemodel.cpp | 4 ++-- src/models/episodemodel.h | 2 +- src/models/episodeproxymodel.cpp | 11 +++------- src/models/episodeproxymodel.h | 3 +-- src/models/feedsmodel.cpp | 14 +++++++------ src/models/feedsmodel.h | 3 --- src/models/feedsproxymodel.cpp | 36 ++++++++++++++++++++++++++++++++ src/models/feedsproxymodel.h | 29 +++++++++++++++++++++++++ src/qml/FeedListDelegate.qml | 24 +++++++++++++++++++-- src/qml/FeedListPage.qml | 18 ++++++---------- 12 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 src/models/feedsproxymodel.cpp create mode 100644 src/models/feedsproxymodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 812ecf15..182038cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,7 @@ set(SRCS_base storagemovejob.cpp models/chaptermodel.cpp models/feedsmodel.cpp + models/feedsproxymodel.cpp models/entriesmodel.cpp models/queuemodel.cpp models/episodemodel.cpp diff --git a/src/main.cpp b/src/main.cpp index 8fde01e1..b6d7a62f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,7 +45,7 @@ #include "models/episodemodel.h" #include "models/episodeproxymodel.h" #include "models/errorlogmodel.h" -#include "models/feedsmodel.h" +#include "models/feedsproxymodel.h" #include "models/podcastsearchmodel.h" #include "models/queuemodel.h" #include "mpris2/mpris2.h" @@ -120,7 +120,7 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty(QStringLiteral("_aboutData"), QVariant::fromValue(about)); - qmlRegisterType("org.kde.kasts", 1, 0, "FeedsModel"); + qmlRegisterType("org.kde.kasts", 1, 0, "FeedsProxyModel"); qmlRegisterType("org.kde.kasts", 1, 0, "QueueModel"); qmlRegisterType("org.kde.kasts", 1, 0, "EpisodeProxyModel"); qmlRegisterType("org.kde.kasts", 1, 0, "Mpris2"); @@ -130,6 +130,7 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("org.kde.kasts", 1, 0, "EntriesModel", QStringLiteral("Get from Feed")); qmlRegisterUncreatableType("org.kde.kasts", 1, 0, "Enclosure", QStringLiteral("Only for enums")); qmlRegisterUncreatableType("org.kde.kasts", 1, 0, "EpisodeModel", QStringLiteral("Only for enums")); + qmlRegisterUncreatableType("org.kde.kasts", 1, 0, "FeedsModel", QStringLiteral("Only for enums")); qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "Fetcher", &Fetcher::instance()); qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "Database", &Database::instance()); diff --git a/src/models/episodemodel.cpp b/src/models/episodemodel.cpp index 05d1ebe7..e944e6a3 100644 --- a/src/models/episodemodel.cpp +++ b/src/models/episodemodel.cpp @@ -12,8 +12,8 @@ #include "datamanager.h" #include "entry.h" -EpisodeModel::EpisodeModel() - : QAbstractListModel(nullptr) +EpisodeModel::EpisodeModel(QObject *parent) + : QAbstractListModel(parent) { // When feed is updated, the entire model needs to be reset because we // cannot know where the new entries will be inserted into the list (or that diff --git a/src/models/episodemodel.h b/src/models/episodemodel.h index 709140d0..cb84ff98 100644 --- a/src/models/episodemodel.h +++ b/src/models/episodemodel.h @@ -25,7 +25,7 @@ public: }; Q_ENUM(Roles) - explicit EpisodeModel(); + explicit EpisodeModel(QObject *parent = nullptr); QVariant data(const QModelIndex &index, int role = Qt::UserRole) const override; QHash roleNames() const override; int rowCount(const QModelIndex &parent) const override; diff --git a/src/models/episodeproxymodel.cpp b/src/models/episodeproxymodel.cpp index f6c20b28..fb309ce7 100644 --- a/src/models/episodeproxymodel.cpp +++ b/src/models/episodeproxymodel.cpp @@ -11,19 +11,14 @@ #include "datamanager.h" #include "entry.h" -EpisodeProxyModel::EpisodeProxyModel() - : QSortFilterProxyModel(nullptr) +EpisodeProxyModel::EpisodeProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) { m_currentFilter = NoFilter; - m_episodeModel = new EpisodeModel(); + m_episodeModel = new EpisodeModel(this); setSourceModel(m_episodeModel); } -EpisodeProxyModel::~EpisodeProxyModel() -{ - delete m_episodeModel; -} - bool EpisodeProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); diff --git a/src/models/episodeproxymodel.h b/src/models/episodeproxymodel.h index 3b5873cb..9a25e8ff 100644 --- a/src/models/episodeproxymodel.h +++ b/src/models/episodeproxymodel.h @@ -30,8 +30,7 @@ public: Q_PROPERTY(FilterType filterType READ filterType WRITE setFilterType NOTIFY filterTypeChanged) Q_PROPERTY(QString filterName READ filterName NOTIFY filterTypeChanged) - explicit EpisodeProxyModel(); - ~EpisodeProxyModel(); + explicit EpisodeProxyModel(QObject *parent = nullptr); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; diff --git a/src/models/feedsmodel.cpp b/src/models/feedsmodel.cpp index d0d401c5..7c94cd93 100644 --- a/src/models/feedsmodel.cpp +++ b/src/models/feedsmodel.cpp @@ -28,6 +28,14 @@ FeedsModel::FeedsModel(QObject *parent) beginRemoveRows(QModelIndex(), index, index); endRemoveRows(); }); + connect(&DataManager::instance(), &DataManager::unreadEntryCountChanged, this, [=](const QString &url) { + for (int i = 0; i < rowCount(QModelIndex()); i++) { + if (data(index(i, 0), UrlRole).toString() == url) { + Q_EMIT dataChanged(index(i, 0), index(i, 0)); + return; + } + } + }); } QHash FeedsModel::roleNames() const @@ -61,9 +69,3 @@ QVariant FeedsModel::data(const QModelIndex &index, int role) const return QVariant(); } } - -// Hack to get a QItemSelection in QML -QItemSelection FeedsModel::createSelection(int rowa, int rowb) -{ - return QItemSelection(index(rowa, 0), index(rowb, 0)); -} diff --git a/src/models/feedsmodel.h b/src/models/feedsmodel.h index d97b2caf..695baeb8 100644 --- a/src/models/feedsmodel.h +++ b/src/models/feedsmodel.h @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -32,6 +31,4 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; int rowCount(const QModelIndex &parent) const override; - - Q_INVOKABLE QItemSelection createSelection(int rowa, int rowb); }; diff --git a/src/models/feedsproxymodel.cpp b/src/models/feedsproxymodel.cpp new file mode 100644 index 00000000..4c1a36e0 --- /dev/null +++ b/src/models/feedsproxymodel.cpp @@ -0,0 +1,36 @@ +/** + * SPDX-FileCopyrightText: 2021 Bart De Vries + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "models/feedsproxymodel.h" + +FeedsProxyModel::FeedsProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + m_feedsModel = new FeedsModel(this); + setSourceModel(m_feedsModel); + setDynamicSortFilter(true); + sort(0, Qt::AscendingOrder); +} + +bool FeedsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + QString leftTitle = sourceModel()->data(left, FeedsModel::TitleRole).toString(); + QString rightTitle = sourceModel()->data(right, FeedsModel::TitleRole).toString(); + int leftUnreadCount = sourceModel()->data(left, FeedsModel::UnreadCountRole).toInt(); + int rightUnreadCount = sourceModel()->data(right, FeedsModel::UnreadCountRole).toInt(); + + if (leftUnreadCount == rightUnreadCount) { + return QString::localeAwareCompare(leftTitle, rightTitle) < 0; + } else { + return leftUnreadCount > rightUnreadCount; + } +} + +// Hack to get a QItemSelection in QML +QItemSelection FeedsProxyModel::createSelection(int rowa, int rowb) +{ + return QItemSelection(index(rowa, 0), index(rowb, 0)); +} diff --git a/src/models/feedsproxymodel.h b/src/models/feedsproxymodel.h new file mode 100644 index 00000000..4200a074 --- /dev/null +++ b/src/models/feedsproxymodel.h @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: 2021 Bart De Vries + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#pragma once + +#include +#include + +#include "models/feedsmodel.h" + +class Entry; + +class FeedsProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit FeedsProxyModel(QObject *parent = nullptr); + + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + Q_INVOKABLE QItemSelection createSelection(int rowa, int rowb); + +private: + FeedsModel *m_feedsModel; +}; diff --git a/src/qml/FeedListDelegate.qml b/src/qml/FeedListDelegate.qml index f7c9bacd..58a667b8 100644 --- a/src/qml/FeedListDelegate.qml +++ b/src/qml/FeedListDelegate.qml @@ -18,14 +18,16 @@ import org.kde.kasts 1.0 Controls.ItemDelegate { id: feedDelegate - required property int cardSize - required property int cardMargin + property int cardSize: 0 + property int cardMargin: 0 property int borderWidth: 1 implicitWidth: cardSize + 2 * cardMargin implicitHeight: cardSize + 2 * cardMargin property QtObject listView: undefined property bool selected: false + property bool isCurrentItem: false // to restore currentItem when model is resorted + property string currentItemUrl: "" property int row: model ? model.row : -1 property var activeBackgroundColor: Qt.lighter(Kirigami.Theme.highlightColor, 1.3) highlighted: selected @@ -135,8 +137,26 @@ Controls.ItemDelegate { Connections { target: listView.model + function onLayoutAboutToBeChanged() { + if (feedList.currentItem === feedDelegate) { + isCurrentItem = true; + currentItemUrl = feed.url; + } else { + isCurrentItem = false; + currentItemUrl = ""; + } + } function onLayoutChanged() { updateIsSelected(); + if (isCurrentItem) { + // yet another hack because "index" is still giving the old + // value here; so we have to manually find the new index. + for (var i = 0; i < feedList.model.rowCount(); i++) { + if (feedList.model.data(feedList.model.index(i, 0), FeedsModel.UrlRole) == currentItemUrl) { + feedList.currentIndex = i; + } + } + } } } diff --git a/src/qml/FeedListPage.qml b/src/qml/FeedListPage.qml index 7343f940..209faba6 100644 --- a/src/qml/FeedListPage.qml +++ b/src/qml/FeedListPage.qml @@ -115,21 +115,14 @@ Kirigami.ScrollablePage { cellWidth: availableWidth / columns cellHeight: availableWidth / columns - model: FeedsModel { + model: FeedsProxyModel { id: feedsModel } - Component { - id: feedListDelegate - FeedListDelegate { - cardSize: feedList.availableWidth / feedList.columns - 2 * feedList.cardMargin - cardMargin: feedList.cardMargin - listView: feedList - } - } - - delegate: Kirigami.DelegateRecycler { - sourceComponent: feedListDelegate + delegate: FeedListDelegate { + cardSize: feedList.availableWidth / feedList.columns - 2 * feedList.cardMargin + cardMargin: feedList.cardMargin + listView: feedList } property var selectionForContextMenu: [] @@ -153,6 +146,7 @@ Kirigami.ScrollablePage { currentIndex = 0; } } + Keys.onPressed: { if (event.matches(StandardKey.SelectAll)) { feedList.selectionModel.select(model.index(0, 0), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Columns);