File indexing completed on 2024-05-12 05:10:47

0001 /*
0002   SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0005 */
0006 
0007 #include "akonadicalendar_debug.h"
0008 #include "incidencetreemodel_p.h"
0009 
0010 #include <Akonadi/EntityTreeModel>
0011 
0012 using namespace Akonadi;
0013 QDebug operator<<(QDebug s, const Node::Ptr &node);
0014 
0015 static void calculateDepth(const Node::Ptr &node)
0016 {
0017     Q_ASSERT(node);
0018     node->depth = node->parentNode ? 1 + node->parentNode->depth : 0;
0019     for (const Node::Ptr &child : std::as_const(node->directChilds)) {
0020         calculateDepth(child);
0021     }
0022 }
0023 
0024 // Desired ordering [...],3,2,1,0,-1
0025 static bool reverseDepthLessThan(const Node::Ptr &node1, const Node::Ptr &node2)
0026 {
0027     return node1->depth > node2->depth;
0028 }
0029 
0030 // Desired ordering 0,1,2,3,[...],-1
0031 static bool depthLessThan(const PreNode::Ptr &node1, const PreNode::Ptr &node2)
0032 {
0033     if (node1->depth == -1) {
0034         return false;
0035     }
0036     return node1->depth < node2->depth || node2->depth == -1;
0037 }
0038 
0039 static PreNode::List sortedPrenodes(const PreNode::List &nodes)
0040 {
0041     const int count = nodes.count();
0042     QHash<QString, PreNode::Ptr> prenodeByUid;
0043     PreNode::List remainingNodes = nodes;
0044 
0045     while (prenodeByUid.count() < count) {
0046         const auto preSize = prenodeByUid.count(); // this saves us from infinite looping if the parent doesn't exist
0047         for (const PreNode::Ptr &node : nodes) {
0048             Q_ASSERT(node);
0049             const QString uid = node->incidence->instanceIdentifier();
0050             const QString parentUid = node->incidence->relatedTo();
0051             if (parentUid.isEmpty()) { // toplevel todo
0052                 prenodeByUid.insert(uid, node);
0053                 remainingNodes.removeAll(node);
0054                 node->depth = 0;
0055             } else {
0056                 if (prenodeByUid.contains(parentUid)) {
0057                     node->depth = 1 + prenodeByUid.value(parentUid)->depth;
0058                     remainingNodes.removeAll(node);
0059                     prenodeByUid.insert(uid, node);
0060                 }
0061             }
0062         }
0063 
0064         if (preSize == prenodeByUid.count()) {
0065             break;
0066         }
0067     }
0068 
0069     PreNode::List sorted = nodes;
0070     std::sort(sorted.begin(), sorted.end(), depthLessThan);
0071     return sorted;
0072 }
0073 
0074 IncidenceTreeModelPrivate::IncidenceTreeModelPrivate(IncidenceTreeModel *qq, const QStringList &mimeTypes)
0075     : QObject()
0076     , m_mimeTypes(mimeTypes)
0077     , q(qq)
0078 {
0079 }
0080 
0081 int IncidenceTreeModelPrivate::rowForNode(const Node::Ptr &node) const
0082 {
0083     // Returns it's row number
0084     const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node);
0085     Q_ASSERT(row != -1);
0086     return row;
0087 }
0088 
0089 void IncidenceTreeModelPrivate::assert_and_dump(bool condition, const QString &message)
0090 {
0091     if (!condition) {
0092         qCCritical(AKONADICALENDAR_LOG) << "This should never happen: " << message;
0093         dumpTree();
0094         Q_ASSERT(false);
0095     }
0096 }
0097 
0098 void IncidenceTreeModelPrivate::dumpTree()
0099 {
0100     for (const Node::Ptr &node : std::as_const(m_toplevelNodeList)) {
0101         qCDebug(AKONADICALENDAR_LOG) << node;
0102     }
0103 }
0104 
0105 QModelIndex IncidenceTreeModelPrivate::indexForNode(const Node::Ptr &node) const
0106 {
0107     if (!node) {
0108         return {};
0109     }
0110     const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node);
0111 
0112     Q_ASSERT(row != -1);
0113     return q->createIndex(row, 0, node.data());
0114 }
0115 
0116 void IncidenceTreeModelPrivate::reset(bool silent)
0117 {
0118     if (!silent) {
0119         q->beginResetModel();
0120     }
0121     m_toplevelNodeList.clear();
0122     m_nodeMap.clear();
0123     m_itemByUid.clear();
0124     m_waitingForParent.clear();
0125     m_uidMap.clear();
0126     if (q->sourceModel()) {
0127         const int sourceCount = q->sourceModel()->rowCount();
0128         for (int i = 0; i < sourceCount; ++i) {
0129             PreNode::Ptr prenode = prenodeFromSourceRow(i);
0130             if (prenode && (m_mimeTypes.isEmpty() || m_mimeTypes.contains(prenode->incidence->mimeType()))) {
0131                 insertNode(prenode, /**silent=*/true);
0132             }
0133         }
0134     }
0135     if (!silent) {
0136         q->endResetModel();
0137     }
0138 }
0139 
0140 void IncidenceTreeModelPrivate::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
0141 {
0142     Q_EMIT q->headerDataChanged(orientation, first, last);
0143 }
0144 
0145 void IncidenceTreeModelPrivate::onDataChanged(const QModelIndex &begin, const QModelIndex &end)
0146 {
0147     Q_ASSERT(begin.isValid());
0148     Q_ASSERT(end.isValid());
0149     Q_ASSERT(q->sourceModel());
0150     Q_ASSERT(!begin.parent().isValid());
0151     Q_ASSERT(!end.parent().isValid());
0152     Q_ASSERT(begin.row() <= end.row());
0153     const int first_row = begin.row();
0154     const int last_row = end.row();
0155 
0156     for (int i = first_row; i <= last_row; ++i) {
0157         QModelIndex sourceIndex = q->sourceModel()->index(i, 0);
0158         Q_ASSERT(sourceIndex.isValid());
0159         QModelIndex index = q->mapFromSource(sourceIndex);
0160         // Index might be invalid if we filter by incidence type.
0161         if (index.isValid()) {
0162             Q_ASSERT(index.internalPointer());
0163 
0164             // Did we this node change parent? If no, just Q_EMIT dataChanged(), if
0165             // yes, we must Q_EMIT rowsMoved(), so we see a visual effect in the view.
0166             Node *rawNode = reinterpret_cast<Node *>(index.internalPointer());
0167             Node::Ptr node = m_uidMap.value(rawNode->uid); // Looks hackish but it's safe
0168             Q_ASSERT(node);
0169             Node::Ptr oldParentNode = node->parentNode;
0170             auto item = q->data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
0171             Q_ASSERT(item.isValid());
0172             KCalendarCore::Incidence::Ptr incidence =
0173                 !item.hasPayload<KCalendarCore::Incidence::Ptr>() ? KCalendarCore::Incidence::Ptr() : item.payload<KCalendarCore::Incidence::Ptr>();
0174             if (!incidence) {
0175                 qCCritical(AKONADICALENDAR_LOG) << "Incidence shouldn't be invalid." << item.hasPayload() << item.id();
0176                 Q_ASSERT(false);
0177                 return;
0178             }
0179 
0180             // An UID could have changed, update hashes!
0181             if (node->uid != incidence->instanceIdentifier()) {
0182                 qCDebug(AKONADICALENDAR_LOG) << "Incidence UID has changed" << node->uid << incidence->instanceIdentifier();
0183                 m_itemByUid.remove(node->uid);
0184                 m_uidMap.remove(node->uid);
0185                 node->uid = incidence->instanceIdentifier();
0186                 m_uidMap.insert(node->uid, node);
0187             }
0188             m_itemByUid.insert(incidence->instanceIdentifier(), item);
0189 
0190             Node::Ptr newParentNode;
0191             const QString newParentUid = incidence->relatedTo();
0192             if (!newParentUid.isEmpty()) {
0193                 Q_ASSERT(m_uidMap.contains(newParentUid));
0194                 newParentNode = m_uidMap.value(newParentUid);
0195                 Q_ASSERT(newParentNode);
0196             }
0197 
0198             const bool parentChanged = newParentNode.data() != oldParentNode.data();
0199 
0200             if (parentChanged) {
0201                 const int fromRow = rowForNode(node);
0202                 int toRow = -1;
0203                 QModelIndex newParentIndex;
0204 
0205                 // Calculate parameters for beginMoveRows()
0206                 if (newParentNode) {
0207                     newParentIndex = q->mapFromSource(newParentNode->sourceIndex);
0208                     Q_ASSERT(newParentIndex.isValid());
0209                     toRow = newParentNode->directChilds.count();
0210                 } else {
0211                     // New parent is 0, it's son of root now
0212                     newParentIndex = QModelIndex();
0213                     toRow = m_toplevelNodeList.count();
0214                 }
0215 
0216                 const bool res = q->beginMoveRows(/**fromParent*/ index.parent(), fromRow, fromRow, newParentIndex, toRow);
0217                 Q_ASSERT(res);
0218                 Q_UNUSED(res)
0219 
0220                 // Now that beginmoveRows() was called, we can do the actual moving:
0221                 if (newParentNode) {
0222                     newParentNode->directChilds.append(node); // Add to new parent
0223                     node->parentNode = newParentNode;
0224 
0225                     if (oldParentNode) {
0226                         oldParentNode->directChilds.remove(fromRow); // Remove from parent
0227                         Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1);
0228                     } else {
0229                         m_toplevelNodeList.remove(fromRow); // Remove from root
0230                         Q_ASSERT(m_toplevelNodeList.indexOf(node) == -1);
0231                     }
0232                 } else {
0233                     // New parent is 0, it's son of root now
0234                     m_toplevelNodeList.append(node);
0235                     node->parentNode = Node::Ptr();
0236                     oldParentNode->directChilds.remove(fromRow);
0237                     Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1);
0238                 }
0239 
0240                 q->endMoveRows();
0241 
0242                 // index is rotten after the move, retrieve it again
0243                 index = indexForNode(node);
0244                 Q_ASSERT(index.isValid());
0245 
0246                 if (newParentNode) {
0247                     Q_EMIT q->indexChangedParent(index.parent());
0248                 }
0249             } else {
0250                 Q_EMIT q->dataChanged(index, index);
0251             }
0252         }
0253     }
0254 }
0255 
0256 void IncidenceTreeModelPrivate::onRowsAboutToBeInserted(const QModelIndex &parent, int, int)
0257 {
0258     // We are a reparenting proxy, the source proxy is flat
0259     Q_ASSERT(!parent.isValid());
0260     Q_UNUSED(parent)
0261     // Nothing to do yet. We don't know if all the new incidences in this range belong to the same
0262     // parent yet.
0263 }
0264 
0265 PreNode::Ptr IncidenceTreeModelPrivate::prenodeFromSourceRow(int row) const
0266 {
0267     PreNode::Ptr node = PreNode::Ptr(new PreNode());
0268     node->sourceIndex = q->sourceModel()->index(row, 0, QModelIndex());
0269     Q_ASSERT(node->sourceIndex.isValid());
0270     Q_ASSERT(node->sourceIndex.model() == q->sourceModel());
0271     const auto item = node->sourceIndex.data(EntityTreeModel::ItemRole).value<Akonadi::Item>();
0272 
0273     if (!item.isValid()) {
0274         // It's a Collection, ignore that, we only want items.
0275         return {};
0276     }
0277 
0278     node->item = item;
0279     node->incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0280     Q_ASSERT(node->incidence);
0281 
0282     return node;
0283 }
0284 
0285 void IncidenceTreeModelPrivate::onRowsInserted(const QModelIndex &parent, int begin, int end)
0286 {
0287     // QElapsedTimer timer;
0288     // timer.start();
0289     Q_ASSERT(!parent.isValid());
0290     Q_UNUSED(parent)
0291     Q_ASSERT(begin <= end);
0292     PreNode::List nodes;
0293     for (int i = begin; i <= end; ++i) {
0294         PreNode::Ptr node = prenodeFromSourceRow(i);
0295         // if m_mimeTypes is empty, we ignore this feature
0296         if (!node || (!m_mimeTypes.isEmpty() && !m_mimeTypes.contains(node->incidence->mimeType()))) {
0297             continue;
0298         }
0299         nodes << node;
0300     }
0301 
0302     const PreNode::List sortedNodes = sortedPrenodes(nodes);
0303 
0304     for (const PreNode::Ptr &node : sortedNodes) {
0305         insertNode(node);
0306     }
0307 
0308     // view can now call KConfigViewStateSaver::restoreState(), to expand nodes.
0309     if (end > begin) {
0310         Q_EMIT q->batchInsertionFinished();
0311     }
0312     // qCDebug(AKONADICALENDAR_LOG) << "Took " << timer.elapsed() << " to insert " << end-begin+1;
0313 }
0314 
0315 void IncidenceTreeModelPrivate::insertNode(const PreNode::Ptr &prenode, bool silent)
0316 {
0317     KCalendarCore::Incidence::Ptr incidence = prenode->incidence;
0318     Akonadi::Item item = prenode->item;
0319     Node::Ptr node(new Node());
0320     node->sourceIndex = prenode->sourceIndex;
0321     node->id = item.id();
0322     node->uid = incidence->instanceIdentifier();
0323     m_itemByUid.insert(node->uid, item);
0324     // qCDebug(AKONADICALENDAR_LOG) << "New node " << node.data() << node->uid << node->id;
0325     node->parentUid = incidence->relatedTo();
0326     if (node->uid == node->parentUid) {
0327         qCWarning(AKONADICALENDAR_LOG) << "Incidence with itself as parent!" << node->uid << "Akonadi item" << item.id() << "remoteId=" << item.remoteId();
0328         node->parentUid.clear();
0329     }
0330 
0331     if (m_uidMap.contains(node->uid)) {
0332         qCWarning(AKONADICALENDAR_LOG) << "Duplicate incidence detected:"
0333                                        << "uid=" << node->uid << ". File a bug against the resource. collection=" << item.storageCollectionId();
0334         return;
0335     }
0336 
0337     Q_ASSERT(!m_nodeMap.contains(node->id));
0338     m_uidMap.insert(node->uid, node);
0339     m_nodeMap.insert(item.id(), node);
0340 
0341     int rowToUse = -1;
0342     bool mustInsertIntoParent = false;
0343 
0344     const bool hasParent = !node->parentUid.isEmpty();
0345     if (hasParent) {
0346         // We have a parent, did he arrive yet ?
0347         if (m_uidMap.contains(node->parentUid)) {
0348             node->parentNode = m_uidMap.value(node->parentUid);
0349 
0350             // We can only insert after beginInsertRows(), because it affects rowCounts
0351             mustInsertIntoParent = true;
0352             rowToUse = node->parentNode->directChilds.count();
0353         } else {
0354             // Parent unknown, we are orphan for now
0355             Q_ASSERT(!m_waitingForParent.contains(node->parentUid, node));
0356             m_waitingForParent.insert(node->parentUid, node);
0357         }
0358     }
0359 
0360     if (!node->parentNode) {
0361         rowToUse = m_toplevelNodeList.count();
0362     }
0363 
0364     // Lets insert the row:
0365     const QModelIndex &parent = indexForNode(node->parentNode);
0366     if (!silent) {
0367         q->beginInsertRows(parent, rowToUse, rowToUse);
0368     }
0369 
0370     if (!node->parentNode) {
0371         m_toplevelNodeList.append(node);
0372     }
0373 
0374     if (mustInsertIntoParent) {
0375         node->parentNode->directChilds.append(node);
0376     }
0377 
0378     if (!silent) {
0379         q->endInsertRows();
0380     }
0381 
0382     // Are we a parent?
0383     if (m_waitingForParent.contains(node->uid)) {
0384         Q_ASSERT(m_waitingForParent.count(node->uid) > 0);
0385         const QList<Node::Ptr> children = m_waitingForParent.values(node->uid);
0386         m_waitingForParent.remove(node->uid);
0387         Q_ASSERT(!children.isEmpty());
0388 
0389         for (const Node::Ptr &child : children) {
0390             const int fromRow = m_toplevelNodeList.indexOf(child);
0391             Q_ASSERT(fromRow != -1);
0392             const QModelIndex toParent = indexForNode(node);
0393             Q_ASSERT(toParent.isValid());
0394             Q_ASSERT(toParent.model() == q);
0395             // const int toRow = node->directChilds.count();
0396 
0397             if (!silent) {
0398                 // const bool res = q->beginMoveRows( /**fromParent*/QModelIndex(), fromRow,
0399                 //                                 fromRow, toParent, toRow );
0400                 // Q_EMIT q->layoutAboutToBeChanged();
0401                 q->beginResetModel();
0402                 // Q_ASSERT( res );
0403             }
0404             child->parentNode = node;
0405             node->directChilds.append(child);
0406             m_toplevelNodeList.remove(fromRow);
0407 
0408             if (!silent) {
0409                 // q->endMoveRows();
0410                 q->endResetModel();
0411                 // Q_EMIT q->layoutChanged();
0412             }
0413         }
0414     }
0415 }
0416 
0417 // Sorts children first parents last
0418 Node::List IncidenceTreeModelPrivate::sorted(const Node::List &nodes) const
0419 {
0420     if (nodes.isEmpty()) {
0421         return nodes;
0422     }
0423 
0424     // Initialize depths
0425     for (const Node::Ptr &topLevelNode : std::as_const(m_toplevelNodeList)) {
0426         calculateDepth(topLevelNode);
0427     }
0428 
0429     Node::List sorted = nodes;
0430     std::sort(sorted.begin(), sorted.end(), reverseDepthLessThan);
0431 
0432     return sorted;
0433 }
0434 
0435 void IncidenceTreeModelPrivate::onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end)
0436 {
0437     // QElapsedTimer timer;
0438     // timer.start();
0439     Q_ASSERT(!parent.isValid());
0440     Q_UNUSED(parent)
0441     Q_ASSERT(begin <= end);
0442 
0443     // First, gather nodes to remove
0444     Node::List nodesToRemove;
0445     for (int i = begin; i <= end; ++i) {
0446         QModelIndex sourceIndex = q->sourceModel()->index(i, 0, QModelIndex());
0447         Q_ASSERT(sourceIndex.isValid());
0448         Q_ASSERT(sourceIndex.model() == q->sourceModel());
0449         const Akonadi::Item::Id id = sourceIndex.data(EntityTreeModel::ItemIdRole).toLongLong();
0450         Q_ASSERT(id != -1);
0451         if (!m_nodeMap.contains(id)) {
0452             // We don't know about this one because we're ignoring it's mime type.
0453             Q_ASSERT(m_mimeTypes.count() != 3);
0454             continue;
0455         }
0456         Node::Ptr node = m_nodeMap.value(id);
0457         Q_ASSERT(node->id == id);
0458         nodesToRemove << node;
0459     }
0460 
0461     // We want to remove children first, to avoid row moving
0462     const Node::List nodesToRemoveSorted = sorted(nodesToRemove);
0463 
0464     for (const Node::Ptr &node : nodesToRemoveSorted) {
0465         // Go ahead and remove it now. We don't do it in ::onRowsRemoved(), because
0466         // while unparenting children with moveRows() the view might call data() on the
0467         // item that is already removed from ETM.
0468         removeNode(node);
0469         // qCDebug(AKONADICALENDAR_LOG) << "Just removed a node, here's the tree";
0470         // dumpTree();
0471     }
0472 
0473     m_removedNodes.clear();
0474     // qCDebug(AKONADICALENDAR_LOG) << "Took " << timer.elapsed() << " to remove " << end-begin+1;
0475 }
0476 
0477 void IncidenceTreeModelPrivate::removeNode(const Node::Ptr &node)
0478 {
0479     Q_ASSERT(node);
0480     // qCDebug(AKONADICALENDAR_LOG) << "Dealing with parent: " << node->id << node.data()
0481     //         << node->uid << node->directChilds.count() << indexForNode( node );
0482 
0483     // First, unparent the children
0484     if (!node->directChilds.isEmpty()) {
0485         const Node::List children = node->directChilds;
0486         const QModelIndex fromParent = indexForNode(node);
0487         Q_ASSERT(fromParent.isValid());
0488         //    const int firstSourceRow = 0;
0489         //  const int lastSourceRow  = node->directChilds.count() - 1;
0490         // const int toRow = m_toplevelNodeList.count();
0491         // q->beginMoveRows( fromParent, firstSourceRow, lastSourceRow,
0492         //                  /**toParent is root*/QModelIndex(), toRow );
0493         q->beginResetModel();
0494         node->directChilds.clear();
0495         for (const Node::Ptr &child : children) {
0496             // qCDebug(AKONADICALENDAR_LOG) << "Dealing with child: " << child.data() << child->uid;
0497             m_toplevelNodeList.append(child);
0498             child->parentNode = Node::Ptr();
0499             m_waitingForParent.insert(node->uid, child);
0500         }
0501         // q->endMoveRows();
0502         q->endResetModel();
0503     }
0504 
0505     const QModelIndex parent = indexForNode(node->parentNode);
0506 
0507     const int rowToRemove = rowForNode(node);
0508 
0509     // Now remove the row
0510     Q_ASSERT(!(parent.isValid() && parent.model() != q));
0511     q->beginRemoveRows(parent, rowToRemove, rowToRemove);
0512     m_itemByUid.remove(node->uid);
0513 
0514     if (parent.isValid()) {
0515         node->parentNode->directChilds.remove(rowToRemove);
0516         node->parentNode = Node::Ptr();
0517     } else {
0518         m_toplevelNodeList.remove(rowToRemove);
0519     }
0520 
0521     if (!node->parentUid.isEmpty()) {
0522         m_waitingForParent.remove(node->parentUid, node);
0523     }
0524 
0525     m_uidMap.remove(node->uid);
0526     m_nodeMap.remove(node->id);
0527 
0528     q->endRemoveRows();
0529     m_removedNodes << node.data();
0530 }
0531 
0532 void IncidenceTreeModelPrivate::onRowsRemoved(const QModelIndex &parent, int begin, int end)
0533 {
0534     Q_UNUSED(parent)
0535     Q_UNUSED(begin)
0536     Q_UNUSED(end)
0537     // Nothing to do here, see comment on ::onRowsAboutToBeRemoved()
0538 }
0539 
0540 void IncidenceTreeModelPrivate::onModelAboutToBeReset()
0541 {
0542     q->beginResetModel();
0543 }
0544 
0545 void IncidenceTreeModelPrivate::onModelReset()
0546 {
0547     reset(/**silent=*/false);
0548     q->endResetModel();
0549 }
0550 
0551 void IncidenceTreeModelPrivate::onLayoutAboutToBeChanged()
0552 {
0553     Q_ASSERT(q->persistentIndexList().isEmpty());
0554     Q_EMIT q->layoutAboutToBeChanged();
0555 }
0556 
0557 void IncidenceTreeModelPrivate::onLayoutChanged()
0558 {
0559     reset(/**silent=*/true);
0560     Q_ASSERT(q->persistentIndexList().isEmpty());
0561     Q_EMIT q->layoutChanged();
0562 }
0563 
0564 void IncidenceTreeModelPrivate::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)
0565 {
0566     // Not implemented yet
0567     Q_ASSERT(false);
0568 }
0569 
0570 void IncidenceTreeModelPrivate::setSourceModel(QAbstractItemModel *model)
0571 {
0572     q->beginResetModel();
0573 
0574     if (q->sourceModel()) {
0575         disconnect(q->sourceModel(), &IncidenceTreeModel::dataChanged, this, &IncidenceTreeModelPrivate::onDataChanged);
0576 
0577         disconnect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged, this, &IncidenceTreeModelPrivate::onHeaderDataChanged);
0578 
0579         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsInserted, this, &IncidenceTreeModelPrivate::onRowsInserted);
0580 
0581         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved, this, &IncidenceTreeModelPrivate::onRowsRemoved);
0582 
0583         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsMoved, this, &IncidenceTreeModelPrivate::onRowsMoved);
0584 
0585         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted, this, &IncidenceTreeModelPrivate::onRowsAboutToBeInserted);
0586 
0587         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved, this, &IncidenceTreeModelPrivate::onRowsAboutToBeRemoved);
0588 
0589         disconnect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset, this, &IncidenceTreeModelPrivate::onModelAboutToBeReset);
0590 
0591         disconnect(q->sourceModel(), &IncidenceTreeModel::modelReset, this, &IncidenceTreeModelPrivate::onModelReset);
0592 
0593         disconnect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged, this, &IncidenceTreeModelPrivate::onLayoutAboutToBeChanged);
0594 
0595         disconnect(q->sourceModel(), &IncidenceTreeModel::layoutChanged, this, &IncidenceTreeModelPrivate::onLayoutChanged);
0596     }
0597 
0598     q->QAbstractProxyModel::setSourceModel(model);
0599 
0600     if (q->sourceModel()) {
0601         connect(q->sourceModel(), &IncidenceTreeModel::dataChanged, this, &IncidenceTreeModelPrivate::onDataChanged);
0602 
0603         connect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged, this, &IncidenceTreeModelPrivate::onHeaderDataChanged);
0604 
0605         connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted, this, &IncidenceTreeModelPrivate::onRowsAboutToBeInserted);
0606 
0607         connect(q->sourceModel(), &IncidenceTreeModel::rowsInserted, this, &IncidenceTreeModelPrivate::onRowsInserted);
0608 
0609         connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved, this, &IncidenceTreeModelPrivate::onRowsAboutToBeRemoved);
0610 
0611         connect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved, this, &IncidenceTreeModelPrivate::onRowsRemoved);
0612 
0613         connect(q->sourceModel(), &IncidenceTreeModel::rowsMoved, this, &IncidenceTreeModelPrivate::onRowsMoved);
0614 
0615         connect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset, this, &IncidenceTreeModelPrivate::onModelAboutToBeReset);
0616 
0617         connect(q->sourceModel(), &IncidenceTreeModel::modelReset, this, &IncidenceTreeModelPrivate::onModelReset);
0618 
0619         connect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged, this, &IncidenceTreeModelPrivate::onLayoutAboutToBeChanged);
0620 
0621         connect(q->sourceModel(), &IncidenceTreeModel::layoutChanged, this, &IncidenceTreeModelPrivate::onLayoutChanged);
0622     }
0623 
0624     reset(/**silent=*/true);
0625     q->endResetModel();
0626 }
0627 
0628 IncidenceTreeModel::IncidenceTreeModel(QObject *parent)
0629     : QAbstractProxyModel(parent)
0630     , d(new IncidenceTreeModelPrivate(this, QStringList()))
0631 {
0632     setObjectName(QLatin1StringView("IncidenceTreeModel"));
0633 }
0634 
0635 IncidenceTreeModel::IncidenceTreeModel(const QStringList &mimeTypes, QObject *parent)
0636     : QAbstractProxyModel(parent)
0637     , d(new IncidenceTreeModelPrivate(this, mimeTypes))
0638 {
0639     setObjectName(QLatin1StringView("IncidenceTreeModel"));
0640 }
0641 
0642 IncidenceTreeModel::~IncidenceTreeModel() = default;
0643 
0644 QVariant IncidenceTreeModel::data(const QModelIndex &index, int role) const
0645 {
0646     Q_ASSERT(index.isValid());
0647     if (!index.isValid() || !sourceModel()) {
0648         return {};
0649     }
0650 
0651     QModelIndex sourceIndex = mapToSource(index);
0652     Q_ASSERT(sourceIndex.isValid());
0653 
0654     return sourceModel()->data(sourceIndex, role);
0655 }
0656 
0657 int IncidenceTreeModel::rowCount(const QModelIndex &parent) const
0658 {
0659     if (parent.isValid()) {
0660         Q_ASSERT(parent.model() == this);
0661         Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
0662         Q_ASSERT(parentNode);
0663         d->assert_and_dump(!d->m_removedNodes.contains(parentNode), QString::number((quintptr)parentNode, 16) + QLatin1StringView(" was already deleted"));
0664 
0665         const int count = parentNode->directChilds.count();
0666         return count;
0667     }
0668 
0669     return d->m_toplevelNodeList.count();
0670 }
0671 
0672 int IncidenceTreeModel::columnCount(const QModelIndex &parent) const
0673 {
0674     if (parent.isValid()) {
0675         Q_ASSERT(parent.model() == this);
0676     }
0677     return sourceModel() ? sourceModel()->columnCount() : 1;
0678 }
0679 
0680 void IncidenceTreeModel::setSourceModel(QAbstractItemModel *model)
0681 {
0682     if (model == sourceModel()) {
0683         return;
0684     }
0685     d->setSourceModel(model);
0686 }
0687 
0688 QModelIndex IncidenceTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
0689 {
0690     if (!sourceIndex.isValid()) {
0691         qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::mapFromSource() source index is invalid";
0692         // Q_ASSERT( false );
0693         return {};
0694     }
0695 
0696     if (!sourceModel()) {
0697         return {};
0698     }
0699     Q_ASSERT(sourceIndex.column() < sourceModel()->columnCount());
0700     Q_ASSERT(sourceModel() == sourceIndex.model());
0701     const Akonadi::Item::Id id = sourceIndex.data(Akonadi::EntityTreeModel::ItemIdRole).toLongLong();
0702 
0703     if (id == -1 || !d->m_nodeMap.contains(id)) {
0704         return {};
0705     }
0706 
0707     const Node::Ptr node = d->m_nodeMap.value(id);
0708     Q_ASSERT(node);
0709 
0710     return d->indexForNode(node);
0711 }
0712 
0713 QModelIndex IncidenceTreeModel::mapToSource(const QModelIndex &proxyIndex) const
0714 {
0715     if (!proxyIndex.isValid() || !sourceModel()) {
0716         return {};
0717     }
0718 
0719     Q_ASSERT(proxyIndex.column() < columnCount());
0720     Q_ASSERT(proxyIndex.internalPointer());
0721     Q_ASSERT(proxyIndex.model() == this);
0722     Node *node = reinterpret_cast<Node *>(proxyIndex.internalPointer());
0723 
0724     /*
0725      This code is slow, using a persistent model index instead.
0726     QModelIndexList indexes = EntityTreeModel::modelIndexesForItem( sourceModel(), Akonadi::Item( node->id ) );
0727     if ( indexes.isEmpty() ) {
0728       Q_ASSERT( sourceModel() );
0729       qCCritical(AKONADICALENDAR_LOG) << "IncidenceTreeModel::mapToSource() no indexes."
0730                << proxyIndex << node->id << "; source.rowCount() = "
0731                << sourceModel()->rowCount() << "; source=" << sourceModel()
0732                << "rowCount()" << rowCount();
0733       Q_ASSERT( false );
0734       return QModelIndex();
0735     }
0736     QModelIndex index = indexes.first();*/
0737     QModelIndex index = node->sourceIndex;
0738     if (!index.isValid()) {
0739         qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid";
0740         Q_ASSERT(false);
0741         return {};
0742     }
0743     Q_ASSERT(index.model() == sourceModel());
0744 
0745     return index.sibling(index.row(), proxyIndex.column());
0746 }
0747 
0748 QModelIndex IncidenceTreeModel::parent(const QModelIndex &child) const
0749 {
0750     if (!child.isValid()) {
0751         qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::parent(): child is invalid";
0752         Q_ASSERT(false);
0753         return {};
0754     }
0755 
0756     Q_ASSERT(child.model() == this);
0757     Q_ASSERT(child.internalPointer());
0758     Node *childNode = reinterpret_cast<Node *>(child.internalPointer());
0759     if (d->m_removedNodes.contains(childNode)) {
0760         qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::parent() Node already removed.";
0761         return {};
0762     }
0763 
0764     if (!childNode->parentNode) {
0765         return {};
0766     }
0767 
0768     const QModelIndex parentIndex = d->indexForNode(childNode->parentNode);
0769 
0770     if (!parentIndex.isValid()) {
0771         qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::parent(): proxyModelIndex is invalid.";
0772         Q_ASSERT(false);
0773         return {};
0774     }
0775 
0776     Q_ASSERT(parentIndex.model() == this);
0777     Q_ASSERT(childNode->parentNode.data());
0778 
0779     // Parent is always at row 0
0780     return parentIndex;
0781 }
0782 
0783 QModelIndex IncidenceTreeModel::index(int row, int column, const QModelIndex &parent) const
0784 {
0785     if (row < 0 || row >= rowCount(parent)) {
0786         // This is ok apparently
0787         /*qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::index() parent.isValid()" << parent.isValid()
0788                    << "; row=" << row << "; column=" << column
0789                    << "; rowCount() = " << rowCount( parent ); */
0790         // Q_ASSERT( false );
0791         return {};
0792     }
0793 
0794     Q_ASSERT(column >= 0);
0795     Q_ASSERT(column < columnCount());
0796 
0797     if (parent.isValid()) {
0798         Q_ASSERT(parent.model() == this);
0799         Q_ASSERT(parent.internalPointer());
0800         Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
0801 
0802         if (row >= parentNode->directChilds.count()) {
0803             qCCritical(AKONADICALENDAR_LOG) << "IncidenceTreeModel::index() row=" << row << "; column=" << column;
0804             Q_ASSERT(false);
0805             return {};
0806         }
0807 
0808         return createIndex(row, column, parentNode->directChilds.at(row).data());
0809     } else {
0810         Q_ASSERT(row < d->m_toplevelNodeList.count());
0811         Node::Ptr node = d->m_toplevelNodeList.at(row);
0812         Q_ASSERT(node);
0813         return createIndex(row, column, node.data());
0814     }
0815 }
0816 
0817 bool IncidenceTreeModel::hasChildren(const QModelIndex &parent) const
0818 {
0819     if (parent.isValid()) {
0820         Q_ASSERT(parent.column() < columnCount());
0821         if (parent.column() != 0) {
0822             // Indexes at column >0 don't have parent, says Qt documentation
0823             return false;
0824         }
0825         Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
0826         Q_ASSERT(parentNode);
0827         return !parentNode->directChilds.isEmpty();
0828     } else {
0829         return !d->m_toplevelNodeList.isEmpty();
0830     }
0831 }
0832 
0833 Akonadi::Item IncidenceTreeModel::item(const QString &uid) const
0834 {
0835     Akonadi::Item item;
0836     if (uid.isEmpty()) {
0837         qCWarning(AKONADICALENDAR_LOG) << "Called with an empty uid";
0838     } else {
0839         if (d->m_itemByUid.contains(uid)) {
0840             item = d->m_itemByUid.value(uid);
0841         } else {
0842             qCWarning(AKONADICALENDAR_LOG) << "There's no incidence with uid " << uid;
0843         }
0844     }
0845 
0846     return item;
0847 }
0848 
0849 QDebug operator<<(QDebug s, const Node::Ptr &node)
0850 {
0851     Q_ASSERT(node);
0852     static int level = 0;
0853     ++level;
0854     QString padding = QString(level - 1, QLatin1Char(' '));
0855     s << padding + QLatin1StringView("node") << node.data() << QStringLiteral(";uid=") << node->uid << QStringLiteral(";id=") << node->id
0856       << QStringLiteral(";parentUid=") << node->parentUid << QStringLiteral(";parentNode=") << (void *)(node->parentNode.data()) << '\n';
0857 
0858     for (const Node::Ptr &child : std::as_const(node->directChilds)) {
0859         s << child;
0860     }
0861 
0862     --level;
0863     return s;
0864 }
0865 
0866 #include "moc_incidencetreemodel_p.cpp"
0867 
0868 #include "moc_incidencetreemodel.cpp"