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 }