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