1 /*
2  * SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com>
3  *
4  * SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
5  */
6 
7 #include "reparentingmodel.h"
8 
9 #include "korganizer_debug.h"
10 
11 /*
12  * Notes:
13  * * layoutChanged must never add or remove nodes.
14  * * rebuildAll can therefore only be called if it doesn't introduce new nodes or within a reset.
15  * * The node memory management is done using the node tree, nodes are deleted by being removed from the node tree.
16  */
17 
Node(ReparentingModel & model)18 ReparentingModel::Node::Node(ReparentingModel &model)
19     : personModel(model)
20     , mIsSourceNode(false)
21 {
22 }
23 
Node(ReparentingModel & model,ReparentingModel::Node * p,const QModelIndex & srcIndex)24 ReparentingModel::Node::Node(ReparentingModel &model, ReparentingModel::Node *p, const QModelIndex &srcIndex)
25     : sourceIndex(srcIndex)
26     , parent(p)
27     , personModel(model)
28     , mIsSourceNode(true)
29 {
30     if (sourceIndex.isValid()) {
31         personModel.mSourceNodes.append(this);
32     }
33     Q_ASSERT(parent);
34 }
35 
~Node()36 ReparentingModel::Node::~Node()
37 {
38     // The source index may be invalid meanwhile (it's a persistent index)
39     personModel.mSourceNodes.removeOne(this);
40 }
41 
operator ==(const ReparentingModel::Node & node) const42 bool ReparentingModel::Node::operator==(const ReparentingModel::Node &node) const
43 {
44     return this == &node;
45 }
46 
searchNode(ReparentingModel::Node * node)47 ReparentingModel::Node::Ptr ReparentingModel::Node::searchNode(ReparentingModel::Node *node)
48 {
49     Node::Ptr nodePtr;
50     if (node->parent) {
51         // Reparent node
52         QVector<Node::Ptr>::iterator it = node->parent->children.begin();
53         for (; it != node->parent->children.end(); ++it) {
54             if (it->data() == node) {
55                 // Reuse smart pointer
56                 nodePtr = *it;
57                 node->parent->children.erase(it);
58                 break;
59             }
60         }
61         Q_ASSERT(nodePtr);
62     } else {
63         nodePtr = Node::Ptr(node);
64     }
65 
66     return nodePtr;
67 }
68 
reparent(ReparentingModel::Node * node)69 void ReparentingModel::Node::reparent(ReparentingModel::Node *node)
70 {
71     Node::Ptr nodePtr = searchNode(node);
72     addChild(nodePtr);
73 }
74 
addChild(const ReparentingModel::Node::Ptr & node)75 void ReparentingModel::Node::addChild(const ReparentingModel::Node::Ptr &node)
76 {
77     node->parent = this;
78     children.append(node);
79 }
80 
clearHierarchy()81 void ReparentingModel::Node::clearHierarchy()
82 {
83     parent = nullptr;
84     children.clear();
85 }
86 
setData(const QVariant &,int)87 bool ReparentingModel::Node::setData(const QVariant & /* value */, int /* role */)
88 {
89     return false;
90 }
91 
data(int role) const92 QVariant ReparentingModel::Node::data(int role) const
93 {
94     if (sourceIndex.isValid()) {
95         return sourceIndex.data(role);
96     }
97     return QVariant();
98 }
99 
adopts(const QModelIndex &)100 bool ReparentingModel::Node::adopts(const QModelIndex & /* sourceIndex */)
101 {
102     return false;
103 }
104 
isDuplicateOf(const QModelIndex &)105 bool ReparentingModel::Node::isDuplicateOf(const QModelIndex & /* sourceIndex */)
106 {
107     return false;
108 }
109 
update(const Node::Ptr &)110 void ReparentingModel::Node::update(const Node::Ptr & /* node */)
111 {
112 }
113 
isSourceNode() const114 bool ReparentingModel::Node::isSourceNode() const
115 {
116     return mIsSourceNode;
117 }
118 
row() const119 int ReparentingModel::Node::row() const
120 {
121     Q_ASSERT(parent);
122     int row = 0;
123     for (const Node::Ptr &node : std::as_const(parent->children)) {
124         if (node.data() == this) {
125             return row;
126         }
127         row++;
128     }
129     return -1;
130 }
131 
ReparentingModel(QObject * parent)132 ReparentingModel::ReparentingModel(QObject *parent)
133     : QAbstractProxyModel(parent)
134     , mRootNode(*this)
135     , mNodeManager(NodeManager::Ptr(new NodeManager(*this)))
136 {
137 }
138 
~ReparentingModel()139 ReparentingModel::~ReparentingModel()
140 {
141     // Otherwise we cannot guarantee that the nodes reference to *this is always valid
142     mRootNode.children.clear();
143     mProxyNodes.clear();
144     mSourceNodes.clear();
145 }
146 
validateNode(const Node * node) const147 bool ReparentingModel::validateNode(const Node *node) const
148 {
149     // Expected:
150     // * Each node tree starts at mRootNode
151     // * Each node is listed in the children of it's parent
152     // * Root node never leaves the model and thus should never enter this function
153     if (!node) {
154         qCWarning(KORGANIZER_LOG) << "nullptr";
155         return false;
156     }
157     if (node == &mRootNode) {
158         qCWarning(KORGANIZER_LOG) << "is root node";
159         return false;
160     }
161     const Node *n = node;
162     int depth = 0;
163     while (n) {
164         if ((intptr_t)(n) < 1000) {
165             // Detect corruptions with unlikely pointers
166             qCWarning(KORGANIZER_LOG) << "corrupt pointer" << depth;
167             return false;
168         }
169         if (!n->parent) {
170             qCWarning(KORGANIZER_LOG) << "nullptr parent" << depth << n->isSourceNode();
171             return false;
172         }
173         if (n->parent == n) {
174             qCWarning(KORGANIZER_LOG) << "loop" << depth;
175             return false;
176         }
177 
178         bool found = false;
179         for (const Node::Ptr &child : std::as_const(n->parent->children)) {
180             if (child.data() == n) {
181                 found = true;
182             }
183         }
184         if (!found) {
185             qCWarning(KORGANIZER_LOG) << "not linked as child" << depth;
186             return false;
187         }
188         depth++;
189         if (depth > 1000) {
190             qCWarning(KORGANIZER_LOG) << "loop detected" << depth;
191             return false;
192         }
193 
194         if (n->parent == &mRootNode) {
195             return true;
196         }
197         // If the parent isn't root there is at least one more level
198         if (!n->parent->parent) {
199             qCWarning(KORGANIZER_LOG) << "missing parent parent" << depth;
200             return false;
201         }
202         if (n->parent->parent == n) {
203             qCWarning(KORGANIZER_LOG) << "parent parent loop" << depth;
204             return false;
205         }
206         n = n->parent;
207     }
208     qCWarning(KORGANIZER_LOG) << "not linked to root" << depth;
209     return false;
210 }
211 
addNode(const ReparentingModel::Node::Ptr & node)212 void ReparentingModel::addNode(const ReparentingModel::Node::Ptr &node)
213 {
214     // We have to make this check before issuing the async method,
215     // otherwise we run into the problem that while a node is being removed,
216     // the async request could be triggered (due to a changed signal),
217     // resulting in the node getting readded immediately after it had been removed.
218     for (const ReparentingModel::Node::Ptr &existing : std::as_const(mProxyNodes)) {
219         if (*existing == *node) {
220             // qCDebug(KORGANIZER_LOG) << "node is already existing";
221             return;
222         }
223     }
224     mNodesToAdd << node;
225     qRegisterMetaType<Node::Ptr>("Node::Ptr");
226     QMetaObject::invokeMethod(this, "doAddNode", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(Node::Ptr, node));
227 }
228 
doAddNode(const Node::Ptr & node)229 void ReparentingModel::doAddNode(const Node::Ptr &node)
230 {
231     for (const ReparentingModel::Node::Ptr &existing : std::as_const(mProxyNodes)) {
232         if (*existing == *node) {
233             // qCDebug(KORGANIZER_LOG) << "node is already existing";
234             return;
235         }
236     }
237     // If a datachanged call triggered this through checkSourceIndex, right after a person node has been removed.
238     // We'd end-up re-inserting the node that has just been removed. Therefore removeNode can cancel the pending addNode
239     // call through mNodesToAdd.
240     bool addNodeAborted = true;
241     for (int i = 0; i < mNodesToAdd.size(); ++i) {
242         if (*mNodesToAdd.at(i) == *node) {
243             mNodesToAdd.remove(i);
244             addNodeAborted = false;
245             break;
246         }
247     }
248     if (addNodeAborted) {
249         return;
250     }
251 
252     if (!isDuplicate(node)) {
253         const int targetRow = mRootNode.children.size();
254         beginInsertRows(QModelIndex(), targetRow, targetRow);
255         mProxyNodes << node;
256         insertProxyNode(node);
257         endInsertRows();
258         reparentSourceNodes(node);
259     } else {
260         mProxyNodes << node;
261     }
262 }
263 
updateNode(const ReparentingModel::Node::Ptr & node)264 void ReparentingModel::updateNode(const ReparentingModel::Node::Ptr &node)
265 {
266     for (const ReparentingModel::Node::Ptr &existing : std::as_const(mProxyNodes)) {
267         if (*existing == *node) {
268             existing->update(node);
269             const QModelIndex i = index(existing.data());
270             Q_EMIT dataChanged(i, i);
271             return;
272         }
273     }
274 
275     qCWarning(KORGANIZER_LOG) << objectName() << "no node to update, create new node";
276     addNode(node);
277 }
278 
removeNode(const ReparentingModel::Node & node)279 void ReparentingModel::removeNode(const ReparentingModel::Node &node)
280 {
281     // If there is an addNode in progress for that node, abort it.
282     for (int i = 0; i < mNodesToAdd.size(); ++i) {
283         if (*mNodesToAdd.at(i) == node) {
284             mNodesToAdd.remove(i);
285         }
286     }
287     for (int i = 0; i < mProxyNodes.size(); ++i) {
288         if (*mProxyNodes.at(i) == node) {
289             // TODO: this does not yet take care of un-reparenting reparented nodes.
290             const Node &n = *mProxyNodes.at(i);
291             Node *parentNode = n.parent;
292             beginRemoveRows(index(parentNode), n.row(), n.row());
293             parentNode->children.remove(n.row()); // deletes node
294             mProxyNodes.remove(i);
295             endRemoveRows();
296             break;
297         }
298     }
299 }
300 
setNodes(const QList<Node::Ptr> & nodes)301 void ReparentingModel::setNodes(const QList<Node::Ptr> &nodes)
302 {
303     for (const ReparentingModel::Node::Ptr &node : nodes) {
304         addNode(node);
305     }
306     const auto currentProxyNodes = mProxyNodes;
307     for (const ReparentingModel::Node::Ptr &node : currentProxyNodes) {
308         if (!nodes.contains(node)) {
309             removeNode(*node);
310         }
311     }
312 }
313 
clear()314 void ReparentingModel::clear()
315 {
316     beginResetModel();
317     mProxyNodes.clear();
318     rebuildAll();
319     endResetModel();
320 }
321 
setNodeManager(const NodeManager::Ptr & nodeManager)322 void ReparentingModel::setNodeManager(const NodeManager::Ptr &nodeManager)
323 {
324     mNodeManager = nodeManager;
325 }
326 
setSourceModel(QAbstractItemModel * sourceModel)327 void ReparentingModel::setSourceModel(QAbstractItemModel *sourceModel)
328 {
329     beginResetModel();
330     QAbstractProxyModel::setSourceModel(sourceModel);
331     if (sourceModel) {
332         connect(sourceModel, &QAbstractProxyModel::rowsAboutToBeInserted, this, &ReparentingModel::onSourceRowsAboutToBeInserted);
333         connect(sourceModel, &QAbstractProxyModel::rowsInserted, this, &ReparentingModel::onSourceRowsInserted);
334         connect(sourceModel, &QAbstractProxyModel::rowsAboutToBeRemoved, this, &ReparentingModel::onSourceRowsAboutToBeRemoved);
335         connect(sourceModel, &QAbstractProxyModel::rowsRemoved, this, &ReparentingModel::onSourceRowsRemoved);
336         connect(sourceModel, &QAbstractProxyModel::rowsAboutToBeMoved, this, &ReparentingModel::onSourceRowsAboutToBeMoved);
337         connect(sourceModel, &QAbstractProxyModel::rowsMoved, this, &ReparentingModel::onSourceRowsMoved);
338         connect(sourceModel, &QAbstractProxyModel::modelAboutToBeReset, this, &ReparentingModel::onSourceModelAboutToBeReset);
339         connect(sourceModel, &QAbstractProxyModel::modelReset, this, &ReparentingModel::onSourceModelReset);
340         connect(sourceModel, &QAbstractProxyModel::dataChanged, this, &ReparentingModel::onSourceDataChanged);
341         //       connect(sourceModel, &QAbstractProxyModel::headerDataChanged, this, &ReparentingModel::_k_sourceHeaderDataChanged);
342         connect(sourceModel, &QAbstractProxyModel::layoutAboutToBeChanged, this, &ReparentingModel::onSourceLayoutAboutToBeChanged);
343         connect(sourceModel, &QAbstractProxyModel::layoutChanged, this, &ReparentingModel::onSourceLayoutChanged);
344         //       connect(sourceModel, &QAbstractProxyModel::destroyed, this, &ReparentingModel::onSourceModelDestroyed);
345     }
346 
347     rebuildAll();
348     endResetModel();
349 }
350 
onSourceRowsAboutToBeInserted(const QModelIndex & parent,int start,int end)351 void ReparentingModel::onSourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
352 {
353     Q_UNUSED(parent)
354     Q_UNUSED(start)
355     Q_UNUSED(end)
356 }
357 
getReparentNode(const QModelIndex & sourceIndex)358 ReparentingModel::Node *ReparentingModel::getReparentNode(const QModelIndex &sourceIndex)
359 {
360     for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) {
361         // Reparent source nodes according to the provided rules
362         // The proxy can be ignored if it is a duplicate, so only reparent to proxies that are in the model
363         if (proxyNode->parent && proxyNode->adopts(sourceIndex)) {
364             Q_ASSERT(validateNode(proxyNode.data()));
365             return proxyNode.data();
366         }
367     }
368     return nullptr;
369 }
370 
getParentNode(const QModelIndex & sourceIndex)371 ReparentingModel::Node *ReparentingModel::getParentNode(const QModelIndex &sourceIndex)
372 {
373     if (Node *node = getReparentNode(sourceIndex)) {
374         return node;
375     }
376     const QModelIndex proxyIndex = mapFromSource(sourceIndex.parent());
377     if (proxyIndex.isValid()) {
378         return extractNode(proxyIndex);
379     }
380     return nullptr;
381 }
382 
appendSourceNode(Node * parentNode,const QModelIndex & sourceIndex,const QModelIndexList & skip)383 void ReparentingModel::appendSourceNode(Node *parentNode, const QModelIndex &sourceIndex, const QModelIndexList &skip)
384 {
385     mNodeManager->checkSourceIndex(sourceIndex);
386 
387     Node::Ptr node(new Node(*this, parentNode, sourceIndex));
388     parentNode->children.append(node);
389     Q_ASSERT(validateNode(node.data()));
390     rebuildFromSource(node.data(), sourceIndex, skip);
391 }
392 
descendants(const QModelIndex & sourceIndex)393 QModelIndexList ReparentingModel::descendants(const QModelIndex &sourceIndex)
394 {
395     if (!sourceModel()) {
396         return QModelIndexList();
397     }
398     QModelIndexList list;
399     if (sourceModel()->hasChildren(sourceIndex)) {
400         const int count = sourceModel()->rowCount(sourceIndex);
401         list.reserve(count * 2);
402         for (int i = 0; i < count; ++i) {
403             const QModelIndex index = sourceModel()->index(i, 0, sourceIndex);
404             list << index;
405             list << descendants(index);
406         }
407     }
408     return list;
409 }
410 
removeDuplicates(const QModelIndex & sourceIndex)411 void ReparentingModel::removeDuplicates(const QModelIndex &sourceIndex)
412 {
413     const QModelIndexList list = QModelIndexList() << sourceIndex << descendants(sourceIndex);
414     for (const QModelIndex &descendant : list) {
415         for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) {
416             if (proxyNode->isDuplicateOf(descendant)) {
417                 // Removenode from proxy
418                 if (!proxyNode->parent) {
419                     qCWarning(KORGANIZER_LOG) << objectName() << "Found proxy that is already not part of the model "
420                                               << proxyNode->data(Qt::DisplayRole).toString();
421                     continue;
422                 }
423                 const int targetRow = proxyNode->row();
424                 beginRemoveRows(index(proxyNode->parent), targetRow, targetRow);
425                 proxyNode->parent->children.remove(targetRow);
426                 proxyNode->parent = nullptr;
427                 endRemoveRows();
428             }
429         }
430     }
431 }
432 
onSourceRowsInserted(const QModelIndex & parent,int start,int end)433 void ReparentingModel::onSourceRowsInserted(const QModelIndex &parent, int start, int end)
434 {
435     // qCDebug(KORGANIZER_LOG) << objectName() << parent << start << end;
436     for (int row = start; row <= end; row++) {
437         QModelIndex sourceIndex = sourceModel()->index(row, 0, parent);
438         Q_ASSERT(sourceIndex.isValid());
439         Node *parentNode = getParentNode(sourceIndex);
440         if (!parentNode) {
441             parentNode = &mRootNode;
442         } else {
443             Q_ASSERT(validateNode(parentNode));
444         }
445         Q_ASSERT(parentNode);
446 
447         // Remove any duplicates that we are going to replace
448         removeDuplicates(sourceIndex);
449 
450         QModelIndexList reparented;
451         // Check for children to reparent
452         {
453             const auto descendantsItem = descendants(sourceIndex);
454             for (const QModelIndex &descendant : descendantsItem) {
455                 if (Node *proxyNode = getReparentNode(descendant)) {
456                     qCDebug(KORGANIZER_LOG) << "reparenting " << descendant.data().toString();
457                     int targetRow = proxyNode->children.size();
458                     beginInsertRows(index(proxyNode), targetRow, targetRow);
459                     appendSourceNode(proxyNode, descendant);
460                     reparented << descendant;
461                     endInsertRows();
462                 }
463             }
464         }
465 
466         if (parentNode->isSourceNode()) {
467             int targetRow = parentNode->children.size();
468             beginInsertRows(mapFromSource(parent), targetRow, targetRow);
469             appendSourceNode(parentNode, sourceIndex, reparented);
470             endInsertRows();
471         } else { // Reparented
472             int targetRow = parentNode->children.size();
473             beginInsertRows(index(parentNode), targetRow, targetRow);
474             appendSourceNode(parentNode, sourceIndex);
475             endInsertRows();
476         }
477     }
478 }
479 
onSourceRowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)480 void ReparentingModel::onSourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
481 {
482     // qCDebug(KORGANIZER_LOG) << objectName() << parent << start << end;
483     // we remove in reverse order as otherwise the indexes in parentNode->children wouldn't be correct
484     for (int row = end; row >= start; row--) {
485         QModelIndex sourceIndex = sourceModel()->index(row, 0, parent);
486         Q_ASSERT(sourceIndex.isValid());
487 
488         const QModelIndex proxyIndex = mapFromSource(sourceIndex);
489         // If the indexes have already been removed (e.g. by removeNode)this can indeed return an invalid index
490         if (proxyIndex.isValid()) {
491             const Node *node = extractNode(proxyIndex);
492             Node *parentNode = node->parent;
493             Q_ASSERT(parentNode);
494             const int targetRow = node->row();
495             beginRemoveRows(index(parentNode), targetRow, targetRow);
496             parentNode->children.remove(targetRow); // deletes node
497             endRemoveRows();
498         }
499     }
500     // Allows the node manager to remove nodes that are no longer relevant
501     for (int row = start; row <= end; row++) {
502         mNodeManager->checkSourceIndexRemoval(sourceModel()->index(row, 0, parent));
503     }
504 }
505 
onSourceRowsRemoved(const QModelIndex &,int,int)506 void ReparentingModel::onSourceRowsRemoved(const QModelIndex & /* parent */, int /* start */, int /* end */)
507 {
508 }
509 
onSourceRowsAboutToBeMoved(const QModelIndex &,int,int,const QModelIndex &,int)510 void ReparentingModel::onSourceRowsAboutToBeMoved(const QModelIndex & /* sourceParent */,
511                                                   int /* sourceStart */,
512                                                   int /* sourceEnd */,
513                                                   const QModelIndex & /* destParent */,
514                                                   int /* dest */)
515 {
516     qCWarning(KORGANIZER_LOG) << "not implemented";
517     // TODO
518     beginResetModel();
519 }
520 
onSourceRowsMoved(const QModelIndex &,int,int,const QModelIndex &,int)521 void ReparentingModel::onSourceRowsMoved(const QModelIndex & /* sourceParent */,
522                                          int /* sourceStart */,
523                                          int /* sourceEnd */,
524                                          const QModelIndex & /* destParent */,
525                                          int /* dest */)
526 {
527     qCWarning(KORGANIZER_LOG) << "not implemented";
528     // TODO
529     endResetModel();
530 }
531 
onSourceLayoutAboutToBeChanged()532 void ReparentingModel::onSourceLayoutAboutToBeChanged()
533 {
534     // layoutAboutToBeChanged();
535     // Q_FOREACH(const QModelIndex &proxyPersistentIndex, persistentIndexList()) {
536     //     Q_ASSERT(proxyPersistentIndex.isValid());
537     //     const QPersistentModelIndex srcPersistentIndex = mapToSource(proxyPersistentIndex);
538     //     // TODO also update the proxy persistent indexes
539     //     //Skip indexes that are not in the source model
540     //     if (!srcPersistentIndex.isValid()) {
541     //         continue;
542     //     }
543     //     mLayoutChangedProxyIndexes << proxyPersistentIndex;
544     //     mLayoutChangedSourcePersistentModelIndexes << srcPersistentIndex;
545     // }
546 }
547 
onSourceLayoutChanged()548 void ReparentingModel::onSourceLayoutChanged()
549 {
550     // By ignoring this we miss structural changes in the sourcemodel, which is mostly ok.
551     // Before we can re-enable this we need to properly deal with skipped duplicates, because
552     // a layout change MUST NOT add/remove new nodes (only shuffling allowed)
553     //
554     // Our source indexes are not endagered since we use persistend model indexes anyways
555 
556     // rebuildAll();
557 
558     // for (int i = 0; i < mLayoutChangedProxyIndexes.size(); ++i) {
559     //     const QModelIndex oldProxyIndex = mLayoutChangedProxyIndexes.at(i);
560     //     const QModelIndex newProxyIndex = mapFromSource(mLayoutChangedSourcePersistentModelIndexes.at(i));
561     //     if (oldProxyIndex != newProxyIndex) {
562     //         changePersistentIndex(oldProxyIndex, newProxyIndex);
563     //     }
564     // }
565 
566     // mLayoutChangedProxyIndexes.clear();
567     // mLayoutChangedSourcePersistentModelIndexes.clear();
568 
569     // layoutChanged();
570 }
571 
onSourceDataChanged(const QModelIndex & begin,const QModelIndex & end)572 void ReparentingModel::onSourceDataChanged(const QModelIndex &begin, const QModelIndex &end)
573 {
574     // qCDebug(KORGANIZER_LOG) << objectName() << begin << end;
575     for (int row = begin.row(); row <= end.row(); row++) {
576         mNodeManager->updateSourceIndex(sourceModel()->index(row, begin.column(), begin.parent()));
577     }
578     Q_EMIT dataChanged(mapFromSource(begin), mapFromSource(end));
579 }
580 
onSourceModelAboutToBeReset()581 void ReparentingModel::onSourceModelAboutToBeReset()
582 {
583     beginResetModel();
584 }
585 
onSourceModelReset()586 void ReparentingModel::onSourceModelReset()
587 {
588     rebuildAll();
589     endResetModel();
590 }
591 
extractNode(const QModelIndex & index) const592 ReparentingModel::Node *ReparentingModel::extractNode(const QModelIndex &index) const
593 {
594     Node *node = static_cast<Node *>(index.internalPointer());
595     Q_ASSERT(node);
596     Q_ASSERT(validateNode(node));
597     return node;
598 }
599 
index(int row,int column,const QModelIndex & parent) const600 QModelIndex ReparentingModel::index(int row, int column, const QModelIndex &parent) const
601 {
602     if (row < 0 || column != 0) {
603         return {};
604     }
605     // qCDebug(KORGANIZER_LOG) << parent << row;
606     const Node *parentNode;
607     if (parent.isValid()) {
608         parentNode = extractNode(parent);
609     } else {
610         parentNode = &mRootNode;
611     }
612     // At least QAbstractItemView expects that we deal with this properly (see rowsAboutToBeRemoved "find the next visible and enabled item")
613     // Also QAbstractItemModel::match does all kinds of weird shit including passing row=-1
614     if (parentNode->children.size() <= row) {
615         return QModelIndex();
616     }
617     Node *node = parentNode->children.at(row).data();
618     Q_ASSERT(validateNode(node));
619     return createIndex(row, column, node);
620 }
621 
mapToSource(const QModelIndex & idx) const622 QModelIndex ReparentingModel::mapToSource(const QModelIndex &idx) const
623 {
624     if (!idx.isValid() || !sourceModel()) {
625         return {};
626     }
627     Node *node = extractNode(idx);
628     if (!node->isSourceNode()) {
629         return QModelIndex();
630     }
631     Q_ASSERT(node->sourceIndex.model() == sourceModel());
632     return node->sourceIndex;
633 }
634 
getSourceNode(const QModelIndex & sourceIndex) const635 ReparentingModel::Node *ReparentingModel::getSourceNode(const QModelIndex &sourceIndex) const
636 {
637     for (Node *n : std::as_const(mSourceNodes)) {
638         if (n->sourceIndex == sourceIndex) {
639             return n;
640         }
641     }
642     // qCDebug(KORGANIZER_LOG) << objectName() <<  "no node found for " << sourceIndex;
643     return nullptr;
644 }
645 
mapFromSource(const QModelIndex & sourceIndex) const646 QModelIndex ReparentingModel::mapFromSource(const QModelIndex &sourceIndex) const
647 {
648     // qCDebug(KORGANIZER_LOG) << sourceIndex << sourceIndex.data().toString();
649     if (!sourceIndex.isValid()) {
650         return {};
651     }
652     Node *node = getSourceNode(sourceIndex);
653     if (!node) {
654         // This can happen if a source nodes is hidden (person collections)
655         return QModelIndex();
656     }
657     Q_ASSERT(validateNode(node));
658     return index(node);
659 }
660 
rebuildFromSource(Node * parentNode,const QModelIndex & sourceParent,const QModelIndexList & skip)661 void ReparentingModel::rebuildFromSource(Node *parentNode, const QModelIndex &sourceParent, const QModelIndexList &skip)
662 {
663     Q_ASSERT(parentNode);
664     if (!sourceModel()) {
665         return;
666     }
667     for (int i = 0; i < sourceModel()->rowCount(sourceParent); ++i) {
668         const QModelIndex &sourceIndex = sourceModel()->index(i, 0, sourceParent);
669         // Skip indexes that should be excluded because they have been reparented
670         if (skip.contains(sourceIndex)) {
671             continue;
672         }
673         appendSourceNode(parentNode, sourceIndex, skip);
674     }
675 }
676 
isDuplicate(const Node::Ptr & proxyNode) const677 bool ReparentingModel::isDuplicate(const Node::Ptr &proxyNode) const
678 {
679     for (const Node *n : std::as_const(mSourceNodes)) {
680         // qCDebug(KORGANIZER_LOG) << index << index.data().toString();
681         if (proxyNode->isDuplicateOf(n->sourceIndex)) {
682             return true;
683         }
684     }
685     return false;
686 }
687 
insertProxyNode(const Node::Ptr & proxyNode)688 void ReparentingModel::insertProxyNode(const Node::Ptr &proxyNode)
689 {
690     // qCDebug(KORGANIZER_LOG) << "checking " << proxyNode->data(Qt::DisplayRole).toString();
691     proxyNode->parent = &mRootNode;
692     mRootNode.addChild(proxyNode);
693     Q_ASSERT(validateNode(proxyNode.data()));
694 }
695 
reparentSourceNodes(const Node::Ptr & proxyNode)696 void ReparentingModel::reparentSourceNodes(const Node::Ptr &proxyNode)
697 {
698     // Reparent source nodes according to the provided rules
699     for (Node *n : std::as_const(mSourceNodes)) {
700         if (proxyNode->adopts(n->sourceIndex)) {
701             // qCDebug(KORGANIZER_LOG) << "reparenting" << n->data(Qt::DisplayRole).toString() << "from" << n->parent->data(Qt::DisplayRole).toString()
702             //         << "to" << proxyNode->data(Qt::DisplayRole).toString();
703 
704             // WARNING: While a beginMoveRows/endMoveRows would be more suitable, QSortFilterProxyModel can't deal with that. Therefore we
705             // cannot use them.
706             const int oldRow = n->row();
707             beginRemoveRows(index(n->parent), oldRow, oldRow);
708             Node::Ptr nodePtr = proxyNode->searchNode(n);
709             // We lie about the row being removed already, but the view can deal with that better than if we call endRemoveRows after beginInsertRows
710             endRemoveRows();
711 
712             const int newRow = proxyNode->children.size();
713             beginInsertRows(index(proxyNode.data()), newRow, newRow);
714             proxyNode->addChild(nodePtr);
715             endInsertRows();
716             Q_ASSERT(validateNode(n));
717         }
718     }
719 }
720 
rebuildAll()721 void ReparentingModel::rebuildAll()
722 {
723     mRootNode.children.clear();
724     for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) {
725         proxyNode->clearHierarchy();
726     }
727     Q_ASSERT(mSourceNodes.isEmpty());
728     mSourceNodes.clear();
729     rebuildFromSource(&mRootNode, QModelIndex());
730     for (const Node::Ptr &proxyNode : std::as_const(mProxyNodes)) {
731         // qCDebug(KORGANIZER_LOG) << "checking " << proxyNode->data(Qt::DisplayRole).toString();
732         // Avoid inserting a node that is already part of the source model
733         if (isDuplicate(proxyNode)) {
734             continue;
735         }
736         insertProxyNode(proxyNode);
737         reparentSourceNodes(proxyNode);
738     }
739 }
740 
data(const QModelIndex & proxyIndex,int role) const741 QVariant ReparentingModel::data(const QModelIndex &proxyIndex, int role) const
742 {
743     if (!proxyIndex.isValid()) {
744         return QVariant();
745     }
746     const Node *node = extractNode(proxyIndex);
747     if (node->isSourceNode()) {
748         return sourceModel()->data(mapToSource(proxyIndex), role);
749     }
750     return node->data(role);
751 }
752 
setData(const QModelIndex & index,const QVariant & value,int role)753 bool ReparentingModel::setData(const QModelIndex &index, const QVariant &value, int role)
754 {
755     if (!index.isValid()) {
756         return false;
757     }
758     Q_ASSERT(index.isValid());
759     if (!sourceModel()) {
760         return false;
761     }
762     Node *node = extractNode(index);
763     if (node->isSourceNode()) {
764         return sourceModel()->setData(mapToSource(index), value, role);
765     }
766     return node->setData(value, role);
767 }
768 
flags(const QModelIndex & index) const769 Qt::ItemFlags ReparentingModel::flags(const QModelIndex &index) const
770 {
771     if (!index.isValid() || !sourceModel()) {
772         return Qt::NoItemFlags;
773     }
774     Node *node = extractNode(index);
775     if (!node->isSourceNode()) {
776         return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
777     }
778     return sourceModel()->flags(mapToSource(index));
779 }
780 
row(ReparentingModel::Node * node) const781 int ReparentingModel::row(ReparentingModel::Node *node) const
782 {
783     Q_ASSERT(node);
784     if (node == &mRootNode) {
785         return -1;
786     }
787     Q_ASSERT(validateNode(node));
788     int row = 0;
789     for (const Node::Ptr &c : std::as_const(node->parent->children)) {
790         if (c.data() == node) {
791             return row;
792         }
793         row++;
794     }
795     return -1;
796 }
797 
index(Node * node) const798 QModelIndex ReparentingModel::index(Node *node) const
799 {
800     const int r = row(node);
801     if (r < 0) {
802         return {};
803     }
804     return createIndex(r, 0, node);
805 }
806 
parent(const QModelIndex & child) const807 QModelIndex ReparentingModel::parent(const QModelIndex &child) const
808 {
809     // qCDebug(KORGANIZER_LOG) << child << child.data().toString();
810     if (!child.isValid()) {
811         return {};
812     }
813     const Node *node = extractNode(child);
814     return index(node->parent);
815 }
816 
buddy(const QModelIndex & index) const817 QModelIndex ReparentingModel::buddy(const QModelIndex &index) const
818 {
819     if (!index.isValid() || !sourceModel()) {
820         return {};
821     }
822     Node *node = extractNode(index);
823     if (node->isSourceNode()) {
824         return mapFromSource(sourceModel()->buddy(mapToSource(index)));
825     }
826     return index;
827 }
828 
rowCount(const QModelIndex & parent) const829 int ReparentingModel::rowCount(const QModelIndex &parent) const
830 {
831     if (!parent.isValid()) {
832         return mRootNode.children.size();
833     }
834 
835     if (parent.column() != 0) {
836         return 0;
837     }
838 
839     Node *node = extractNode(parent);
840     return node->children.size();
841 }
842 
hasChildren(const QModelIndex & parent) const843 bool ReparentingModel::hasChildren(const QModelIndex &parent) const
844 {
845     return rowCount(parent) != 0;
846 }
847 
columnCount(const QModelIndex &) const848 int ReparentingModel::columnCount(const QModelIndex & /* parent */) const
849 {
850     return 1;
851 }
852