Akonadi Calendar

incidencetreemodel.cpp
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 "akonadicalendar_debug.h"
8#include "incidencetreemodel_p.h"
9
10#include <Akonadi/EntityTreeModel>
11
12using namespace Akonadi;
13QDebug operator<<(QDebug s, const Node::Ptr &node);
14
15static 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
25static 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
31static 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
39static PreNode::List sortedPrenodes(const PreNode::List &nodes)
40{
41 const int count = nodes.count();
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
74IncidenceTreeModelPrivate::IncidenceTreeModelPrivate(IncidenceTreeModel *qq, const QStringList &mimeTypes)
75 : QObject()
76 , m_mimeTypes(mimeTypes)
77 , q(qq)
78{
79}
80
81int 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
89void IncidenceTreeModelPrivate::assert_and_dump(bool condition, const QString &message)
90{
91 if (!condition) {
92 qCCritical(AKONADICALENDAR_LOG) << "This should never happen: " << message;
93 dumpTree();
94 Q_ASSERT(false);
95 }
96}
97
98void IncidenceTreeModelPrivate::dumpTree()
99{
100 for (const Node::Ptr &node : std::as_const(m_toplevelNodeList)) {
101 qCDebug(AKONADICALENDAR_LOG) << node;
102 }
103}
104
105QModelIndex 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
116void 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
140void IncidenceTreeModelPrivate::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
141{
142 Q_EMIT q->headerDataChanged(orientation, first, last);
143}
144
145void 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());
173 !item.hasPayload<KCalendarCore::Incidence::Ptr>() ? KCalendarCore::Incidence::Ptr() : item.payload<KCalendarCore::Incidence::Ptr>();
174 if (!incidence) {
175 qCCritical(AKONADICALENDAR_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(AKONADICALENDAR_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
256void 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
265PreNode::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 {};
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
285void 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(AKONADICALENDAR_LOG) << "Took " << timer.elapsed() << " to insert " << end-begin+1;
313}
314
315void 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(AKONADICALENDAR_LOG) << "New node " << node.data() << node->uid << node->id;
325 node->parentUid = incidence->relatedTo();
326 if (node->uid == node->parentUid) {
327 qCWarning(AKONADICALENDAR_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(AKONADICALENDAR_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
418Node::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
435void 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());
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(AKONADICALENDAR_LOG) << "Just removed a node, here's the tree";
470 // dumpTree();
471 }
472
473 m_removedNodes.clear();
474 // qCDebug(AKONADICALENDAR_LOG) << "Took " << timer.elapsed() << " to remove " << end-begin+1;
475}
476
477void IncidenceTreeModelPrivate::removeNode(const Node::Ptr &node)
478{
479 Q_ASSERT(node);
480 // qCDebug(AKONADICALENDAR_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(AKONADICALENDAR_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
532void 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
540void IncidenceTreeModelPrivate::onModelAboutToBeReset()
541{
542 q->beginResetModel();
543}
544
545void IncidenceTreeModelPrivate::onModelReset()
546{
547 reset(/**silent=*/false);
548 q->endResetModel();
549}
550
551void IncidenceTreeModelPrivate::onLayoutAboutToBeChanged()
552{
553 Q_ASSERT(q->persistentIndexList().isEmpty());
554 Q_EMIT q->layoutAboutToBeChanged();
555}
556
557void IncidenceTreeModelPrivate::onLayoutChanged()
558{
559 reset(/**silent=*/true);
560 Q_ASSERT(q->persistentIndexList().isEmpty());
561 Q_EMIT q->layoutChanged();
562}
563
564void IncidenceTreeModelPrivate::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)
565{
566 // Not implemented yet
567 Q_ASSERT(false);
568}
569
570void 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
629 : QAbstractProxyModel(parent)
630 , d(new IncidenceTreeModelPrivate(this, QStringList()))
631{
632 setObjectName(QLatin1StringView("IncidenceTreeModel"));
633}
634
636 : QAbstractProxyModel(parent)
637 , d(new IncidenceTreeModelPrivate(this, mimeTypes))
638{
639 setObjectName(QLatin1StringView("IncidenceTreeModel"));
640}
641
642IncidenceTreeModel::~IncidenceTreeModel() = default;
643
644QVariant IncidenceTreeModel::data(const QModelIndex &index, int role) const
645{
646 Q_ASSERT(index.isValid());
647 if (!index.isValid() || !sourceModel()) {
648 return {};
649 }
650
651 QModelIndex sourceIndex = mapToSource(index);
652 Q_ASSERT(sourceIndex.isValid());
653
654 return sourceModel()->data(sourceIndex, role);
655}
656
657int 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) + QLatin1StringView(" was already deleted"));
664
665 const int count = parentNode->directChilds.count();
666 return count;
667 }
668
669 return d->m_toplevelNodeList.count();
670}
671
672int 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
680void IncidenceTreeModel::setSourceModel(QAbstractItemModel *model)
681{
682 if (model == sourceModel()) {
683 return;
684 }
685 d->setSourceModel(model);
686}
687
688QModelIndex IncidenceTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
689{
690 if (!sourceIndex.isValid()) {
691 qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::mapFromSource() source index is invalid";
692 // Q_ASSERT( false );
693 return {};
694 }
695
696 if (!sourceModel()) {
697 return {};
698 }
699 Q_ASSERT(sourceIndex.column() < sourceModel()->columnCount());
700 Q_ASSERT(sourceModel() == sourceIndex.model());
702
703 if (id == -1 || !d->m_nodeMap.contains(id)) {
704 return {};
705 }
706
707 const Node::Ptr node = d->m_nodeMap.value(id);
708 Q_ASSERT(node);
709
710 return d->indexForNode(node);
711}
712
713QModelIndex 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(AKONADICALENDAR_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(AKONADICALENDAR_LOG) << "IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid";
740 Q_ASSERT(false);
741 return {};
742 }
743 Q_ASSERT(index.model() == sourceModel());
744
745 return index.sibling(index.row(), proxyIndex.column());
746}
747
749{
750 if (!child.isValid()) {
751 qCWarning(AKONADICALENDAR_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(AKONADICALENDAR_LOG) << "IncidenceTreeModel::parent() Node already removed.";
761 return {};
762 }
763
764 if (!childNode->parentNode) {
765 return {};
766 }
767
768 const QModelIndex parentIndex = d->indexForNode(childNode->parentNode);
769
770 if (!parentIndex.isValid()) {
771 qCWarning(AKONADICALENDAR_LOG) << "IncidenceTreeModel::parent(): proxyModelIndex is invalid.";
772 Q_ASSERT(false);
773 return {};
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
783QModelIndex 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(AKONADICALENDAR_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(AKONADICALENDAR_LOG) << "IncidenceTreeModel::index() row=" << row << "; column=" << column;
804 Q_ASSERT(false);
805 return {};
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
817bool 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
834{
836 if (uid.isEmpty()) {
837 qCWarning(AKONADICALENDAR_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(AKONADICALENDAR_LOG) << "There's no incidence with uid " << uid;
843 }
844 }
845
846 return item;
847}
848
849QDebug 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 + QLatin1StringView("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
866#include "moc_incidencetreemodel_p.cpp"
867
868#include "moc_incidencetreemodel.cpp"
Hierarchical incidence model.
Akonadi::Item item(const QString &incidenceUid) const
Returns the akonadi item containing the incidence with incidenceUid.
IncidenceTreeModel(QObject *parent=nullptr)
Constructs a new IncidenceTreeModel.
Id id() const
T payload() const
Collection::Id storageCollectionId() const
QString remoteId() const
Node * parentNode() const
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
Returns the incidence from an Akonadi item, or a null pointer if the item has no such payload.
FreeBusyManager::Singleton.
KGuiItem reset()
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
QDebug operator<<(QDebug dbg, const PerceptualColor::MultiSpinBoxSection &value)
QModelIndex createIndex(int row, int column, const void *ptr) const const
bool contains(const Key &key) const const
qsizetype count() const const
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
const char * data() const const
bool isEmpty() const const
void remove(qsizetype i, qsizetype n)
int column() const const
QVariant data(int role) const const
void * internalPointer() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QObject * parent() const const
void setObjectName(QAnyStringView name)
void clear()
bool isEmpty() const const
QString number(double n, char format, int precision)
Orientation
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
qlonglong toLongLong(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:07:18 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.