uisupport: Provide helpers for dealing with widget changes
[quassel.git] / src / client / treemodel.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "treemodel.h"
22
23 #include <QCoreApplication>
24 #include <QDebug>
25 #include <utility>
26
27 #include "quassel.h"
28
29 class RemoveChildLaterEvent : public QEvent
30 {
31 public:
32     RemoveChildLaterEvent(AbstractTreeItem *child) : QEvent(QEvent::User), _child(child) {};
33     inline AbstractTreeItem *child() { return _child; }
34 private:
35     AbstractTreeItem *_child;
36 };
37
38
39 /*****************************************
40  *  Abstract Items of a TreeModel
41  *****************************************/
42 AbstractTreeItem::AbstractTreeItem(AbstractTreeItem *parent)
43     : QObject(parent),
44     _flags(Qt::ItemIsSelectable | Qt::ItemIsEnabled),
45     _treeItemFlags(nullptr)
46 {
47 }
48
49
50 bool AbstractTreeItem::newChild(AbstractTreeItem *item)
51 {
52     int newRow = childCount();
53     emit beginAppendChilds(newRow, newRow);
54     _childItems.append(item);
55     emit endAppendChilds();
56     return true;
57 }
58
59
60 bool AbstractTreeItem::newChilds(const QList<AbstractTreeItem *> &items)
61 {
62     if (items.isEmpty())
63         return false;
64
65     int nextRow = childCount();
66     int lastRow = nextRow + items.count() - 1;
67
68     emit beginAppendChilds(nextRow, lastRow);
69     _childItems << items;
70     emit endAppendChilds();
71
72     return true;
73 }
74
75
76 bool AbstractTreeItem::removeChild(int row)
77 {
78     if (row < 0 || childCount() <= row)
79         return false;
80
81     child(row)->removeAllChilds();
82     emit beginRemoveChilds(row, row);
83     AbstractTreeItem *treeitem = _childItems.takeAt(row);
84     delete treeitem;
85     emit endRemoveChilds();
86
87     checkForDeletion();
88
89     return true;
90 }
91
92
93 void AbstractTreeItem::removeAllChilds()
94 {
95     const int numChilds = childCount();
96
97     if (numChilds == 0)
98         return;
99
100     AbstractTreeItem *child;
101
102     QList<AbstractTreeItem *>::iterator childIter;
103
104     childIter = _childItems.begin();
105     while (childIter != _childItems.end()) {
106         child = *childIter;
107         child->setTreeItemFlags(nullptr); // disable self deletion, as this would only fuck up consitency and the child gets deleted anyways
108         child->removeAllChilds();
109         ++childIter;
110     }
111
112     emit beginRemoveChilds(0, numChilds - 1);
113     childIter = _childItems.begin();
114     while (childIter != _childItems.end()) {
115         child = *childIter;
116         childIter = _childItems.erase(childIter);
117         delete child;
118     }
119     emit endRemoveChilds();
120
121     checkForDeletion();
122 }
123
124
125 void AbstractTreeItem::removeChildLater(AbstractTreeItem *child)
126 {
127     Q_ASSERT(child);
128     QCoreApplication::postEvent(this, new RemoveChildLaterEvent(child));
129 }
130
131
132 void AbstractTreeItem::customEvent(QEvent *event)
133 {
134     if (event->type() != QEvent::User)
135         return;
136
137     event->accept();
138
139     auto *removeEvent = static_cast<RemoveChildLaterEvent *>(event);
140     int childRow = _childItems.indexOf(removeEvent->child());
141     if (childRow == -1)
142         return;
143
144     // since we are called asynchronously we have to recheck if the item in question still has no childs
145     if (removeEvent->child()->childCount())
146         return;
147
148     removeChild(childRow);
149 }
150
151
152 bool AbstractTreeItem::reParent(AbstractTreeItem *newParent)
153 {
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.";
158         return false;
159     }
160
161     int oldRow = row();
162     if (oldRow == -1)
163         return false;
164
165     emit parent()->beginRemoveChilds(oldRow, oldRow);
166     parent()->_childItems.removeAt(oldRow);
167     emit parent()->endRemoveChilds();
168
169     AbstractTreeItem *oldParent = parent();
170     setParent(newParent);
171
172     bool success = newParent->newChild(this);
173     if (!success)
174         qWarning() << "AbstractTreeItem::reParent(): failed to attach to new parent after removing from old parent! this:" << this << "new parent:" << newParent;
175
176     if (oldParent)
177         oldParent->checkForDeletion();
178
179     return success;
180 }
181
182
183 AbstractTreeItem *AbstractTreeItem::child(int row) const
184 {
185     if (childCount() <= row)
186         return nullptr;
187     else
188         return _childItems[row];
189 }
190
191
192 int AbstractTreeItem::childCount(int column) const
193 {
194     if (column > 0)
195         return 0;
196     else
197         return _childItems.count();
198 }
199
200
201 int AbstractTreeItem::row() const
202 {
203     if (!parent()) {
204         qWarning() << "AbstractTreeItem::row():" << this << "has no parent AbstractTreeItem as it's parent! parent is" << QObject::parent();
205         return -1;
206     }
207
208     int row_ = parent()->_childItems.indexOf(const_cast<AbstractTreeItem *>(this));
209     if (row_ == -1)
210         qWarning() << "AbstractTreeItem::row():" << this << "is not in the child list of" << QObject::parent();
211     return row_;
212 }
213
214
215 void AbstractTreeItem::dumpChildList()
216 {
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()) {
222             child = *childIter;
223             qDebug() << "Row:" << child->row() << child << child->data(0, Qt::DisplayRole);
224             ++childIter;
225         }
226     }
227     qDebug() << "==== End Of Childlist ====";
228 }
229
230
231 /*****************************************
232  * SimpleTreeItem
233  *****************************************/
234 SimpleTreeItem::SimpleTreeItem(QList<QVariant> data, AbstractTreeItem *parent)
235     : AbstractTreeItem(parent),
236     _itemData(std::move(data))
237 {
238 }
239
240
241 QVariant SimpleTreeItem::data(int column, int role) const
242 {
243     if (column >= columnCount() || role != Qt::DisplayRole)
244         return QVariant();
245     else
246         return _itemData[column];
247 }
248
249
250 bool SimpleTreeItem::setData(int column, const QVariant &value, int role)
251 {
252     if (column > columnCount() || role != Qt::DisplayRole)
253         return false;
254
255     if (column == columnCount())
256         _itemData.append(value);
257     else
258         _itemData[column] = value;
259
260     emit dataChanged(column);
261     return true;
262 }
263
264
265 int SimpleTreeItem::columnCount() const
266 {
267     return _itemData.count();
268 }
269
270
271 /*****************************************
272  * PropertyMapItem
273  *****************************************/
274 PropertyMapItem::PropertyMapItem(AbstractTreeItem *parent)
275     : AbstractTreeItem(parent)
276 {
277 }
278
279
280 QVariant PropertyMapItem::data(int column, int role) const
281 {
282     if (column >= columnCount())
283         return QVariant();
284
285     switch (role) {
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());
291     default:
292         return QVariant();
293     }
294 }
295
296
297 bool PropertyMapItem::setData(int column, const QVariant &value, int role)
298 {
299     if (column >= columnCount() || role != Qt::DisplayRole)
300         return false;
301
302     setProperty(propertyOrder()[column].toLatin1(), value);
303     emit dataChanged(column);
304     return true;
305 }
306
307
308 int PropertyMapItem::columnCount() const
309 {
310     return propertyOrder().count();
311 }
312
313
314 /*****************************************
315  * TreeModel
316  *****************************************/
317 TreeModel::TreeModel(const QList<QVariant> &data, QObject *parent)
318     : QAbstractItemModel(parent),
319     _childStatus(QModelIndex(), 0, 0, 0),
320     _aboutToRemoveOrInsert(false)
321 {
322     rootItem = new SimpleTreeItem(data, nullptr);
323     connectItem(rootItem);
324
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);
336     }
337 }
338
339
340 TreeModel::~TreeModel()
341 {
342     delete rootItem;
343 }
344
345
346 AbstractTreeItem *TreeModel::root() const
347 {
348     return rootItem;
349 }
350
351
352 QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
353 {
354     if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
355         return {};
356
357     AbstractTreeItem *parentItem;
358
359     if (!parent.isValid())
360         parentItem = rootItem;
361     else
362         parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
363
364     AbstractTreeItem *childItem = parentItem->child(row);
365
366     if (childItem)
367         return createIndex(row, column, childItem);
368     else
369         return {};
370 }
371
372
373 QModelIndex TreeModel::indexByItem(AbstractTreeItem *item) const
374 {
375     if (item == nullptr) {
376         qWarning() << "TreeModel::indexByItem(AbstractTreeItem *item) received NULL-Pointer";
377         return {};
378     }
379
380     if (item == rootItem)
381         return {};
382     else
383         return createIndex(item->row(), 0, item);
384 }
385
386
387 QModelIndex TreeModel::parent(const QModelIndex &index) const
388 {
389     if (!index.isValid()) {
390         // ModelTest does this
391         // qWarning() << "TreeModel::parent(): has been asked for the rootItems Parent!";
392         return {};
393     }
394
395     auto *childItem = static_cast<AbstractTreeItem *>(index.internalPointer());
396     AbstractTreeItem *parentItem = childItem->parent();
397
398     Q_ASSERT(parentItem);
399     if (parentItem == rootItem)
400         return {};
401
402     return createIndex(parentItem->row(), 0, parentItem);
403 }
404
405
406 int TreeModel::rowCount(const QModelIndex &parent) const
407 {
408     AbstractTreeItem *parentItem;
409     if (!parent.isValid())
410         parentItem = rootItem;
411     else
412         parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
413
414     return parentItem->childCount(parent.column());
415 }
416
417
418 int TreeModel::columnCount(const QModelIndex &parent) const
419 {
420     Q_UNUSED(parent)
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
426
427 //   AbstractTreeItem *parentItem;
428 //   if(!parent.isValid())
429 //     parentItem = rootItem;
430 //   else
431 //     parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
432 //   return parentItem->columnCount();
433 }
434
435
436 QVariant TreeModel::data(const QModelIndex &index, int role) const
437 {
438     if (!index.isValid())
439         return QVariant();
440
441     auto *item = static_cast<AbstractTreeItem *>(index.internalPointer());
442     return item->data(index.column(), role);
443 }
444
445
446 bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
447 {
448     if (!index.isValid())
449         return false;
450
451     auto *item = static_cast<AbstractTreeItem *>(index.internalPointer());
452     return item->setData(index.column(), value, role);
453 }
454
455
456 Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
457 {
458     if (!index.isValid()) {
459         return rootItem->flags() & Qt::ItemIsDropEnabled;
460     }
461     else {
462         auto *item = static_cast<AbstractTreeItem *>(index.internalPointer());
463         return item->flags();
464     }
465 }
466
467
468 QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
469 {
470     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
471         return rootItem->data(section, role);
472     else
473         return QVariant();
474 }
475
476
477 void TreeModel::itemDataChanged(int column)
478 {
479     auto *item = qobject_cast<AbstractTreeItem *>(sender());
480     QModelIndex leftIndex, rightIndex;
481
482     if (item == rootItem)
483         return;
484
485     if (column == -1) {
486         leftIndex = createIndex(item->row(), 0, item);
487         rightIndex = createIndex(item->row(), item->columnCount() - 1, item);
488     }
489     else {
490         leftIndex = createIndex(item->row(), column, item);
491         rightIndex = leftIndex;
492     }
493
494     emit dataChanged(leftIndex, rightIndex);
495 }
496
497
498 void TreeModel::connectItem(AbstractTreeItem *item)
499 {
500     connect(item, &AbstractTreeItem::dataChanged,
501         this, &TreeModel::itemDataChanged);
502
503     connect(item, &AbstractTreeItem::beginAppendChilds,
504         this, &TreeModel::beginAppendChilds);
505     connect(item, &AbstractTreeItem::endAppendChilds,
506         this, &TreeModel::endAppendChilds);
507
508     connect(item, &AbstractTreeItem::beginRemoveChilds,
509         this, &TreeModel::beginRemoveChilds);
510     connect(item, &AbstractTreeItem::endRemoveChilds,
511         this, &TreeModel::endRemoveChilds);
512 }
513
514
515 void TreeModel::beginAppendChilds(int firstRow, int lastRow)
516 {
517     auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
518     if (!parentItem) {
519         qWarning() << "TreeModel::beginAppendChilds(): cannot append Children to unknown parent";
520         return;
521     }
522
523     QModelIndex parent = indexByItem(parentItem);
524     Q_ASSERT(!_aboutToRemoveOrInsert);
525
526     _aboutToRemoveOrInsert = true;
527     _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
528     beginInsertRows(parent, firstRow, lastRow);
529 }
530
531
532 void TreeModel::endAppendChilds()
533 {
534     auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
535     if (!parentItem) {
536         qWarning() << "TreeModel::endAppendChilds(): cannot append Children to unknown parent";
537         return;
538     }
539     Q_ASSERT(_aboutToRemoveOrInsert);
540     ChildStatus cs = _childStatus;
541 #ifndef QT_NO_DEBUG
542     QModelIndex parent = indexByItem(parentItem);
543     Q_ASSERT(cs.parent == parent);
544     Q_ASSERT(rowCount(parent) == cs.childCount + cs.end - cs.start + 1);
545 #endif
546     _aboutToRemoveOrInsert = false;
547     for (int i = cs.start; i <= cs.end; i++) {
548         connectItem(parentItem->child(i));
549     }
550     endInsertRows();
551 }
552
553
554 void TreeModel::beginRemoveChilds(int firstRow, int lastRow)
555 {
556     auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
557     if (!parentItem) {
558         qWarning() << "TreeModel::beginRemoveChilds(): cannot append Children to unknown parent";
559         return;
560     }
561
562     for (int i = firstRow; i <= lastRow; i++) {
563         disconnect(parentItem->child(i), nullptr, this, nullptr);
564     }
565
566     // consitency checks
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);
573
574     beginRemoveRows(parent, firstRow, lastRow);
575 }
576
577
578 void TreeModel::endRemoveChilds()
579 {
580     auto *parentItem = qobject_cast<AbstractTreeItem *>(sender());
581     if (!parentItem) {
582         qWarning() << "TreeModel::endRemoveChilds(): cannot remove Children from unknown parent";
583         return;
584     }
585
586     // concistency checks
587     Q_ASSERT(_aboutToRemoveOrInsert);
588 #ifndef QT_NO_DEBUG
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);
593 #endif
594     _aboutToRemoveOrInsert = false;
595
596     endRemoveRows();
597 }
598
599
600 void TreeModel::clear()
601 {
602     rootItem->removeAllChilds();
603 }
604
605
606 void TreeModel::debug_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
607 {
608     qDebug() << "debug_rowsAboutToBeInserted" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
609 }
610
611
612 void TreeModel::debug_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
613 {
614     AbstractTreeItem *parentItem;
615     parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
616     if (!parentItem)
617         parentItem = rootItem;
618     qDebug() << "debug_rowsAboutToBeRemoved" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
619
620     QModelIndex child;
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();
625     }
626 }
627
628
629 void TreeModel::debug_rowsInserted(const QModelIndex &parent, int start, int end)
630 {
631     AbstractTreeItem *parentItem;
632     parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
633     if (!parentItem)
634         parentItem = rootItem;
635     qDebug() << "debug_rowsInserted:" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
636
637     QModelIndex child;
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();
642     }
643 }
644
645
646 void TreeModel::debug_rowsRemoved(const QModelIndex &parent, int start, int end)
647 {
648     qDebug() << "debug_rowsRemoved" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
649 }
650
651
652 void TreeModel::debug_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
653 {
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();
660         }
661         qDebug() << "  row:" << row << displayData;
662     }
663 }