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