File indexing completed on 2023-12-03 08:28:34
0001 /* 0002 * Turns a list model into a tree allowing nodes to be in multiple groups 0003 * 0004 * Copyright (C) 2012 David Edmundson <kde@davidedmundson.co.uk> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Lesser General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2.1 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Lesser General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Lesser General Public 0017 * License along with this library; if not, write to the Free Software 0018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 0019 */ 0020 0021 #include "abstract-grouping-proxy-model.h" 0022 0023 #include <QSet> 0024 #include <QTimer> 0025 0026 #include "debug.h" 0027 0028 0029 class KTp::AbstractGroupingProxyModel::Private 0030 { 0031 public: 0032 QAbstractItemModel *source; 0033 0034 //keep a cache of what groups an item belongs to 0035 QHash<QPersistentModelIndex, QSet<QString> > groupCache; 0036 0037 //item -> groups 0038 QMultiHash<QPersistentModelIndex, ProxyNode*> proxyMap; 0039 QHash<QString, GroupNode*> groupMap; 0040 }; 0041 0042 class ProxyNode : public QStandardItem 0043 { 0044 public: 0045 ProxyNode(const QPersistentModelIndex &sourceIndex); 0046 QVariant data(int role) const override; 0047 void changed(); //expose protected method in QStandardItem 0048 QString group() const; 0049 private: 0050 const QPersistentModelIndex m_sourceIndex; 0051 }; 0052 0053 class GroupNode : public QStandardItem { 0054 public: 0055 GroupNode(const QString &groupId); 0056 QString group() const; 0057 QVariant data(int role) const override; 0058 bool forced() const; 0059 void changed(); //expose protected method in QStandardItem 0060 void setForced(bool forced); 0061 private: 0062 const QString m_groupId; 0063 bool m_forced; 0064 }; 0065 0066 0067 ProxyNode::ProxyNode(const QPersistentModelIndex &sourceIndex): 0068 QStandardItem(), 0069 m_sourceIndex(sourceIndex) 0070 { 0071 } 0072 0073 QVariant ProxyNode::data(int role) const 0074 { 0075 return m_sourceIndex.data(role); 0076 } 0077 0078 void ProxyNode::changed() 0079 { 0080 QStandardItem::emitDataChanged(); 0081 } 0082 0083 QString ProxyNode::group() const 0084 { 0085 //FIXME is this a hack? 0086 GroupNode *groupNode = static_cast<GroupNode*>(parent()); 0087 if (groupNode) { 0088 return groupNode->group(); 0089 } 0090 return QString(); 0091 } 0092 0093 0094 0095 GroupNode::GroupNode(const QString &groupId): 0096 QStandardItem(), 0097 m_groupId(groupId), 0098 m_forced(false) 0099 { 0100 } 0101 0102 QString GroupNode::group() const 0103 { 0104 return m_groupId; 0105 } 0106 0107 QVariant GroupNode::data(int role) const 0108 { 0109 KTp::AbstractGroupingProxyModel *proxyModel = qobject_cast<KTp::AbstractGroupingProxyModel*>(model()); 0110 Q_ASSERT(proxyModel); 0111 return proxyModel->dataForGroup(m_groupId, role); 0112 } 0113 0114 void GroupNode::setForced(bool forced) 0115 { 0116 m_forced = forced; 0117 } 0118 0119 bool GroupNode::forced() const 0120 { 0121 return m_forced; 0122 } 0123 0124 void GroupNode::changed() 0125 { 0126 QStandardItem::emitDataChanged(); 0127 } 0128 0129 0130 KTp::AbstractGroupingProxyModel::AbstractGroupingProxyModel(QAbstractItemModel *source): 0131 QStandardItemModel(source), 0132 d(new KTp::AbstractGroupingProxyModel::Private()) 0133 { 0134 d->source = source; 0135 0136 //we have to process all existing rows in the model, but must never call a virtual method from a constructor as it will crash. 0137 //we use a single shot timer to get round this 0138 QTimer::singleShot(0, this, SLOT(onLoad())); 0139 } 0140 0141 KTp::AbstractGroupingProxyModel::~AbstractGroupingProxyModel() 0142 { 0143 delete d; 0144 } 0145 0146 QHash<int, QByteArray> KTp::AbstractGroupingProxyModel::roleNames() const 0147 { 0148 return d->source->roleNames(); 0149 } 0150 0151 void KTp::AbstractGroupingProxyModel::forceGroup(const QString &group) 0152 { 0153 GroupNode* groupNode = itemForGroup(group); 0154 groupNode->setForced(true); 0155 } 0156 0157 void KTp::AbstractGroupingProxyModel::unforceGroup(const QString &group) 0158 { 0159 GroupNode* groupNode = d->groupMap[group]; 0160 if (!groupNode) { 0161 return; 0162 } 0163 0164 //mark that this group can be removed when it's empty 0165 groupNode->setForced(false); 0166 0167 //if group is already empty remove it 0168 if (groupNode->rowCount() == 0) { 0169 takeRow(groupNode->row()); 0170 d->groupMap.remove(groupNode->group()); 0171 } 0172 } 0173 0174 void KTp::AbstractGroupingProxyModel::groupChanged(const QString &group) 0175 { 0176 GroupNode *node = d->groupMap[group]; 0177 if (node) { 0178 node->changed(); 0179 } 0180 } 0181 0182 0183 /* Called when source items inserts a row 0184 * 0185 * For each new row, create a proxyNode. 0186 * If it's at the top level add it to the relevant group nodes. 0187 * Otherwise add it to the relevant proxy nodes in our model 0188 */ 0189 0190 void KTp::AbstractGroupingProxyModel::onRowsInserted(const QModelIndex &sourceParent, int start, int end) 0191 { 0192 //if top level in root model 0193 if (!sourceParent.isValid()) { 0194 for (int i = start; i <= end; i++) { 0195 QModelIndex index = d->source->index(i, 0, sourceParent); 0196 Q_FOREACH(const QString &group, groupsForIndex(index)) { 0197 addProxyNode(index, itemForGroup(group)); 0198 } 0199 } 0200 } else { 0201 for (int i = start; i <= end; i++) { 0202 QModelIndex index = d->source->index(i, 0, sourceParent); 0203 QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.constFind(sourceParent); 0204 while (it != d->proxyMap.constEnd() && it.key() == sourceParent) { 0205 addProxyNode(index, it.value()); 0206 it++; 0207 } 0208 } 0209 } 0210 } 0211 0212 #include <KTp/types.h> 0213 0214 void KTp::AbstractGroupingProxyModel::addProxyNode(const QModelIndex &sourceIndex, QStandardItem *parent) 0215 { 0216 Q_ASSERT(sourceIndex.isValid()); 0217 if (!sourceIndex.isValid()) { 0218 return; 0219 } 0220 0221 ProxyNode *proxyNode = new ProxyNode(sourceIndex); 0222 d->proxyMap.insertMulti(sourceIndex, proxyNode); 0223 parent->appendRow(proxyNode); 0224 0225 //add proxy nodes for all children of this sourceIndex 0226 for (int i=0; i < d->source->rowCount(sourceIndex); i++) { 0227 addProxyNode(sourceIndex.child(i,0), proxyNode); 0228 } 0229 0230 } 0231 0232 void KTp::AbstractGroupingProxyModel::removeProxyNodes(const QModelIndex &sourceIndex, const QList<ProxyNode *> &removedItems) 0233 { 0234 Q_FOREACH(ProxyNode *proxy, removedItems) { 0235 QStandardItem *parentItem = proxy->parent(); 0236 0237 //also remove child items of this proxy node from the proxy map 0238 for (int i = 0 ; i < d->source->rowCount(sourceIndex) ; i++) { 0239 //we always remove child(0) because we're deleting the first child each time 0240 removeProxyNodes(sourceIndex.child(i,0), QList<ProxyNode*>() << dynamic_cast<ProxyNode*>(proxy->child(0))); 0241 } 0242 0243 parentItem->removeRow(proxy->row()); 0244 d->proxyMap.remove(sourceIndex, proxy); 0245 0246 //if the parent item to this proxy node is now empty, and is a top level item 0247 if (parentItem->rowCount() == 0 && parentItem->parent() == nullptr ) { 0248 GroupNode* groupNode = dynamic_cast<GroupNode*>(parentItem); 0249 0250 //do not delete forced groups 0251 if (groupNode->forced() == false) { 0252 takeRow(groupNode->row()); 0253 d->groupMap.remove(groupNode->group()); 0254 } 0255 } 0256 } 0257 } 0258 0259 /* 0260 * Called when a row is remove from the source model model 0261 * Find all existing proxy models and delete thems 0262 */ 0263 void KTp::AbstractGroupingProxyModel::onRowsRemoved(const QModelIndex &sourceParent, int start, int end) 0264 { 0265 for (int i = start; i<=end; i++) { 0266 QPersistentModelIndex index = d->source->index(i, 0, sourceParent); 0267 QList<ProxyNode *> itemsToRemove; 0268 0269 QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.constFind(index); 0270 while (it != d->proxyMap.constEnd() && it.key() == index) { 0271 // qCDebug(KTP_MODELS) << "removing row" << index.data(); 0272 itemsToRemove.append(it.value()); 0273 ++it; 0274 } 0275 d->groupCache.remove(index); 0276 removeProxyNodes(index, itemsToRemove); 0277 } 0278 } 0279 0280 /* 0281 * Called when source model changes data 0282 * If it's the top level item in the source model detect if the groups have changed, if so update as appropriately 0283 * Find all proxy nodes, and make dataChanged() get emitted 0284 */ 0285 0286 void KTp::AbstractGroupingProxyModel::onDataChanged(const QModelIndex &sourceTopLeft, const QModelIndex &sourceBottomRight) 0287 { 0288 for (int i = sourceTopLeft.row(); i <= sourceBottomRight.row(); i++) { 0289 QPersistentModelIndex index = sourceTopLeft.sibling(i,0); 0290 if (!index.isValid()) { 0291 continue; 0292 } 0293 0294 //if top level item 0295 if (!sourceTopLeft.parent().isValid()) { 0296 //groupsSet has changed...update as appropriate 0297 QSet<QString> itemGroups = groupsForIndex(d->source->index(i, 0, sourceTopLeft.parent())); 0298 if (d->groupCache[index] != itemGroups) { 0299 d->groupCache[index] = itemGroups; 0300 0301 //loop through existing proxy nodes, and check each one is still valid. 0302 QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.constFind(index); 0303 QList<ProxyNode*> removedItems; 0304 while (it != d->proxyMap.constEnd() && it.key() == index) { 0305 // if proxy's group is still in the item's groups. 0306 if (itemGroups.contains(it.value()->group())) { 0307 itemGroups.remove(it.value()->group()); 0308 } else { 0309 //remove the proxy item 0310 //cache to list and remove once outside the const_iterator 0311 removedItems.append(it.value()); 0312 0313 qCDebug(KTP_MODELS) << "removing " << index.data().toString() << " from group " << it.value()->group(); 0314 } 0315 ++it; 0316 } 0317 0318 removeProxyNodes(index, removedItems); 0319 0320 //remaining items in itemGroups are now the new groups 0321 Q_FOREACH(const QString &group, itemGroups) { 0322 GroupNode *groupNode = itemForGroup(group); 0323 addProxyNode(index, groupNode); 0324 0325 qCDebug(KTP_MODELS) << "adding " << index.data().toString() << " to group " << group; 0326 } 0327 } 0328 } 0329 0330 //mark all proxy nodes as changed 0331 QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.constFind(index); 0332 while (it != d->proxyMap.constEnd() && it.key() == index) { 0333 it.value()->changed(); 0334 ++it; 0335 } 0336 } 0337 } 0338 0339 0340 void KTp::AbstractGroupingProxyModel::onLoad() 0341 { 0342 if (d->source->rowCount() > 0) { 0343 onRowsInserted(QModelIndex(), 0, d->source->rowCount()-1); 0344 } 0345 connect(d->source, SIGNAL(modelReset()), SLOT(onModelReset())); 0346 connect(d->source, SIGNAL(rowsInserted(QModelIndex, int,int)), SLOT(onRowsInserted(QModelIndex,int,int))); 0347 connect(d->source, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(onRowsRemoved(QModelIndex,int,int))); 0348 connect(d->source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(onDataChanged(QModelIndex,QModelIndex))); 0349 } 0350 0351 /* Called when source model gets reset 0352 * Delete all local caches/maps and wipe the current QStandardItemModel 0353 */ 0354 0355 void KTp::AbstractGroupingProxyModel::onModelReset() 0356 { 0357 clear(); 0358 d->groupCache.clear(); 0359 d->proxyMap.clear(); 0360 d->groupMap.clear(); 0361 qCDebug(KTP_MODELS) << "reset"; 0362 0363 if (d->source->rowCount() > 0) { 0364 onRowsInserted(QModelIndex(), 0, d->source->rowCount()-1); 0365 } 0366 } 0367 0368 GroupNode* KTp::AbstractGroupingProxyModel::itemForGroup(const QString &group) 0369 { 0370 if (d->groupMap.contains(group)) { 0371 return d->groupMap[group]; 0372 } else { 0373 GroupNode* item = new GroupNode(group); 0374 appendRow(item); 0375 d->groupMap[group] = item; 0376 return item; 0377 } 0378 }