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