File indexing completed on 2025-03-09 04:51:37
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0005 */ 0006 0007 #include "reparentingmodel.h" 0008 0009 #include "korganizer_debug.h" 0010 0011 /* 0012 * Notes: 0013 * * layoutChanged must never add or remove nodes. 0014 * * rebuildAll can therefore only be called if it doesn't introduce new nodes or within a reset. 0015 * * The node memory management is done using the node tree, nodes are deleted by being removed from the node tree. 0016 */ 0017 0018 ReparentingModel::Node::Node(ReparentingModel &model) 0019 : personModel(model) 0020 , mIsSourceNode(false) 0021 { 0022 } 0023 0024 ReparentingModel::Node::Node(ReparentingModel &model, ReparentingModel::Node *p, const QModelIndex &srcIndex) 0025 : sourceIndex(srcIndex) 0026 , parent(p) 0027 , personModel(model) 0028 , mIsSourceNode(true) 0029 { 0030 if (sourceIndex.isValid()) { 0031 personModel.mSourceNodes.append(this); 0032 } 0033 Q_ASSERT(parent); 0034 } 0035 0036 ReparentingModel::Node::~Node() 0037 { 0038 // The source index may be invalid meanwhile (it's a persistent index) 0039 personModel.mSourceNodes.removeOne(this); 0040 } 0041 0042 bool ReparentingModel::Node::operator==(const ReparentingModel::Node &node) const 0043 { 0044 return this == &node; 0045 } 0046 0047 ReparentingModel::Node::Ptr ReparentingModel::Node::searchNode(ReparentingModel::Node *node) 0048 { 0049 Node::Ptr nodePtr; 0050 if (node->parent) { 0051 // Reparent node 0052 QList<Node::Ptr>::iterator it = node->parent->children.begin(); 0053 for (; it != node->parent->children.end(); ++it) { 0054 if (it->data() == node) { 0055 // Reuse smart pointer 0056 nodePtr = *it; 0057 node->parent->children.erase(it); 0058 break; 0059 } 0060 } 0061 Q_ASSERT(nodePtr); 0062 } else { 0063 nodePtr = Node::Ptr(node); 0064 } 0065 0066 return nodePtr; 0067 } 0068 0069 void ReparentingModel::Node::reparent(ReparentingModel::Node *node) 0070 { 0071 Node::Ptr nodePtr = searchNode(node); 0072 addChild(nodePtr); 0073 } 0074 0075 void ReparentingModel::Node::addChild(const ReparentingModel::Node::Ptr &node) 0076 { 0077 node->parent = this; 0078 children.append(node); 0079 } 0080 0081 void ReparentingModel::Node::clearHierarchy() 0082 { 0083 parent = nullptr; 0084 children.clear(); 0085 } 0086 0087 bool ReparentingModel::Node::setData(const QVariant & /* value */, int /* role */) 0088 { 0089 return false; 0090 } 0091 0092 QVariant ReparentingModel::Node::data(int role) const 0093 { 0094 if (sourceIndex.isValid()) { 0095 return sourceIndex.data(role); 0096 } 0097 return {}; 0098 } 0099 0100 bool ReparentingModel::Node::adopts(const QModelIndex & /* sourceIndex */) 0101 { 0102 return false; 0103 } 0104 0105 bool ReparentingModel::Node::isDuplicateOf(const QModelIndex & /* sourceIndex */) 0106 { 0107 return false; 0108 } 0109 0110 void ReparentingModel::Node::update(const Node::Ptr & /* node */) 0111 { 0112 } 0113 0114 bool ReparentingModel::Node::isSourceNode() const 0115 { 0116 return mIsSourceNode; 0117 } 0118 0119 int ReparentingModel::Node::row() const 0120 { 0121 Q_ASSERT(parent); 0122 int row = 0; 0123 for (const Node::Ptr &node : std::as_const(parent->children)) { 0124 if (node.data() == this) { 0125 return row; 0126 } 0127 row++; 0128 } 0129 return -1; 0130 } 0131 0132 ReparentingModel::ReparentingModel(QObject *parent) 0133 : QAbstractProxyModel(parent) 0134 , mRootNode(*this) 0135 , mNodeManager(NodeManager::Ptr(new NodeManager(*this))) 0136 { 0137 } 0138 0139 ReparentingModel::~ReparentingModel() 0140 { 0141 // Otherwise we cannot guarantee that the nodes reference to *this is always valid 0142 mRootNode.children.clear(); 0143 mProxyNodes.clear(); 0144 mSourceNodes.clear(); 0145 } 0146 0147 bool ReparentingModel::validateNode(const Node *node) const 0148 { 0149 // Expected: 0150 // * Each node tree starts at mRootNode 0151 // * Each node is listed in the children of it's parent 0152 // * Root node never leaves the model and thus should never enter this function 0153 if (!node) { 0154 qCWarning(KORGANIZER_LOG) << "nullptr"; 0155 return false; 0156 } 0157 if (node == &mRootNode) { 0158 qCWarning(KORGANIZER_LOG) << "is root node"; 0159 return false; 0160 } 0161 const Node *n = node; 0162 int depth = 0; 0163 while (n) { 0164 if ((intptr_t)(n) < 1000) { 0165 // Detect corruptions with unlikely pointers 0166 qCWarning(KORGANIZER_LOG) << "corrupt pointer" << depth; 0167 return false; 0168 } 0169 if (!n->parent) { 0170 qCWarning(KORGANIZER_LOG) << "nullptr parent" << depth << n->isSourceNode(); 0171 return false; 0172 } 0173 if (n->parent == n) { 0174 qCWarning(KORGANIZER_LOG) << "loop" << depth; 0175 return false; 0176 } 0177 0178 bool found = false; 0179 for (const Node::Ptr &child : std::as_const(n->parent->children)) { 0180 if (child.data() == n) { 0181 found = true; 0182 } 0183 } 0184 if (!found) { 0185 qCWarning(KORGANIZER_LOG) << "not linked as child" << depth; 0186 return false; 0187 } 0188 depth++; 0189 if (depth > 1000) { 0190 qCWarning(KORGANIZER_LOG) << "loop detected" << depth; 0191 return false; 0192 } 0193 0194 if (n->parent == &mRootNode) { 0195 return true; 0196 } 0197 // If the parent isn't root there is at least one more level 0198 if (!n->parent->parent) { 0199 qCWarning(KORGANIZER_LOG) << "missing parent parent" << depth; 0200 return false; 0201 } 0202 if (n->parent->parent == n) { 0203 qCWarning(KORGANIZER_LOG) << "parent parent loop" << depth; 0204 return false; 0205 } 0206 n = n->parent; 0207 } 0208 qCWarning(KORGANIZER_LOG) << "not linked to root" << depth; 0209 return false; 0210 } 0211 0212 void ReparentingModel::addNode(const ReparentingModel::Node::Ptr &node) 0213 { 0214 // We have to make this check before issuing the async method, 0215 // otherwise we run into the problem that while a node is being removed, 0216 // the async request could be triggered (due to a changed signal), 0217 // resulting in the node getting read immediately after it had been removed. 0218 for (const ReparentingModel::Node::Ptr &existing : std::as_const(mProxyNodes)) { 0219 if (*existing == *node) { 0220 // qCDebug(KORGANIZER_LOG) << "node is already existing"; 0221 return; 0222 } 0223 } 0224 mNodesToAdd << node; 0225 qRegisterMetaType<Node::Ptr>("Node::Ptr"); 0226 QMetaObject::invokeMethod(this, "doAddNode", Qt::QueuedConnection, Q_ARG(Node::Ptr, node)); 0227 } 0228 0229 void ReparentingModel::doAddNode(const Node::Ptr &node) 0230 { 0231 for (const ReparentingModel::Node::Ptr &existing : std::as_const(mProxyNodes)) { 0232 if (*existing == *node) { 0233 // qCDebug(KORGANIZER_LOG) << "node is already existing"; 0234 return; 0235 } 0236 } 0237 // If a datachanged call triggered this through checkSourceIndex, right after a person node has been removed. 0238 // We'd end-up re-inserting the node that has just been removed. Therefore removeNode can cancel the pending addNode 0239 // call through mNodesToAdd. 0240 bool addNodeAborted = true; 0241 for (int i = 0; i < mNodesToAdd.size(); ++i) { 0242 if (*mNodesToAdd.at(i) == *node) { 0243 mNodesToAdd.remove(i); 0244 addNodeAborted = false; 0245 break; 0246 } 0247 } 0248 if (addNodeAborted) { 0249 return; 0250 } 0251 0252 if (!isDuplicate(node)) { 0253 const int targetRow = mRootNode.children.size(); 0254 beginInsertRows(QModelIndex(), targetRow, targetRow); 0255 mProxyNodes << node; 0256 insertProxyNode(node); 0257 endInsertRows(); 0258 reparentSourceNodes(node); 0259 } else { 0260 mProxyNodes << node; 0261 } 0262 } 0263 0264 void ReparentingModel::updateNode(const ReparentingModel::Node::Ptr &node) 0265 { 0266 for (const ReparentingModel::Node::Ptr &existing : std::as_const(mProxyNodes)) { 0267 if (*existing == *node) { 0268 existing->update(node); 0269 const QModelIndex i = index(existing.data()); 0270 Q_EMIT dataChanged(i, i); 0271 return; 0272 } 0273 } 0274 0275 qCWarning(KORGANIZER_LOG) << objectName() << "no node to update, create new node"; 0276 addNode(node); 0277 } 0278 0279 void ReparentingModel::removeNode(const ReparentingModel::Node &node) 0280 { 0281 // If there is an addNode in progress for that node, abort it. 0282 for (int i = 0; i < mNodesToAdd.size(); ++i) { 0283 if (*mNodesToAdd.at(i) == node) { 0284 mNodesToAdd.remove(i); 0285 } 0286 } 0287 for (int i = 0; i < mProxyNodes.size(); ++i) { 0288 if (*mProxyNodes.at(i) == node) { 0289 // TODO: this does not yet take care of un-reparenting reparented nodes. 0290 const Node &n = *mProxyNodes.at(i); 0291 Node *parentNode = n.parent; 0292 beginRemoveRows(index(parentNode), n.row(), n.row()); 0293 parentNode->children.remove(n.row()); // deletes node 0294 mProxyNodes.remove(i); 0295 endRemoveRows(); 0296 break; 0297 } 0298 } 0299 } 0300 0301 void ReparentingModel::setNodes(const QList<Node::Ptr> &nodes) 0302 { 0303 for (const ReparentingModel::Node::Ptr &node : nodes) { 0304 addNode(node); 0305 } 0306 const auto currentProxyNodes = mProxyNodes; 0307 for (const ReparentingModel::Node::Ptr &node : currentProxyNodes) { 0308 if (!nodes.contains(node)) { 0309 removeNode(*node); 0310 } 0311 } 0312 } 0313 0314 void ReparentingModel::clear() 0315 { 0316 beginResetModel(); 0317 mProxyNodes.clear(); 0318 rebuildAll(); 0319 endResetModel(); 0320 } 0321 0322 void ReparentingModel::setNodeManager(const NodeManager::Ptr &nodeManager) 0323 { 0324 mNodeManager = nodeManager; 0325 } 0326 0327 void ReparentingModel::setSourceModel(QAbstractItemModel *sourceModel) 0328 { 0329 beginResetModel(); 0330 QAbstractProxyModel::setSourceModel(sourceModel); 0331 if (sourceModel) { 0332 connect(sourceModel, &QAbstractProxyModel::rowsAboutToBeInserted, this, &ReparentingModel::onSourceRowsAboutToBeInserted); 0333 connect(sourceModel, &QAbstractProxyModel::rowsInserted, this, &ReparentingModel::onSourceRowsInserted); 0334 connect(sourceModel, &QAbstractProxyModel::rowsAboutToBeRemoved, this, &ReparentingModel::onSourceRowsAboutToBeRemoved); 0335 connect(sourceModel, &QAbstractProxyModel::rowsRemoved, this, &ReparentingModel::onSourceRowsRemoved); 0336 connect(sourceModel, &QAbstractProxyModel::rowsAboutToBeMoved, this, &ReparentingModel::onSourceRowsAboutToBeMoved); 0337 connect(sourceModel, &QAbstractProxyModel::rowsMoved, this, &ReparentingModel::onSourceRowsMoved); 0338 connect(sourceModel, &QAbstractProxyModel::modelAboutToBeReset, this, &ReparentingModel::onSourceModelAboutToBeReset); 0339 connect(sourceModel, &QAbstractProxyModel::modelReset, this, &ReparentingModel::onSourceModelReset); 0340 connect(sourceModel, &QAbstractProxyModel::dataChanged, this, &ReparentingModel::onSourceDataChanged); 0341 // connect(sourceModel, &QAbstractProxyModel::headerDataChanged, this, &ReparentingModel::_k_sourceHeaderDataChanged); 0342 connect(sourceModel, &QAbstractProxyModel::layoutAboutToBeChanged, this, &ReparentingModel::onSourceLayoutAboutToBeChanged); 0343 connect(sourceModel, &QAbstractProxyModel::layoutChanged, this, &ReparentingModel::onSourceLayoutChanged); 0344 // connect(sourceModel, &QAbstractProxyModel::destroyed, this, &ReparentingModel::onSourceModelDestroyed); 0345 } 0346 0347 rebuildAll(); 0348 endResetModel(); 0349 } 0350 0351 void ReparentingModel::onSourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) 0352 { 0353 Q_UNUSED(parent) 0354 Q_UNUSED(start) 0355 Q_UNUSED(end) 0356 } 0357 0358 ReparentingModel::Node *ReparentingModel::getReparentNode(const QModelIndex &sourceIndex) 0359 { 0360 for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) { 0361 // Reparent source nodes according to the provided rules 0362 // The proxy can be ignored if it is a duplicate, so only reparent to proxies that are in the model 0363 if (proxyNode->parent && proxyNode->adopts(sourceIndex)) { 0364 Q_ASSERT(validateNode(proxyNode.data())); 0365 return proxyNode.data(); 0366 } 0367 } 0368 return nullptr; 0369 } 0370 0371 ReparentingModel::Node *ReparentingModel::getParentNode(const QModelIndex &sourceIndex) 0372 { 0373 if (Node *node = getReparentNode(sourceIndex)) { 0374 return node; 0375 } 0376 const QModelIndex proxyIndex = mapFromSource(sourceIndex.parent()); 0377 if (proxyIndex.isValid()) { 0378 return extractNode(proxyIndex); 0379 } 0380 return nullptr; 0381 } 0382 0383 void ReparentingModel::appendSourceNode(Node *parentNode, const QModelIndex &sourceIndex, const QModelIndexList &skip) 0384 { 0385 mNodeManager->checkSourceIndex(sourceIndex); 0386 0387 Node::Ptr node(new Node(*this, parentNode, sourceIndex)); 0388 parentNode->children.append(node); 0389 Q_ASSERT(validateNode(node.data())); 0390 rebuildFromSource(node.data(), sourceIndex, skip); 0391 } 0392 0393 QModelIndexList ReparentingModel::descendants(const QModelIndex &sourceIndex) 0394 { 0395 if (!sourceModel()) { 0396 return {}; 0397 } 0398 QModelIndexList list; 0399 if (sourceModel()->hasChildren(sourceIndex)) { 0400 const int count = sourceModel()->rowCount(sourceIndex); 0401 list.reserve(count * 2); 0402 for (int i = 0; i < count; ++i) { 0403 const QModelIndex index = sourceModel()->index(i, 0, sourceIndex); 0404 list << index; 0405 list << descendants(index); 0406 } 0407 } 0408 return list; 0409 } 0410 0411 void ReparentingModel::removeDuplicates(const QModelIndex &sourceIndex) 0412 { 0413 const QModelIndexList list = QModelIndexList() << sourceIndex << descendants(sourceIndex); 0414 for (const QModelIndex &descendant : list) { 0415 for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) { 0416 if (proxyNode->isDuplicateOf(descendant)) { 0417 // Removenode from proxy 0418 if (!proxyNode->parent) { 0419 qCWarning(KORGANIZER_LOG) << objectName() << "Found proxy that is already not part of the model " 0420 << proxyNode->data(Qt::DisplayRole).toString(); 0421 continue; 0422 } 0423 const int targetRow = proxyNode->row(); 0424 beginRemoveRows(index(proxyNode->parent), targetRow, targetRow); 0425 proxyNode->parent->children.remove(targetRow); 0426 proxyNode->parent = nullptr; 0427 endRemoveRows(); 0428 } 0429 } 0430 } 0431 } 0432 0433 void ReparentingModel::onSourceRowsInserted(const QModelIndex &parent, int start, int end) 0434 { 0435 // qCDebug(KORGANIZER_LOG) << objectName() << parent << start << end; 0436 for (int row = start; row <= end; row++) { 0437 QModelIndex sourceIndex = sourceModel()->index(row, 0, parent); 0438 Q_ASSERT(sourceIndex.isValid()); 0439 Node *parentNode = getParentNode(sourceIndex); 0440 if (!parentNode) { 0441 parentNode = &mRootNode; 0442 } else { 0443 Q_ASSERT(validateNode(parentNode)); 0444 } 0445 Q_ASSERT(parentNode); 0446 0447 // Remove any duplicates that we are going to replace 0448 removeDuplicates(sourceIndex); 0449 0450 QModelIndexList reparented; 0451 // Check for children to reparent 0452 { 0453 const auto descendantsItem = descendants(sourceIndex); 0454 for (const QModelIndex &descendant : descendantsItem) { 0455 if (Node *proxyNode = getReparentNode(descendant)) { 0456 qCDebug(KORGANIZER_LOG) << "reparenting " << descendant.data().toString(); 0457 int targetRow = proxyNode->children.size(); 0458 beginInsertRows(index(proxyNode), targetRow, targetRow); 0459 appendSourceNode(proxyNode, descendant); 0460 reparented << descendant; 0461 endInsertRows(); 0462 } 0463 } 0464 } 0465 0466 if (parentNode->isSourceNode()) { 0467 int targetRow = parentNode->children.size(); 0468 beginInsertRows(mapFromSource(parent), targetRow, targetRow); 0469 appendSourceNode(parentNode, sourceIndex, reparented); 0470 endInsertRows(); 0471 } else { // Reparented 0472 int targetRow = parentNode->children.size(); 0473 beginInsertRows(index(parentNode), targetRow, targetRow); 0474 appendSourceNode(parentNode, sourceIndex); 0475 endInsertRows(); 0476 } 0477 } 0478 } 0479 0480 void ReparentingModel::onSourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) 0481 { 0482 // qCDebug(KORGANIZER_LOG) << objectName() << parent << start << end; 0483 // we remove in reverse order as otherwise the indexes in parentNode->children wouldn't be correct 0484 for (int row = end; row >= start; row--) { 0485 QModelIndex sourceIndex = sourceModel()->index(row, 0, parent); 0486 Q_ASSERT(sourceIndex.isValid()); 0487 0488 const QModelIndex proxyIndex = mapFromSource(sourceIndex); 0489 // If the indexes have already been removed (e.g. by removeNode)this can indeed return an invalid index 0490 if (proxyIndex.isValid()) { 0491 const Node *node = extractNode(proxyIndex); 0492 Node *parentNode = node->parent; 0493 Q_ASSERT(parentNode); 0494 const int targetRow = node->row(); 0495 beginRemoveRows(index(parentNode), targetRow, targetRow); 0496 parentNode->children.remove(targetRow); // deletes node 0497 endRemoveRows(); 0498 } 0499 } 0500 // Allows the node manager to remove nodes that are no longer relevant 0501 for (int row = start; row <= end; row++) { 0502 mNodeManager->checkSourceIndexRemoval(sourceModel()->index(row, 0, parent)); 0503 } 0504 } 0505 0506 void ReparentingModel::onSourceRowsRemoved(const QModelIndex & /* parent */, int /* start */, int /* end */) 0507 { 0508 } 0509 0510 void ReparentingModel::onSourceRowsAboutToBeMoved(const QModelIndex & /* sourceParent */, 0511 int /* sourceStart */, 0512 int /* sourceEnd */, 0513 const QModelIndex & /* destParent */, 0514 int /* dest */) 0515 { 0516 qCWarning(KORGANIZER_LOG) << "not implemented"; 0517 // TODO 0518 beginResetModel(); 0519 } 0520 0521 void ReparentingModel::onSourceRowsMoved(const QModelIndex & /* sourceParent */, 0522 int /* sourceStart */, 0523 int /* sourceEnd */, 0524 const QModelIndex & /* destParent */, 0525 int /* dest */) 0526 { 0527 qCWarning(KORGANIZER_LOG) << "not implemented"; 0528 // TODO 0529 endResetModel(); 0530 } 0531 0532 void ReparentingModel::onSourceLayoutAboutToBeChanged() 0533 { 0534 layoutAboutToBeChanged(); 0535 // Q_FOREACH(const QModelIndex &proxyPersistentIndex, persistentIndexList()) { 0536 // Q_ASSERT(proxyPersistentIndex.isValid()); 0537 // const QPersistentModelIndex srcPersistentIndex = mapToSource(proxyPersistentIndex); 0538 // // TODO also update the proxy persistent indexes 0539 // //Skip indexes that are not in the source model 0540 // if (!srcPersistentIndex.isValid()) { 0541 // continue; 0542 // } 0543 // mLayoutChangedProxyIndexes << proxyPersistentIndex; 0544 // mLayoutChangedSourcePersistentModelIndexes << srcPersistentIndex; 0545 // } 0546 } 0547 0548 void ReparentingModel::onSourceLayoutChanged() 0549 { 0550 // By ignoring this we miss structural changes in the sourcemodel, which is mostly ok. 0551 // Before we can re-enable this we need to properly deal with skipped duplicates, because 0552 // a layout change MUST NOT add/remove new nodes (only shuffling allowed) 0553 // 0554 // Our source indexes are not endagered since we use persistend model indexes anyways 0555 0556 // rebuildAll(); 0557 0558 // for (int i = 0; i < mLayoutChangedProxyIndexes.size(); ++i) { 0559 // const QModelIndex oldProxyIndex = mLayoutChangedProxyIndexes.at(i); 0560 // const QModelIndex newProxyIndex = mapFromSource(mLayoutChangedSourcePersistentModelIndexes.at(i)); 0561 // if (oldProxyIndex != newProxyIndex) { 0562 // changePersistentIndex(oldProxyIndex, newProxyIndex); 0563 // } 0564 // } 0565 0566 // mLayoutChangedProxyIndexes.clear(); 0567 // mLayoutChangedSourcePersistentModelIndexes.clear(); 0568 0569 // layoutChanged(); 0570 } 0571 0572 void ReparentingModel::onSourceDataChanged(const QModelIndex &begin, const QModelIndex &end) 0573 { 0574 // qCDebug(KORGANIZER_LOG) << objectName() << begin << end; 0575 for (int row = begin.row(); row <= end.row(); row++) { 0576 mNodeManager->updateSourceIndex(sourceModel()->index(row, begin.column(), begin.parent())); 0577 } 0578 Q_EMIT dataChanged(mapFromSource(begin), mapFromSource(end)); 0579 } 0580 0581 void ReparentingModel::onSourceModelAboutToBeReset() 0582 { 0583 beginResetModel(); 0584 } 0585 0586 void ReparentingModel::onSourceModelReset() 0587 { 0588 rebuildAll(); 0589 endResetModel(); 0590 } 0591 0592 ReparentingModel::Node *ReparentingModel::extractNode(const QModelIndex &index) const 0593 { 0594 Node *node = static_cast<Node *>(index.internalPointer()); 0595 Q_ASSERT(node); 0596 Q_ASSERT(validateNode(node)); 0597 return node; 0598 } 0599 0600 QModelIndex ReparentingModel::index(int row, int column, const QModelIndex &parent) const 0601 { 0602 if (row < 0 || column != 0) { 0603 return {}; 0604 } 0605 // qCDebug(KORGANIZER_LOG) << parent << row; 0606 const Node *parentNode; 0607 if (parent.isValid()) { 0608 parentNode = extractNode(parent); 0609 } else { 0610 parentNode = &mRootNode; 0611 } 0612 // At least QAbstractItemView expects that we deal with this properly (see rowsAboutToBeRemoved "find the next visible and enabled item") 0613 // Also QAbstractItemModel::match does all kinds of weird shit including passing row=-1 0614 if (parentNode->children.size() <= row) { 0615 return {}; 0616 } 0617 Node *node = parentNode->children.at(row).data(); 0618 Q_ASSERT(validateNode(node)); 0619 return createIndex(row, column, node); 0620 } 0621 0622 QModelIndex ReparentingModel::mapToSource(const QModelIndex &idx) const 0623 { 0624 if (!idx.isValid() || !sourceModel()) { 0625 return {}; 0626 } 0627 Node *node = extractNode(idx); 0628 if (!node->isSourceNode()) { 0629 return {}; 0630 } 0631 Q_ASSERT(node->sourceIndex.model() == sourceModel()); 0632 return node->sourceIndex; 0633 } 0634 0635 ReparentingModel::Node *ReparentingModel::getSourceNode(const QModelIndex &sourceIndex) const 0636 { 0637 for (Node *n : std::as_const(mSourceNodes)) { 0638 if (n->sourceIndex == sourceIndex) { 0639 return n; 0640 } 0641 } 0642 // qCDebug(KORGANIZER_LOG) << objectName() << "no node found for " << sourceIndex; 0643 return nullptr; 0644 } 0645 0646 QModelIndex ReparentingModel::mapFromSource(const QModelIndex &sourceIndex) const 0647 { 0648 // qCDebug(KORGANIZER_LOG) << sourceIndex << sourceIndex.data().toString(); 0649 if (!sourceIndex.isValid()) { 0650 return {}; 0651 } 0652 Node *node = getSourceNode(sourceIndex); 0653 if (!node) { 0654 // This can happen if a source nodes is hidden (person collections) 0655 return {}; 0656 } 0657 Q_ASSERT(validateNode(node)); 0658 return index(node); 0659 } 0660 0661 void ReparentingModel::rebuildFromSource(Node *parentNode, const QModelIndex &sourceParent, const QModelIndexList &skip) 0662 { 0663 Q_ASSERT(parentNode); 0664 if (!sourceModel()) { 0665 return; 0666 } 0667 for (int i = 0; i < sourceModel()->rowCount(sourceParent); ++i) { 0668 const QModelIndex &sourceIndex = sourceModel()->index(i, 0, sourceParent); 0669 // Skip indexes that should be excluded because they have been reparented 0670 if (skip.contains(sourceIndex)) { 0671 continue; 0672 } 0673 appendSourceNode(parentNode, sourceIndex, skip); 0674 } 0675 } 0676 0677 bool ReparentingModel::isDuplicate(const Node::Ptr &proxyNode) const 0678 { 0679 for (const Node *n : std::as_const(mSourceNodes)) { 0680 // qCDebug(KORGANIZER_LOG) << index << index.data().toString(); 0681 if (proxyNode->isDuplicateOf(n->sourceIndex)) { 0682 return true; 0683 } 0684 } 0685 return false; 0686 } 0687 0688 void ReparentingModel::insertProxyNode(const Node::Ptr &proxyNode) 0689 { 0690 // qCDebug(KORGANIZER_LOG) << "checking " << proxyNode->data(Qt::DisplayRole).toString(); 0691 proxyNode->parent = &mRootNode; 0692 mRootNode.addChild(proxyNode); 0693 Q_ASSERT(validateNode(proxyNode.data())); 0694 } 0695 0696 void ReparentingModel::reparentSourceNodes(const Node::Ptr &proxyNode) 0697 { 0698 // Reparent source nodes according to the provided rules 0699 for (Node *n : std::as_const(mSourceNodes)) { 0700 if (proxyNode->adopts(n->sourceIndex)) { 0701 // qCDebug(KORGANIZER_LOG) << "reparenting" << n->data(Qt::DisplayRole).toString() << "from" << n->parent->data(Qt::DisplayRole).toString() 0702 // << "to" << proxyNode->data(Qt::DisplayRole).toString(); 0703 0704 // WARNING: While a beginMoveRows/endMoveRows would be more suitable, QSortFilterProxyModel can't deal with that. Therefore we 0705 // cannot use them. 0706 const int oldRow = n->row(); 0707 beginRemoveRows(index(n->parent), oldRow, oldRow); 0708 Node::Ptr nodePtr = proxyNode->searchNode(n); 0709 // We lie about the row being removed already, but the view can deal with that better than if we call endRemoveRows after beginInsertRows 0710 endRemoveRows(); 0711 0712 const int newRow = proxyNode->children.size(); 0713 beginInsertRows(index(proxyNode.data()), newRow, newRow); 0714 proxyNode->addChild(nodePtr); 0715 endInsertRows(); 0716 Q_ASSERT(validateNode(n)); 0717 } 0718 } 0719 } 0720 0721 void ReparentingModel::rebuildAll() 0722 { 0723 mRootNode.children.clear(); 0724 for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) { 0725 proxyNode->clearHierarchy(); 0726 } 0727 Q_ASSERT(mSourceNodes.isEmpty()); 0728 mSourceNodes.clear(); 0729 rebuildFromSource(&mRootNode, QModelIndex()); 0730 for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) { 0731 // qCDebug(KORGANIZER_LOG) << "checking " << proxyNode->data(Qt::DisplayRole).toString(); 0732 // Avoid inserting a node that is already part of the source model 0733 if (isDuplicate(proxyNode)) { 0734 continue; 0735 } 0736 insertProxyNode(proxyNode); 0737 reparentSourceNodes(proxyNode); 0738 } 0739 } 0740 0741 QVariant ReparentingModel::data(const QModelIndex &proxyIndex, int role) const 0742 { 0743 if (!proxyIndex.isValid()) { 0744 return {}; 0745 } 0746 const Node *node = extractNode(proxyIndex); 0747 if (node->isSourceNode()) { 0748 return sourceModel()->data(mapToSource(proxyIndex), role); 0749 } 0750 return node->data(role); 0751 } 0752 0753 bool ReparentingModel::setData(const QModelIndex &index, const QVariant &value, int role) 0754 { 0755 if (!index.isValid()) { 0756 return false; 0757 } 0758 Q_ASSERT(index.isValid()); 0759 if (!sourceModel()) { 0760 return false; 0761 } 0762 Node *node = extractNode(index); 0763 if (node->isSourceNode()) { 0764 return sourceModel()->setData(mapToSource(index), value, role); 0765 } 0766 return node->setData(value, role); 0767 } 0768 0769 Qt::ItemFlags ReparentingModel::flags(const QModelIndex &index) const 0770 { 0771 if (!index.isValid() || !sourceModel()) { 0772 return Qt::NoItemFlags; 0773 } 0774 Node *node = extractNode(index); 0775 if (!node->isSourceNode()) { 0776 return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; 0777 } 0778 return sourceModel()->flags(mapToSource(index)); 0779 } 0780 0781 int ReparentingModel::row(ReparentingModel::Node *node) const 0782 { 0783 Q_ASSERT(node); 0784 if (node == &mRootNode) { 0785 return -1; 0786 } 0787 Q_ASSERT(validateNode(node)); 0788 int row = 0; 0789 for (const Node::Ptr &c : std::as_const(node->parent->children)) { 0790 if (c.data() == node) { 0791 return row; 0792 } 0793 row++; 0794 } 0795 return -1; 0796 } 0797 0798 QModelIndex ReparentingModel::index(Node *node) const 0799 { 0800 const int r = row(node); 0801 if (r < 0) { 0802 return {}; 0803 } 0804 return createIndex(r, 0, node); 0805 } 0806 0807 QModelIndex ReparentingModel::parent(const QModelIndex &child) const 0808 { 0809 // qCDebug(KORGANIZER_LOG) << child << child.data().toString(); 0810 if (!child.isValid()) { 0811 return {}; 0812 } 0813 const Node *node = extractNode(child); 0814 return index(node->parent); 0815 } 0816 0817 QModelIndex ReparentingModel::buddy(const QModelIndex &index) const 0818 { 0819 if (!index.isValid() || !sourceModel()) { 0820 return {}; 0821 } 0822 Node *node = extractNode(index); 0823 if (node->isSourceNode()) { 0824 return mapFromSource(sourceModel()->buddy(mapToSource(index))); 0825 } 0826 return index; 0827 } 0828 0829 int ReparentingModel::rowCount(const QModelIndex &parent) const 0830 { 0831 if (!parent.isValid()) { 0832 return mRootNode.children.size(); 0833 } 0834 0835 if (parent.column() != 0) { 0836 return 0; 0837 } 0838 0839 Node *node = extractNode(parent); 0840 return node->children.size(); 0841 } 0842 0843 bool ReparentingModel::hasChildren(const QModelIndex &parent) const 0844 { 0845 return rowCount(parent) != 0; 0846 } 0847 0848 int ReparentingModel::columnCount(const QModelIndex & /* parent */) const 0849 { 0850 return 1; 0851 } 0852 0853 #include "moc_reparentingmodel.cpp"