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