7#include "akonadicalendar_debug.h"
8#include "incidencetreemodel_p.h"
10#include <Akonadi/EntityTreeModel>
15static void calculateDepth(
const Node::Ptr &node)
18 node->depth = node->parentNode ? 1 + node->parentNode->depth : 0;
19 for (
const Node::Ptr &child : std::as_const(node->directChilds)) {
20 calculateDepth(child);
25static bool reverseDepthLessThan(
const Node::Ptr &node1,
const Node::Ptr &node2)
27 return node1->depth > node2->depth;
31static bool depthLessThan(
const PreNode::Ptr &node1,
const PreNode::Ptr &node2)
33 if (node1->depth == -1) {
36 return node1->depth < node2->depth || node2->depth == -1;
39static PreNode::List sortedPrenodes(
const PreNode::List &nodes)
41 const int count = nodes.count();
43 PreNode::List remainingNodes = nodes;
45 while (prenodeByUid.
count() < count) {
46 const auto preSize = prenodeByUid.
count();
47 for (
const PreNode::Ptr &node : nodes) {
49 const QString uid = node->incidence->instanceIdentifier();
50 const QString parentUid = node->incidence->relatedTo();
52 prenodeByUid.
insert(uid, node);
53 remainingNodes.removeAll(node);
56 if (prenodeByUid.
contains(parentUid)) {
57 node->depth = 1 + prenodeByUid.
value(parentUid)->depth;
58 remainingNodes.removeAll(node);
59 prenodeByUid.
insert(uid, node);
64 if (preSize == prenodeByUid.
count()) {
69 PreNode::List sorted = nodes;
70 std::sort(sorted.begin(), sorted.end(), depthLessThan);
76 , m_mimeTypes(mimeTypes)
81int IncidenceTreeModelPrivate::rowForNode(
const Node::Ptr &node)
const
84 const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node);
89void IncidenceTreeModelPrivate::assert_and_dump(
bool condition,
const QString &message)
92 qCCritical(AKONADICALENDAR_LOG) <<
"This should never happen: " << message;
98void IncidenceTreeModelPrivate::dumpTree()
100 for (
const Node::Ptr &node : std::as_const(m_toplevelNodeList)) {
101 qCDebug(AKONADICALENDAR_LOG) << node;
105QModelIndex IncidenceTreeModelPrivate::indexForNode(
const Node::Ptr &node)
const
110 const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node);
113 return q->createIndex(row, 0, node.data());
116void IncidenceTreeModelPrivate::reset(
bool silent)
119 q->beginResetModel();
121 m_toplevelNodeList.clear();
124 m_waitingForParent.clear();
126 if (q->sourceModel()) {
127 const int sourceCount = q->sourceModel()->rowCount();
128 for (
int i = 0; i < sourceCount; ++i) {
129 PreNode::Ptr prenode = prenodeFromSourceRow(i);
130 if (prenode && (m_mimeTypes.isEmpty() || m_mimeTypes.contains(prenode->incidence->mimeType()))) {
131 insertNode(prenode,
true);
140void IncidenceTreeModelPrivate::onHeaderDataChanged(
Qt::Orientation orientation,
int first,
int last)
142 Q_EMIT q->headerDataChanged(orientation, first, last);
147 Q_ASSERT(
begin.isValid());
148 Q_ASSERT(
end.isValid());
149 Q_ASSERT(q->sourceModel());
150 Q_ASSERT(!
begin.parent().isValid());
153 const int first_row =
begin.row();
154 const int last_row =
end.row();
156 for (
int i = first_row; i <= last_row; ++i) {
157 QModelIndex sourceIndex = q->sourceModel()->index(i, 0);
158 Q_ASSERT(sourceIndex.
isValid());
167 Node::Ptr node = m_uidMap.value(rawNode->uid);
169 Node::Ptr oldParentNode = node->parentNode;
171 Q_ASSERT(item.isValid());
175 qCCritical(AKONADICALENDAR_LOG) <<
"Incidence shouldn't be invalid." << item.hasPayload() << item.id();
181 if (node->uid !=
incidence->instanceIdentifier()) {
182 qCDebug(AKONADICALENDAR_LOG) <<
"Incidence UID has changed" << node->uid << incidence->instanceIdentifier();
183 m_itemByUid.remove(node->uid);
184 m_uidMap.remove(node->uid);
185 node->uid = incidence->instanceIdentifier();
186 m_uidMap.insert(node->uid, node);
188 m_itemByUid.insert(
incidence->instanceIdentifier(), item);
190 Node::Ptr newParentNode;
192 if (!newParentUid.isEmpty()) {
193 Q_ASSERT(m_uidMap.contains(newParentUid));
194 newParentNode = m_uidMap.value(newParentUid);
195 Q_ASSERT(newParentNode);
198 const bool parentChanged = newParentNode.data() != oldParentNode.data();
201 const int fromRow = rowForNode(node);
207 newParentIndex = q->mapFromSource(newParentNode->sourceIndex);
208 Q_ASSERT(newParentIndex.
isValid());
209 toRow = newParentNode->directChilds.count();
213 toRow = m_toplevelNodeList.count();
216 const bool res = q->beginMoveRows( index.
parent(), fromRow, fromRow, newParentIndex, toRow);
222 newParentNode->directChilds.append(node);
223 node->parentNode = newParentNode;
226 oldParentNode->directChilds.remove(fromRow);
227 Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1);
229 m_toplevelNodeList.remove(fromRow);
230 Q_ASSERT(m_toplevelNodeList.indexOf(node) == -1);
234 m_toplevelNodeList.append(node);
235 node->parentNode = Node::Ptr();
236 oldParentNode->directChilds.remove(fromRow);
237 Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1);
243 index = indexForNode(node);
247 Q_EMIT q->indexChangedParent(index.
parent());
250 Q_EMIT q->dataChanged(index, index);
256void IncidenceTreeModelPrivate::onRowsAboutToBeInserted(
const QModelIndex &parent,
int,
int)
265PreNode::Ptr IncidenceTreeModelPrivate::prenodeFromSourceRow(
int row)
const
267 PreNode::Ptr node = PreNode::Ptr(
new PreNode());
268 node->sourceIndex = q->sourceModel()->index(row, 0,
QModelIndex());
269 Q_ASSERT(node->sourceIndex.isValid());
270 Q_ASSERT(node->sourceIndex.model() == q->sourceModel());
273 if (!item.isValid()) {
280 Q_ASSERT(node->incidence);
285void IncidenceTreeModelPrivate::onRowsInserted(
const QModelIndex &parent,
int begin,
int end)
291 Q_ASSERT(begin <= end);
293 for (
int i = begin; i <=
end; ++i) {
294 PreNode::Ptr node = prenodeFromSourceRow(i);
296 if (!node || (!m_mimeTypes.isEmpty() && !m_mimeTypes.contains(node->incidence->mimeType()))) {
302 const PreNode::List sortedNodes = sortedPrenodes(nodes);
304 for (
const PreNode::Ptr &node : sortedNodes) {
310 Q_EMIT q->batchInsertionFinished();
315void IncidenceTreeModelPrivate::insertNode(
const PreNode::Ptr &prenode,
bool silent)
319 Node::Ptr node(
new Node());
320 node->sourceIndex = prenode->sourceIndex;
321 node->id = item.
id();
322 node->uid =
incidence->instanceIdentifier();
323 m_itemByUid.insert(node->uid, item);
325 node->parentUid =
incidence->relatedTo();
326 if (node->uid == node->parentUid) {
327 qCWarning(AKONADICALENDAR_LOG) <<
"Incidence with itself as parent!" << node->uid <<
"Akonadi item" << item.
id() <<
"remoteId=" << item.
remoteId();
328 node->parentUid.
clear();
331 if (m_uidMap.contains(node->uid)) {
332 qCWarning(AKONADICALENDAR_LOG) <<
"Duplicate incidence detected:"
333 <<
"uid=" << node->uid <<
". File a bug against the resource. collection=" << item.
storageCollectionId();
337 Q_ASSERT(!m_nodeMap.contains(node->id));
338 m_uidMap.insert(node->uid, node);
339 m_nodeMap.insert(item.
id(), node);
342 bool mustInsertIntoParent =
false;
344 const bool hasParent = !node->parentUid.isEmpty();
347 if (m_uidMap.contains(node->parentUid)) {
348 node->parentNode = m_uidMap.value(node->parentUid);
351 mustInsertIntoParent =
true;
352 rowToUse = node->parentNode->directChilds.count();
355 Q_ASSERT(!m_waitingForParent.contains(node->parentUid, node));
356 m_waitingForParent.insert(node->parentUid, node);
360 if (!node->parentNode) {
361 rowToUse = m_toplevelNodeList.count();
365 const QModelIndex &parent = indexForNode(node->parentNode);
367 q->beginInsertRows(parent, rowToUse, rowToUse);
370 if (!node->parentNode) {
371 m_toplevelNodeList.append(node);
374 if (mustInsertIntoParent) {
375 node->parentNode->directChilds.append(node);
383 if (m_waitingForParent.contains(node->uid)) {
384 Q_ASSERT(m_waitingForParent.count(node->uid) > 0);
386 m_waitingForParent.
remove(node->uid);
389 for (
const Node::Ptr &child : children) {
390 const int fromRow = m_toplevelNodeList.indexOf(child);
391 Q_ASSERT(fromRow != -1);
394 Q_ASSERT(toParent.
model() == q);
401 q->beginResetModel();
404 child->parentNode = node;
405 node->directChilds.append(child);
406 m_toplevelNodeList.remove(fromRow);
418Node::List IncidenceTreeModelPrivate::sorted(
const Node::List &nodes)
const
420 if (nodes.isEmpty()) {
425 for (
const Node::Ptr &topLevelNode : std::as_const(m_toplevelNodeList)) {
426 calculateDepth(topLevelNode);
429 Node::List sorted = nodes;
430 std::sort(sorted.begin(), sorted.end(), reverseDepthLessThan);
435void IncidenceTreeModelPrivate::onRowsAboutToBeRemoved(
const QModelIndex &parent,
int begin,
int end)
441 Q_ASSERT(begin <= end);
444 Node::List nodesToRemove;
445 for (
int i = begin; i <=
end; ++i) {
447 Q_ASSERT(sourceIndex.
isValid());
448 Q_ASSERT(sourceIndex.
model() == q->sourceModel());
451 if (!m_nodeMap.contains(
id)) {
453 Q_ASSERT(m_mimeTypes.count() != 3);
456 Node::Ptr node = m_nodeMap.value(
id);
457 Q_ASSERT(node->id ==
id);
458 nodesToRemove << node;
462 const Node::List nodesToRemoveSorted = sorted(nodesToRemove);
464 for (
const Node::Ptr &node : nodesToRemoveSorted) {
473 m_removedNodes.clear();
477void IncidenceTreeModelPrivate::removeNode(
const Node::Ptr &node)
484 if (!node->directChilds.isEmpty()) {
485 const Node::List children = node->directChilds;
487 Q_ASSERT(fromParent.
isValid());
493 q->beginResetModel();
494 node->directChilds.clear();
495 for (
const Node::Ptr &child : children) {
497 m_toplevelNodeList.append(child);
498 child->parentNode = Node::Ptr();
499 m_waitingForParent.insert(node->uid, child);
505 const QModelIndex parent = indexForNode(node->parentNode);
507 const int rowToRemove = rowForNode(node);
511 q->beginRemoveRows(parent, rowToRemove, rowToRemove);
512 m_itemByUid.remove(node->uid);
515 node->parentNode->directChilds.remove(rowToRemove);
516 node->parentNode = Node::Ptr();
518 m_toplevelNodeList.remove(rowToRemove);
521 if (!node->parentUid.isEmpty()) {
522 m_waitingForParent.remove(node->parentUid, node);
525 m_uidMap.remove(node->uid);
526 m_nodeMap.remove(node->id);
529 m_removedNodes << node.data();
532void IncidenceTreeModelPrivate::onRowsRemoved(
const QModelIndex &parent,
int begin,
int end)
540void IncidenceTreeModelPrivate::onModelAboutToBeReset()
542 q->beginResetModel();
545void IncidenceTreeModelPrivate::onModelReset()
551void IncidenceTreeModelPrivate::onLayoutAboutToBeChanged()
553 Q_ASSERT(q->persistentIndexList().isEmpty());
554 Q_EMIT q->layoutAboutToBeChanged();
557void IncidenceTreeModelPrivate::onLayoutChanged()
560 Q_ASSERT(q->persistentIndexList().isEmpty());
561 Q_EMIT q->layoutChanged();
572 q->beginResetModel();
574 if (q->sourceModel()) {
575 disconnect(q->sourceModel(), &IncidenceTreeModel::dataChanged,
this, &IncidenceTreeModelPrivate::onDataChanged);
577 disconnect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged,
this, &IncidenceTreeModelPrivate::onHeaderDataChanged);
579 disconnect(q->sourceModel(), &IncidenceTreeModel::rowsInserted,
this, &IncidenceTreeModelPrivate::onRowsInserted);
581 disconnect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved,
this, &IncidenceTreeModelPrivate::onRowsRemoved);
583 disconnect(q->sourceModel(), &IncidenceTreeModel::rowsMoved,
this, &IncidenceTreeModelPrivate::onRowsMoved);
585 disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted,
this, &IncidenceTreeModelPrivate::onRowsAboutToBeInserted);
587 disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved,
this, &IncidenceTreeModelPrivate::onRowsAboutToBeRemoved);
589 disconnect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset,
this, &IncidenceTreeModelPrivate::onModelAboutToBeReset);
591 disconnect(q->sourceModel(), &IncidenceTreeModel::modelReset,
this, &IncidenceTreeModelPrivate::onModelReset);
593 disconnect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged,
this, &IncidenceTreeModelPrivate::onLayoutAboutToBeChanged);
595 disconnect(q->sourceModel(), &IncidenceTreeModel::layoutChanged,
this, &IncidenceTreeModelPrivate::onLayoutChanged);
598 q->QAbstractProxyModel::setSourceModel(model);
600 if (q->sourceModel()) {
601 connect(q->sourceModel(), &IncidenceTreeModel::dataChanged,
this, &IncidenceTreeModelPrivate::onDataChanged);
603 connect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged,
this, &IncidenceTreeModelPrivate::onHeaderDataChanged);
605 connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted,
this, &IncidenceTreeModelPrivate::onRowsAboutToBeInserted);
607 connect(q->sourceModel(), &IncidenceTreeModel::rowsInserted,
this, &IncidenceTreeModelPrivate::onRowsInserted);
609 connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved,
this, &IncidenceTreeModelPrivate::onRowsAboutToBeRemoved);
611 connect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved,
this, &IncidenceTreeModelPrivate::onRowsRemoved);
613 connect(q->sourceModel(), &IncidenceTreeModel::rowsMoved,
this, &IncidenceTreeModelPrivate::onRowsMoved);
615 connect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset,
this, &IncidenceTreeModelPrivate::onModelAboutToBeReset);
617 connect(q->sourceModel(), &IncidenceTreeModel::modelReset,
this, &IncidenceTreeModelPrivate::onModelReset);
619 connect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged,
this, &IncidenceTreeModelPrivate::onLayoutAboutToBeChanged);
621 connect(q->sourceModel(), &IncidenceTreeModel::layoutChanged,
this, &IncidenceTreeModelPrivate::onLayoutChanged);
630 , d(new IncidenceTreeModelPrivate(this,
QStringList()))
637 , d(new IncidenceTreeModelPrivate(this, mimeTypes))
642IncidenceTreeModel::~IncidenceTreeModel() =
default;
652 Q_ASSERT(sourceIndex.
isValid());
657int IncidenceTreeModel::rowCount(
const QModelIndex &parent)
const
660 Q_ASSERT(
parent.model() ==
this);
661 Node *parentNode =
reinterpret_cast<Node *
>(
parent.internalPointer());
662 Q_ASSERT(parentNode);
665 const int count = parentNode->directChilds.count();
669 return d->m_toplevelNodeList.count();
672int IncidenceTreeModel::columnCount(
const QModelIndex &parent)
const
675 Q_ASSERT(
parent.model() ==
this);
685 d->setSourceModel(model);
691 qCWarning(AKONADICALENDAR_LOG) <<
"IncidenceTreeModel::mapFromSource() source index is invalid";
703 if (
id == -1 || !d->m_nodeMap.contains(
id)) {
707 const Node::Ptr node = d->m_nodeMap.value(
id);
710 return d->indexForNode(node);
719 Q_ASSERT(proxyIndex.
column() < columnCount());
721 Q_ASSERT(proxyIndex.
model() ==
this);
739 qCWarning(AKONADICALENDAR_LOG) <<
"IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid";
751 qCWarning(AKONADICALENDAR_LOG) <<
"IncidenceTreeModel::parent(): child is invalid";
756 Q_ASSERT(child.
model() ==
this);
759 if (d->m_removedNodes.contains(childNode)) {
760 qCWarning(AKONADICALENDAR_LOG) <<
"IncidenceTreeModel::parent() Node already removed.";
771 qCWarning(AKONADICALENDAR_LOG) <<
"IncidenceTreeModel::parent(): proxyModelIndex is invalid.";
776 Q_ASSERT(parentIndex.
model() ==
this);
785 if (row < 0 || row >= rowCount(
parent)) {
794 Q_ASSERT(column >= 0);
795 Q_ASSERT(column < columnCount());
798 Q_ASSERT(
parent.model() ==
this);
799 Q_ASSERT(
parent.internalPointer());
800 Node *parentNode =
reinterpret_cast<Node *
>(
parent.internalPointer());
802 if (row >= parentNode->directChilds.count()) {
803 qCCritical(AKONADICALENDAR_LOG) <<
"IncidenceTreeModel::index() row=" << row <<
"; column=" << column;
808 return createIndex(row, column, parentNode->directChilds.at(row).data());
810 Q_ASSERT(row < d->m_toplevelNodeList.count());
811 Node::Ptr node = d->m_toplevelNodeList.at(row);
817bool IncidenceTreeModel::hasChildren(
const QModelIndex &parent)
const
820 Q_ASSERT(
parent.column() < columnCount());
821 if (
parent.column() != 0) {
825 Node *parentNode =
reinterpret_cast<Node *
>(
parent.internalPointer());
826 Q_ASSERT(parentNode);
827 return !parentNode->directChilds.isEmpty();
829 return !d->m_toplevelNodeList.isEmpty();
837 qCWarning(AKONADICALENDAR_LOG) <<
"Called with an empty uid";
839 if (d->m_itemByUid.contains(uid)) {
840 item = d->m_itemByUid.value(uid);
842 qCWarning(AKONADICALENDAR_LOG) <<
"There's no incidence with uid " << uid;
852 static int level = 0;
855 s << padding +
QLatin1StringView(
"node") << node.
data() << QStringLiteral(
";uid=") << node->uid << QStringLiteral(
";id=") << node->id
856 << QStringLiteral(
";parentUid=") << node->parentUid << QStringLiteral(
";parentNode=") << (
void *)(node->parentNode.data()) <<
'\n';
858 for (
const Node::Ptr &child : std::as_const(node->directChilds)) {
866#include "moc_incidencetreemodel_p.cpp"
868#include "moc_incidencetreemodel.cpp"
Hierarchical incidence model.
Akonadi::Item item(const QString &incidenceUid) const
Returns the akonadi item containing the incidence with incidenceUid.
IncidenceTreeModel(QObject *parent=nullptr)
Constructs a new IncidenceTreeModel.
Collection::Id storageCollectionId() const
Node * parentNode() const
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
Returns the incidence from an Akonadi item, or a null pointer if the item has no such payload.
FreeBusyManager::Singleton.
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & begin()
QModelIndex createIndex(int row, int column, const void *ptr) const const
bool contains(const Key &key) const const
qsizetype count() const const
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
const char * data() const const
bool isEmpty() const const
void remove(qsizetype i, qsizetype n)
QVariant data(int role) const const
void * internalPointer() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
QModelIndex sibling(int row, int column) const const
QObject * parent() const const
void setObjectName(QAnyStringView name)
bool isEmpty() const const
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
qlonglong toLongLong(bool *ok) const const