Merge branch 'network-sync'
[quassel.git] / src / client / networkmodel.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 "networkmodel.h"
22
23 #include <QAbstractItemView>
24
25 #include "bufferinfo.h"
26 #include "client.h"
27 #include "signalproxy.h"
28 #include "network.h"
29 #include "ircchannel.h"
30
31 #include "buffersettings.h"
32
33 #include "util.h" // get rid of this (needed for isChannelName)
34
35 /*****************************************
36 *  Network Items
37 *****************************************/
38 NetworkItem::NetworkItem(const NetworkId &netid, AbstractTreeItem *parent)
39   : PropertyMapItem(QList<QString>() << "networkName" << "currentServer" << "nickCount", parent),
40     _networkId(netid)
41 {
42   setFlags(Qt::ItemIsEnabled);
43 }
44
45 QVariant NetworkItem::data(int column, int role) const {
46   switch(role) {
47   case NetworkModel::NetworkIdRole:
48     return qVariantFromValue(_networkId);
49   case NetworkModel::ItemTypeRole:
50     return NetworkModel::NetworkItemType;
51   case NetworkModel::ItemActiveRole:
52     return isActive();
53   default:
54     return PropertyMapItem::data(column, role);
55   }
56 }
57
58 BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo) {
59   BufferItem *bufferItem = qobject_cast<BufferItem *>(childById(qHash(bufferInfo.bufferId())));
60   if(bufferItem)
61     return bufferItem;
62   
63   switch(bufferInfo.type()) {
64   case BufferInfo::StatusBuffer:
65     bufferItem = new StatusBufferItem(bufferInfo, this);
66     break;
67   case BufferInfo::ChannelBuffer:
68     bufferItem = new ChannelBufferItem(bufferInfo, this);
69     break;
70   case BufferInfo::QueryBuffer:
71     bufferItem = new QueryBufferItem(bufferInfo, this);
72     break;
73   default:
74     bufferItem = new BufferItem(bufferInfo, this);
75   }
76
77   newChild(bufferItem);
78   return bufferItem;
79 }
80
81 void NetworkItem::attachNetwork(Network *network) {
82   if(!network)
83     return;
84   
85   _network = network;
86
87   connect(network, SIGNAL(networkNameSet(QString)),
88           this, SLOT(setNetworkName(QString)));
89   connect(network, SIGNAL(currentServerSet(QString)),
90           this, SLOT(setCurrentServer(QString)));
91   connect(network, SIGNAL(ircChannelAdded(IrcChannel *)),
92           this, SLOT(attachIrcChannel(IrcChannel *)));
93   connect(network, SIGNAL(connectedSet(bool)),
94           this, SIGNAL(dataChanged()));
95   connect(network, SIGNAL(destroyed()),
96           this, SIGNAL(dataChanged()));
97   
98   emit dataChanged();
99 }
100
101 void NetworkItem::attachIrcChannel(IrcChannel *ircChannel) {
102   ChannelBufferItem *channelItem;
103   for(int i = 0; i < childCount(); i++) {
104     channelItem = qobject_cast<ChannelBufferItem *>(child(i));
105     if(!channelItem)
106       continue;
107
108     if(channelItem->bufferName().toLower() == ircChannel->name().toLower()) {
109       channelItem->attachIrcChannel(ircChannel);
110       break;
111     }
112   }
113 }
114
115 void NetworkItem::setNetworkName(const QString &networkName) {
116   Q_UNUSED(networkName);
117   emit dataChanged(0);
118 }
119
120 void NetworkItem::setCurrentServer(const QString &serverName) {
121   Q_UNUSED(serverName);
122   emit dataChanged(1);
123 }
124
125
126 QString NetworkItem::toolTip(int column) const {
127   Q_UNUSED(column);
128
129   QStringList toolTip(QString("<b>%1</b>").arg(networkName()));
130   toolTip.append(QString("Server: %1").arg(currentServer()));
131   toolTip.append(QString("Users: %1").arg(nickCount()));
132
133   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
134 }
135
136 /*****************************************
137 *  Fancy Buffer Items
138 *****************************************/
139 BufferItem::BufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
140   : PropertyMapItem(QStringList() << "bufferName" << "topic" << "nickCount", parent),
141     _bufferInfo(bufferInfo),
142     _activity(Buffer::NoActivity)
143 {
144   setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
145 }
146
147 void BufferItem::setActivityLevel(Buffer::ActivityLevel level) {
148   if(_activity != level) {
149     _activity = level;
150     emit dataChanged();
151   }
152 }
153
154 void BufferItem::updateActivityLevel(Buffer::ActivityLevel level) {
155   Buffer::ActivityLevel oldActivity = _activity;
156   _activity |= level;
157   if(oldActivity != _activity)
158     emit dataChanged();
159 }
160
161 QVariant BufferItem::data(int column, int role) const {
162   switch(role) {
163   case NetworkModel::ItemTypeRole:
164     return NetworkModel::BufferItemType;
165   case NetworkModel::BufferIdRole:
166     return qVariantFromValue(bufferInfo().bufferId());
167   case NetworkModel::NetworkIdRole:
168     return qVariantFromValue(bufferInfo().networkId());
169   case NetworkModel::BufferInfoRole:
170     return qVariantFromValue(bufferInfo());
171   case NetworkModel::BufferTypeRole:
172     return int(bufferType());
173   case NetworkModel::ItemActiveRole:
174     return isActive();
175   case NetworkModel::BufferActivityRole:
176     return (int)activityLevel();
177   default:
178     return PropertyMapItem::data(column, role);
179   }
180 }
181
182 bool BufferItem::setData(int column, const QVariant &value, int role) {
183   qDebug() << "BufferItem::setData(int column, const QVariant &value, int role):" << this << column << value << role;
184   switch(role) {
185   case NetworkModel::BufferActivityRole:
186     setActivityLevel((Buffer::ActivityLevel)value.toInt());
187     return true;
188   default:
189     return PropertyMapItem::setData(column, value, role);
190   }
191   return true;
192 }
193
194 void BufferItem::setBufferName(const QString &name) {
195   _bufferInfo = BufferInfo(_bufferInfo.bufferId(), _bufferInfo.networkId(), _bufferInfo.type(), _bufferInfo.groupId(), name);
196   emit dataChanged(0);
197 }
198
199 QString BufferItem::toolTip(int column) const {
200   Q_UNUSED(column);
201   return tr("<p> %1 - %2 </p>").arg(bufferInfo().bufferId().toInt()).arg(bufferName());
202 }
203
204 /*
205 void BufferItem::setLastMsgInsert(QDateTime msgDate) {
206   if(msgDate.isValid() && msgDate > _lastMsgInsert)
207     _lastMsgInsert = msgDate;
208 }
209 */
210 /*
211 // FIXME emit dataChanged()
212 bool BufferItem::setLastSeen() {
213   if(_lastSeen > _lastMsgInsert)
214     return false;
215   
216   _lastSeen = _lastMsgInsert;
217   BufferSettings(bufferInfo().bufferId()).setLastSeen(_lastSeen);
218   return true;
219 }
220
221 QDateTime BufferItem::lastSeen() {
222   return _lastSeen;
223 }
224 */
225
226 /*****************************************
227 *  StatusBufferItem
228 *****************************************/
229 StatusBufferItem::StatusBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
230   : BufferItem(bufferInfo, parent)
231 {
232   Q_ASSERT(parent);
233   connect(parent, SIGNAL(dataChanged()), this, SIGNAL(dataChanged()));
234 }
235
236 QString StatusBufferItem::toolTip(int column) const {
237   Q_UNUSED(column);
238   QStringList toolTip;
239
240   QString netName = Client::network(bufferInfo().networkId())->networkName();
241   toolTip.append(tr("<b>Status buffer of %1</b>").arg(netName));
242
243   return tr("<p> %1 </p>").arg(toolTip.join("<br />"));
244 }
245
246 /*****************************************
247 *  QueryBufferItem
248 *****************************************/
249 QueryBufferItem::QueryBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
250   : BufferItem(bufferInfo, parent)
251 {
252   setFlags(flags() | Qt::ItemIsDropEnabled);
253 }
254
255 QString QueryBufferItem::toolTip(int column) const {
256   Q_UNUSED(column);
257   QStringList toolTip;
258
259   toolTip.append(tr("<b>Query with %1</b>").arg(bufferName()));
260   if(topic() != "") {
261     toolTip.append(tr("Away Message: %1").arg(topic()));
262   }
263
264   return tr("<p> %1 </p>").arg(toolTip.join("<br />"));
265 }
266
267 /*****************************************
268 *  ChannelBufferItem
269 *****************************************/
270 ChannelBufferItem::ChannelBufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
271   : BufferItem(bufferInfo, parent),
272     _ircChannel(0)
273 {
274 }
275
276 QString ChannelBufferItem::toolTip(int column) const {
277   Q_UNUSED(column);
278   QStringList toolTip;
279
280   toolTip.append(tr("<b>Channel %1</b>").arg(bufferName()));
281   if(isActive()) {
282     //TODO: add channel modes 
283     toolTip.append(tr("<b>Users:</b> %1").arg(nickCount()));
284     if(_ircChannel) {
285       QString channelMode = _ircChannel->channelModeString(); // channelModeString is compiled on the fly -> thus cache the result
286       if(!channelMode.isEmpty())
287         toolTip.append(tr("<b>Mode:</b> %1").arg(channelMode));
288     }
289     
290     BufferSettings s;
291     bool showTopic = s.value("DisplayTopicInTooltip", QVariant(false)).toBool();
292     if(showTopic) {
293       QString _topic = topic();
294       if(_topic != "") {
295         _topic.replace(QString("<"), QString("&lt;"));
296         _topic.replace(QString(">"), QString("&gt;"));
297         toolTip.append(QString("<font size='-2'>&nbsp;</font>"));
298         toolTip.append(tr("<b>Topic:</b> %1").arg(_topic));
299       }
300     }
301   } else {
302     toolTip.append(tr("Not active <br /> Double-click to join"));
303   }
304
305   return tr("<p> %1 </p>").arg(toolTip.join("<br />"));  
306 }
307
308 void ChannelBufferItem::attachIrcChannel(IrcChannel *ircChannel) {
309   Q_ASSERT(!_ircChannel && ircChannel);
310   
311   _ircChannel = ircChannel;
312
313   connect(ircChannel, SIGNAL(topicSet(QString)),
314           this, SLOT(setTopic(QString)));
315   connect(ircChannel, SIGNAL(ircUsersJoined(QList<IrcUser *>)),
316           this, SLOT(join(QList<IrcUser *>)));
317   connect(ircChannel, SIGNAL(ircUserParted(IrcUser *)),
318           this, SLOT(part(IrcUser *)));
319   connect(ircChannel, SIGNAL(destroyed()),
320           this, SLOT(ircChannelDestroyed()));
321   connect(ircChannel, SIGNAL(ircUserModesSet(IrcUser *, QString)),
322           this, SLOT(userModeChanged(IrcUser *)));
323   connect(ircChannel, SIGNAL(ircUserModeAdded(IrcUser *, QString)),
324           this, SLOT(userModeChanged(IrcUser *)));
325   connect(ircChannel, SIGNAL(ircUserModeRemoved(IrcUser *, QString)),
326           this, SLOT(userModeChanged(IrcUser *)));
327
328   if(!ircChannel->ircUsers().isEmpty())
329     join(ircChannel->ircUsers());
330   
331   emit dataChanged();
332 }
333
334 void ChannelBufferItem::ircChannelDestroyed() {
335   Q_CHECK_PTR(_ircChannel);
336   disconnect(_ircChannel, 0, this, 0);
337   _ircChannel = 0;
338   emit dataChanged();
339   removeAllChilds();
340 }
341
342 void ChannelBufferItem::ircUserDestroyed() {
343   // PRIVATE
344   IrcUser *ircUser = static_cast<IrcUser *>(sender());
345   removeUserFromCategory(ircUser);
346   emit dataChanged(2);
347 }
348
349 void ChannelBufferItem::join(const QList<IrcUser *> &ircUsers) {
350   addUsersToCategory(ircUsers);
351
352   foreach(IrcUser *ircUser, ircUsers) {
353     if(!ircUser)
354       continue;
355     connect(ircUser, SIGNAL(destroyed()), this, SLOT(ircUserDestroyed()));
356   }
357   
358   emit dataChanged(2);
359 }
360
361 void ChannelBufferItem::addUserToCategory(IrcUser *ircUser) {
362   addUsersToCategory(QList<IrcUser *>() << ircUser);
363 }
364
365 void ChannelBufferItem::addUsersToCategory(const QList<IrcUser *> &ircUsers) {
366   Q_ASSERT(_ircChannel);
367
368   QHash<UserCategoryItem *, QList<IrcUser *> > categories;
369
370   int categoryId = -1;
371   UserCategoryItem *categoryItem = 0;
372   
373   foreach(IrcUser *ircUser, ircUsers) {
374     categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
375     categoryItem = qobject_cast<UserCategoryItem *>(childById(qHash(categoryId)));
376     if(!categoryItem) {
377       categoryItem = new UserCategoryItem(categoryId, this);
378       categories[categoryItem] = QList<IrcUser *>();
379       newChild(categoryItem);
380     }
381     categories[categoryItem] << ircUser;
382   }
383
384   QHash<UserCategoryItem *, QList<IrcUser *> >::const_iterator catIter = categories.constBegin();
385   while(catIter != categories.constEnd()) {
386     catIter.key()->addUsers(catIter.value());
387     catIter++;
388   }
389 }
390
391 void ChannelBufferItem::part(IrcUser *ircUser) {
392   if(!ircUser) {
393     qWarning() << bufferName() << "ChannelBufferItem::part(): unknown User" << ircUser;
394     return;
395   }
396
397   disconnect(ircUser, 0, this, 0);
398   removeUserFromCategory(ircUser);
399   emit dataChanged(2);
400 }
401
402 void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser) {
403   if(!_ircChannel) {
404     // If we parted the channel there might still be some ircUsers connected.
405     // in that case we just ignore the call
406     Q_ASSERT(childCount() == 0);
407     return;
408   }
409
410   UserCategoryItem *categoryItem = 0;
411   for(int i = 0; i < childCount(); i++) {
412     categoryItem = qobject_cast<UserCategoryItem *>(child(i));
413     if(categoryItem->removeUser(ircUser)) {
414       if(categoryItem->childCount() == 0)
415         removeChild(i);
416       break;
417     }
418   }
419 }
420
421 void ChannelBufferItem::userModeChanged(IrcUser *ircUser) {
422   Q_ASSERT(_ircChannel);
423
424   int categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
425   UserCategoryItem *categoryItem = qobject_cast<UserCategoryItem *>(childById(qHash(categoryId)));
426     
427   if(categoryItem) {
428     if(categoryItem->childById(qHash(ircUser)))
429       return; // already in the right category;
430   } else {
431     categoryItem = new UserCategoryItem(categoryId, this);
432     newChild(categoryItem);
433   }
434
435   // find the item that needs reparenting
436   IrcUserItem *ircUserItem = 0;
437   for(int i = 0; i < childCount(); i++) {
438     UserCategoryItem *categoryItem = qobject_cast<UserCategoryItem *>(child(i));
439     IrcUserItem *userItem = qobject_cast<IrcUserItem *>(categoryItem->childById(qHash(ircUser)));
440     if(userItem) {
441       ircUserItem = userItem;
442       break;
443     }
444   }
445
446   if(!ircUserItem) {
447     qWarning() << "ChannelBufferItem::userModeChanged(IrcUser *): unable to determine old category of" << ircUser;
448     return;
449   }
450   ircUserItem->reParent(categoryItem);
451 }
452
453 /*****************************************
454 *  User Category Items (like @vh etc.)
455 *****************************************/
456 // we hardcode this even though we have PREFIX in network... but that wouldn't help with mapping modes to
457 // category strings anyway.
458 const QList<QChar> UserCategoryItem::categories = QList<QChar>() << 'q' << 'a' << 'o' << 'h' << 'v';
459
460 UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent)
461   : PropertyMapItem(QStringList() << "categoryName", parent),
462     _category(category)
463 {
464 }
465
466 // caching this makes no sense, since we display the user number dynamically
467 QString UserCategoryItem::categoryName() const {
468   int n = childCount();
469   switch(_category) {
470     case 0: return tr("%n Owner(s)", 0, n);
471     case 1: return tr("%n Admin(s)", 0, n);
472     case 2: return tr("%n Operator(s)", 0, n);
473     case 3: return tr("%n Half-Op(s)", 0, n);
474     case 4: return tr("%n Voiced", 0, n);
475     default: return tr("%n User(s)", 0, n);
476   }
477 }
478
479 quint64 UserCategoryItem::id() const {
480   return qHash(_category);
481 }
482
483 void UserCategoryItem::addUsers(const QList<IrcUser *> &ircUsers) {
484   QList<AbstractTreeItem *> userItems;
485   foreach(IrcUser *ircUser, ircUsers)
486     userItems << new IrcUserItem(ircUser, this);
487   newChilds(userItems);
488   emit dataChanged(0);
489 }
490
491 bool UserCategoryItem::removeUser(IrcUser *ircUser) {
492   bool success = removeChildById(qHash(ircUser));
493   if(success)
494     emit dataChanged(0);
495   return success;
496 }
497
498 int UserCategoryItem::categoryFromModes(const QString &modes) {
499   for(int i = 0; i < categories.count(); i++) {
500     if(modes.contains(categories[i]))
501       return i;
502   }
503   return categories.count();
504 }
505
506 QVariant UserCategoryItem::data(int column, int role) const {
507   switch(role) {
508   case TreeModel::SortRole:
509     return _category;
510   case NetworkModel::ItemActiveRole:
511     return true;
512   case NetworkModel::ItemTypeRole:
513     return NetworkModel::UserCategoryItemType;
514   case NetworkModel::BufferIdRole:
515     return parent()->data(column, role);
516   case NetworkModel::NetworkIdRole:
517     return parent()->data(column, role);
518   case NetworkModel::BufferInfoRole:
519     return parent()->data(column, role);
520   default:
521     return PropertyMapItem::data(column, role);
522   }
523 }
524
525
526 /*****************************************
527 *  Irc User Items
528 *****************************************/
529 IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent)
530   : PropertyMapItem(QStringList() << "nickName", parent),
531     _ircUser(ircUser),
532     _id(qHash(ircUser))
533 {
534   // we don't need to handle the ircUser's destroyed signal since it's automatically removed
535   // by the IrcChannel::ircUserParted();
536   
537   connect(ircUser, SIGNAL(nickSet(QString)),
538           this, SLOT(setNick(QString)));
539   connect(ircUser, SIGNAL(awaySet(bool)),
540           this, SLOT(setAway(bool)));
541 }
542
543 QString IrcUserItem::nickName() const {
544   if(_ircUser)
545     return _ircUser->nick();
546   else
547     return QString();
548 }
549
550 bool IrcUserItem::isActive() const {
551   if(_ircUser)
552     return !_ircUser->isAway();
553   else
554     return false;
555 }
556
557 QVariant IrcUserItem::data(int column, int role) const {
558   switch(role) {
559   case NetworkModel::ItemActiveRole:
560     return isActive();
561   case NetworkModel::ItemTypeRole:
562     return NetworkModel::IrcUserItemType;
563   case NetworkModel::BufferIdRole:
564     return parent()->data(column, role);
565   case NetworkModel::NetworkIdRole:
566     return parent()->data(column, role);
567   case NetworkModel::BufferInfoRole:
568     return parent()->data(column, role);
569   default:
570     return PropertyMapItem::data(column, role);
571   }
572 }
573
574 QString IrcUserItem::toolTip(int column) const {
575   Q_UNUSED(column);
576   QStringList toolTip(QString("<b>%1</b>").arg(nickName()));
577   if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
578   if(_ircUser->isAway()) {
579     toolTip[0].append(" is away");
580     if(!_ircUser->awayMessage().isEmpty())
581       toolTip[0].append(QString(" (%1)").arg(_ircUser->awayMessage()));
582   }
583   if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
584   if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(nickName()).arg(_ircUser->ircOperator()));
585   if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
586   if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
587
588   toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
589
590   if(_ircUser->idleTime().isValid()) {
591     QDateTime now = QDateTime::currentDateTime();
592     QDateTime idle = _ircUser->idleTime();
593     int idleTime = idle.secsTo(now);
594     toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
595   }
596   if(_ircUser->loginTime().isValid()) {
597     toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
598   }
599
600   if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
601
602   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
603 }
604
605 void IrcUserItem::setNick(QString newNick) {
606   Q_UNUSED(newNick);
607   emit dataChanged(0);
608 }
609
610 void IrcUserItem::setAway(bool away) {
611   Q_UNUSED(away);
612   emit dataChanged(0);
613 }
614
615 /*****************************************
616  * NetworkModel
617  *****************************************/
618 NetworkModel::NetworkModel(QObject *parent)
619   : TreeModel(NetworkModel::defaultHeader(), parent)
620 {
621 }
622
623 QList<QVariant >NetworkModel::defaultHeader() {
624   QList<QVariant> data;
625   data << tr("Buffer") << tr("Topic") << tr("Nick Count");
626   return data;
627 }
628
629 bool NetworkModel::isBufferIndex(const QModelIndex &index) const {
630   return index.data(NetworkModel::ItemTypeRole) == NetworkModel::BufferItemType;
631 }
632
633 /*
634 Buffer *NetworkModel::getBufferByIndex(const QModelIndex &index) const {
635   BufferItem *item = static_cast<BufferItem *>(index.internalPointer());
636   return Client::instance()->buffer(item->id());
637 }
638 */
639
640
641 // experimental stuff :)
642 QModelIndex NetworkModel::networkIndex(NetworkId networkId) {
643   return indexById(qHash(networkId));
644 }
645
646 NetworkItem *NetworkModel::existsNetworkItem(NetworkId networkId) {
647   return qobject_cast<NetworkItem *>(rootItem->childById(networkId.toInt()));
648 }
649
650 NetworkItem *NetworkModel::networkItem(NetworkId networkId) {
651   NetworkItem *netItem = existsNetworkItem(networkId);
652
653   if(netItem == 0) {
654     netItem = new NetworkItem(networkId, rootItem);
655     rootItem->newChild(netItem);
656   }
657
658   Q_ASSERT(netItem);
659   return netItem;
660 }
661
662 void NetworkModel::networkRemoved(const NetworkId &networkId) {
663   rootItem->removeChildById(qHash(networkId));
664 }
665
666 QModelIndex NetworkModel::bufferIndex(BufferId bufferId) {
667   AbstractTreeItem *netItem, *bufferItem;
668   for(int i = 0; i < rootItem->childCount(); i++) {
669     netItem = rootItem->child(i);
670     if((bufferItem = netItem->childById(qHash(bufferId)))) {
671       return indexByItem(bufferItem);
672     }
673   }
674   return QModelIndex();
675 }
676
677 BufferItem *NetworkModel::existsBufferItem(const BufferInfo &bufferInfo) {
678   QModelIndex bufferIdx = bufferIndex(bufferInfo.bufferId());
679   if(bufferIdx.isValid())
680     return static_cast<BufferItem *>(bufferIdx.internalPointer());
681   else
682     return 0;
683 }
684
685 BufferItem *NetworkModel::bufferItem(const BufferInfo &bufferInfo) {
686   NetworkItem *netItem = networkItem(bufferInfo.networkId());
687   return netItem->bufferItem(bufferInfo);
688 }
689
690 QStringList NetworkModel::mimeTypes() const {
691   // mimetypes we accept for drops
692   QStringList types;
693   // comma separated list of colon separated pairs of networkid and bufferid
694   // example: 0:1,0:2,1:4
695   types << "application/Quassel/BufferItemList";
696   return types;
697 }
698
699 bool NetworkModel::mimeContainsBufferList(const QMimeData *mimeData) {
700   return mimeData->hasFormat("application/Quassel/BufferItemList");
701 }
702
703 QList< QPair<NetworkId, BufferId> > NetworkModel::mimeDataToBufferList(const QMimeData *mimeData) {
704   QList< QPair<NetworkId, BufferId> > bufferList;
705
706   if(!mimeContainsBufferList(mimeData))
707     return bufferList;
708
709   QStringList rawBufferList = QString::fromAscii(mimeData->data("application/Quassel/BufferItemList")).split(",");
710   NetworkId networkId;
711   BufferId bufferUid;
712   foreach(QString rawBuffer, rawBufferList) {
713     if(!rawBuffer.contains(":"))
714       continue;
715     networkId = rawBuffer.section(":", 0, 0).toInt();
716     bufferUid = rawBuffer.section(":", 1, 1).toInt();
717     bufferList.append(qMakePair(networkId, bufferUid));
718   }
719   return bufferList;
720 }
721
722
723 QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const {
724   QMimeData *mimeData = new QMimeData();
725
726   QStringList bufferlist;
727   QString netid, uid, bufferid;
728   foreach(QModelIndex index, indexes) {
729     netid = QString::number(index.data(NetworkIdRole).value<NetworkId>().toInt());
730     uid = QString::number(index.data(BufferIdRole).value<BufferId>().toInt());
731     bufferid = QString("%1:%2").arg(netid).arg(uid);
732     if(!bufferlist.contains(bufferid))
733       bufferlist << bufferid;
734   }
735
736   mimeData->setData("application/Quassel/BufferItemList", bufferlist.join(",").toAscii());
737
738   return mimeData;
739 }
740
741 bool NetworkModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) {
742   Q_UNUSED(action)
743   Q_UNUSED(row)
744   Q_UNUSED(column)
745
746   if(!mimeContainsBufferList(data))
747     return false;
748
749   // target must be a query
750   BufferInfo::Type targetType = (BufferInfo::Type)parent.data(NetworkModel::BufferTypeRole).toInt();
751   if(targetType != BufferInfo::QueryBuffer)
752     return false;
753
754   QList< QPair<NetworkId, BufferId> > bufferList = mimeDataToBufferList(data);
755
756   // exactly one buffer has to be dropped
757   if(bufferList.count() != 1)
758     return false;
759
760   NetworkId netId = bufferList.first().first;
761   BufferId bufferId = bufferList.first().second;
762
763   // no self merges (would kill us)
764   if(bufferId == parent.data(BufferIdRole).value<BufferId>())
765     return false; 
766   
767   Q_ASSERT(rootItem->childById(qHash(netId)));
768   Q_ASSERT(rootItem->childById(qHash(netId))->childById(qHash(bufferId)));
769
770   // source must be a query too
771   BufferInfo::Type sourceType = (BufferInfo::Type)rootItem->childById(qHash(netId))->childById(qHash(bufferId))->data(0, BufferTypeRole).toInt();
772   if(sourceType != BufferInfo::QueryBuffer)
773     return false;
774     
775   // TODO: warn user about buffermerge!
776   qDebug() << "merging" << bufferId << parent.data(BufferIdRole).value<BufferId>();
777   removeRow(parent.row(), parent.parent());
778   
779   return true;
780 }
781
782 void NetworkModel::attachNetwork(Network *net) {
783   NetworkItem *netItem = networkItem(net->networkId());
784   netItem->attachNetwork(net);
785 }
786
787 void NetworkModel::bufferUpdated(BufferInfo bufferInfo) {
788   BufferItem *bufItem = bufferItem(bufferInfo);
789   QModelIndex itemindex = indexByItem(bufItem);
790   emit dataChanged(itemindex, itemindex);
791 }
792
793 void NetworkModel::removeBuffer(BufferId bufferId) {
794   const int numNetworks = rootItem->childCount();
795   if(numNetworks == 0)
796     return;
797
798   for(int i = 0; i < numNetworks; i++) {
799     if(rootItem->child(i)->removeChildById(qHash(bufferId)))
800       break;
801   }
802 }
803
804 /*
805 void NetworkModel::updateBufferActivity(const Message &msg) {
806   BufferItem *buff = bufferItem(msg.bufferInfo());
807   Q_ASSERT(buff);
808
809   buff->setLastMsgInsert(msg.timestamp());
810
811   if(buff->lastSeen() >= msg.timestamp())
812     return;
813
814   BufferItem::ActivityLevel level = BufferItem::OtherActivity;
815   if(msg.type() == Message::Plain || msg.type() == Message::Notice)
816     level |= BufferItem::NewMessage;
817
818   if(msg.flags() & Message::Highlight) 
819       level |= BufferItem::Highlight;
820
821   bufferItem(msg.bufferInfo())->updateActivity(level);
822 }
823 */
824
825 void NetworkModel::setBufferActivity(const BufferInfo &info, Buffer::ActivityLevel level) {
826   BufferItem *buff = bufferItem(info);
827   Q_ASSERT(buff);
828
829   buff->setActivityLevel(level);
830 }
831
832 const Network *NetworkModel::networkByIndex(const QModelIndex &index) const {
833   QVariant netVariant = index.data(NetworkIdRole);
834   if(!netVariant.isValid())
835     return 0;
836
837   NetworkId networkId = netVariant.value<NetworkId>();
838   return Client::network(networkId);
839 }
840