File indexing completed on 2024-05-12 16:25:03
0001 // SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org> 0002 // SPDX-License-Identifier: LGPL-2.0-or-later 0003 0004 #include "collapsestateproxymodel.h" 0005 0006 #include <KLocalizedString> 0007 0008 bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0009 { 0010 Q_UNUSED(source_parent); 0011 return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole) 0012 != MessageEventModel::DelegateType::State // If this is not a state, show it 0013 || (source_row < sourceModel()->rowCount() - 1 0014 && sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole) 0015 != MessageEventModel::DelegateType::State) // If this is the first state in a block, show it. TODO hidden events? 0016 || sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it 0017 } 0018 0019 QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const 0020 { 0021 if (role == AggregateDisplayRole) { 0022 return aggregateEventToString(mapToSource(index).row()); 0023 } else if (role == StateEventsRole) { 0024 return stateEventsList(mapToSource(index).row()); 0025 } else if (role == AuthorListRole) { 0026 return authorList(mapToSource(index).row()); 0027 } else if (role == ExcessAuthorsRole) { 0028 return excessAuthors(mapToSource(index).row()); 0029 } 0030 return sourceModel()->data(mapToSource(index), role); 0031 } 0032 0033 QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const 0034 { 0035 auto roles = sourceModel()->roleNames(); 0036 roles[AggregateDisplayRole] = "aggregateDisplay"; 0037 roles[StateEventsRole] = "stateEvents"; 0038 roles[AuthorListRole] = "authorList"; 0039 roles[ExcessAuthorsRole] = "excessAuthors"; 0040 return roles; 0041 } 0042 0043 QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const 0044 { 0045 QStringList parts; 0046 QVariantList uniqueAuthors; 0047 for (int i = sourceRow; i >= 0; i--) { 0048 parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString(); 0049 QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); 0050 if (!uniqueAuthors.contains(nextAuthor)) { 0051 uniqueAuthors.append(nextAuthor); 0052 } 0053 if (i > 0 0054 && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) 0055 != MessageEventModel::DelegateType::State // If it's not a state event 0056 || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible 0057 )) { 0058 break; 0059 } 0060 } 0061 parts.sort(); // Sort them so that all identical events can be collected. 0062 if (!parts.isEmpty()) { 0063 QStringList chunks; 0064 while (!parts.isEmpty()) { 0065 chunks += QString(); 0066 int count = 1; 0067 auto part = parts.takeFirst(); 0068 chunks.last() += part; 0069 while (!parts.isEmpty() && parts.first() == part) { 0070 parts.removeFirst(); 0071 count++; 0072 } 0073 if (count > 1 && uniqueAuthors.length() == 1) { 0074 chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count); 0075 } 0076 } 0077 chunks.removeDuplicates(); 0078 QString text = "<style>a {text-decoration: none;}</style>"; // There can be links in the event text so make sure all are styled. 0079 // The author text is either "n users" if > 1 user or the matrix.to link to a single user. 0080 QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length()) 0081 : QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ") 0082 .arg(uniqueAuthors[0].toMap()["id"].toString(), 0083 uniqueAuthors[0].toMap()["color"].toString(), 0084 uniqueAuthors[0].toMap()["displayName"].toString().toHtmlEscaped()); 0085 text += userText; 0086 text += chunks.takeFirst(); 0087 0088 if (chunks.size() > 0) { 0089 while (chunks.size() > 1) { 0090 text += i18nc("[action 1], [action 2 and/or action 3]", ", "); 0091 text += chunks.takeFirst(); 0092 } 0093 text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and "); 0094 text += chunks.takeFirst(); 0095 } 0096 return text; 0097 } else { 0098 return {}; 0099 } 0100 } 0101 0102 QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const 0103 { 0104 QVariantList stateEvents; 0105 for (int i = sourceRow; i >= 0; i--) { 0106 auto nextState = QVariantMap{ 0107 {"author", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)}, 0108 {"authorDisplayName", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()}, 0109 {"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()}, 0110 }; 0111 stateEvents.append(nextState); 0112 if (i > 0 0113 && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) 0114 != MessageEventModel::DelegateType::State // If it's not a state event 0115 || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible 0116 )) { 0117 break; 0118 } 0119 } 0120 return stateEvents; 0121 } 0122 0123 QVariantList CollapseStateProxyModel::authorList(int sourceRow) const 0124 { 0125 QVariantList uniqueAuthors; 0126 for (int i = sourceRow; i >= 0; i--) { 0127 QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); 0128 if (!uniqueAuthors.contains(nextAvatar)) { 0129 uniqueAuthors.append(nextAvatar); 0130 } 0131 if (i > 0 0132 && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) 0133 != MessageEventModel::DelegateType::State // If it's not a state event 0134 || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible 0135 )) { 0136 break; 0137 } 0138 } 0139 0140 if (uniqueAuthors.count() > 5) { 0141 uniqueAuthors = uniqueAuthors.mid(0, 5); 0142 } 0143 return uniqueAuthors; 0144 } 0145 0146 QString CollapseStateProxyModel::excessAuthors(int row) const 0147 { 0148 QVariantList uniqueAuthors; 0149 for (int i = row; i >= 0; i--) { 0150 QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); 0151 if (!uniqueAuthors.contains(nextAvatar)) { 0152 uniqueAuthors.append(nextAvatar); 0153 } 0154 if (i > 0 0155 && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) 0156 != MessageEventModel::DelegateType::State // If it's not a state event 0157 || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible 0158 )) { 0159 break; 0160 } 0161 } 0162 0163 int excessAuthors; 0164 if (uniqueAuthors.count() > 5) { 0165 excessAuthors = uniqueAuthors.count() - 5; 0166 } else { 0167 excessAuthors = 0; 0168 } 0169 QString excessAuthorsString; 0170 if (excessAuthors == 0) { 0171 return QString(); 0172 } else { 0173 return QStringLiteral("+ %1").arg(excessAuthors); 0174 } 0175 } 0176 0177 #include "moc_collapsestateproxymodel.cpp"