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