1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "treemodel.h"
25 #include <QCoreApplication>
30 class RemoveChildLaterEvent : public QEvent
33 RemoveChildLaterEvent(AbstractTreeItem* child)
34 : QEvent(QEvent::User)
36 inline AbstractTreeItem* child() { return _child; }
39 AbstractTreeItem* _child;
42 /*****************************************
43 * Abstract Items of a TreeModel
44 *****************************************/
45 AbstractTreeItem::AbstractTreeItem(AbstractTreeItem* parent)
47 , _flags(Qt::ItemIsSelectable | Qt::ItemIsEnabled)
50 bool AbstractTreeItem::newChild(AbstractTreeItem* item)
52 int newRow = childCount();
53 emit beginAppendChilds(newRow, newRow);
54 _childItems.append(item);
55 emit endAppendChilds();
59 bool AbstractTreeItem::newChilds(const QList<AbstractTreeItem*>& items)
64 int nextRow = childCount();
65 int lastRow = nextRow + items.count() - 1;
67 emit beginAppendChilds(nextRow, lastRow);
69 emit endAppendChilds();
74 bool AbstractTreeItem::removeChild(int row)
76 if (row < 0 || childCount() <= row)
79 child(row)->removeAllChilds();
80 emit beginRemoveChilds(row, row);
81 AbstractTreeItem* treeitem = _childItems.takeAt(row);
83 emit endRemoveChilds();
90 void AbstractTreeItem::removeAllChilds()
92 const int numChilds = childCount();
97 AbstractTreeItem* child;
99 QList<AbstractTreeItem*>::iterator childIter;
101 childIter = _childItems.begin();
102 while (childIter != _childItems.end()) {
104 child->setTreeItemFlags({}); // disable self deletion, as this would only fuck up consitency and the child gets deleted anyways
105 child->removeAllChilds();
109 emit beginRemoveChilds(0, numChilds - 1);
110 childIter = _childItems.begin();
111 while (childIter != _childItems.end()) {
113 childIter = _childItems.erase(childIter);
116 emit endRemoveChilds();
121 void AbstractTreeItem::removeChildLater(AbstractTreeItem* child)
124 QCoreApplication::postEvent(this, new RemoveChildLaterEvent(child));
127 void AbstractTreeItem::customEvent(QEvent* event)
129 if (event->type() != QEvent::User)
134 auto* removeEvent = static_cast<RemoveChildLaterEvent*>(event);
135 int childRow = _childItems.indexOf(removeEvent->child());
139 // since we are called asynchronously we have to recheck if the item in question still has no childs
140 if (removeEvent->child()->childCount())
143 removeChild(childRow);
146 bool AbstractTreeItem::reParent(AbstractTreeItem* newParent)
148 // currently we support only re parenting if the child that's about to be
149 // adopted does not have any children itself.
150 if (childCount() != 0) {
151 qDebug() << "AbstractTreeItem::reParent(): cannot reparent" << this << "with children.";
159 emit parent()->beginRemoveChilds(oldRow, oldRow);
160 parent()->_childItems.removeAt(oldRow);
161 emit parent()->endRemoveChilds();
163 AbstractTreeItem* oldParent = parent();
164 setParent(newParent);
166 bool success = newParent->newChild(this);
168 qWarning() << "AbstractTreeItem::reParent(): failed to attach to new parent after removing from old parent! this:" << this
169 << "new parent:" << newParent;
172 oldParent->checkForDeletion();
177 AbstractTreeItem* AbstractTreeItem::child(int row) const
179 if (childCount() <= row)
182 return _childItems[row];
185 int AbstractTreeItem::childCount(int column) const
190 return _childItems.count();
193 int AbstractTreeItem::row() const
196 qWarning() << "AbstractTreeItem::row():" << this << "has no parent AbstractTreeItem as it's parent! parent is" << QObject::parent();
200 int row_ = parent()->_childItems.indexOf(const_cast<AbstractTreeItem*>(this));
202 qWarning() << "AbstractTreeItem::row():" << this << "is not in the child list of" << QObject::parent();
206 void AbstractTreeItem::dumpChildList()
208 qDebug() << "==== Childlist for Item:" << this << "====";
209 if (childCount() > 0) {
210 AbstractTreeItem* child;
211 QList<AbstractTreeItem*>::const_iterator childIter = _childItems.constBegin();
212 while (childIter != _childItems.constEnd()) {
214 qDebug() << "Row:" << child->row() << child << child->data(0, Qt::DisplayRole);
218 qDebug() << "==== End Of Childlist ====";
221 /*****************************************
223 *****************************************/
224 SimpleTreeItem::SimpleTreeItem(QList<QVariant> data, AbstractTreeItem* parent)
225 : AbstractTreeItem(parent)
226 , _itemData(std::move(data))
229 QVariant SimpleTreeItem::data(int column, int role) const
231 if (column >= columnCount() || role != Qt::DisplayRole)
234 return _itemData[column];
237 bool SimpleTreeItem::setData(int column, const QVariant& value, int role)
239 if (column > columnCount() || role != Qt::DisplayRole)
242 if (column == columnCount())
243 _itemData.append(value);
245 _itemData[column] = value;
247 emit dataChanged(column);
251 int SimpleTreeItem::columnCount() const
253 return _itemData.count();
256 /*****************************************
258 *****************************************/
259 PropertyMapItem::PropertyMapItem(AbstractTreeItem* parent)
260 : AbstractTreeItem(parent)
263 QVariant PropertyMapItem::data(int column, int role) const
265 if (column >= columnCount())
269 case Qt::ToolTipRole:
270 return toolTip(column);
271 case Qt::DisplayRole:
272 case TreeModel::SortRole: // fallthrough, since SortRole should default to DisplayRole
273 return property(propertyOrder()[column].toLatin1());
279 bool PropertyMapItem::setData(int column, const QVariant& value, int role)
281 if (column >= columnCount() || role != Qt::DisplayRole)
284 setProperty(propertyOrder()[column].toLatin1(), value);
285 emit dataChanged(column);
289 int PropertyMapItem::columnCount() const
291 return propertyOrder().count();
294 /*****************************************
296 *****************************************/
297 TreeModel::TreeModel(const QList<QVariant>& data, QObject* parent)
298 : QAbstractItemModel(parent)
299 , _childStatus(QModelIndex(), 0, 0, 0)
300 , _aboutToRemoveOrInsert(false)
302 rootItem = new SimpleTreeItem(data, nullptr);
303 connectItem(rootItem);
305 if (Quassel::isOptionSet("debugmodel")) {
306 connect(this, &QAbstractItemModel::rowsAboutToBeInserted, this, &TreeModel::debug_rowsAboutToBeInserted);
307 connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TreeModel::debug_rowsAboutToBeRemoved);
308 connect(this, &QAbstractItemModel::rowsInserted, this, &TreeModel::debug_rowsInserted);
309 connect(this, &QAbstractItemModel::rowsRemoved, this, &TreeModel::debug_rowsRemoved);
310 connect(this, &QAbstractItemModel::dataChanged, this, &TreeModel::debug_dataChanged);
314 TreeModel::~TreeModel()
319 AbstractTreeItem* TreeModel::root() const
324 QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const
326 if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
329 AbstractTreeItem* parentItem;
331 if (!parent.isValid())
332 parentItem = rootItem;
334 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
336 AbstractTreeItem* childItem = parentItem->child(row);
339 return createIndex(row, column, childItem);
344 QModelIndex TreeModel::indexByItem(AbstractTreeItem* item) const
346 if (item == nullptr) {
347 qWarning() << "TreeModel::indexByItem(AbstractTreeItem *item) received NULL-Pointer";
351 if (item == rootItem)
354 return createIndex(item->row(), 0, item);
357 QModelIndex TreeModel::parent(const QModelIndex& index) const
359 if (!index.isValid()) {
360 // ModelTest does this
361 // qWarning() << "TreeModel::parent(): has been asked for the rootItems Parent!";
365 auto* childItem = static_cast<AbstractTreeItem*>(index.internalPointer());
366 AbstractTreeItem* parentItem = childItem->parent();
368 Q_ASSERT(parentItem);
369 if (parentItem == rootItem)
372 return createIndex(parentItem->row(), 0, parentItem);
375 int TreeModel::rowCount(const QModelIndex& parent) const
377 AbstractTreeItem* parentItem;
378 if (!parent.isValid())
379 parentItem = rootItem;
381 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
383 return parentItem->childCount(parent.column());
386 int TreeModel::columnCount(const QModelIndex& parent) const
389 return rootItem->columnCount();
390 // since there the Qt Views don't draw more columns than the header has columns
391 // we can be lazy and simply return the count of header columns
392 // actually this gives us more freedom cause we don't have to ensure that a rows parent
393 // has equal or more columns than that row
395 // AbstractTreeItem *parentItem;
396 // if(!parent.isValid())
397 // parentItem = rootItem;
399 // parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
400 // return parentItem->columnCount();
403 QVariant TreeModel::data(const QModelIndex& index, int role) const
405 if (!index.isValid())
408 auto* item = static_cast<AbstractTreeItem*>(index.internalPointer());
409 return item->data(index.column(), role);
412 bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
414 if (!index.isValid())
417 auto* item = static_cast<AbstractTreeItem*>(index.internalPointer());
418 return item->setData(index.column(), value, role);
421 Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const
423 if (!index.isValid()) {
424 return rootItem->flags() & Qt::ItemIsDropEnabled;
427 auto* item = static_cast<AbstractTreeItem*>(index.internalPointer());
428 return item->flags();
432 QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
434 if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
435 return rootItem->data(section, role);
440 void TreeModel::itemDataChanged(int column)
442 auto* item = qobject_cast<AbstractTreeItem*>(sender());
443 QModelIndex leftIndex, rightIndex;
445 if (item == rootItem)
449 leftIndex = createIndex(item->row(), 0, item);
450 rightIndex = createIndex(item->row(), item->columnCount() - 1, item);
453 leftIndex = createIndex(item->row(), column, item);
454 rightIndex = leftIndex;
457 emit dataChanged(leftIndex, rightIndex);
460 void TreeModel::connectItem(AbstractTreeItem* item)
462 connect(item, &AbstractTreeItem::dataChanged, this, &TreeModel::itemDataChanged);
464 connect(item, &AbstractTreeItem::beginAppendChilds, this, &TreeModel::beginAppendChilds);
465 connect(item, &AbstractTreeItem::endAppendChilds, this, &TreeModel::endAppendChilds);
467 connect(item, &AbstractTreeItem::beginRemoveChilds, this, &TreeModel::beginRemoveChilds);
468 connect(item, &AbstractTreeItem::endRemoveChilds, this, &TreeModel::endRemoveChilds);
471 void TreeModel::beginAppendChilds(int firstRow, int lastRow)
473 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
475 qWarning() << "TreeModel::beginAppendChilds(): cannot append Children to unknown parent";
479 QModelIndex parent = indexByItem(parentItem);
480 Q_ASSERT(!_aboutToRemoveOrInsert);
482 _aboutToRemoveOrInsert = true;
483 _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
484 beginInsertRows(parent, firstRow, lastRow);
487 void TreeModel::endAppendChilds()
489 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
491 qWarning() << "TreeModel::endAppendChilds(): cannot append Children to unknown parent";
494 Q_ASSERT(_aboutToRemoveOrInsert);
495 ChildStatus cs = _childStatus;
497 QModelIndex parent = indexByItem(parentItem);
498 Q_ASSERT(cs.parent == parent);
499 Q_ASSERT(rowCount(parent) == cs.childCount + cs.end - cs.start + 1);
501 _aboutToRemoveOrInsert = false;
502 for (int i = cs.start; i <= cs.end; i++) {
503 connectItem(parentItem->child(i));
508 void TreeModel::beginRemoveChilds(int firstRow, int lastRow)
510 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
512 qWarning() << "TreeModel::beginRemoveChilds(): cannot append Children to unknown parent";
516 for (int i = firstRow; i <= lastRow; i++) {
517 disconnect(parentItem->child(i), nullptr, this, nullptr);
521 QModelIndex parent = indexByItem(parentItem);
522 Q_ASSERT(firstRow <= lastRow);
523 Q_ASSERT(parentItem->childCount() > lastRow);
524 Q_ASSERT(!_aboutToRemoveOrInsert);
525 _aboutToRemoveOrInsert = true;
526 _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
528 beginRemoveRows(parent, firstRow, lastRow);
531 void TreeModel::endRemoveChilds()
533 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
535 qWarning() << "TreeModel::endRemoveChilds(): cannot remove Children from unknown parent";
539 // concistency checks
540 Q_ASSERT(_aboutToRemoveOrInsert);
542 ChildStatus cs = _childStatus;
543 QModelIndex parent = indexByItem(parentItem);
544 Q_ASSERT(cs.parent == parent);
545 Q_ASSERT(rowCount(parent) == cs.childCount - cs.end + cs.start - 1);
547 _aboutToRemoveOrInsert = false;
552 void TreeModel::clear()
554 rootItem->removeAllChilds();
557 void TreeModel::debug_rowsAboutToBeInserted(const QModelIndex& parent, int start, int end)
559 qDebug() << "debug_rowsAboutToBeInserted" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start
563 void TreeModel::debug_rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
565 AbstractTreeItem* parentItem;
566 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
568 parentItem = rootItem;
569 qDebug() << "debug_rowsAboutToBeRemoved" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
571 // Make sure model is valid first
572 if (!parent.model()) {
573 qDebug() << "Parent model is not valid!" << end;
578 for (int i = end; i >= start; i--) {
579 child = parent.model()->index(i, 0, parent);
580 Q_ASSERT(parentItem->child(i));
581 qDebug() << ">>>" << i << child << child.data().toString();
585 void TreeModel::debug_rowsInserted(const QModelIndex& parent, int start, int end)
587 AbstractTreeItem* parentItem;
588 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
590 parentItem = rootItem;
591 qDebug() << "debug_rowsInserted:" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
593 // Make sure model is valid first
594 if (!parent.model()) {
595 qDebug() << "Parent model is not valid!" << end;
600 for (int i = start; i <= end; i++) {
601 child = parent.model()->index(i, 0, parent);
602 Q_ASSERT(parentItem->child(i));
603 qDebug() << "<<<" << i << child << child.data().toString();
607 void TreeModel::debug_rowsRemoved(const QModelIndex& parent, int start, int end)
609 qDebug() << "debug_rowsRemoved" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
612 void TreeModel::debug_dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
614 qDebug() << "debug_dataChanged" << topLeft << bottomRight;
615 QStringList displayData;
616 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
617 displayData = QStringList();
618 for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
619 displayData << data(topLeft.sibling(row, column), Qt::DisplayRole).toString();
621 qDebug() << " row:" << row << displayData;