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"