internal tweaks
[quassel.git] / src / client / treemodel.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "treemodel.h"
22
23 #include <QDebug>
24 #include <QCoreApplication>
25
26 /*****************************************
27  *  Abstract Items of a TreeModel
28  *****************************************/
29 AbstractTreeItem::AbstractTreeItem(AbstractTreeItem *parent)
30   : QObject(parent),
31     _flags(Qt::ItemIsSelectable | Qt::ItemIsEnabled)
32 {
33 }
34
35 AbstractTreeItem::~AbstractTreeItem() {
36 }
37
38 quint64 AbstractTreeItem::id() const {
39   return qHash(this);
40 }
41
42 bool AbstractTreeItem::newChild(AbstractTreeItem *item) {
43   // check if a child with that ID is already known
44   Q_ASSERT(childById(item->id()) == 0);
45     
46   int newRow = childCount();
47   emit beginAppendChilds(newRow, newRow);
48   _childItems.append(item);
49   emit endAppendChilds();
50   
51   return true;
52 }
53
54 bool AbstractTreeItem::newChilds(const QList<AbstractTreeItem *> &items) {
55   if(items.isEmpty())
56     return false;
57   
58   QList<AbstractTreeItem *>::const_iterator itemIter = items.constBegin();
59   AbstractTreeItem *item;
60   while(itemIter != items.constEnd()) {
61     item = *itemIter;
62     if(childById(item->id()) != 0) {
63       qWarning() << "AbstractTreeItem::newChilds(): received child that is already attached" << item << item->id();
64       return false;
65     }
66     itemIter++;
67   }
68
69   int nextRow = childCount();
70   int lastRow = nextRow + items.count() - 1;
71
72   emit beginAppendChilds(nextRow, lastRow);
73   itemIter = items.constBegin();
74   while(itemIter != items.constEnd()) {
75     _childItems.append(*itemIter);
76     itemIter++;
77   }
78   emit endAppendChilds();
79
80   return true;
81 }
82
83 bool AbstractTreeItem::removeChild(int row) {
84   if(childCount() <= row)
85     return false;
86
87   child(row)->removeAllChilds();
88   emit beginRemoveChilds(row, row);
89   AbstractTreeItem *treeitem = _childItems.takeAt(row);
90   treeitem->deleteLater();
91   emit endRemoveChilds();
92
93   return true;
94 }
95
96 bool AbstractTreeItem::removeChildById(const quint64 &id) {
97   const int numChilds = childCount();
98   
99   for(int i = 0; i < numChilds; i++) {
100     if(_childItems[i]->id() == id)
101       return removeChild(i);
102   }
103   
104   return false;
105 }
106
107 void AbstractTreeItem::removeAllChilds() {
108   const int numChilds = childCount();
109   
110   if(numChilds == 0)
111     return;
112   
113   AbstractTreeItem *child;
114
115   QList<AbstractTreeItem *>::iterator childIter;
116
117   childIter = _childItems.begin();
118   while(childIter != _childItems.end()) {
119     child = *childIter;
120     child->removeAllChilds();
121     childIter++;
122   }
123
124   emit beginRemoveChilds(0, numChilds - 1);
125   childIter = _childItems.begin();
126   while(childIter != _childItems.end()) {
127     child = *childIter;
128     childIter = _childItems.erase(childIter);
129     child->deleteLater();
130   }
131   emit endRemoveChilds();
132 }
133
134 bool AbstractTreeItem::reParent(AbstractTreeItem *newParent) {
135   // currently we support only re parenting if the child that's about to be
136   // adopted does not have any children itself.
137   if(childCount() != 0) {
138     qDebug() << "AbstractTreeItem::reParent(): cannot reparent"  << this << "with children.";
139     return false;
140   }
141
142   int oldRow = row();
143   if(oldRow == -1)
144     return false;
145   
146   emit parent()->beginRemoveChilds(oldRow, oldRow);
147   parent()->_childItems.removeAt(oldRow);
148   emit parent()->endRemoveChilds();
149
150   setParent(newParent);
151
152   bool success = newParent->newChild(this);
153   if(!success)
154     qWarning() << "AbstractTreeItem::reParent(): failed to attach to new parent after removing from old parent! this:" << this << "new parent:" << newParent;
155
156   return success;
157 }
158
159 AbstractTreeItem *AbstractTreeItem::child(int row) const {
160   if(childCount() <= row)
161     return 0;
162   else
163     return _childItems[row];
164 }
165
166 AbstractTreeItem *AbstractTreeItem::childById(const quint64 &id) const {
167   const int numChilds = childCount();
168   for(int i = 0; i < numChilds; i++) {
169     if(_childItems[i]->id() == id)
170       return _childItems[i];
171   }
172   return 0;
173 }
174
175 int AbstractTreeItem::childCount(int column) const {
176   if(column > 0)
177     return 0;
178   else
179     return _childItems.count();
180 }
181
182 int AbstractTreeItem::row() const {
183   if(!parent()) {
184     qWarning() << "AbstractTreeItem::row():" << this << "has no parent AbstractTreeItem as it's parent! parent is" << QObject::parent();
185     return -1;
186   }
187   
188   int row_ = parent()->_childItems.indexOf(const_cast<AbstractTreeItem *>(this));
189   if(row_ == -1)
190     qWarning() << "AbstractTreeItem::row():" << this << "is not in the child list of" << QObject::parent();
191   return row_;
192 }
193
194 AbstractTreeItem *AbstractTreeItem::parent() const {
195   return qobject_cast<AbstractTreeItem *>(QObject::parent());
196 }
197
198 Qt::ItemFlags AbstractTreeItem::flags() const {
199   return _flags;
200 }
201
202 void AbstractTreeItem::setFlags(Qt::ItemFlags flags) {
203   _flags = flags;
204 }
205
206 void AbstractTreeItem::dumpChildList() {
207   qDebug() << "==== Childlist for Item:" << this << id() << "====";
208   if(childCount() > 0) {
209     AbstractTreeItem *child;
210     QList<AbstractTreeItem *>::const_iterator childIter = _childItems.constBegin();
211     while(childIter != _childItems.constEnd()) {
212       child = *childIter;
213       qDebug() << "Row:" << child->row() << child << child->id() << child->data(0, Qt::DisplayRole);
214       childIter++;
215     }
216   }
217   qDebug() << "==== End Of Childlist ====";  
218 }
219
220 /*****************************************
221  * SimpleTreeItem
222  *****************************************/
223 SimpleTreeItem::SimpleTreeItem(const QList<QVariant> &data, AbstractTreeItem *parent)
224   : AbstractTreeItem(parent),
225     _itemData(data)
226 {
227 }
228
229 SimpleTreeItem::~SimpleTreeItem() {
230 }
231
232 QVariant SimpleTreeItem::data(int column, int role) const {
233   if(column >= columnCount() || role != Qt::DisplayRole)
234     return QVariant();
235   else
236     return _itemData[column];
237 }
238
239 bool SimpleTreeItem::setData(int column, const QVariant &value, int role) {
240   if(column > columnCount() || role != Qt::DisplayRole)
241     return false;
242
243   if(column == columnCount())
244     _itemData.append(value);
245   else
246     _itemData[column] = value;
247
248   emit dataChanged(column);
249   return true;
250 }
251
252 int SimpleTreeItem::columnCount() const {
253   return _itemData.count();
254 }
255
256 /*****************************************
257  * PropertyMapItem
258  *****************************************/
259 PropertyMapItem::PropertyMapItem(const QStringList &propertyOrder, AbstractTreeItem *parent)
260   : AbstractTreeItem(parent),
261     _propertyOrder(propertyOrder)
262 {
263 }
264
265 PropertyMapItem::PropertyMapItem(AbstractTreeItem *parent)
266   : AbstractTreeItem(parent),
267     _propertyOrder(QStringList())
268 {
269 }
270
271
272 PropertyMapItem::~PropertyMapItem() {
273 }
274   
275 QVariant PropertyMapItem::data(int column, int role) const {
276   if(column >= columnCount())
277     return QVariant();
278
279   switch(role) {
280   case Qt::ToolTipRole:
281     return toolTip(column);
282   case Qt::DisplayRole:
283   case TreeModel::SortRole:  // fallthrough, since SortRole should default to DisplayRole
284     return property(_propertyOrder[column].toAscii());
285   default:
286     return QVariant();
287   }
288   
289 }
290
291 bool PropertyMapItem::setData(int column, const QVariant &value, int role) {
292   if(column >= columnCount() || role != Qt::DisplayRole)
293     return false;
294
295   emit dataChanged(column);
296   return setProperty(_propertyOrder[column].toAscii(), value);
297 }
298
299 int PropertyMapItem::columnCount() const {
300   return _propertyOrder.count();
301 }
302   
303 void PropertyMapItem::appendProperty(const QString &property) {
304   _propertyOrder << property;
305 }
306
307
308
309 /*****************************************
310  * TreeModel
311  *****************************************/
312 TreeModel::TreeModel(const QList<QVariant> &data, QObject *parent)
313   : QAbstractItemModel(parent),
314     _childStatus(QModelIndex(), 0, 0, 0),
315     _aboutToRemoveOrInsert(false)
316 {
317   rootItem = new SimpleTreeItem(data, 0);
318   connectItem(rootItem);
319
320   if(QCoreApplication::instance()->arguments().contains("--debugmodel")) {
321     connect(this, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
322             this, SLOT(debug_rowsAboutToBeInserted(const QModelIndex &, int, int)));
323     connect(this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
324             this, SLOT(debug_rowsAboutToBeRemoved(const QModelIndex &, int, int)));
325     connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
326             this, SLOT(debug_rowsInserted(const QModelIndex &, int, int)));
327     connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
328             this, SLOT(debug_rowsRemoved(const QModelIndex &, int, int)));
329     connect(this, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
330             this, SLOT(debug_dataChanged(const QModelIndex &, const QModelIndex &)));
331   }
332 }
333
334 TreeModel::~TreeModel() {
335   delete rootItem;
336 }
337
338 QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const {
339   if(!hasIndex(row, column, parent))
340     return QModelIndex();
341   
342   AbstractTreeItem *parentItem;
343   
344   if(!parent.isValid())
345     parentItem = rootItem;
346   else
347     parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
348   
349   AbstractTreeItem *childItem = parentItem->child(row);
350
351   if(childItem)
352     return createIndex(row, column, childItem);
353   else
354     return QModelIndex();
355 }
356
357 QModelIndex TreeModel::indexById(quint64 id, const QModelIndex &parent) const {
358   AbstractTreeItem *parentItem; 
359   
360   if(!parent.isValid())
361     parentItem = rootItem;
362   else
363     parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
364   
365   AbstractTreeItem *childItem = parentItem->childById(id);
366   
367   if(childItem)
368     return createIndex(childItem->row(), 0, childItem);
369   else
370     return QModelIndex();
371 }
372
373 QModelIndex TreeModel::indexByItem(AbstractTreeItem *item) const {
374   if(item == 0) {
375     qWarning() << "TreeModel::indexByItem(AbstractTreeItem *item) received NULL-Pointer";
376     return QModelIndex();
377   }
378   
379   if(item == rootItem)
380     return QModelIndex();
381   else
382     return createIndex(item->row(), 0, item);
383 }
384
385 QModelIndex TreeModel::parent(const QModelIndex &index) const {
386   if(!index.isValid()) {
387     qWarning() << "TreeModel::parent(): has been asked for the rootItems Parent!";
388     return QModelIndex();
389   }
390   
391   AbstractTreeItem *childItem = static_cast<AbstractTreeItem *>(index.internalPointer());
392   AbstractTreeItem *parentItem = childItem->parent();
393
394   Q_ASSERT(parentItem);
395   if(parentItem == rootItem)
396     return QModelIndex();
397   
398   return createIndex(parentItem->row(), 0, parentItem);
399 }
400
401 int TreeModel::rowCount(const QModelIndex &parent) const {
402   AbstractTreeItem *parentItem;
403   if(!parent.isValid())
404     parentItem = rootItem;
405   else
406     parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
407
408   return parentItem->childCount(parent.column());
409 }
410
411 int TreeModel::columnCount(const QModelIndex &parent) const {
412   Q_UNUSED(parent)
413   return rootItem->columnCount();
414   // since there the Qt Views don't draw more columns than the header has columns
415   // we can be lazy and simply return the count of header columns
416   // actually this gives us more freedom cause we don't have to ensure that a rows parent
417   // has equal or more columns than that row
418
419 //   AbstractTreeItem *parentItem;
420 //   if(!parent.isValid())
421 //     parentItem = rootItem;
422 //   else
423 //     parentItem = static_cast<AbstractTreeItem*>(parent.internalPointer());
424 //   return parentItem->columnCount();
425 }
426
427 QVariant TreeModel::data(const QModelIndex &index, int role) const {
428   if(!index.isValid())
429     return QVariant();
430
431   AbstractTreeItem *item = static_cast<AbstractTreeItem *>(index.internalPointer());
432   return item->data(index.column(), role);
433 }
434
435 bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role) {
436   if(!index.isValid())
437     return false;
438
439   AbstractTreeItem *item = static_cast<AbstractTreeItem *>(index.internalPointer());
440   return item->setData(index.column(), value, role);
441 }
442
443 Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const {
444   if(!index.isValid()) {
445     return rootItem->flags() & Qt::ItemIsDropEnabled;
446   } else {
447     AbstractTreeItem *item = static_cast<AbstractTreeItem *>(index.internalPointer());
448     return item->flags();
449   }
450 }
451
452 QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const {
453   if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
454     return rootItem->data(section, role);
455   else
456     return QVariant();
457 }
458
459 void TreeModel::itemDataChanged(int column) {
460   AbstractTreeItem *item = qobject_cast<AbstractTreeItem *>(sender());
461   QModelIndex leftIndex, rightIndex;
462
463   if(item == rootItem)
464     return;
465
466   if(column == -1) {
467     leftIndex = createIndex(item->row(), 0, item);
468     rightIndex = createIndex(item->row(), item->columnCount() - 1, item);
469   } else {
470     leftIndex = createIndex(item->row(), column, item);
471     rightIndex = leftIndex;
472   }
473
474   emit dataChanged(leftIndex, rightIndex);
475 }
476
477 void TreeModel::connectItem(AbstractTreeItem *item) {
478   connect(item, SIGNAL(dataChanged(int)),
479           this, SLOT(itemDataChanged(int)));
480   
481   connect(item, SIGNAL(beginAppendChilds(int, int)),
482           this, SLOT(beginAppendChilds(int, int)));
483   connect(item, SIGNAL(endAppendChilds()),
484           this, SLOT(endAppendChilds()));
485   
486   connect(item, SIGNAL(beginRemoveChilds(int, int)),
487           this, SLOT(beginRemoveChilds(int, int)));
488   connect(item, SIGNAL(endRemoveChilds()),
489           this, SLOT(endRemoveChilds()));
490 }
491
492 void TreeModel::beginAppendChilds(int firstRow, int lastRow) {
493   AbstractTreeItem *parentItem = qobject_cast<AbstractTreeItem *>(sender());
494   if(!parentItem) {
495     qWarning() << "TreeModel::beginAppendChilds(): cannot append Childs to unknown parent";
496     return;
497   }
498
499   QModelIndex parent = indexByItem(parentItem);
500   Q_ASSERT(!_aboutToRemoveOrInsert);
501   
502   _aboutToRemoveOrInsert = true;
503   _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
504   beginInsertRows(parent, firstRow, lastRow);
505 }
506
507 void TreeModel::endAppendChilds() {
508   AbstractTreeItem *parentItem = qobject_cast<AbstractTreeItem *>(sender());
509   if(!parentItem) {
510     qWarning() << "TreeModel::endAppendChilds(): cannot append Childs to unknown parent";
511     return;
512   }
513   Q_ASSERT(_aboutToRemoveOrInsert);
514   ChildStatus cs = _childStatus;
515   QModelIndex parent = indexByItem(parentItem);
516   Q_ASSERT(cs.parent == parent);
517   Q_ASSERT(rowCount(parent) == cs.childCount + cs.end - cs.start + 1);
518
519   _aboutToRemoveOrInsert = false;
520   for(int i = cs.start; i <= cs.end; i++) {
521     connectItem(parentItem->child(i));
522   }
523   endInsertRows();
524 }
525
526 void TreeModel::beginRemoveChilds(int firstRow, int lastRow) {
527   AbstractTreeItem *parentItem = qobject_cast<AbstractTreeItem *>(sender());
528   if(!parentItem) {
529     qWarning() << "TreeModel::beginRemoveChilds(): cannot append Childs to unknown parent";
530     return;
531   }
532
533   for(int i = firstRow; i <= lastRow; i++) {
534     disconnect(parentItem->child(i), 0, this, 0);
535   }
536   
537   // consitency checks
538   QModelIndex parent = indexByItem(parentItem);
539   Q_ASSERT(firstRow <= lastRow);
540   Q_ASSERT(parentItem->childCount() > lastRow);
541   Q_ASSERT(!_aboutToRemoveOrInsert);
542   _aboutToRemoveOrInsert = true;
543   _childStatus = ChildStatus(parent, rowCount(parent), firstRow, lastRow);
544
545   beginRemoveRows(parent, firstRow, lastRow);
546 }
547
548 void TreeModel::endRemoveChilds() {
549   AbstractTreeItem *parentItem = qobject_cast<AbstractTreeItem *>(sender());
550   if(!parentItem) {
551     qWarning() << "TreeModel::endRemoveChilds(): cannot remove Childs from unknown parent";
552     return;
553   }
554
555   // concistency checks
556   Q_ASSERT(_aboutToRemoveOrInsert);
557   ChildStatus cs = _childStatus;
558   QModelIndex parent = indexByItem(parentItem);
559   Q_ASSERT(cs.parent == parent);
560   Q_ASSERT(rowCount(parent) == cs.childCount - cs.end + cs.start - 1);
561   _aboutToRemoveOrInsert = false;
562
563   endRemoveRows();
564 }
565
566 void TreeModel::clear() {
567   rootItem->removeAllChilds();
568 }
569
570 void TreeModel::debug_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) {
571   qDebug() << "debug_rowsAboutToBeInserted" << parent << parent.internalPointer() << parent.data().toString() << rowCount(parent) << start << end;
572 }
573
574 void TreeModel::debug_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
575   AbstractTreeItem *parentItem;
576   parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
577   if(!parentItem)
578     parentItem = rootItem;
579   qDebug() << "debug_rowsAboutToBeRemoved" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
580
581   QModelIndex child;
582   AbstractTreeItem *childItem;
583   for(int i = end; i >= start; i--) {
584     child = parent.child(i, 0);
585     childItem = parentItem->child(i);
586     Q_ASSERT(childItem);
587     qDebug() << ">>>" << i << child << childItem->id() << child.data().toString();
588   }
589 }
590
591 void TreeModel::debug_rowsInserted(const QModelIndex &parent, int start, int end) {
592   AbstractTreeItem *parentItem;
593   parentItem = static_cast<AbstractTreeItem *>(parent.internalPointer());
594   if(!parentItem)
595     parentItem = rootItem;
596   qDebug() << "debug_rowsInserted:" << parent << parentItem << parent.data().toString() << rowCount(parent) << start << end;
597
598   QModelIndex child;
599   AbstractTreeItem *childItem;
600   for(int i = start; i <= end; i++) {
601     child = parent.child(i, 0);
602     childItem = parentItem->child(i);
603     Q_ASSERT(childItem);
604     qDebug() << "<<<" << i << child << childItem->id() << child.data().toString();
605   }
606 }
607
608 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;
610 }
611
612 void TreeModel::debug_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
613   qDebug() << "debug_dataChanged" << topLeft << bottomRight;
614   QStringList displayData;
615   for(int row = topLeft.row(); row <= bottomRight.row(); row++) {
616     displayData = QStringList();
617     for(int column = topLeft.column(); column <= bottomRight.column(); column++) {
618       displayData << data(topLeft.sibling(row, column), Qt::DisplayRole).toString();
619     }
620     qDebug() << "  row:" << row << displayData;
621   }
622 }