1 /*
2   SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
3 
4   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
5 */
6 
7 #include "calendarview_debug.h"
8 #include "incidencetreemodel_p.h"
9 
10 #include <Akonadi/EntityTreeModel>
11 
12 using namespace Akonadi;
13 QDebug operator<<(QDebug s, const Node::Ptr &node);
14 
calculateDepth(const Node::Ptr & node)15 static void calculateDepth(const Node::Ptr &node)
16 {
17     Q_ASSERT(node);
18     node->depth = node->parentNode ? 1 + node->parentNode->depth : 0;
19     for (const Node::Ptr &child : std::as_const(node->directChilds)) {
20         calculateDepth(child);
21     }
22 }
23 
24 // Desired ordering [...],3,2,1,0,-1
reverseDepthLessThan(const Node::Ptr & node1,const Node::Ptr & node2)25 static bool reverseDepthLessThan(const Node::Ptr &node1, const Node::Ptr &node2)
26 {
27     return node1->depth > node2->depth;
28 }
29 
30 // Desired ordering 0,1,2,3,[...],-1
depthLessThan(const PreNode::Ptr & node1,const PreNode::Ptr & node2)31 static bool depthLessThan(const PreNode::Ptr &node1, const PreNode::Ptr &node2)
32 {
33     if (node1->depth == -1) {
34         return false;
35     }
36     return node1->depth < node2->depth || node2->depth == -1;
37 }
38 
sortedPrenodes(const PreNode::List & nodes)39 static PreNode::List sortedPrenodes(const PreNode::List &nodes)
40 {
41     const int count = nodes.count();
42     QHash<QString, PreNode::Ptr> prenodeByUid;
43     PreNode::List remainingNodes = nodes;
44 
45     while (prenodeByUid.count() < count) {
46         const auto preSize = prenodeByUid.count(); // this saves us from infinite looping if the parent doesn't exist
47         for (const PreNode::Ptr &node : nodes) {
48             Q_ASSERT(node);
49             const QString uid = node->incidence->instanceIdentifier();
50             const QString parentUid = node->incidence->relatedTo();
51             if (parentUid.isEmpty()) { // toplevel todo
52                 prenodeByUid.insert(uid, node);
53                 remainingNodes.removeAll(node);
54                 node->depth = 0;
55             } else {
56                 if (prenodeByUid.contains(parentUid)) {
57                     node->depth = 1 + prenodeByUid.value(parentUid)->depth;
58                     remainingNodes.removeAll(node);
59                     prenodeByUid.insert(uid, node);
60                 }
61             }
62         }
63 
64         if (preSize == prenodeByUid.count()) {
65             break;
66         }
67     }
68 
69     PreNode::List sorted = nodes;
70     std::sort(sorted.begin(), sorted.end(), depthLessThan);
71     return sorted;
72 }
73 
IncidenceTreeModelPrivate(IncidenceTreeModel * qq,const QStringList & mimeTypes)74 IncidenceTreeModelPrivate::IncidenceTreeModelPrivate(IncidenceTreeModel *qq, const QStringList &mimeTypes)
75     : QObject()
76     , m_mimeTypes(mimeTypes)
77     , q(qq)
78 {
79 }
80 
rowForNode(const Node::Ptr & node) const81 int IncidenceTreeModelPrivate::rowForNode(const Node::Ptr &node) const
82 {
83     // Returns it's row number
84     const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node);
85     Q_ASSERT(row != -1);
86     return row;
87 }
88 
assert_and_dump(bool condition,const QString & message)89 void IncidenceTreeModelPrivate::assert_and_dump(bool condition, const QString &message)
90 {
91     if (!condition) {
92         qCCritical(CALENDARVIEW_LOG) << "This should never happen: " << message;
93         dumpTree();
94         Q_ASSERT(false);
95     }
96 }
97 
dumpTree()98 void IncidenceTreeModelPrivate::dumpTree()
99 {
100     for (const Node::Ptr &node : std::as_const(m_toplevelNodeList)) {
101         qCDebug(CALENDARVIEW_LOG) << node;
102     }
103 }
104 
indexForNode(const Node::Ptr & node) const105 QModelIndex IncidenceTreeModelPrivate::indexForNode(const Node::Ptr &node) const
106 {
107     if (!node) {
108         return {};
109     }
110     const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node);
111 
112     Q_ASSERT(row != -1);
113     return q->createIndex(row, 0, node.data());
114 }
115 
reset(bool silent)116 void IncidenceTreeModelPrivate::reset(bool silent)
117 {
118     if (!silent) {
119         q->beginResetModel();
120     }
121     m_toplevelNodeList.clear();
122     m_nodeMap.clear();
123     m_itemByUid.clear();
124     m_waitingForParent.clear();
125     m_uidMap.clear();
126     if (q->sourceModel()) {
127         const int sourceCount = q->sourceModel()->rowCount();
128         for (int i = 0; i < sourceCount; ++i) {
129             PreNode::Ptr prenode = prenodeFromSourceRow(i);
130             if (prenode && (m_mimeTypes.isEmpty() || m_mimeTypes.contains(prenode->incidence->mimeType()))) {
131                 insertNode(prenode, /**silent=*/true);
132             }
133         }
134     }
135     if (!silent) {
136         q->endResetModel();
137     }
138 }
139 
onHeaderDataChanged(Qt::Orientation orientation,int first,int last)140 void IncidenceTreeModelPrivate::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
141 {
142     Q_EMIT q->headerDataChanged(orientation, first, last);
143 }
144 
onDataChanged(const QModelIndex & begin,const QModelIndex & end)145 void IncidenceTreeModelPrivate::onDataChanged(const QModelIndex &begin, const QModelIndex &end)
146 {
147     Q_ASSERT(begin.isValid());
148     Q_ASSERT(end.isValid());
149     Q_ASSERT(q->sourceModel());
150     Q_ASSERT(!begin.parent().isValid());
151     Q_ASSERT(!end.parent().isValid());
152     Q_ASSERT(begin.row() <= end.row());
153     const int first_row = begin.row();
154     const int last_row = end.row();
155 
156     for (int i = first_row; i <= last_row; ++i) {
157         QModelIndex sourceIndex = q->sourceModel()->index(i, 0);
158         Q_ASSERT(sourceIndex.isValid());
159         QModelIndex index = q->mapFromSource(sourceIndex);
160         // Index might be invalid if we filter by incidence type.
161         if (index.isValid()) {
162             Q_ASSERT(index.internalPointer());
163 
164             // Did we this node change parent? If no, just Q_EMIT dataChanged(), if
165             // yes, we must Q_EMIT rowsMoved(), so we see a visual effect in the view.
166             Node *rawNode = reinterpret_cast<Node *>(index.internalPointer());
167             Node::Ptr node = m_uidMap.value(rawNode->uid); // Looks hackish but it's safe
168             Q_ASSERT(node);
169             Node::Ptr oldParentNode = node->parentNode;
170             auto item = q->data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
171             Q_ASSERT(item.isValid());
172             KCalendarCore::Incidence::Ptr incidence =
173                 !item.hasPayload<KCalendarCore::Incidence::Ptr>() ? KCalendarCore::Incidence::Ptr() : item.payload<KCalendarCore::Incidence::Ptr>();
174             if (!incidence) {
175                 qCCritical(CALENDARVIEW_LOG) << "Incidence shouldn't be invalid." << item.hasPayload() << item.id();
176                 Q_ASSERT(false);
177                 return;
178             }
179 
180             // An UID could have changed, update hashes!
181             if (node->uid != incidence->instanceIdentifier()) {
182                 qCDebug(CALENDARVIEW_LOG) << "Incidence UID has changed" << node->uid << incidence->instanceIdentifier();
183                 m_itemByUid.remove(node->uid);
184                 m_uidMap.remove(node->uid);
185                 node->uid = incidence->instanceIdentifier();
186                 m_uidMap.insert(node->uid, node);
187             }
188             m_itemByUid.insert(incidence->instanceIdentifier(), item);
189 
190             Node::Ptr newParentNode;
191             const QString newParentUid = incidence->relatedTo();
192             if (!newParentUid.isEmpty()) {
193                 Q_ASSERT(m_uidMap.contains(newParentUid));
194                 newParentNode = m_uidMap.value(newParentUid);
195                 Q_ASSERT(newParentNode);
196             }
197 
198             const bool parentChanged = newParentNode.data() != oldParentNode.data();
199 
200             if (parentChanged) {
201                 const int fromRow = rowForNode(node);
202                 int toRow = -1;
203                 QModelIndex newParentIndex;
204 
205                 // Calculate parameters for beginMoveRows()
206                 if (newParentNode) {
207                     newParentIndex = q->mapFromSource(newParentNode->sourceIndex);
208                     Q_ASSERT(newParentIndex.isValid());
209                     toRow = newParentNode->directChilds.count();
210                 } else {
211                     // New parent is 0, it's son of root now
212                     newParentIndex = QModelIndex();
213                     toRow = m_toplevelNodeList.count();
214                 }
215 
216                 const bool res = q->beginMoveRows(/**fromParent*/ index.parent(), fromRow, fromRow, newParentIndex, toRow);
217                 Q_ASSERT(res);
218                 Q_UNUSED(res)
219 
220                 // Now that beginmoveRows() was called, we can do the actual moving:
221                 if (newParentNode) {
222                     newParentNode->directChilds.append(node); // Add to new parent
223                     node->parentNode = newParentNode;
224 
225                     if (oldParentNode) {
226                         oldParentNode->directChilds.remove(fromRow); // Remove from parent
227                         Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1);
228                     } else {
229                         m_toplevelNodeList.remove(fromRow); // Remove from root
230                         Q_ASSERT(m_toplevelNodeList.indexOf(node) == -1);
231                     }
232                 } else {
233                     // New parent is 0, it's son of root now
234                     m_toplevelNodeList.append(node);
235                     node->parentNode = Node::Ptr();
236                     oldParentNode->directChilds.remove(fromRow);
237                     Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1);
238                 }
239 
240                 q->endMoveRows();
241 
242                 // index is rotten after the move, retrieve it again
243                 index = indexForNode(node);
244                 Q_ASSERT(index.isValid());
245 
246                 if (newParentNode) {
247                     Q_EMIT q->indexChangedParent(index.parent());
248                 }
249             } else {
250                 Q_EMIT q->dataChanged(index, index);
251             }
252         }
253     }
254 }
255 
onRowsAboutToBeInserted(const QModelIndex & parent,int,int)256 void IncidenceTreeModelPrivate::onRowsAboutToBeInserted(const QModelIndex &parent, int, int)
257 {
258     // We are a reparenting proxy, the source proxy is flat
259     Q_ASSERT(!parent.isValid());
260     Q_UNUSED(parent)
261     // Nothing to do yet. We don't know if all the new incidences in this range belong to the same
262     // parent yet.
263 }
264 
prenodeFromSourceRow(int row) const265 PreNode::Ptr IncidenceTreeModelPrivate::prenodeFromSourceRow(int row) const
266 {
267     PreNode::Ptr node = PreNode::Ptr(new PreNode());
268     node->sourceIndex = q->sourceModel()->index(row, 0, QModelIndex());
269     Q_ASSERT(node->sourceIndex.isValid());
270     Q_ASSERT(node->sourceIndex.model() == q->sourceModel());
271     const auto item = node->sourceIndex.data(EntityTreeModel::ItemRole).value<Akonadi::Item>();
272 
273     if (!item.isValid()) {
274         // It's a Collection, ignore that, we only want items.
275         return PreNode::Ptr();
276     }
277 
278     node->item = item;
279     node->incidence = item.payload<KCalendarCore::Incidence::Ptr>();
280     Q_ASSERT(node->incidence);
281 
282     return node;
283 }
284 
onRowsInserted(const QModelIndex & parent,int begin,int end)285 void IncidenceTreeModelPrivate::onRowsInserted(const QModelIndex &parent, int begin, int end)
286 {
287     // QElapsedTimer timer;
288     // timer.start();
289     Q_ASSERT(!parent.isValid());
290     Q_UNUSED(parent)
291     Q_ASSERT(begin <= end);
292     PreNode::List nodes;
293     for (int i = begin; i <= end; ++i) {
294         PreNode::Ptr node = prenodeFromSourceRow(i);
295         // if m_mimeTypes is empty, we ignore this feature
296         if (!node || (!m_mimeTypes.isEmpty() && !m_mimeTypes.contains(node->incidence->mimeType()))) {
297             continue;
298         }
299         nodes << node;
300     }
301 
302     const PreNode::List sortedNodes = sortedPrenodes(nodes);
303 
304     for (const PreNode::Ptr &node : sortedNodes) {
305         insertNode(node);
306     }
307 
308     // view can now call KConfigViewStateSaver::restoreState(), to expand nodes.
309     if (end > begin) {
310         Q_EMIT q->batchInsertionFinished();
311     }
312     // qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed() << " to insert " << end-begin+1;
313 }
314 
insertNode(const PreNode::Ptr & prenode,bool silent)315 void IncidenceTreeModelPrivate::insertNode(const PreNode::Ptr &prenode, bool silent)
316 {
317     KCalendarCore::Incidence::Ptr incidence = prenode->incidence;
318     Akonadi::Item item = prenode->item;
319     Node::Ptr node(new Node());
320     node->sourceIndex = prenode->sourceIndex;
321     node->id = item.id();
322     node->uid = incidence->instanceIdentifier();
323     m_itemByUid.insert(node->uid, item);
324     // qCDebug(CALENDARVIEW_LOG) << "New node " << node.data() << node->uid << node->id;
325     node->parentUid = incidence->relatedTo();
326     if (node->uid == node->parentUid) {
327         qCWarning(CALENDARVIEW_LOG) << "Incidence with itself as parent!" << node->uid << "Akonadi item" << item.id() << "remoteId=" << item.remoteId();
328         node->parentUid.clear();
329     }
330 
331     if (m_uidMap.contains(node->uid)) {
332         qCWarning(CALENDARVIEW_LOG) << "Duplicate incidence detected:"
333                                     << "uid=" << node->uid << ". File a bug against the resource. collection=" << item.storageCollectionId();
334         return;
335     }
336 
337     Q_ASSERT(!m_nodeMap.contains(node->id));
338     m_uidMap.insert(node->uid, node);
339     m_nodeMap.insert(item.id(), node);
340 
341     int rowToUse = -1;
342     bool mustInsertIntoParent = false;
343 
344     const bool hasParent = !node->parentUid.isEmpty();
345     if (hasParent) {
346         // We have a parent, did he arrive yet ?
347         if (m_uidMap.contains(node->parentUid)) {
348             node->parentNode = m_uidMap.value(node->parentUid);
349 
350             // We can only insert after beginInsertRows(), because it affects rowCounts
351             mustInsertIntoParent = true;
352             rowToUse = node->parentNode->directChilds.count();
353         } else {
354             // Parent unknown, we are orphan for now
355             Q_ASSERT(!m_waitingForParent.contains(node->parentUid, node));
356             m_waitingForParent.insert(node->parentUid, node);
357         }
358     }
359 
360     if (!node->parentNode) {
361         rowToUse = m_toplevelNodeList.count();
362     }
363 
364     // Lets insert the row:
365     const QModelIndex &parent = indexForNode(node->parentNode);
366     if (!silent) {
367         q->beginInsertRows(parent, rowToUse, rowToUse);
368     }
369 
370     if (!node->parentNode) {
371         m_toplevelNodeList.append(node);
372     }
373 
374     if (mustInsertIntoParent) {
375         node->parentNode->directChilds.append(node);
376     }
377 
378     if (!silent) {
379         q->endInsertRows();
380     }
381 
382     // Are we a parent?
383     if (m_waitingForParent.contains(node->uid)) {
384         Q_ASSERT(m_waitingForParent.count(node->uid) > 0);
385         const QList<Node::Ptr> children = m_waitingForParent.values(node->uid);
386         m_waitingForParent.remove(node->uid);
387         Q_ASSERT(!children.isEmpty());
388 
389         for (const Node::Ptr &child : children) {
390             const int fromRow = m_toplevelNodeList.indexOf(child);
391             Q_ASSERT(fromRow != -1);
392             const QModelIndex toParent = indexForNode(node);
393             Q_ASSERT(toParent.isValid());
394             Q_ASSERT(toParent.model() == q);
395             // const int toRow = node->directChilds.count();
396 
397             if (!silent) {
398                 // const bool res = q->beginMoveRows( /**fromParent*/QModelIndex(), fromRow,
399                 //                                 fromRow, toParent, toRow );
400                 // Q_EMIT q->layoutAboutToBeChanged();
401                 q->beginResetModel();
402                 // Q_ASSERT( res );
403             }
404             child->parentNode = node;
405             node->directChilds.append(child);
406             m_toplevelNodeList.remove(fromRow);
407 
408             if (!silent) {
409                 // q->endMoveRows();
410                 q->endResetModel();
411                 // Q_EMIT q->layoutChanged();
412             }
413         }
414     }
415 }
416 
417 // Sorts children first parents last
sorted(const Node::List & nodes) const418 Node::List IncidenceTreeModelPrivate::sorted(const Node::List &nodes) const
419 {
420     if (nodes.isEmpty()) {
421         return nodes;
422     }
423 
424     // Initialize depths
425     for (const Node::Ptr &topLevelNode : std::as_const(m_toplevelNodeList)) {
426         calculateDepth(topLevelNode);
427     }
428 
429     Node::List sorted = nodes;
430     std::sort(sorted.begin(), sorted.end(), reverseDepthLessThan);
431 
432     return sorted;
433 }
434 
onRowsAboutToBeRemoved(const QModelIndex & parent,int begin,int end)435 void IncidenceTreeModelPrivate::onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end)
436 {
437     // QElapsedTimer timer;
438     // timer.start();
439     Q_ASSERT(!parent.isValid());
440     Q_UNUSED(parent)
441     Q_ASSERT(begin <= end);
442 
443     // First, gather nodes to remove
444     Node::List nodesToRemove;
445     for (int i = begin; i <= end; ++i) {
446         QModelIndex sourceIndex = q->sourceModel()->index(i, 0, QModelIndex());
447         Q_ASSERT(sourceIndex.isValid());
448         Q_ASSERT(sourceIndex.model() == q->sourceModel());
449         const Akonadi::Item::Id id = sourceIndex.data(EntityTreeModel::ItemIdRole).toLongLong();
450         Q_ASSERT(id != -1);
451         if (!m_nodeMap.contains(id)) {
452             // We don't know about this one because we're ignoring it's mime type.
453             Q_ASSERT(m_mimeTypes.count() != 3);
454             continue;
455         }
456         Node::Ptr node = m_nodeMap.value(id);
457         Q_ASSERT(node->id == id);
458         nodesToRemove << node;
459     }
460 
461     // We want to remove children first, to avoid row moving
462     const Node::List nodesToRemoveSorted = sorted(nodesToRemove);
463 
464     for (const Node::Ptr &node : nodesToRemoveSorted) {
465         // Go ahead and remove it now. We don't do it in ::onRowsRemoved(), because
466         // while unparenting children with moveRows() the view might call data() on the
467         // item that is already removed from ETM.
468         removeNode(node);
469         // qCDebug(CALENDARVIEW_LOG) << "Just removed a node, here's the tree";
470         // dumpTree();
471     }
472 
473     m_removedNodes.clear();
474     // qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed() << " to remove " << end-begin+1;
475 }
476 
removeNode(const Node::Ptr & node)477 void IncidenceTreeModelPrivate::removeNode(const Node::Ptr &node)
478 {
479     Q_ASSERT(node);
480     // qCDebug(CALENDARVIEW_LOG) << "Dealing with parent: " << node->id << node.data()
481     //         << node->uid << node->directChilds.count() << indexForNode( node );
482 
483     // First, unparent the children
484     if (!node->directChilds.isEmpty()) {
485         const Node::List children = node->directChilds;
486         const QModelIndex fromParent = indexForNode(node);
487         Q_ASSERT(fromParent.isValid());
488         //    const int firstSourceRow = 0;
489         //  const int lastSourceRow  = node->directChilds.count() - 1;
490         // const int toRow = m_toplevelNodeList.count();
491         // q->beginMoveRows( fromParent, firstSourceRow, lastSourceRow,
492         //                  /**toParent is root*/QModelIndex(), toRow );
493         q->beginResetModel();
494         node->directChilds.clear();
495         for (const Node::Ptr &child : children) {
496             // qCDebug(CALENDARVIEW_LOG) << "Dealing with child: " << child.data() << child->uid;
497             m_toplevelNodeList.append(child);
498             child->parentNode = Node::Ptr();
499             m_waitingForParent.insert(node->uid, child);
500         }
501         // q->endMoveRows();
502         q->endResetModel();
503     }
504 
505     const QModelIndex parent = indexForNode(node->parentNode);
506 
507     const int rowToRemove = rowForNode(node);
508 
509     // Now remove the row
510     Q_ASSERT(!(parent.isValid() && parent.model() != q));
511     q->beginRemoveRows(parent, rowToRemove, rowToRemove);
512     m_itemByUid.remove(node->uid);
513 
514     if (parent.isValid()) {
515         node->parentNode->directChilds.remove(rowToRemove);
516         node->parentNode = Node::Ptr();
517     } else {
518         m_toplevelNodeList.remove(rowToRemove);
519     }
520 
521     if (!node->parentUid.isEmpty()) {
522         m_waitingForParent.remove(node->parentUid, node);
523     }
524 
525     m_uidMap.remove(node->uid);
526     m_nodeMap.remove(node->id);
527 
528     q->endRemoveRows();
529     m_removedNodes << node.data();
530 }
531 
onRowsRemoved(const QModelIndex & parent,int begin,int end)532 void IncidenceTreeModelPrivate::onRowsRemoved(const QModelIndex &parent, int begin, int end)
533 {
534     Q_UNUSED(parent)
535     Q_UNUSED(begin)
536     Q_UNUSED(end)
537     // Nothing to do here, see comment on ::onRowsAboutToBeRemoved()
538 }
539 
onModelAboutToBeReset()540 void IncidenceTreeModelPrivate::onModelAboutToBeReset()
541 {
542     q->beginResetModel();
543 }
544 
onModelReset()545 void IncidenceTreeModelPrivate::onModelReset()
546 {
547     reset(/**silent=*/false);
548     q->endResetModel();
549 }
550 
onLayoutAboutToBeChanged()551 void IncidenceTreeModelPrivate::onLayoutAboutToBeChanged()
552 {
553     Q_ASSERT(q->persistentIndexList().isEmpty());
554     Q_EMIT q->layoutAboutToBeChanged();
555 }
556 
onLayoutChanged()557 void IncidenceTreeModelPrivate::onLayoutChanged()
558 {
559     reset(/**silent=*/true);
560     Q_ASSERT(q->persistentIndexList().isEmpty());
561     Q_EMIT q->layoutChanged();
562 }
563 
onRowsMoved(const QModelIndex &,int,int,const QModelIndex &,int)564 void IncidenceTreeModelPrivate::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)
565 {
566     // Not implemented yet
567     Q_ASSERT(false);
568 }
569 
setSourceModel(QAbstractItemModel * model)570 void IncidenceTreeModelPrivate::setSourceModel(QAbstractItemModel *model)
571 {
572     q->beginResetModel();
573 
574     if (q->sourceModel()) {
575         disconnect(q->sourceModel(), &IncidenceTreeModel::dataChanged, this, &IncidenceTreeModelPrivate::onDataChanged);
576 
577         disconnect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged, this, &IncidenceTreeModelPrivate::onHeaderDataChanged);
578 
579         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsInserted, this, &IncidenceTreeModelPrivate::onRowsInserted);
580 
581         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved, this, &IncidenceTreeModelPrivate::onRowsRemoved);
582 
583         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsMoved, this, &IncidenceTreeModelPrivate::onRowsMoved);
584 
585         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted, this, &IncidenceTreeModelPrivate::onRowsAboutToBeInserted);
586 
587         disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved, this, &IncidenceTreeModelPrivate::onRowsAboutToBeRemoved);
588 
589         disconnect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset, this, &IncidenceTreeModelPrivate::onModelAboutToBeReset);
590 
591         disconnect(q->sourceModel(), &IncidenceTreeModel::modelReset, this, &IncidenceTreeModelPrivate::onModelReset);
592 
593         disconnect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged, this, &IncidenceTreeModelPrivate::onLayoutAboutToBeChanged);
594 
595         disconnect(q->sourceModel(), &IncidenceTreeModel::layoutChanged, this, &IncidenceTreeModelPrivate::onLayoutChanged);
596     }
597 
598     q->QAbstractProxyModel::setSourceModel(model);
599 
600     if (q->sourceModel()) {
601         connect(q->sourceModel(), &IncidenceTreeModel::dataChanged, this, &IncidenceTreeModelPrivate::onDataChanged);
602 
603         connect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged, this, &IncidenceTreeModelPrivate::onHeaderDataChanged);
604 
605         connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted, this, &IncidenceTreeModelPrivate::onRowsAboutToBeInserted);
606 
607         connect(q->sourceModel(), &IncidenceTreeModel::rowsInserted, this, &IncidenceTreeModelPrivate::onRowsInserted);
608 
609         connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved, this, &IncidenceTreeModelPrivate::onRowsAboutToBeRemoved);
610 
611         connect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved, this, &IncidenceTreeModelPrivate::onRowsRemoved);
612 
613         connect(q->sourceModel(), &IncidenceTreeModel::rowsMoved, this, &IncidenceTreeModelPrivate::onRowsMoved);
614 
615         connect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset, this, &IncidenceTreeModelPrivate::onModelAboutToBeReset);
616 
617         connect(q->sourceModel(), &IncidenceTreeModel::modelReset, this, &IncidenceTreeModelPrivate::onModelReset);
618 
619         connect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged, this, &IncidenceTreeModelPrivate::onLayoutAboutToBeChanged);
620 
621         connect(q->sourceModel(), &IncidenceTreeModel::layoutChanged, this, &IncidenceTreeModelPrivate::onLayoutChanged);
622     }
623 
624     reset(/**silent=*/true);
625     q->endResetModel();
626 }
627 
IncidenceTreeModel(QObject * parent)628 IncidenceTreeModel::IncidenceTreeModel(QObject *parent)
629     : QAbstractProxyModel(parent)
630     , d(new IncidenceTreeModelPrivate(this, QStringList()))
631 {
632     setObjectName(QStringLiteral("IncidenceTreeModel"));
633 }
634 
IncidenceTreeModel(const QStringList & mimeTypes,QObject * parent)635 IncidenceTreeModel::IncidenceTreeModel(const QStringList &mimeTypes, QObject *parent)
636     : QAbstractProxyModel(parent)
637     , d(new IncidenceTreeModelPrivate(this, mimeTypes))
638 {
639     setObjectName(QStringLiteral("IncidenceTreeModel"));
640 }
641 
642 IncidenceTreeModel::~IncidenceTreeModel() = default;
643 
data(const QModelIndex & index,int role) const644 QVariant IncidenceTreeModel::data(const QModelIndex &index, int role) const
645 {
646     Q_ASSERT(index.isValid());
647     if (!index.isValid() || !sourceModel()) {
648         return QVariant();
649     }
650 
651     QModelIndex sourceIndex = mapToSource(index);
652     Q_ASSERT(sourceIndex.isValid());
653 
654     return sourceModel()->data(sourceIndex, role);
655 }
656 
rowCount(const QModelIndex & parent) const657 int IncidenceTreeModel::rowCount(const QModelIndex &parent) const
658 {
659     if (parent.isValid()) {
660         Q_ASSERT(parent.model() == this);
661         Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
662         Q_ASSERT(parentNode);
663         d->assert_and_dump(!d->m_removedNodes.contains(parentNode), QString::number((quintptr)parentNode, 16) + QLatin1String(" was already deleted"));
664 
665         const int count = parentNode->directChilds.count();
666         return count;
667     }
668 
669     return d->m_toplevelNodeList.count();
670 }
671 
columnCount(const QModelIndex & parent) const672 int IncidenceTreeModel::columnCount(const QModelIndex &parent) const
673 {
674     if (parent.isValid()) {
675         Q_ASSERT(parent.model() == this);
676     }
677     return sourceModel() ? sourceModel()->columnCount() : 1;
678 }
679 
setSourceModel(QAbstractItemModel * model)680 void IncidenceTreeModel::setSourceModel(QAbstractItemModel *model)
681 {
682     if (model == sourceModel()) {
683         return;
684     }
685     d->setSourceModel(model);
686 }
687 
mapFromSource(const QModelIndex & sourceIndex) const688 QModelIndex IncidenceTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
689 {
690     if (!sourceIndex.isValid()) {
691         qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::mapFromSource() source index is invalid";
692         // Q_ASSERT( false );
693         return {};
694     }
695 
696     if (!sourceModel()) {
697         return QModelIndex();
698     }
699     Q_ASSERT(sourceIndex.column() < sourceModel()->columnCount());
700     Q_ASSERT(sourceModel() == sourceIndex.model());
701     const Akonadi::Item::Id id = sourceIndex.data(Akonadi::EntityTreeModel::ItemIdRole).toLongLong();
702 
703     if (id == -1 || !d->m_nodeMap.contains(id)) {
704         return QModelIndex();
705     }
706 
707     const Node::Ptr node = d->m_nodeMap.value(id);
708     Q_ASSERT(node);
709 
710     return d->indexForNode(node);
711 }
712 
mapToSource(const QModelIndex & proxyIndex) const713 QModelIndex IncidenceTreeModel::mapToSource(const QModelIndex &proxyIndex) const
714 {
715     if (!proxyIndex.isValid() || !sourceModel()) {
716         return {};
717     }
718 
719     Q_ASSERT(proxyIndex.column() < columnCount());
720     Q_ASSERT(proxyIndex.internalPointer());
721     Q_ASSERT(proxyIndex.model() == this);
722     Node *node = reinterpret_cast<Node *>(proxyIndex.internalPointer());
723 
724     /*
725      This code is slow, using a persistent model index instead.
726     QModelIndexList indexes = EntityTreeModel::modelIndexesForItem( sourceModel(), Akonadi::Item( node->id ) );
727     if ( indexes.isEmpty() ) {
728       Q_ASSERT( sourceModel() );
729       qCCritical(CALENDARVIEW_LOG) << "IncidenceTreeModel::mapToSource() no indexes."
730                << proxyIndex << node->id << "; source.rowCount() = "
731                << sourceModel()->rowCount() << "; source=" << sourceModel()
732                << "rowCount()" << rowCount();
733       Q_ASSERT( false );
734       return QModelIndex();
735     }
736     QModelIndex index = indexes.first();*/
737     QModelIndex index = node->sourceIndex;
738     if (!index.isValid()) {
739         qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid";
740         Q_ASSERT(false);
741         return QModelIndex();
742     }
743     Q_ASSERT(index.model() == sourceModel());
744 
745     return index.sibling(index.row(), proxyIndex.column());
746 }
747 
parent(const QModelIndex & child) const748 QModelIndex IncidenceTreeModel::parent(const QModelIndex &child) const
749 {
750     if (!child.isValid()) {
751         qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::parent(): child is invalid";
752         Q_ASSERT(false);
753         return {};
754     }
755 
756     Q_ASSERT(child.model() == this);
757     Q_ASSERT(child.internalPointer());
758     Node *childNode = reinterpret_cast<Node *>(child.internalPointer());
759     if (d->m_removedNodes.contains(childNode)) {
760         qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::parent() Node already removed.";
761         return QModelIndex();
762     }
763 
764     if (!childNode->parentNode) {
765         return QModelIndex();
766     }
767 
768     const QModelIndex parentIndex = d->indexForNode(childNode->parentNode);
769 
770     if (!parentIndex.isValid()) {
771         qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::parent(): proxyModelIndex is invalid.";
772         Q_ASSERT(false);
773         return QModelIndex();
774     }
775 
776     Q_ASSERT(parentIndex.model() == this);
777     Q_ASSERT(childNode->parentNode.data());
778 
779     // Parent is always at row 0
780     return parentIndex;
781 }
782 
index(int row,int column,const QModelIndex & parent) const783 QModelIndex IncidenceTreeModel::index(int row, int column, const QModelIndex &parent) const
784 {
785     if (row < 0 || row >= rowCount(parent)) {
786         // This is ok apparently
787         /*qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::index() parent.isValid()" << parent.isValid()
788                    << "; row=" << row << "; column=" << column
789                    << "; rowCount() = " << rowCount( parent ); */
790         // Q_ASSERT( false );
791         return {};
792     }
793 
794     Q_ASSERT(column >= 0);
795     Q_ASSERT(column < columnCount());
796 
797     if (parent.isValid()) {
798         Q_ASSERT(parent.model() == this);
799         Q_ASSERT(parent.internalPointer());
800         Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
801 
802         if (row >= parentNode->directChilds.count()) {
803             qCCritical(CALENDARVIEW_LOG) << "IncidenceTreeModel::index() row=" << row << "; column=" << column;
804             Q_ASSERT(false);
805             return QModelIndex();
806         }
807 
808         return createIndex(row, column, parentNode->directChilds.at(row).data());
809     } else {
810         Q_ASSERT(row < d->m_toplevelNodeList.count());
811         Node::Ptr node = d->m_toplevelNodeList.at(row);
812         Q_ASSERT(node);
813         return createIndex(row, column, node.data());
814     }
815 }
816 
hasChildren(const QModelIndex & parent) const817 bool IncidenceTreeModel::hasChildren(const QModelIndex &parent) const
818 {
819     if (parent.isValid()) {
820         Q_ASSERT(parent.column() < columnCount());
821         if (parent.column() != 0) {
822             // Indexes at column >0 don't have parent, says Qt documentation
823             return false;
824         }
825         Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
826         Q_ASSERT(parentNode);
827         return !parentNode->directChilds.isEmpty();
828     } else {
829         return !d->m_toplevelNodeList.isEmpty();
830     }
831 }
832 
item(const QString & uid) const833 Akonadi::Item IncidenceTreeModel::item(const QString &uid) const
834 {
835     Akonadi::Item item;
836     if (uid.isEmpty()) {
837         qCWarning(CALENDARVIEW_LOG) << "Called with an empty uid";
838     } else {
839         if (d->m_itemByUid.contains(uid)) {
840             item = d->m_itemByUid.value(uid);
841         } else {
842             qCWarning(CALENDARVIEW_LOG) << "There's no incidence with uid " << uid;
843         }
844     }
845 
846     return item;
847 }
848 
operator <<(QDebug s,const Node::Ptr & node)849 QDebug operator<<(QDebug s, const Node::Ptr &node)
850 {
851     Q_ASSERT(node);
852     static int level = 0;
853     ++level;
854     QString padding = QString(level - 1, QLatin1Char(' '));
855     s << padding + QLatin1String("node") << node.data() << QStringLiteral(";uid=") << node->uid << QStringLiteral(";id=") << node->id
856       << QStringLiteral(";parentUid=") << node->parentUid << QStringLiteral(";parentNode=") << (void *)(node->parentNode.data()) << '\n';
857 
858     for (const Node::Ptr &child : std::as_const(node->directChilds)) {
859         s << child;
860     }
861 
862     --level;
863     return s;
864 }
865