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)
48 , _treeItemFlags(nullptr)
51 bool AbstractTreeItem::newChild(AbstractTreeItem* item)
53 int newRow = childCount();
54 emit beginAppendChilds(newRow, newRow);
55 _childItems.append(item);
56 emit endAppendChilds();
60 bool AbstractTreeItem::newChilds(const QList<AbstractTreeItem*>& items)
65 int nextRow = childCount();
66 int lastRow = nextRow + items.count() - 1;
68 emit beginAppendChilds(nextRow, lastRow);
70 emit endAppendChilds();
75 bool AbstractTreeItem::removeChild(int row)
77 if (row < 0 || childCount() <= row)
80 child(row)->removeAllChilds();
81 emit beginRemoveChilds(row, row);
82 AbstractTreeItem* treeitem = _childItems.takeAt(row);
84 emit endRemoveChilds();
91 void AbstractTreeItem::removeAllChilds()
93 const int numChilds = childCount();
98 AbstractTreeItem* child;
100 QList<AbstractTreeItem*>::iterator childIter;
102 childIter = _childItems.begin();
103 while (childIter != _childItems.end()) {
105 child->setTreeItemFlags(nullptr); // disable self deletion, as this would only fuck up consitency and the child gets deleted anyways
106 child->removeAllChilds();
110 emit beginRemoveChilds(0, numChilds - 1);
111 childIter = _childItems.begin();
112 while (childIter != _childItems.end()) {
114 childIter = _childItems.erase(childIter);
117 emit endRemoveChilds();
122 void AbstractTreeItem::removeChildLater(AbstractTreeItem* child)
125 QCoreApplication::postEvent(this, new RemoveChildLaterEvent(child));
128 void AbstractTreeItem::customEvent(QEvent* event)
130 if (event->type() != QEvent::User)
135 auto* removeEvent = static_cast<RemoveChildLaterEvent*>(event);
136 int childRow = _childItems.indexOf(removeEvent->child());
140 // since we are called asynchronously we have to recheck if the item in question still has no childs
141 if (removeEvent->child()->childCount())
144 removeChild(childRow);
147 bool AbstractTreeItem::reParent(AbstractTreeItem* newParent)
149 // currently we support only re parenting if the child that's about to be
150 // adopted does not have any children itself.
151 if (childCount() != 0) {
152 qDebug() << "AbstractTreeItem::reParent(): cannot reparent" << this << "with children.";
160 emit parent()->beginRemoveChilds(oldRow, oldRow);
161 parent()->_childItems.removeAt(oldRow);
162 emit parent()->endRemoveChilds();
164 AbstractTreeItem* oldParent = parent();
165 setParent(newParent);
167 bool success = newParent->newChild(this);
169 qWarning() << "AbstractTreeItem::reParent(): failed to attach to new parent after removing from old parent! this:" << this
170 << "new parent:" << newParent;
173 oldParent->checkForDeletion();
178 AbstractTreeItem* AbstractTreeItem::child(int row) const
180 if (childCount() <= row)
183 return _childItems[row];
186 int AbstractTreeItem::childCount(int column) const
191 return _childItems.count();
194 int AbstractTreeItem::row() const
197 qWarning() << "AbstractTreeItem::row():" << this << "has no parent AbstractTreeItem as it's parent! parent is" << QObject::parent();
201 int row_ = parent()->_childItems.indexOf(const_cast<AbstractTreeItem*>(this));
203 qWarning() << "AbstractTreeItem::row():" << this << "is not in the child list of" << QObject::parent();
207 void AbstractTreeItem::dumpChildList()
209 qDebug() << "==== Childlist for Item:" << this << "====";
210 if (childCount() > 0) {
211 AbstractTreeItem* child;
212 QList<AbstractTreeItem*>::const_iterator childIter = _childItems.constBegin();
213 while (childIter != _childItems.constEnd()) {
215 qDebug() << "Row:" << child->row() << child << child->data(0, Qt::DisplayRole);
219 qDebug() << "==== End Of Childlist ====";
222 /*****************************************
224 *****************************************/
225 SimpleTreeItem::SimpleTreeItem(QList<QVariant> data, AbstractTreeItem* parent)
226 : AbstractTreeItem(parent)
227 , _itemData(std::move(data))
230 QVariant SimpleTreeItem::data(int column, int role) const
232 if (column >= columnCount() || role != Qt::DisplayRole)
235 return _itemData[column];
238 bool SimpleTreeItem::setData(int column, const QVariant& value, int role)
240 if (column > columnCount() || role != Qt::DisplayRole)
243 if (column == columnCount())
244 _itemData.append(value);
246 _itemData[column] = value;
248 emit dataChanged(column);
252 int SimpleTreeItem::columnCount() const
254 return _itemData.count();
257 /*****************************************
259 *****************************************/
260 PropertyMapItem::PropertyMapItem(AbstractTreeItem* parent)
261 : AbstractTreeItem(parent)
264 QVariant PropertyMapItem::data(int column, int role) const
266 if (column >= columnCount())
270 case Qt::ToolTipRole:
271 return toolTip(column);
272 case Qt::DisplayRole:
273 case TreeModel::SortRole: // fallthrough, since SortRole should default to DisplayRole
274 return property(propertyOrder()[column].toLatin1());
280 bool PropertyMapItem::setData(int column, const QVariant& value, int role)
282 if (column >= columnCount() || role != Qt::DisplayRole)
285 setProperty(propertyOrder()[column].toLatin1(), value);
286 emit dataChanged(column);
290 int PropertyMapItem::columnCount() const
292 return propertyOrder().count();
295 /*****************************************
297 *****************************************/
298 TreeModel::TreeModel(const QList<QVariant>& data, QObject* parent)
299 : QAbstractItemModel(parent)
300 , _childStatus(QModelIndex(), 0, 0, 0)
301 , _aboutToRemoveOrInsert(false)
303 rootItem = new SimpleTreeItem(data, nullptr);
304 connectItem(rootItem);
306 if (Quassel::isOptionSet("debugmodel")) {
307 connect(this, &QAbstractItemModel::rowsAboutToBeInserted, this, &TreeModel::debug_rowsAboutToBeInserted);
308 connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TreeModel::debug_rowsAboutToBeRemoved);
309 connect(this, &QAbstractItemModel::rowsInserted, this, &TreeModel::debug_rowsInserted);
310 connect(this, &QAbstractItemModel::rowsRemoved, this, &TreeModel::debug_rowsRemoved);
311 connect(this, &QAbstractItemModel::dataChanged, this, &TreeModel::debug_dataChanged);
315 TreeModel::~TreeModel()
320 AbstractTreeItem* TreeModel::root() const
325 QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const
327 if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
330 AbstractTreeItem* parentItem;
332 if (!parent.isValid())
333 parentItem = rootItem;
335 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
337 AbstractTreeItem* childItem = parentItem->child(row);
340 return createIndex(row, column, childItem);
345 QModelIndex TreeModel::indexByItem(AbstractTreeItem* item) const
347 if (item == nullptr) {
348 qWarning() << "TreeModel::indexByItem(AbstractTreeItem *item) received NULL-Pointer";
352 if (item == rootItem)
355 return createIndex(item->row(), 0, item);
358 QModelIndex TreeModel::parent(const QModelIndex& index) const
360 if (!index.isValid()) {
361 // ModelTest does this
362 // qWarning() << "TreeModel::parent(): has been asked for the rootItems Parent!";
366 auto* childItem = static_cast<AbstractTreeItem*>(index.internalPointer());
367 AbstractTreeItem* parentItem = childItem->parent();
369 Q_ASSERT(parentItem);
370 if (parentItem == rootItem)
373 return createIndex(parentItem->row(), 0, parentItem);
376 int TreeModel::rowCount(const QModelIndex& parent) const
378 AbstractTreeItem* parentItem;
379 if (!parent.isValid())
380 parentItem = rootItem;
382 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
384 return parentItem->childCount(parent.column());
387 int TreeModel::columnCount(const QModelIndex& parent) const
390 return rootItem->columnCount();
391 // since there the Qt Views don't draw more columns than the header has columns
392 // we can be lazy and simply return the count of header columns
393 // actually this gives us more freedom cause we don't have to ensure that a rows parent
394 // has equal or more columns than that row
396 // AbstractTreeItem *parentItem;
397 // if(!parent.isValid())
398 // parentItem = rootItem;
400 // parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
401 // return parentItem->columnCount();
404 QVariant TreeModel::data(const QModelIndex& index, int role) const
406 if (!index.isValid())
409 auto* item = static_cast<AbstractTreeItem*>(index.internalPointer());
410 return item->data(index.column(), role);
413 bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
415 if (!index.isValid())
418 auto* item = static_cast<AbstractTreeItem*>(index.internalPointer());
419 return item->setData(index.column(), value, role);
422 Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const
424 if (!index.isValid()) {
425 return rootItem->flags() & Qt::ItemIsDropEnabled;
428 auto* item = static_cast<AbstractTreeItem*>(index.internalPointer());
429 return item->flags();
433 QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
435 if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
436 return rootItem->data(section, role);
441 void TreeModel::itemDataChanged(int column)
443 auto* item = qobject_cast<AbstractTreeItem*>(sender());
444 QModelIndex leftIndex, rightIndex;
446 if (item == rootItem)
450 leftIndex = createIndex(item->row(), 0, item);
451 rightIndex = createIndex(item->row(), item->columnCount() - 1, item);
454 leftIndex = createIndex(item->row(), column, item);
455 rightIndex = leftIndex;
458 emit dataChanged(leftIndex, rightIndex);
461 void TreeModel::connectItem(AbstractTreeItem* item)
463 connect(item, &AbstractTreeItem::dataChanged, this, &TreeModel::itemDataChanged);
465 connect(item, &AbstractTreeItem::beginAppendChilds, this, &TreeModel::beginAppendChilds);
466 connect(item, &AbstractTreeItem::endAppendChilds, this, &TreeModel::endAppendChilds);
468 connect(item, &AbstractTreeItem::beginRemoveChilds, this, &TreeModel::beginRemoveChilds);
469 connect(item, &AbstractTreeItem::endRemoveChilds, this, &TreeModel::endRemoveChilds);
472 void TreeModel::beginAppendChilds(int firstRow, int lastRow)
474 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
476 qWarning() << "TreeModel::beginAppendChilds(): cannot append Children to unknown parent";
480 QModelIndex parent = indexByItem(parentItem);
481 Q_ASSERT(!_aboutToRemoveOrInsert);
483 _aboutToRemoveOrInsert = true;
484 _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
485 beginInsertRows(parent, firstRow, lastRow);
488 void TreeModel::endAppendChilds()
490 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
492 qWarning() << "TreeModel::endAppendChilds(): cannot append Children to unknown parent";
495 Q_ASSERT(_aboutToRemoveOrInsert);
496 ChildStatus cs = _childStatus;
498 QModelIndex parent = indexByItem(parentItem);
499 Q_ASSERT(cs.parent == parent);
500 Q_ASSERT(rowCount(parent) == cs.childCount + cs.end - cs.start + 1);
502 _aboutToRemoveOrInsert = false;
503 for (int i = cs.start; i <= cs.end; i++) {
504 connectItem(parentItem->child(i));
509 void TreeModel::beginRemoveChilds(int firstRow, int lastRow)
511 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
513 qWarning() << "TreeModel::beginRemoveChilds(): cannot append Children to unknown parent";
517 for (int i = firstRow; i <= lastRow; i++) {
518 disconnect(parentItem->child(i), nullptr, this, nullptr);
522 QModelIndex parent = indexByItem(parentItem);
523 Q_ASSERT(firstRow <= lastRow);
524 Q_ASSERT(parentItem->childCount() > lastRow);
525 Q_ASSERT(!_aboutToRemoveOrInsert);
526 _aboutToRemoveOrInsert = true;
527 _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
529 beginRemoveRows(parent, firstRow, lastRow);
532 void TreeModel::endRemoveChilds()
534 auto* parentItem = qobject_cast<AbstractTreeItem*>(sender());
536 qWarning() << "TreeModel::endRemoveChilds(): cannot remove Children from unknown parent";
540 // concistency checks
541 Q_ASSERT(_aboutToRemoveOrInsert);
543 ChildStatus cs = _childStatus;
544 QModelIndex parent = indexByItem(parentItem);
545 Q_ASSERT(cs.parent == parent);
546 Q_ASSERT(rowCount(parent) == cs.childCount - cs.end + cs.start - 1);
548 _aboutToRemoveOrInsert = false;
553 void TreeModel::clear()
555 rootItem->removeAllChilds();
558 void TreeModel::debug_rowsAboutToBeInserted(const QModelIndex& parent, int start, int end)
560 qDebug() << "debug_rowsAboutToBeInserted" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start
564 void TreeModel::debug_rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
566 AbstractTreeItem* parentItem;
567 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
569 parentItem = rootItem;
570 qDebug() << "debug_rowsAboutToBeRemoved" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
573 for (int i = end; i >= start; i--) {
574 child = parent.model()->index(i, 0, parent);
575 Q_ASSERT(parentItem->child(i));
576 qDebug() << ">>>" << i << child << child.data().toString();
580 void TreeModel::debug_rowsInserted(const QModelIndex& parent, int start, int end)
582 AbstractTreeItem* parentItem;
583 parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
585 parentItem = rootItem;
586 qDebug() << "debug_rowsInserted:" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
589 for (int i = start; i <= end; i++) {
590 child = parent.model()->index(i, 0, parent);
591 Q_ASSERT(parentItem->child(i));
592 qDebug() << "<<<" << i << child << child.data().toString();
596 void TreeModel::debug_rowsRemoved(const QModelIndex& parent, int start, int end)
598 qDebug() << "debug_rowsRemoved" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
601 void TreeModel::debug_dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
603 qDebug() << "debug_dataChanged" << topLeft << bottomRight;
604 QStringList displayData;
605 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
606 displayData = QStringList();
607 for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
608 displayData << data(topLeft.sibling(row, column), Qt::DisplayRole).toString();
610 qDebug() << " row:" << row << displayData;