Updated room sorting

Change the LastActivity sort order to Activity and update to a more flexible way of sorting based on an order from model roles.
Add options to actually switch between Alphabetical and Activity

Based on some old work by @tdfischer 

implements #103
This commit is contained in:
James Graham
2024-02-28 17:57:32 +00:00
parent 0c24996b44
commit 1ae3fc86da
7 changed files with 107 additions and 43 deletions

View File

@@ -220,6 +220,8 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[IsDirectChat] = "isDirectChat";
roles[DelegateTypeRole] = "delegateType";
roles[IconRole] = "icon";
roles[AttentionRole] = "attention";
roles[FavouriteRole] = "favourite";
return roles;
}
@@ -270,10 +272,10 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
return NeoChatRoomType::typeForRoom(room);
}
if (role == NotificationCountRole) {
return room->notificationCount();
return int(room->notificationCount());
}
if (role == HighlightCountRole) {
return room->highlightCount();
return int(room->highlightCount());
}
if (role == LastActiveTimeRole) {
return room->lastActiveTime();
@@ -315,6 +317,12 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == DelegateTypeRole) {
return QStringLiteral("normal");
}
if (role == AttentionRole) {
return room->notificationCount() + room->highlightCount() > 0;
}
if (role == FavouriteRole) {
return room->isFavourite();
}
return {};
}

View File

@@ -47,6 +47,8 @@ public:
IsDirectChat, /**< Whether this room is a direct chat. */
DelegateTypeRole,
IconRole,
AttentionRole, /**< Whether there are any notifications. */
FavouriteRole, /**< Whether the room is favourited. */
};
Q_ENUM(EventRoles)
explicit RoomTreeModel(QObject *parent = nullptr);

View File

