1 /***************************************************************************
2 * Copyright (C) 2005-2018 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"
23 #include <QCoreApplication>
29 class RemoveChildLaterEvent : public QEvent
32 RemoveChildLaterEvent(AbstractTreeItem *child) : QEvent(QEvent::User), _child(child) {};
33 inline AbstractTreeItem *child() { return _child; }
35 AbstractTreeItem *_child;
39 /*****************************************
40 * Abstract Items of a TreeModel
41 *****************************************/
42 AbstractTreeItem::AbstractTreeItem(AbstractTreeItem *parent)
44 _flags(Qt::ItemIsSelectable | Qt::ItemIsEnabled),
45 _treeItemFlags(nullptr)
50 bool AbstractTreeItem::newChild(AbstractTreeItem *item)
52 int newRow = childCount();
53 emit beginAppendChilds(newRow, newRow);
54 _childItems.append(item);
55 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();
76 bool AbstractTreeItem::removeChild(int row)
78 if (row < 0 || childCount() <= row)
81 child(row)->removeAllChilds();
82 emit beginRemoveChilds(row, row);
83 AbstractTreeItem *treeitem = _childItems.takeAt(row);
85 emit endRemoveChilds();
93 void AbstractTreeItem::removeAllChilds()
95 const int numChilds = childCount();
100 AbstractTreeItem *child;
102 QList<AbstractTreeItem *>::iterator childIter;
104 childIter = _childItems.begin();
105 while (childIter != _childItems.end()) {
107 child->setTreeItemFlags(nullptr); // disable self deletion, as this would only fuck up consitency and the child gets deleted anyways
108 child->removeAllChilds();
112 emit beginRemoveChilds(0, numChilds - 1);
113 childIter = _childItems.begin();
114 while (childIter != _childItems.end()) {
116 childIter = _childItems.erase(childIter);
119 emit endRemoveChilds();
125 void AbstractTreeItem::removeChildLater(AbstractTreeItem *child)
128 QCoreApplication::postEvent(this, new RemoveChildLaterEvent(child));
132 void AbstractTreeItem::customEvent(QEvent *event)
134 if (event->type() != QEvent::User)
139 auto *removeEvent = static_cast<RemoveChildLaterEvent *>(event);
140 int childRow = _childItems.indexOf(removeEvent->child());
144 // since we are called asynchronously we have to recheck if the item in question still has no childs
145 if (removeEvent->child()->childCount())
148 removeChild(childRow);
152 bool AbstractTreeItem::reParent(AbstractTreeItem *newParent)
154 // currently we support only re parenting if the child that's about to be
155 // adopted does not have any children itself.
156 if (childCount() != 0) {
157 qDebug() << "AbstractTreeItem::reParent(): cannot reparent" << this << "with children.";
165 emit parent()->beginRemoveChilds(oldRow, oldRow);
166 parent()->_childItems.removeAt(oldRow);
167 emit parent()->endRemoveChilds();
169 AbstractTreeItem *oldParent = parent();
170 setParent(newParent);
172 bool success = newParent->newChild(this);
174 qWarning() << "AbstractTreeItem::reParent(): failed to attach to new parent after removing from old parent! this:" << this << "new parent:" << newParent;
177 oldParent->checkForDeletion();
183 AbstractTreeItem *AbstractTreeItem::child(int row) const
185 if (childCount() <= row)
188 return _childItems[row];
192 int AbstractTreeItem::childCount(int column) const
197 return _childItems.count();
201 int AbstractTreeItem::row() const
204 qWarning() << "AbstractTreeItem::row():" << this << "has no parent AbstractTreeItem as it's parent! parent is" << QObject::parent();
208 int row_ = parent()->_childItems.indexOf(const_cast<AbstractTreeItem *>(this));
210 qWarning() << "AbstractTreeItem::row():" << this << "is not in the child list of" << QObject::parent();
215 void AbstractTreeItem::dumpChildList()
217 qDebug() << "==== Childlist for Item:" << this << "====";
218 if (childCount() > 0) {
219 AbstractTreeItem *child;
220 QList<AbstractTreeItem *>::const_iterator childIter = _childItems.constBegin();
221 while (childIter != _childItems.constEnd()) {
223 qDebug() << "Row:" << child->row() << child << child->data(0, Qt::DisplayRole);
227 qDebug() << "==== End Of Childlist ====";
231 /*****************************************
233 *****************************************/
234 SimpleTreeItem::SimpleTreeItem(QList<QVariant> data, AbstractTreeItem *parent)
235 : AbstractTreeItem(parent),
236 _itemData(std::move(data))
241 QVariant SimpleTreeItem::data(int column, int role) const
243 if (column >= columnCount() || role != Qt::DisplayRole)
246 return _itemData[column];
250 bool SimpleTreeItem::setData(int column, const QVariant &value, int role)
252 if (column > columnCount() || role != Qt::DisplayRole)
255 if (column == columnCount())
256 _itemData.append(value);
258 _itemData[column] = value;
260 emit dataChanged(column);
265 int SimpleTreeItem::columnCount() const
267 return _itemData.count();
271 /*****************************************
273 *****************************************/
274 PropertyMapItem::PropertyMapItem(AbstractTreeItem *parent)
275 : AbstractTreeItem(parent)
280 QVariant PropertyMapItem::data(int column, int role) const
282 if (column >= columnCount())
286 case Qt::ToolTipRole:
287 return toolTip(column);
288 case Qt::DisplayRole:
289 case TreeModel::SortRole: // fallthrough, since SortRole should default to DisplayRole
290 return property(propertyOrder()[column].toLatin1());
297 bool PropertyMapItem::setData(int column, const QVariant &value, int role)
299 if (column >= columnCount() || role != Qt::DisplayRole)
302 setProperty(propertyOrder()[column].toLatin1(), value);
303 emit dataChanged(column);
308 int PropertyMapItem::columnCount() const
310 return propertyOrder().count();
314 /*****************************************
316 *****************************************/
317 TreeModel::TreeModel(const QList<QVariant> &data, QObject *parent)
318 : QAbstractItemModel(parent),
319 _childStatus(QModelIndex(), 0, 0, 0),
320 _aboutToRemoveOrInsert(false)
322 rootItem = new SimpleTreeItem(data, nullptr);
323 connectItem(rootItem);
325 if (Quassel::isOptionSet("debugmodel")) {
326 connect(this, &QAbstractItemModel::rowsAboutToBeInserted,
327 this, &TreeModel::debug_rowsAboutToBeInserted);
328 connect(this, &QAbstractItemModel::rowsAboutToBeRemoved,
329 this, &TreeModel::debug_rowsAboutToBeRemoved);
330 connect(this, &QAbstractItemModel::rowsInserted,
331 this, &TreeModel::debug_rowsInserted);
332 connect(this, &QAbstractItemModel::rowsRemoved,
333 this, &TreeModel::debug_rowsRemoved);
334 connect(this, &QAbstractItemModel::dataChanged,
335 this, &TreeModel::debug_dataChanged);
340 TreeModel::~TreeModel()
346 AbstractTreeItem *TreeModel::root() const
352 QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
354 if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
357 AbstractTreeItem *parentItem;
359 if (!parent.isValid())
360 parentItem = rootItem;
362 parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
364 AbstractTreeItem *childItem = parentItem->child(row);
367 return createIndex(row, column, childItem);
373 QModelIndex TreeModel::indexByItem(AbstractTreeItem *item) const
375 if (item == nullptr) {
376 qWarning() << "TreeModel::indexByItem(AbstractTreeItem *item) received NULL-Pointer";
380 if (item == rootItem)
383 return createIndex(item->row(), 0, item);
387 QModelIndex TreeModel::parent(const QModelIndex &index) const
389 if (!index.isValid()) {
390 // ModelTest does this
391 // qWarning() << "TreeModel::parent(): has been asked for the rootItems Parent!";
395 auto *childItem = static_cast<AbstractTreeItem *>(index.internalPointer());
396 AbstractTreeItem *parentItem = childItem->parent();
398 Q_ASSERT(parentItem);
399 if (parentItem == rootItem)
402 return createIndex(parentItem->row(), 0, parentItem);
406 int TreeModel::rowCount(const QModelIndex &parent) const
408 AbstractTreeItem *parentItem;
409 if (!parent.isValid())
410 parentItem = rootItem;
412 parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
414 return parentItem->childCount(parent.column());
418 int TreeModel::columnCount(const QModelIndex &parent) const
421 return rootItem->columnCount();
422 // since there the Qt Views don't draw more columns than the header has columns
423 // we can be lazy and simply return the count of header columns
424 // actually this gives us more freedom cause we don't have to ensure that a rows parent
425 // has equal or more columns than that row
427 // AbstractTreeItem *parentItem;
428 // if(!parent.isValid())
429 // parentItem = rootItem;
431 // parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
432 // return parentItem->columnCount();
436 QVariant TreeModel::data(const QModelIndex &index, int role) const
438 if (!index.isValid())
441 auto *item = static_cast<AbstractTreeItem *>(index.internalPointer());
442 return item->data(index.column(), role);
446 bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
448 if (!index.isValid())
451 auto *item = static_cast<AbstractTreeItem *>(index.internalPointer());
452 return item->setData(index.column(), value, role);
456 Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
458 if (!index.isValid()) {
459 return rootItem->flags() & Qt::ItemIsDropEnabled;
462 auto *item = static_cast<AbstractTreeItem *>(index.internalPointer());
463 return item->flags();
468 QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
470 if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
471 return rootItem->data(section, role);
477 void TreeModel::itemDataChanged(int column)
479 auto *item = qobject_cast<AbstractTreeItem *>(sender());
480 QModelIndex leftIndex, rightIndex;
482 if (item == rootItem)
486 leftIndex = createIndex(item->row(), 0, item);
487 rightIndex = createIndex(item->row(), item->columnCount() - 1, item);
490 leftIndex = createIndex(item->row(), column, item);
491 rightIndex = leftIndex;
494 emit dataChanged(leftIndex, rightIndex);
498 void TreeModel::connectItem(AbstractTreeItem *item)
500 connect(item, &AbstractTreeItem::dataChanged,
501 this, &TreeModel::itemDataChanged);
503 connect(item, &AbstractTreeItem::beginAppendChilds,
504 this, &TreeModel::beginAppendChilds);
505 connect(item, &AbstractTreeItem::endAppendChilds,
506 this, &TreeModel::endAppendChilds);
508 connect(item, &AbstractTreeItem::beginRemoveChilds,
509 this, &TreeModel::beginRemoveChilds);
510 connect(item, &AbstractTreeItem::endRemoveChilds,
511 this, &TreeModel::endRemoveChilds);
515 void TreeModel::beginAppendChilds(int firstRow, int lastRow)
517 auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
519 qWarning() << "TreeModel::beginAppendChilds(): cannot append Children to unknown parent";
523 QModelIndex parent = indexByItem(parentItem);
524 Q_ASSERT(!_aboutToRemoveOrInsert);
526 _aboutToRemoveOrInsert = true;
527 _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
528 beginInsertRows(parent, firstRow, lastRow);
532 void TreeModel::endAppendChilds()
534 auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
536 qWarning() << "TreeModel::endAppendChilds(): cannot append Children to unknown parent";
539 Q_ASSERT(_aboutToRemoveOrInsert);
540 ChildStatus cs = _childStatus;
542 QModelIndex parent = indexByItem(parentItem);
543 Q_ASSERT(cs.parent == parent);
544 Q_ASSERT(rowCount(parent) == cs.childCount + cs.end - cs.start + 1);
546 _aboutToRemoveOrInsert = false;
547 for (int i = cs.start; i <= cs.end; i++) {
548 connectItem(parentItem->child(i));
554 void TreeModel::beginRemoveChilds(int firstRow, int lastRow)
556 auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
558 qWarning() << "TreeModel::beginRemoveChilds(): cannot append Children to unknown parent";
562 for (int i = firstRow; i <= lastRow; i++) {
563 disconnect(parentItem->child(i), nullptr, this, nullptr);
567 QModelIndex parent = indexByItem(parentItem);
568 Q_ASSERT(firstRow <= lastRow);
569 Q_ASSERT(parentItem->childCount() > lastRow);
570 Q_ASSERT(!_aboutToRemoveOrInsert);
571 _aboutToRemoveOrInsert = true;
572 _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
574 beginRemoveRows(parent, firstRow, lastRow);
578 void TreeModel::endRemoveChilds()
580 auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
582 qWarning() << "TreeModel::endRemoveChilds(): cannot remove Children from unknown parent";
586 // concistency checks
587 Q_ASSERT(_aboutToRemoveOrInsert);
589 ChildStatus cs = _childStatus;
590 QModelIndex parent = indexByItem(parentItem);
591 Q_ASSERT(cs.parent == parent);
592 Q_ASSERT(rowCount(parent) == cs.childCount - cs.end + cs.start - 1);
594 _aboutToRemoveOrInsert = false;
600 void TreeModel::clear()
602 rootItem->removeAllChilds();
606 void TreeModel::debug_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
608 qDebug() << "debug_rowsAboutToBeInserted" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
612 void TreeModel::debug_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
614 AbstractTreeItem *parentItem;
615 parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
617 parentItem = rootItem;
618 qDebug() << "debug_rowsAboutToBeRemoved" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
621 for (int i = end; i >= start; i--) {
622 child = parent.child(i, 0);
623 Q_ASSERT(parentItem->child(i));
624 qDebug() << ">>>" << i << child << child.data().toString();
629 void TreeModel::debug_rowsInserted(const QModelIndex &parent, int start, int end)
631 AbstractTreeItem *parentItem;
632 parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
634 parentItem = rootItem;
635 qDebug() << "debug_rowsInserted:" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
638 for (int i = start; i <= end; i++) {
639 child = parent.child(i, 0);
640 Q_ASSERT(parentItem->child(i));
641 qDebug() << "<<<" << i << child << child.data().toString();
646 void TreeModel::debug_rowsRemoved(const QModelIndex &parent, int start, int end)
648 qDebug() << "debug_rowsRemoved" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
652 void TreeModel::debug_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
654 qDebug() << "debug_dataChanged" << topLeft << bottomRight;
655 QStringList displayData;
656 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
657 displayData = QStringList();
658 for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
659 displayData << data(topLeft.sibling(row, column), Qt::DisplayRole).toString();
661 qDebug() << " row:" << row << displayData;