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