1a692ca50c17162d2c57da29de3e61b6b655ee9c
[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     qWarning() << "Channel" << ircChannel->name() << "has already users which is quite surprising :)";
330     join(ircChannel->ircUsers());
331   }
332   
333   emit dataChanged();
334 }
335
336 void ChannelBufferItem::ircChannelDestroyed() {
337   Q_CHECK_PTR(_ircChannel);
338   disconnect(_ircChannel, 0, this, 0);
339   _ircChannel = 0;
340   emit dataChanged();
341   removeAllChilds();
342 }
343
344 void ChannelBufferItem::ircUserDestroyed() {
345   // PRIVATE
346   IrcUser *ircUser = static_cast<IrcUser *>(sender());
347   removeUserFromCategory(ircUser);
348   emit dataChanged(2);
349 }
350
351 void ChannelBufferItem::join(const QList<IrcUser *> &ircUsers) {
352   addUsersToCategory(ircUsers);
353
354   foreach(IrcUser *ircUser, ircUsers) {
355     if(!ircUser)
356       continue;
357     connect(ircUser, SIGNAL(destroyed()), this, SLOT(ircUserDestroyed()));
358   }
359   
360   emit dataChanged(2);
361 }
362
363 void ChannelBufferItem::addUserToCategory(IrcUser *ircUser) {
364   addUsersToCategory(QList<IrcUser *>() << ircUser);
365 }
366
367 void ChannelBufferItem::addUsersToCategory(const QList<IrcUser *> &ircUsers) {
368   Q_ASSERT(_ircChannel);
369
370   QHash<UserCategoryItem *, QList<IrcUser *> > categories;
371
372   int categoryId = -1;
373   UserCategoryItem *categoryItem = 0;
374   
375   foreach(IrcUser *ircUser, ircUsers) {
376     categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
377     categoryItem = qobject_cast<UserCategoryItem *>(childById(qHash(categoryId)));
378     if(!categoryItem) {
379       categoryItem = new UserCategoryItem(categoryId, this);
380       categories[categoryItem] = QList<IrcUser *>();
381       newChild(categoryItem);
382     }
383     categories[categoryItem] << ircUser;
384   }
385
386   QHash<UserCategoryItem *, QList<IrcUser *> >::const_iterator catIter = categories.constBegin();
387   while(catIter != categories.constEnd()) {
388     catIter.key()->addUsers(catIter.value());
389     catIter++;
390   }
391 }
392
393 void ChannelBufferItem::part(IrcUser *ircUser) {
394   if(!ircUser) {
395     qWarning() << bufferName() << "ChannelBufferItem::part(): unknown User" << ircUser;
396     return;
397   }
398
399   disconnect(ircUser, 0, this, 0);
400   removeUserFromCategory(ircUser);
401   emit dataChanged(2);
402 }
403
404 void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser) {
405   if(!_ircChannel) {
406     // If we parted the channel there might still be some ircUsers connected.
407     // in that case we just ignore the call
408     Q_ASSERT(childCount() == 0);
409     return;
410   }
411
412   UserCategoryItem *categoryItem = 0;
413   for(int i = 0; i < childCount(); i++) {
414     categoryItem = qobject_cast<UserCategoryItem *>(child(i));
415     if(categoryItem->removeUser(ircUser)) {
416       if(categoryItem->childCount() == 0)
417         removeChild(i);
418       break;
419     }
420   }
421 }
422
423 void ChannelBufferItem::userModeChanged(IrcUser *ircUser) {
424   Q_ASSERT(_ircChannel);
425
426   int categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
427   UserCategoryItem *categoryItem = qobject_cast<UserCategoryItem *>(childById(qHash(categoryId)));
428     
429   if(categoryItem && categoryItem->childById(qHash(ircUser)))
430     return; // already in the right category;
431
432   // find the item that needs reparenting
433   IrcUserItem *ircUserItem = 0;
434   for(int i = 0; i < childCount(); i++) {
435     UserCategoryItem *categoryItem = qobject_cast<UserCategoryItem *>(child(i));
436     IrcUserItem *userItem = qobject_cast<IrcUserItem *>(categoryItem->childById(qHash(ircUser)));
437     if(userItem) {
438       ircUserItem = userItem;
439       break;
440     }
441   }
442
443   if(!ircUserItem) {
444     qWarning() << "ChannelBufferItem::userModeChanged(IrcUser *): unable to determine old category of" << ircUser;
445     return;
446   }
447   ircUserItem->reParent(categoryItem);
448 }
449
450 /*****************************************
451 *  User Category Items (like @vh etc.)
452 *****************************************/
453 // we hardcode this even though we have PREFIX in network... but that wouldn't help with mapping modes to
454 // category strings anyway.
455 const QList<QChar> UserCategoryItem::categories = QList<QChar>() << 'q' << 'a' << 'o' << 'h' << 'v';
456
457 UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent)
458   : PropertyMapItem(QStringList() << "categoryName", parent),
459     _category(category)
460 {
461 }
462
463 // caching this makes no sense, since we display the user number dynamically
464 QString UserCategoryItem::categoryName() const {
465   int n = childCount();
466   switch(_category) {
467     case 0: return tr("%n Owner(s)", 0, n);
468     case 1: return tr("%n Admin(s)", 0, n);
469     case 2: return tr("%n Operator(s)", 0, n);
470     case 3: return tr("%n Half-Op(s)", 0, n);
471     case 4: return tr("%n Voiced", 0, n);
472     default: return tr("%n User(s)", 0, n);
473   }
474 }
475
476 quint64 UserCategoryItem::id() const {
477   return qHash(_category);
478 }
479
480 void UserCategoryItem::addUsers(const QList<IrcUser *> &ircUsers) {
481   QList<AbstractTreeItem *> userItems;
482   foreach(IrcUser *ircUser, ircUsers)
483     userItems << new IrcUserItem(ircUser, this);
484   newChilds(userItems);
485   emit dataChanged(0);
486 }
487
488 bool UserCategoryItem::removeUser(IrcUser *ircUser) {
489   bool success = removeChildById(qHash(ircUser));
490   if(success)
491     emit dataChanged(0);
492   return success;
493 }
494
495 int UserCategoryItem::categoryFromModes(const QString &modes) {
496   for(int i = 0; i < categories.count(); i++) {
497     if(modes.contains(categories[i]))
498       return i;
499   }
500   return categories.count();
501 }
502
503 QVariant UserCategoryItem::data(int column, int role) const {
504   switch(role) {
505   case TreeModel::SortRole:
506     return _category;
507   case NetworkModel::ItemActiveRole:
508     return true;
509   case NetworkModel::ItemTypeRole:
510     return NetworkModel::UserCategoryItemType;
511   case NetworkModel::BufferIdRole:
512     return parent()->data(column, role);
513   case NetworkModel::NetworkIdRole:
514     return parent()->data(column, role);
515   case NetworkModel::BufferInfoRole:
516     return parent()->data(column, role);
517   default:
518     return PropertyMapItem::data(column, role);
519   }
520 }
521
522
523 /*****************************************
524 *  Irc User Items
525 *****************************************/
526 IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent)
527   : PropertyMapItem(QStringList() << "nickName", parent),
528     _ircUser(ircUser),
529     _id(qHash(ircUser))
530 {
531   // we don't need to handle the ircUser's destroyed signal since it's automatically removed
532   // by the IrcChannel::ircUserParted();
533   
534   connect(ircUser, SIGNAL(nickSet(QString)),
535           this, SLOT(setNick(QString)));
536   connect(ircUser, SIGNAL(awaySet(bool)),
537           this, SLOT(setAway(bool)));
538 }
539
540 QString IrcUserItem::nickName() const {
541   if(_ircUser)
542     return _ircUser->nick();
543   else
544     return QString();
545 }
546
547 bool IrcUserItem::isActive() const {
548   if(_ircUser)
549     return !_ircUser->isAway();
550   else
551     return false;
552 }
553
554 QVariant IrcUserItem::data(int column, int role) const {
555   switch(role) {
556   case NetworkModel::ItemActiveRole:
557     return isActive();
558   case NetworkModel::ItemTypeRole:
559     return NetworkModel::IrcUserItemType;
560   case NetworkModel::BufferIdRole:
561     return parent()->data(column, role);
562   case NetworkModel::NetworkIdRole:
563     return parent()->data(column, role);
564   case NetworkModel::BufferInfoRole:
565     return parent()->data(column, role);
566   default:
567     return PropertyMapItem::data(column, role);
568   }
569 }
570
571 QString IrcUserItem::toolTip(int column) const {
572   Q_UNUSED(column);
573   QStringList toolTip(QString("<b>%1</b>").arg(nickName()));
574   if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
575   if(_ircUser->isAway()) toolTip[0].append(" is away");
576   if(!_ircUser->awayMessage().isEmpty()) toolTip[0].append(QString(" (%1)").arg(_ircUser->awayMessage()));
577   if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
578   if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(nickName()).arg(_ircUser->ircOperator()));
579   if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
580   if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
581
582   toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
583
584   if(_ircUser->idleTime().isValid()) {
585     QDateTime now = QDateTime::currentDateTime();
586     QDateTime idle = _ircUser->idleTime();
587     int idleTime = idle.secsTo(now);
588     toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
589   }
590   if(_ircUser->loginTime().isValid()) {
591     toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
592   }
593
594   if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
595
596   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
597 }
598
599 void IrcUserItem::setNick(QString newNick) {
600   Q_UNUSED(newNick);
601   emit dataChanged(0);
602 }
603
604 void IrcUserItem::setAway(bool away) {
605   Q_UNUSED(away);
606   emit dataChanged(0);
607 }
608
609 /*****************************************
610  * NetworkModel
611  *****************************************/
612 NetworkModel::NetworkModel(QObject *parent)
613   : TreeModel(NetworkModel::defaultHeader(), parent)
614 {
615 }
616
617 QList<QVariant >NetworkModel::defaultHeader() {
618   QList<QVariant> data;
619   data << tr("Buffer") << tr("Topic") << tr("Nick Count");
620   return data;
621 }
622
623 bool NetworkModel::isBufferIndex(const QModelIndex &index) const {
624   return index.data(NetworkModel::ItemTypeRole) == NetworkModel::BufferItemType;
625 }
626
627 /*
628 Buffer *NetworkModel::getBufferByIndex(const QModelIndex &index) const {
629   BufferItem *item = static_cast<BufferItem *>(index.internalPointer());
630   return Client::instance()->buffer(item->id());
631 }
632 */
633
634
635 // experimental stuff :)
636 QModelIndex NetworkModel::networkIndex(NetworkId networkId) {
637   return indexById(qHash(networkId));
638 }
639
640 NetworkItem *NetworkModel::existsNetworkItem(NetworkId networkId) {
641   return qobject_cast<NetworkItem *>(rootItem->childById(networkId.toInt()));
642 }
643
644 NetworkItem *NetworkModel::networkItem(NetworkId networkId) {
645   NetworkItem *netItem = existsNetworkItem(networkId);
646
647   if(netItem == 0) {
648     netItem = new NetworkItem(networkId, rootItem);
649     rootItem->newChild(netItem);
650   }
651
652   Q_ASSERT(netItem);
653   return netItem;
654 }
655
656 void NetworkModel::networkRemoved(const NetworkId &networkId) {
657   rootItem->removeChildById(qHash(networkId));
658 }
659
660 QModelIndex NetworkModel::bufferIndex(BufferId bufferId) {
661   AbstractTreeItem *netItem, *bufferItem;
662   for(int i = 0; i < rootItem->childCount(); i++) {
663     netItem = rootItem->child(i);
664     if((bufferItem = netItem->childById(qHash(bufferId)))) {
665       return indexByItem(bufferItem);
666     }
667   }
668   return QModelIndex();
669 }
670
671 BufferItem *NetworkModel::existsBufferItem(const BufferInfo &bufferInfo) {
672   QModelIndex bufferIdx = bufferIndex(bufferInfo.bufferId());
673   if(bufferIdx.isValid())
674     return static_cast<BufferItem *>(bufferIdx.internalPointer());
675   else
676     return 0;
677 }
678
679 BufferItem *NetworkModel::bufferItem(const BufferInfo &bufferInfo) {
680   NetworkItem *netItem = networkItem(bufferInfo.networkId());
681   return netItem->bufferItem(bufferInfo);
682 }
683
684 QStringList NetworkModel::mimeTypes() const {
685   // mimetypes we accept for drops
686   QStringList types;
687   // comma separated list of colon separated pairs of networkid and bufferid
688   // example: 0:1,0:2,1:4
689   types << "application/Quassel/BufferItemList";
690   return types;
691 }
692
693 bool NetworkModel::mimeContainsBufferList(const QMimeData *mimeData) {
694   return mimeData->hasFormat("application/Quassel/BufferItemList");
695 }
696
697 QList< QPair<NetworkId, BufferId> > NetworkModel::mimeDataToBufferList(const QMimeData *mimeData) {
698   QList< QPair<NetworkId, BufferId> > bufferList;
699
700   if(!mimeContainsBufferList(mimeData))
701     return bufferList;
702
703   QStringList rawBufferList = QString::fromAscii(mimeData->data("application/Quassel/BufferItemList")).split(",");
704   NetworkId networkId;
705   BufferId bufferUid;
706   foreach(QString rawBuffer, rawBufferList) {
707     if(!rawBuffer.contains(":"))
708       continue;
709     networkId = rawBuffer.section(":", 0, 0).toInt();
710     bufferUid = rawBuffer.section(":", 1, 1).toInt();
711     bufferList.append(qMakePair(networkId, bufferUid));
712   }
713   return bufferList;
714 }
715
716
717 QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const {
718   QMimeData *mimeData = new QMimeData();
719
720   QStringList bufferlist;
721   QString netid, uid, bufferid;
722   foreach(QModelIndex index, indexes) {
723     netid = QString::number(index.data(NetworkIdRole).value<NetworkId>().toInt());
724     uid = QString::number(index.data(BufferIdRole).value<BufferId>().toInt());
725     bufferid = QString("%1:%2").arg(netid).arg(uid);
726     if(!bufferlist.contains(bufferid))
727       bufferlist << bufferid;
728   }
729
730   mimeData->setData("application/Quassel/BufferItemList", bufferlist.join(",").toAscii());
731
732   return mimeData;
733 }
734
735 bool NetworkModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) {
736   Q_UNUSED(action)
737   Q_UNUSED(row)
738   Q_UNUSED(column)
739
740   if(!mimeContainsBufferList(data))
741     return false;
742
743   // target must be a query
744   BufferInfo::Type targetType = (BufferInfo::Type)parent.data(NetworkModel::BufferTypeRole).toInt();
745   if(targetType != BufferInfo::QueryBuffer)
746     return false;
747
748   QList< QPair<NetworkId, BufferId> > bufferList = mimeDataToBufferList(data);
749
750   // exactly one buffer has to be dropped
751   if(bufferList.count() != 1)
752     return false;
753
754   NetworkId netId = bufferList.first().first;
755   BufferId bufferId = bufferList.first().second;
756
757   // no self merges (would kill us)
758   if(bufferId == parent.data(BufferIdRole).value<BufferId>())
759     return false; 
760   
761   Q_ASSERT(rootItem->childById(qHash(netId)));
762   Q_ASSERT(rootItem->childById(qHash(netId))->childById(qHash(bufferId)));
763
764   // source must be a query too
765   BufferInfo::Type sourceType = (BufferInfo::Type)rootItem->childById(qHash(netId))->childById(qHash(bufferId))->data(0, BufferTypeRole).toInt();
766   if(sourceType != BufferInfo::QueryBuffer)
767     return false;
768     
769   // TODO: warn user about buffermerge!
770   qDebug() << "merging" << bufferId << parent.data(BufferIdRole).value<BufferId>();
771   removeRow(parent.row(), parent.parent());
772   
773   return true;
774 }
775
776 void NetworkModel::attachNetwork(Network *net) {
777   NetworkItem *netItem = networkItem(net->networkId());
778   netItem->attachNetwork(net);
779 }
780
781 void NetworkModel::bufferUpdated(BufferInfo bufferInfo) {
782   BufferItem *bufItem = bufferItem(bufferInfo);
783   QModelIndex itemindex = indexByItem(bufItem);
784   emit dataChanged(itemindex, itemindex);
785 }
786
787 void NetworkModel::removeBuffer(BufferId bufferId) {
788   const int numNetworks = rootItem->childCount();
789   if(numNetworks == 0)
790     return;
791
792   for(int i = 0; i < numNetworks; i++) {
793     if(rootItem->child(i)->removeChildById(qHash(bufferId)))
794       break;
795   }
796 }
797
798 /*
799 void NetworkModel::updateBufferActivity(const Message &msg) {
800   BufferItem *buff = bufferItem(msg.bufferInfo());
801   Q_ASSERT(buff);
802
803   buff->setLastMsgInsert(msg.timestamp());
804
805   if(buff->lastSeen() >= msg.timestamp())
806     return;
807
808   BufferItem::ActivityLevel level = BufferItem::OtherActivity;
809   if(msg.type() == Message::Plain || msg.type() == Message::Notice)
810     level |= BufferItem::NewMessage;
811
812   if(msg.flags() & Message::Highlight) 
813       level |= BufferItem::Highlight;
814
815   bufferItem(msg.bufferInfo())->updateActivity(level);
816 }
817 */
818
819 void NetworkModel::setBufferActivity(const BufferInfo &info, Buffer::ActivityLevel level) {
820   BufferItem *buff = bufferItem(info);
821   Q_ASSERT(buff);
822
823   buff->setActivityLevel(level);
824 }
825
826 const Network *NetworkModel::networkByIndex(const QModelIndex &index) const {
827   QVariant netVariant = index.data(NetworkIdRole);
828   if(!netVariant.isValid())
829     return 0;
830
831   NetworkId networkId = netVariant.value<NetworkId>();
832   return Client::network(networkId);
833 }
834