File indexing completed on 2024-12-01 13:09:06
0001 /* 0002 SPDX-FileCopyrightText: 2021 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include "roomlistheadingsproxymodel.h" 0007 0008 #include <QApplication> 0009 #include <QPalette> 0010 0011 RoomListHeadingsProxyModel::RoomListHeadingsProxyModel(QObject *parent) 0012 : QAbstractProxyModel(parent) 0013 { 0014 } 0015 0016 void RoomListHeadingsProxyModel::setSourceModel(QAbstractItemModel *sourceModel) 0017 { 0018 beginResetModel(); 0019 0020 if (auto *oldModel = this->sourceModel()) { 0021 disconnect(oldModel, nullptr, this, nullptr); 0022 } 0023 0024 QAbstractProxyModel::setSourceModel(sourceModel); 0025 0026 connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &RoomListHeadingsProxyModel::onRowsInserted); 0027 0028 connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RoomListHeadingsProxyModel::onRowsAboutToBeRemoved); 0029 0030 connect(sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, [this] { 0031 Q_EMIT layoutAboutToBeChanged(); 0032 }); 0033 connect(sourceModel, &QAbstractItemModel::rowsMoved, this, &RoomListHeadingsProxyModel::rebuildSections); 0034 connect(sourceModel, &QAbstractItemModel::rowsMoved, this, [this] { 0035 Q_EMIT layoutChanged(); 0036 }); 0037 0038 connect(sourceModel, &QAbstractItemModel::dataChanged, this, &RoomListHeadingsProxyModel::onDataChanged); 0039 0040 connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &RoomListHeadingsProxyModel::beginResetModel); 0041 connect(sourceModel, &QAbstractItemModel::modelReset, this, &RoomListHeadingsProxyModel::rebuildSections); 0042 connect(sourceModel, &QAbstractItemModel::modelReset, this, &RoomListHeadingsProxyModel::endResetModel); 0043 0044 connect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &RoomListHeadingsProxyModel::layoutAboutToBeChanged); 0045 connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &RoomListHeadingsProxyModel::rebuildSections); 0046 connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &RoomListHeadingsProxyModel::layoutChanged); 0047 0048 rebuildSections(); 0049 0050 endResetModel(); 0051 } 0052 0053 int RoomListHeadingsProxyModel::rowCount(const QModelIndex &parent) const 0054 { 0055 switch (type(parent)) { 0056 case IndexType::Root: 0057 return sectionCount; 0058 case IndexType::Section: 0059 return int(mSections.at(parent.row()).size()); 0060 case IndexType::Channel: 0061 return 0; 0062 } 0063 Q_UNREACHABLE(); 0064 } 0065 0066 int RoomListHeadingsProxyModel::columnCount(const QModelIndex &parent) const 0067 { 0068 switch (type(parent)) { 0069 case IndexType::Root: 0070 case IndexType::Section: 0071 return 1; 0072 case IndexType::Channel: 0073 return 0; 0074 } 0075 Q_UNREACHABLE(); 0076 } 0077 0078 bool RoomListHeadingsProxyModel::hasChildren(const QModelIndex &index) const 0079 { 0080 // Don't use the QAbstractProxyModel override which fails on non-proxied indices 0081 return QAbstractItemModel::hasChildren(index); // NOLINT(bugprone-parent-virtual-call) 0082 } 0083 0084 QModelIndex RoomListHeadingsProxyModel::index(int row, int column, const QModelIndex &parent) const 0085 { 0086 switch (type(parent)) { 0087 case IndexType::Root: 0088 return createIndex(row, column, sectionCount); 0089 case IndexType::Section: 0090 return createIndex(row, column, parent.row()); 0091 case IndexType::Channel: 0092 return {}; 0093 } 0094 Q_UNREACHABLE(); 0095 } 0096 0097 QModelIndex RoomListHeadingsProxyModel::parent(const QModelIndex &child) const 0098 { 0099 switch (type(child)) { 0100 case IndexType::Root: 0101 case IndexType::Section: 0102 return {}; 0103 case IndexType::Channel: 0104 return createIndex(int(child.internalId()), 0, sectionCount); 0105 } 0106 Q_UNREACHABLE(); 0107 } 0108 0109 QVariant RoomListHeadingsProxyModel::data(const QModelIndex &index, int role) const 0110 { 0111 switch (type(index)) { 0112 case IndexType::Root: 0113 return {}; 0114 case IndexType::Section: 0115 switch (role) { 0116 case Qt::ItemDataRole::DisplayRole: 0117 return RoomModel::sectionName(RoomModel::Section(index.row())); 0118 case Qt::BackgroundRole: 0119 return QApplication::palette().brush(QPalette::Window); 0120 default: 0121 return {}; 0122 } 0123 case IndexType::Channel: 0124 return sourceModel()->data(mapToSource(index), role); 0125 } 0126 Q_UNREACHABLE(); 0127 } 0128 0129 QModelIndex RoomListHeadingsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const 0130 { 0131 if (!sourceModel()) 0132 return {}; 0133 0134 if (!sourceIndex.isValid()) 0135 return {}; 0136 0137 for (auto sectionId = size_t(0), iMax = mSections.size(); sectionId < iMax; ++sectionId) { 0138 const auto §ion = mSections.at(sectionId); 0139 0140 const auto it = std::lower_bound(section.cbegin(), section.cend(), sourceIndex); 0141 if (it != section.cend() && *it == sourceIndex) 0142 return createIndex(int(it - section.cbegin()), 0, sectionId); 0143 } 0144 0145 return {}; 0146 } 0147 0148 QModelIndex RoomListHeadingsProxyModel::mapToSource(const QModelIndex &proxyIndex) const 0149 { 0150 if (!sourceModel()) 0151 return {}; 0152 0153 switch (type(proxyIndex)) { 0154 case IndexType::Root: 0155 case IndexType::Section: 0156 return {}; 0157 case IndexType::Channel: 0158 return mSections.at(proxyIndex.internalId()).at(proxyIndex.row()); 0159 } 0160 Q_UNREACHABLE(); 0161 } 0162 0163 Qt::ItemFlags RoomListHeadingsProxyModel::flags(const QModelIndex &proxyIndex) const 0164 { 0165 switch (type(proxyIndex)) { 0166 case IndexType::Root: 0167 return {}; 0168 case IndexType::Section: 0169 return Qt::ItemFlag::ItemIsEnabled; 0170 case IndexType::Channel: 0171 return QAbstractProxyModel::flags(proxyIndex); 0172 } 0173 Q_UNREACHABLE(); 0174 } 0175 0176 void RoomListHeadingsProxyModel::onRowsInserted(const QModelIndex &parent, int first, int last) 0177 { 0178 for (auto row = first; row <= last; ++row) { 0179 const QPersistentModelIndex index = sourceModel()->index(row, 0, parent); 0180 const auto newSectionId = int(index.data(RoomModel::RoomSection).value<RoomModel::Section>()); 0181 auto &newSection = mSections.at(newSectionId); 0182 0183 const auto newLocation = std::lower_bound(newSection.cbegin(), newSection.cend(), index); 0184 const auto newLocationRow = int(newLocation - newSection.cbegin()); 0185 0186 beginInsertRows(createIndex(newSectionId, 0, sectionCount), newLocationRow, newLocationRow); 0187 0188 newSection.insert(newLocation, index); 0189 0190 endInsertRows(); 0191 } 0192 } 0193 0194 void RoomListHeadingsProxyModel::onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) 0195 { 0196 for (auto row = first; row <= last; ++row) { 0197 const auto index = sourceModel()->index(row, 0, parent); 0198 0199 const auto ourOldIndex = mapFromSource(index); 0200 const auto oldSectionId = ourOldIndex.internalId(); 0201 auto &oldSection = mSections.at(oldSectionId); 0202 0203 beginRemoveRows(ourOldIndex.parent(), ourOldIndex.row(), ourOldIndex.row()); 0204 0205 oldSection.erase(oldSection.begin() + ourOldIndex.row()); 0206 0207 endRemoveRows(); 0208 } 0209 } 0210 0211 void RoomListHeadingsProxyModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) 0212 { 0213 for (auto row = topLeft.row(), last = bottomRight.row(); row <= last; ++row) { 0214 const auto sourceIndex = topLeft.siblingAtRow(row); 0215 const auto proxyIndex = mapFromSource(sourceIndex); 0216 Q_EMIT dataChanged(proxyIndex, proxyIndex, roles); 0217 } 0218 0219 if (!roles.empty() && !roles.contains(RoomModel::RoomSection)) 0220 return; 0221 0222 for (auto row = topLeft.row(), last = bottomRight.row(); row <= last; ++row) { 0223 const auto sourceIndex = topLeft.siblingAtRow(row); 0224 const auto ourOldIndex = mapFromSource(sourceIndex); 0225 0226 const auto oldSectionId = int(ourOldIndex.internalId()); 0227 const auto newSectionId = int(sourceIndex.data(RoomModel::RoomSection).value<RoomModel::Section>()); 0228 0229 if (oldSectionId == newSectionId) 0230 continue; 0231 0232 auto &oldSection = mSections.at(oldSectionId); 0233 auto &newSection = mSections.at(newSectionId); 0234 0235 const auto newLocation = std::lower_bound(newSection.cbegin(), newSection.cend(), sourceIndex); 0236 const auto newLocationRow = int(newLocation - newSection.cbegin()); 0237 0238 beginMoveRows(ourOldIndex.parent(), ourOldIndex.row(), ourOldIndex.row(), createIndex(newSectionId, 0, sectionCount), newLocationRow); 0239 0240 auto persistantIndex = oldSection[ourOldIndex.row()]; 0241 oldSection.erase(oldSection.begin() + ourOldIndex.row()); 0242 newSection.insert(newLocation, persistantIndex); 0243 0244 endMoveRows(); 0245 } 0246 } 0247 0248 void RoomListHeadingsProxyModel::rebuildSections() 0249 { 0250 for (auto §ion : mSections) 0251 section.clear(); 0252 0253 for (auto row = 0, until = sourceModel()->rowCount(); row < until; ++row) { 0254 const QPersistentModelIndex index = sourceModel()->index(row, 0); 0255 const auto newSectionId = uint(index.data(RoomModel::RoomSection).value<RoomModel::Section>()); 0256 auto &newSection = mSections.at(newSectionId); 0257 0258 newSection.push_back(index); 0259 } 0260 0261 for (auto §ion : mSections) 0262 std::sort(section.begin(), section.end()); 0263 } 0264 0265 auto RoomListHeadingsProxyModel::type(const QModelIndex &index) const -> IndexType 0266 { 0267 if (!index.isValid()) 0268 return IndexType::Root; 0269 0270 if (index.internalId() == sectionCount) 0271 return IndexType::Section; 0272 0273 return IndexType::Channel; 0274 } 0275 0276 #include "moc_roomlistheadingsproxymodel.cpp"