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 }