File indexing completed on 2024-04-28 16:54:35

0001 /*
0002     SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "notificationgroupcollapsingproxymodel_p.h"
0008 
0009 #include "notifications.h"
0010 
0011 #include "debug.h"
0012 
0013 using namespace NotificationManager;
0014 
0015 NotificationGroupCollapsingProxyModel::NotificationGroupCollapsingProxyModel(QObject *parent)
0016     : QSortFilterProxyModel(parent)
0017 {
0018 }
0019 
0020 NotificationGroupCollapsingProxyModel::~NotificationGroupCollapsingProxyModel() = default;
0021 
0022 void NotificationGroupCollapsingProxyModel::setSourceModel(QAbstractItemModel *source)
0023 {
0024     if (source == QAbstractProxyModel::sourceModel()) {
0025         return;
0026     }
0027 
0028     if (QAbstractProxyModel::sourceModel()) {
0029         disconnect(QAbstractProxyModel::sourceModel(), nullptr, this, nullptr);
0030     }
0031 
0032     QSortFilterProxyModel::setSourceModel(source);
0033 
0034     if (source) {
0035         connect(source, &QAbstractItemModel::rowsInserted, this, &NotificationGroupCollapsingProxyModel::invalidateFilter);
0036         connect(source, &QAbstractItemModel::rowsRemoved, this, &NotificationGroupCollapsingProxyModel::invalidateFilter);
0037 
0038         // When a group is removed, there is no item that's being removed, instead the item morphs back into a single notification
0039         connect(source,
0040                 &QAbstractItemModel::dataChanged,
0041                 this,
0042                 [this, source](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) {
0043                     if (roles.isEmpty() || roles.contains(Notifications::IsGroupRole)) {
0044                         for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
0045                             const QModelIndex sourceIdx = source->index(i, 0);
0046 
0047                             if (!sourceIdx.data(Notifications::IsGroupRole).toBool()) {
0048                                 if (m_expandedGroups.contains(sourceIdx)) {
0049                                     setGroupExpanded(topLeft, false);
0050                                 }
0051                             }
0052                         }
0053                     }
0054                 });
0055     }
0056 }
0057 
0058 QVariant NotificationGroupCollapsingProxyModel::data(const QModelIndex &index, int role) const
0059 {
0060     switch (role) {
0061     case Notifications::IsGroupExpandedRole: {
0062         if (m_limit > 0) {
0063             // so each item in a group knows whether the group is expanded
0064             const QModelIndex sourceIdx = mapToSource(index);
0065             return m_expandedGroups.contains(sourceIdx.parent().isValid() ? sourceIdx.parent() : sourceIdx);
0066         }
0067         return true;
0068     }
0069     case Notifications::ExpandedGroupChildrenCountRole:
0070         return rowCount(index.parent().isValid() ? index.parent() : index);
0071     }
0072 
0073     return QSortFilterProxyModel::data(index, role);
0074 }
0075 
0076 bool NotificationGroupCollapsingProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
0077 {
0078     if (role == Notifications::IsGroupExpandedRole && m_limit > 0) {
0079         QModelIndex groupIdx = index;
0080         // so an item inside a group can expand/collapse the group
0081         if (groupIdx.parent().isValid()) {
0082             groupIdx = groupIdx.parent();
0083         }
0084 
0085         const bool expanded = value.toBool();
0086         if (!groupIdx.data(Notifications::IsGroupRole).toBool()) {
0087             qCWarning(NOTIFICATIONMANAGER) << "Cannot" << (expanded ? "expand" : "collapse") << "an item isn't a group or inside of one";
0088             return false;
0089         }
0090 
0091         return setGroupExpanded(groupIdx, expanded);
0092     }
0093 
0094     return QSortFilterProxyModel::setData(index, value, role);
0095 }
0096 
0097 int NotificationGroupCollapsingProxyModel::limit() const
0098 {
0099     return m_limit;
0100 }
0101 
0102 void NotificationGroupCollapsingProxyModel::setLimit(int limit)
0103 {
0104     if (m_limit != limit) {
0105         m_limit = limit;
0106         invalidateFilter();
0107         invalidateGroupRoles();
0108         Q_EMIT limitChanged();
0109     }
0110 }
0111 
0112 QDateTime NotificationGroupCollapsingProxyModel::lastRead() const
0113 {
0114     return m_lastRead;
0115 }
0116 
0117 void NotificationGroupCollapsingProxyModel::setLastRead(const QDateTime &lastRead)
0118 {
0119     if (m_lastRead != lastRead) {
0120         m_lastRead = lastRead;
0121         invalidateFilter();
0122         invalidateGroupRoles();
0123         Q_EMIT lastReadChanged();
0124     }
0125 }
0126 
0127 bool NotificationGroupCollapsingProxyModel::expandUnread() const
0128 {
0129     return m_expandUnread;
0130 }
0131 
0132 void NotificationGroupCollapsingProxyModel::setExpandUnread(bool expand)
0133 {
0134     if (m_expandUnread != expand) {
0135         m_expandUnread = expand;
0136         invalidateFilter();
0137         invalidateGroupRoles();
0138         Q_EMIT expandUnreadChanged();
0139     }
0140 }
0141 
0142 void NotificationGroupCollapsingProxyModel::collapseAll()
0143 {
0144     m_expandedGroups.clear();
0145 
0146     invalidateFilter();
0147     invalidateGroupRoles();
0148 }
0149 
0150 bool NotificationGroupCollapsingProxyModel::setGroupExpanded(const QModelIndex &idx, bool expanded)
0151 {
0152     if (idx.data(Notifications::IsGroupExpandedRole).toBool() == expanded) {
0153         return false;
0154     }
0155 
0156     QPersistentModelIndex persistentIdx(mapToSource(idx));
0157     if (expanded) {
0158         m_expandedGroups.append(persistentIdx);
0159     } else {
0160         m_expandedGroups.removeOne(persistentIdx);
0161     }
0162 
0163     invalidateFilter();
0164 
0165     const QVector<int> dirtyRoles = {Notifications::ExpandedGroupChildrenCountRole, Notifications::IsGroupExpandedRole};
0166 
0167     Q_EMIT dataChanged(idx, idx, dirtyRoles);
0168     Q_EMIT dataChanged(index(0, 0, idx), index(rowCount(idx) - 1, 0, idx), dirtyRoles);
0169 
0170     return true;
0171 }
0172 
0173 void NotificationGroupCollapsingProxyModel::invalidateGroupRoles()
0174 {
0175     const QVector<int> dirtyRoles = {Notifications::ExpandedGroupChildrenCountRole, Notifications::IsGroupExpandedRole};
0176 
0177     Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), dirtyRoles);
0178 
0179     for (int row = 0; row < rowCount(); ++row) {
0180         const QModelIndex groupIdx = index(row, 0);
0181         Q_EMIT dataChanged(index(0, 0, groupIdx), index(rowCount(groupIdx) - 1, 0, groupIdx), dirtyRoles);
0182     }
0183 }
0184 
0185 bool NotificationGroupCollapsingProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
0186 {
0187     if (m_limit > 0 && source_parent.isValid()) {
0188         if (!m_expandedGroups.isEmpty() && m_expandedGroups.contains(source_parent)) {
0189             return true;
0190         }
0191 
0192         if (m_expandUnread && m_lastRead.isValid()) {
0193             const QModelIndex sourceIdx = sourceModel()->index(source_row, 0, source_parent);
0194 
0195             if (!sourceIdx.data(Notifications::ReadRole).toBool()) {
0196                 QDateTime time = sourceIdx.data(Notifications::UpdatedRole).toDateTime();
0197                 if (!time.isValid()) {
0198                     time = sourceIdx.data(Notifications::CreatedRole).toDateTime();
0199                 }
0200 
0201                 if (time.isValid() && m_lastRead < time) {
0202                     return true;
0203                 }
0204             }
0205         }
0206 
0207         // should we raise the limit when there's just one group?
0208 
0209         // FIXME why is this reversed?
0210         // grouping proxy model seems to reverse the order?
0211         return source_row >= sourceModel()->rowCount(source_parent) - m_limit;
0212     }
0213 
0214     return true;
0215 }