Akonadi Calendar

incidencetreemodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Sérgio Martins <[email protected]>
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 
12 using namespace Akonadi;
13 QDebug operator<<(QDebug s, const Node::Ptr &node);
14 
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
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
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 
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 
74 IncidenceTreeModelPrivate::IncidenceTreeModelPrivate(IncidenceTreeModel *qq, const QStringList &mimeTypes)
75  : QObject()
76  , m_mimeTypes(mimeTypes)
77  , q(qq)
78 {
79 }
80 
81 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 
89 void 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 
98 void IncidenceTreeModelPrivate::dumpTree()
99 {
100  for (const Node::Ptr &node : std::as_const(m_toplevelNodeList)) {
101  qCDebug(AKONADICALENDAR_LOG) << node;
102  }
103 }
104 
105 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 
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 
140 void IncidenceTreeModelPrivate::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
141 {
142  Q_EMIT q->headerDataChanged(orientation, first, last);
143 }
144 
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());
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 
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 
265 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 {};
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 
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(AKONADICALENDAR_LOG) << "Took " << timer.elapsed() << " to insert " << end-begin+1;
313 }
314 
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(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
418 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 
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());
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 
477 void 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 
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 
540 void IncidenceTreeModelPrivate::onModelAboutToBeReset()
541 {
542  q->beginResetModel();
543 }
544 
545 void IncidenceTreeModelPrivate::onModelReset()
546 {
547  reset(/**silent=*/false);
548  q->endResetModel();
549 }
550 
551 void IncidenceTreeModelPrivate::onLayoutAboutToBeChanged()
552 {
553  Q_ASSERT(q->persistentIndexList().isEmpty());
554  Q_EMIT q->layoutAboutToBeChanged();
555 }
556 
557 void IncidenceTreeModelPrivate::onLayoutChanged()
558 {
559  reset(/**silent=*/true);
560  Q_ASSERT(q->persistentIndexList().isEmpty());
561  Q_EMIT q->layoutChanged();
562 }
563 
564 void IncidenceTreeModelPrivate::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)
565 {
566  // Not implemented yet
567  Q_ASSERT(false);
568 }
569 
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 
629  : QAbstractProxyModel(parent)
630  , d(new IncidenceTreeModelPrivate(this, QStringList()))
631 {
632  setObjectName(QStringLiteral("IncidenceTreeModel"));
633 }
634 
636  : QAbstractProxyModel(parent)
637  , d(new IncidenceTreeModelPrivate(this, mimeTypes))
638 {
639  setObjectName(QStringLiteral("IncidenceTreeModel"));
640 }
641 
642 IncidenceTreeModel::~IncidenceTreeModel() = default;
643 
644 QVariant 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 
657 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 
672 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 
680 void IncidenceTreeModel::setSourceModel(QAbstractItemModel *model)
681 {
682  if (model == sourceModel()) {
683  return;
684  }
685  d->setSourceModel(model);
686 }
687 
688 QModelIndex 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 
713 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(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 
783 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(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 
817 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 
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 
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 }
const T value(const Key &key) const const
QString number(int n, int base)
QModelIndex sibling(int row, int column) const const
void * internalPointer() const const
int column() const const
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void clear()
QDataStream & operator<<(QDataStream &out, const KDateTime &dateTime)
qlonglong toLongLong(bool *ok) const const
const QList< QKeySequence > & begin()
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.
QHash::iterator insert(const Key &key, const T &value)
void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
QVariant data(int role) const const
QModelIndex createIndex(int row, int column, void *ptr) const const
Collection::Id storageCollectionId() const
Akonadi::Item item(const QString &incidenceUid) const
Returns the akonadi item containing the incidence with incidenceUid.
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
Hierarchical incidence model.
Orientation
bool isEmpty() const const
bool isEmpty() const const
void headerDataChanged(Qt::Orientation orientation, int first, int last)
Node * parentNode() const
bool isValid() const const
int row() const const
IncidenceTreeModel(QObject *parent=nullptr)
Constructs a new IncidenceTreeModel.
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
Id id() const
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
void setObjectName(const QString &name)
QStringList mimeTypes(Mode mode=Writing)
KGuiItem reset()
void layoutAboutToBeChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
QModelIndex parent() const const
const char * data() const const
int count(const Key &key) const const
T payload() const
bool contains(const Key &key) const const
QObject * parent() const const
QString message
const QAbstractItemModel * model() const const
const QList< QKeySequence > & end()
QString remoteId() const
void modelAboutToBeReset()
FreeBusyManager::Singleton.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Wed Sep 28 2022 03:52:06 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.