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