@@ -13,6 +13,12 @@
SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() {
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
invalidateFilter();
});
setRecursiveFilteringEnabled(true);
sort(0);
invalidateFilter();
@@ -29,48 +35,75 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
{
m_sortOrder = sortOrder;
Q_EMIT roomSortOrderChanged();
if (sortOrder == SortFilterRoomTreeModel::Alphabetical) {
setSortRole(RoomTreeModel::DisplayNameRole);
} else if (sortOrder == SortFilterRoomTreeModel::LastActivity) {
} else if (sortOrder == SortFilterRoomTreeModel::Activity) {
setSortRole(RoomTreeModel::LastActiveTimeRole);
}
invalidate();
}
SortFilterRoomTreeModel::RoomSortOrder SortFilterRoomTreeModel::roomSortOrder() const
static const QVector<RoomTreeModel::EventRoles> alphabeticalSortPriorities{
// Does exactly what it says on the tin.
RoomTreeModel::DisplayNameRole,
};
static const QVector<RoomTreeModel::EventRoles> activitySortPriorities{
// Anything useful at the top, quiet rooms at the bottom
RoomTreeModel::AttentionRole,
// Organize by highlights, notifications, unread favorites, all other unread, in that order
RoomTreeModel::HighlightCountRole,
RoomTreeModel::NotificationCountRole,
RoomTreeModel::FavouriteRole,
// Finally sort by last activity time
RoomTreeModel::LastActiveTimeRole,
};
bool SortFilterRoomTreeModel::roleCmp(const QVariant &sortLeft, const QVariant &sortRight) const
{
return m_sortOrder;
switch (sortLeft.typeId()) {
case QMetaType::Bool:
return (sortLeft == sortRight) ? false : sortLeft.toBool();
case QMetaType::QString:
return sortLeft.toString() < sortRight.toString();
case QMetaType::Int:
return sortLeft.toInt() > sortRight.toInt();
case QMetaType::QDateTime:
return sortLeft.toDateTime() > sortRight.toDateTime();
default:
return false;
}
}
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities,
const QModelIndex &source_left,
const QModelIndex &source_right) const
{
for (RoomTreeModel::EventRoles sortRole : priorities) {
const auto sortLeft = sourceModel()->data(source_left, sortRole);
const auto sortRight = sourceModel()->data(source_right, sortRole);
if (sortLeft != sortRight) {
return roleCmp(sortLeft, sortRight);
}
}
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
if (m_sortOrder == SortFilterRoomTreeModel::LastActivity) {
// display favorite rooms always on top
const auto categoryLeft = static_cast<NeoChatRoomType::Types>(sourceModel()->data(source_left, RoomTreeModel::CategoryRole).toInt());
const auto categoryRight = static_cast<NeoChatRoomType::Types>(sourceModel()->data(source_right, RoomTreeModel::CategoryRole).toInt());
// Don't sort the top level categories.
if (!source_left.parent().isValid() || !source_right.parent().isValid()) {
return false;
}
if (categoryLeft == NeoChatRoomType::Types::Favorite && categoryRight == NeoChatRoomType::Types::Favorite) {
return sourceModel()->data(source_left, RoomTreeModel::LastActiveTimeRole).toDateTime()
> sourceModel()->data(source_right, RoomTreeModel::LastActiveTimeRole).toDateTime();
}
if (categoryLeft == NeoChatRoomType::Types::Favorite) {
return true;
} else if (categoryRight == NeoChatRoomType::Types::Favorite) {
return false;
}
switch (m_sortOrder) {
case SortFilterRoomTreeModel::Alphabetical:
return prioritiesCmp(alphabeticalSortPriorities, source_left, source_right);
case SortFilterRoomTreeModel::Activity:
return prioritiesCmp(activitySortPriorities, source_left, source_right);
}
return sourceModel()->data(source_left, RoomTreeModel::LastActiveTimeRole).toDateTime()
> sourceModel()->data(source_right, RoomTreeModel::LastActiveTimeRole).toDateTime();
}
if (m_sortOrder != SortFilterRoomTreeModel::Categories) {
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
if (sourceModel()->data(source_left, RoomTreeModel::CategoryRole) != sourceModel()->data(source_right, RoomTreeModel::CategoryRole)) {
return sourceModel()->data(source_left, RoomTreeModel::CategoryRole).toInt() < sourceModel()->data(source_right, RoomTreeModel::CategoryRole).toInt();
}
return sourceModel()->data(source_left, RoomTreeModel::LastActiveTimeRole).toDateTime()
> sourceModel()->data(source_right, RoomTreeModel::LastActiveTimeRole).toDateTime();
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
void SortFilterRoomTreeModel::setFilterText(const QString &text)

View File

@@ -7,6 +7,8 @@
#include <QQmlEngine>
#include <QSortFilterProxyModel>
#include "models/roomtreemodel.h"
/**
* @class SortFilterRoomTreeModel
*
@@ -31,13 +33,6 @@ class SortFilterRoomTreeModel : public QSortFilterProxyModel
Q_OBJECT
QML_ELEMENT
/**
* @brief The order by which the rooms will be sorted.
*
* @sa RoomSortOrder
*/
Q_PROPERTY(RoomSortOrder roomSortOrder READ roomSortOrder WRITE setRoomSortOrder NOTIFY roomSortOrderChanged)
/**
* @brief The text to use to filter room names.
*/
@@ -56,8 +51,7 @@ class SortFilterRoomTreeModel : public QSortFilterProxyModel
public:
enum RoomSortOrder {
Alphabetical,
LastActivity,
Categories,
Activity,
};
Q_ENUM(RoomSortOrder)
@@ -71,7 +65,6 @@ public:
explicit SortFilterRoomTreeModel(QObject *parent = nullptr);
void setRoomSortOrder(RoomSortOrder sortOrder);
[[nodiscard]] RoomSortOrder roomSortOrder() const;
void setFilterText(const QString &text);
[[nodiscard]] QString filterText() const;
@@ -98,14 +91,16 @@ protected:
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
Q_SIGNALS:
void roomSortOrderChanged();
void filterTextChanged();
void activeSpaceIdChanged();
void modeChanged();
private:
RoomSortOrder m_sortOrder = Categories;
RoomSortOrder m_sortOrder = Activity;
Mode m_mode = All;
QString m_filterText;
QString m_activeSpaceId;
bool roleCmp(const QVariant &left, const QVariant &right) const;
bool prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities, const QModelIndex &left, const QModelIndex &right) const;
};

View File

@@ -118,6 +118,10 @@
<label>Save the collapsed state of the room list</label>
<default>false</default>
</entry>
<entry name="SortOrder" type="int">
<label>The sort order for the rooms in the list.</label>
<default>1</default>
</entry>
</group>
<group name="NetworkProxy">
<entry name="ProxyType" type="Enum">

View File

@@ -65,6 +65,29 @@ FormCard.FormCardPage {
}
}
}
FormCard.FormHeader {
title: i18n("Room list sort order")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18nc("As in 'sort something based on last activity'", "Activity")
checked: Config.sortOrder === 1
enabled: !Config.isSortOrderImmutable
onToggled: {
Config.sortOrder = 1
Config.save()
}
}
FormCard.FormRadioDelegate {
text: i18nc("As in 'sort something alphabetically'", "Alphabetical")
checked: Config.sortOrder === 0
enabled: !Config.isSortOrderImmutable
onToggled: {
Config.sortOrder = 0
Config.save()
}
}
}
FormCard.FormHeader {
title: i18n("Timeline Events")
}

View File

@@ -148,7 +148,6 @@ Kirigami.Page {
property bool filterTextJustChanged: false
sourceModel: root.roomTreeModel
roomSortOrder: SortFilterRoomTreeModel.Categories
activeSpaceId: spaceDrawer.selectedSpaceId
mode: spaceDrawer.showDirectChats ? SortFilterRoomTreeModel.DirectChats : SortFilterRoomTreeModel.Rooms
}