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