Fixes #510 - Network item shows wrong buffer
[quassel.git] / src / client / networkmodel.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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 "buffermodel.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     _statusBufferItem(0)
42 {
43   // DO NOT EMIT dataChanged() DIRECTLY IN NetworkItem
44   // use networkDataChanged() instead. Otherwise you will end up in a infinite loop
45   // as we "sync" the dataChanged() signals of NetworkItem and StatusBufferItem
46   setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
47   connect(this, SIGNAL(networkDataChanged(int)), this, SIGNAL(dataChanged(int)));
48 }
49
50 QVariant NetworkItem::data(int column, int role) const {
51   switch(role) {
52   case NetworkModel::BufferIdRole:
53   case NetworkModel::BufferInfoRole:
54   case NetworkModel::BufferTypeRole:
55   case NetworkModel::BufferActivityRole:
56     if(_statusBufferItem)
57       return _statusBufferItem->data(column, role);
58     else
59       return QVariant();
60   case NetworkModel::NetworkIdRole:
61     return qVariantFromValue(_networkId);
62   case NetworkModel::ItemTypeRole:
63     return NetworkModel::NetworkItemType;
64   case NetworkModel::ItemActiveRole:
65     return isActive();
66   default:
67     return PropertyMapItem::data(column, role);
68   }
69 }
70
71 BufferItem *NetworkItem::findBufferItem(BufferId bufferId) {
72   BufferItem *bufferItem = 0;
73
74   for(int i = 0; i < childCount(); i++) {
75     bufferItem = qobject_cast<BufferItem *>(child(i));
76     if(!bufferItem)
77       continue;
78     if(bufferItem->bufferId() == bufferId)
79       return bufferItem;
80   }
81   return 0;
82 }
83
84
85 BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo) {
86   BufferItem *bufferItem = findBufferItem(bufferInfo);
87   if(bufferItem)
88     return bufferItem;
89
90   switch(bufferInfo.type()) {
91   case BufferInfo::StatusBuffer:
92     _statusBufferItem = new StatusBufferItem(bufferInfo, this);
93     bufferItem = _statusBufferItem;
94     disconnect(this, SIGNAL(networkDataChanged(int)), this, SIGNAL(dataChanged(int)));
95     connect(this, SIGNAL(networkDataChanged(int)), bufferItem, SIGNAL(dataChanged(int)));
96     connect(bufferItem, SIGNAL(dataChanged(int)), this, SIGNAL(dataChanged(int)));
97     break;
98   case BufferInfo::ChannelBuffer:
99     bufferItem = new ChannelBufferItem(bufferInfo, this);
100     break;
101   case BufferInfo::QueryBuffer:
102     bufferItem = new QueryBufferItem(bufferInfo, this);
103     break;
104   default:
105     bufferItem = new BufferItem(bufferInfo, this);
106   }
107
108   newChild(bufferItem);
109   return bufferItem;
110 }
111
112 void NetworkItem::attachNetwork(Network *network) {
113   if(!network)
114     return;
115
116   _network = network;
117
118   connect(network, SIGNAL(networkNameSet(QString)),
119           this, SLOT(setNetworkName(QString)));
120   connect(network, SIGNAL(currentServerSet(QString)),
121           this, SLOT(setCurrentServer(QString)));
122   connect(network, SIGNAL(ircChannelAdded(IrcChannel *)),
123           this, SLOT(attachIrcChannel(IrcChannel *)));
124   connect(network, SIGNAL(ircUserAdded(IrcUser *)),
125           this, SLOT(attachIrcUser(IrcUser *)));
126   connect(network, SIGNAL(connectedSet(bool)),
127           this, SIGNAL(networkDataChanged()));
128   connect(network, SIGNAL(destroyed()),
129           this, SIGNAL(networkDataChanged()));
130
131   emit networkDataChanged();
132 }
133
134 void NetworkItem::attachIrcChannel(IrcChannel *ircChannel) {
135   ChannelBufferItem *channelItem;
136   for(int i = 0; i < childCount(); i++) {
137     channelItem = qobject_cast<ChannelBufferItem *>(child(i));
138     if(!channelItem)
139       continue;
140
141     if(channelItem->bufferName().toLower() == ircChannel->name().toLower()) {
142       channelItem->attachIrcChannel(ircChannel);
143       return;
144     }
145   }
146 }
147
148 void NetworkItem::attachIrcUser(IrcUser *ircUser) {
149   QueryBufferItem *queryItem = 0;
150   for(int i = 0; i < childCount(); i++) {
151     queryItem = qobject_cast<QueryBufferItem *>(child(i));
152     if(!queryItem)
153       continue;
154
155     if(queryItem->bufferName().toLower() == ircUser->nick().toLower()) {
156       queryItem->setIrcUser(ircUser);
157       break;
158     }
159   }
160 }
161
162 void NetworkItem::setNetworkName(const QString &networkName) {
163   Q_UNUSED(networkName);
164   emit networkDataChanged(0);
165 }
166
167 void NetworkItem::setCurrentServer(const QString &serverName) {
168   Q_UNUSED(serverName);
169   emit networkDataChanged(1);
170 }
171
172
173 QString NetworkItem::toolTip(int column) const {
174   Q_UNUSED(column);
175
176   QStringList toolTip(QString("<b>%1</b>").arg(networkName()));
177   toolTip.append(tr("Server: %1").arg(currentServer()));
178   toolTip.append(tr("Users: %1").arg(nickCount()));
179
180   if(_network) {
181     toolTip.append(tr("Lag: %1 msecs").arg(_network->latency()));
182   }
183
184   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
185 }
186
187 void NetworkItem::onBeginRemoveChilds(int start, int end) {
188   for(int i = start; i <= end; i++) {
189     StatusBufferItem *statusBufferItem = qobject_cast<StatusBufferItem *>(child(i));
190     if(statusBufferItem) {
191       _statusBufferItem = 0;
192       break;
193     }
194   }
195 }
196
197 /*****************************************
198 *  Fancy Buffer Items
199 *****************************************/
200 BufferItem::BufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
201   : PropertyMapItem(QStringList() << "bufferName" << "topic" << "nickCount", parent),
202     _bufferInfo(bufferInfo),
203     _activity(BufferInfo::NoActivity)
204 {
205   setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
206 }
207
208 void BufferItem::setActivityLevel(BufferInfo::ActivityLevel level) {
209   if(_activity != level) {
210     _activity = level;
211     emit dataChanged();
212   }
213 }
214
215 void BufferItem::clearActivityLevel() {
216   _activity = BufferInfo::NoActivity;
217   _lastSeenMarkerMsgId = _lastSeenMsgId;
218   emit dataChanged();
219 }
220
221 void BufferItem::updateActivityLevel(const Message &msg) {
222   if(isCurrentBuffer()) {
223     return;
224   }
225
226   if(msg.flags() & Message::Self)       // don't update activity for our own messages
227     return;
228
229   if(lastSeenMsgId() >= msg.msgId())
230     return;
231
232   BufferInfo::ActivityLevel oldLevel = activityLevel();
233
234   _activity |= BufferInfo::OtherActivity;
235   if(msg.type() & (Message::Plain | Message::Notice | Message::Action))
236     _activity |= BufferInfo::NewMessage;
237
238   if(msg.flags() & Message::Highlight)
239     _activity |= BufferInfo::Highlight;
240
241   if(oldLevel != _activity)
242     emit dataChanged();
243 }
244
245 QVariant BufferItem::data(int column, int role) const {
246   switch(role) {
247   case NetworkModel::ItemTypeRole:
248     return NetworkModel::BufferItemType;
249   case NetworkModel::BufferIdRole:
250     return qVariantFromValue(bufferInfo().bufferId());
251   case NetworkModel::NetworkIdRole:
252     return qVariantFromValue(bufferInfo().networkId());
253   case NetworkModel::BufferInfoRole:
254     return qVariantFromValue(bufferInfo());
255   case NetworkModel::BufferTypeRole:
256     return int(bufferType());
257   case NetworkModel::ItemActiveRole:
258     return isActive();
259   case NetworkModel::BufferActivityRole:
260     return (int)activityLevel();
261   default:
262     return PropertyMapItem::data(column, role);
263   }
264 }
265
266 bool BufferItem::setData(int column, const QVariant &value, int role) {
267   switch(role) {
268   case NetworkModel::BufferActivityRole:
269     setActivityLevel((BufferInfo::ActivityLevel)value.toInt());
270     return true;
271   default:
272     return PropertyMapItem::setData(column, value, role);
273   }
274   return true;
275 }
276
277 void BufferItem::setBufferName(const QString &name) {
278   _bufferInfo = BufferInfo(_bufferInfo.bufferId(), _bufferInfo.networkId(), _bufferInfo.type(), _bufferInfo.groupId(), name);
279   emit dataChanged(0);
280 }
281
282 void BufferItem::setLastSeenMsgId(const MsgId &msgId) {
283   _lastSeenMsgId = msgId;
284   if(!isCurrentBuffer()) {
285     _lastSeenMarkerMsgId = msgId;
286   }
287   setActivityLevel(BufferInfo::NoActivity);
288 }
289
290 bool BufferItem::isCurrentBuffer() const {
291   return _bufferInfo.bufferId() == Client::bufferModel()->currentIndex().data(NetworkModel::BufferIdRole).value<BufferId>();
292 }
293
294 QString BufferItem::toolTip(int column) const {
295   Q_UNUSED(column);
296   return tr("<p> %1 - %2 </p>").arg(bufferInfo().bufferId().toInt()).arg(bufferName());
297 }
298
299 /*****************************************
300 *  StatusBufferItem
301 *****************************************/
302 StatusBufferItem::StatusBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
303   : BufferItem(bufferInfo, parent)
304 {
305 }
306
307 QString StatusBufferItem::toolTip(int column) const {
308   NetworkItem *networkItem = qobject_cast<NetworkItem *>(parent());
309   if(networkItem)
310     return networkItem->toolTip(column);
311   else
312     return QString();
313 }
314
315 /*****************************************
316 *  QueryBufferItem
317 *****************************************/
318 QueryBufferItem::QueryBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
319   : BufferItem(bufferInfo, parent),
320     _ircUser(0)
321 {
322   setFlags(flags() | Qt::ItemIsDropEnabled | Qt::ItemIsEditable);
323
324   const Network *net = Client::network(bufferInfo.networkId());
325   if(!net)
326     return;
327
328   IrcUser *ircUser = net->ircUser(bufferInfo.bufferName());
329   setIrcUser(ircUser);
330 }
331
332 QVariant QueryBufferItem::data(int column, int role) const {
333   switch(role) {
334   case NetworkModel::IrcUserRole:
335     return QVariant::fromValue<QObject *>(_ircUser);
336   case NetworkModel::UserAwayRole:
337     return (bool)_ircUser ? _ircUser->isAway() : false;
338   default:
339     return BufferItem::data(column, role);
340   }
341 }
342
343 bool QueryBufferItem::setData(int column, const QVariant &value, int role) {
344   if(column != 0)
345     return BufferItem::setData(column, value, role);
346
347   switch(role) {
348   case Qt::EditRole:
349     {
350       QString newName = value.toString();
351       if(!newName.isEmpty()) {
352         Client::renameBuffer(bufferId(), newName);
353         return true;
354       } else {
355         return false;
356       }
357     }
358     break;
359   default:
360     return BufferItem::setData(column, value, role);
361   }
362 }
363
364 void QueryBufferItem::setBufferName(const QString &name) {
365   BufferItem::setBufferName(name);
366   NetworkId netId = data(0, NetworkModel::NetworkIdRole).value<NetworkId>();
367   const Network *net = Client::network(netId);
368   if(net)
369     setIrcUser(net->ircUser(name));
370 }
371
372 QString QueryBufferItem::toolTip(int column) const {
373   // pretty much code duplication of IrcUserItem::toolTip() but inheritance won't solve this...
374   Q_UNUSED(column);
375   QStringList toolTip;
376
377   toolTip.append(tr("<b>Query with %1</b>").arg(bufferName()));
378
379   if(_ircUser) {
380     if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
381     if(_ircUser->isAway()) {
382       toolTip[0].append(QString(" (away%1)").arg(!_ircUser->awayMessage().isEmpty() ? (QString(" ") + _ircUser->awayMessage()) : QString()));
383     }
384     if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
385     if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(_ircUser->nick()).arg(_ircUser->ircOperator()));
386     if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
387     if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
388
389     toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
390
391     if(_ircUser->idleTime().isValid()) {
392       QDateTime now = QDateTime::currentDateTime();
393       QDateTime idle = _ircUser->idleTime();
394       int idleTime = idle.secsTo(now);
395       toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
396     }
397     if(_ircUser->loginTime().isValid()) {
398       toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
399     }
400
401     if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
402   }
403
404   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
405 }
406
407 void QueryBufferItem::setIrcUser(IrcUser *ircUser) {
408   if(_ircUser == ircUser)
409     return;
410
411   if(_ircUser) {
412     disconnect(_ircUser, 0, this, 0);
413   }
414
415   if(ircUser) {
416     connect(ircUser, SIGNAL(quited()), this, SLOT(removeIrcUser()));
417     connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
418   }
419
420   _ircUser = ircUser;
421   emit dataChanged();
422 }
423
424 void QueryBufferItem::removeIrcUser() {
425   _ircUser = 0;
426   emit dataChanged();
427 }
428
429 /*****************************************
430 *  ChannelBufferItem
431 *****************************************/
432 ChannelBufferItem::ChannelBufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
433   : BufferItem(bufferInfo, parent),
434     _ircChannel(0)
435 {
436   const Network *net = Client::network(bufferInfo.networkId());
437   if(!net)
438     return;
439
440   IrcChannel *ircChannel = net->ircChannel(bufferInfo.bufferName());
441   if(ircChannel)
442     attachIrcChannel(ircChannel);
443 }
444
445 QVariant ChannelBufferItem::data(int column, int role) const {
446   switch(role) {
447     case NetworkModel::IrcChannelRole:
448       return QVariant::fromValue<QObject *>(_ircChannel);
449     default:
450       return BufferItem::data(column, role);
451   }
452 }
453
454 QString ChannelBufferItem::toolTip(int column) const {
455   Q_UNUSED(column);
456   QStringList toolTip;
457
458   toolTip.append(tr("<b>Channel %1</b>").arg(bufferName()));
459   if(isActive()) {
460     //TODO: add channel modes
461     toolTip.append(tr("<b>Users:</b> %1").arg(nickCount()));
462     if(_ircChannel) {
463       QString channelMode = _ircChannel->channelModeString(); // channelModeString is compiled on the fly -> thus cache the result
464       if(!channelMode.isEmpty())
465         toolTip.append(tr("<b>Mode:</b> %1").arg(channelMode));
466     }
467
468     BufferSettings s;
469     bool showTopic = s.value("DisplayTopicInTooltip", QVariant(false)).toBool();
470     if(showTopic) {
471       QString _topic = topic();
472       if(_topic != "") {
473         _topic = stripFormatCodes(_topic);
474         _topic.replace(QString("<"), QString("&lt;"));
475         _topic.replace(QString(">"), QString("&gt;"));
476         toolTip.append(QString("<font size='-2'>&nbsp;</font>"));
477         toolTip.append(tr("<b>Topic:</b> %1").arg(_topic));
478       }
479     }
480   } else {
481     toolTip.append(tr("Not active <br /> Double-click to join"));
482   }
483
484   return tr("<p> %1 </p>").arg(toolTip.join("<br />"));
485 }
486
487 void ChannelBufferItem::attachIrcChannel(IrcChannel *ircChannel) {
488   Q_ASSERT(!_ircChannel && ircChannel);
489
490   _ircChannel = ircChannel;
491
492   connect(ircChannel, SIGNAL(topicSet(QString)),
493           this, SLOT(setTopic(QString)));
494   connect(ircChannel, SIGNAL(ircUsersJoined(QList<IrcUser *>)),
495           this, SLOT(join(QList<IrcUser *>)));
496   connect(ircChannel, SIGNAL(ircUserParted(IrcUser *)),
497           this, SLOT(part(IrcUser *)));
498   connect(ircChannel, SIGNAL(parted()),
499           this, SLOT(ircChannelParted()));
500   connect(ircChannel, SIGNAL(ircUserModesSet(IrcUser *, QString)),
501           this, SLOT(userModeChanged(IrcUser *)));
502   connect(ircChannel, SIGNAL(ircUserModeAdded(IrcUser *, QString)),
503           this, SLOT(userModeChanged(IrcUser *)));
504   connect(ircChannel, SIGNAL(ircUserModeRemoved(IrcUser *, QString)),
505           this, SLOT(userModeChanged(IrcUser *)));
506
507   if(!ircChannel->ircUsers().isEmpty())
508     join(ircChannel->ircUsers());
509
510   emit dataChanged();
511 }
512
513 void ChannelBufferItem::ircChannelParted() {
514   Q_CHECK_PTR(_ircChannel);
515   disconnect(_ircChannel, 0, this, 0);
516   _ircChannel = 0;
517   emit dataChanged();
518   removeAllChilds();
519 }
520
521 void ChannelBufferItem::join(const QList<IrcUser *> &ircUsers) {
522   addUsersToCategory(ircUsers);
523   emit dataChanged(2);
524 }
525
526 UserCategoryItem *ChannelBufferItem::findCategoryItem(int categoryId) {
527   UserCategoryItem *categoryItem = 0;
528
529   for(int i = 0; i < childCount(); i++) {
530     categoryItem = qobject_cast<UserCategoryItem *>(child(i));
531     if(!categoryItem)
532       continue;
533     if(categoryItem->categoryId() == categoryId)
534       return categoryItem;
535   }
536   return 0;
537 }
538
539 void ChannelBufferItem::addUserToCategory(IrcUser *ircUser) {
540   addUsersToCategory(QList<IrcUser *>() << ircUser);
541 }
542
543 void ChannelBufferItem::addUsersToCategory(const QList<IrcUser *> &ircUsers) {
544   Q_ASSERT(_ircChannel);
545
546   QHash<UserCategoryItem *, QList<IrcUser *> > categories;
547
548   int categoryId = -1;
549   UserCategoryItem *categoryItem = 0;
550
551   foreach(IrcUser *ircUser, ircUsers) {
552     categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
553     categoryItem = findCategoryItem(categoryId);
554     if(!categoryItem) {
555       categoryItem = new UserCategoryItem(categoryId, this);
556       categories[categoryItem] = QList<IrcUser *>();
557       newChild(categoryItem);
558     }
559     categories[categoryItem] << ircUser;
560   }
561
562   QHash<UserCategoryItem *, QList<IrcUser *> >::const_iterator catIter = categories.constBegin();
563   while(catIter != categories.constEnd()) {
564     catIter.key()->addUsers(catIter.value());
565     catIter++;
566   }
567 }
568
569 void ChannelBufferItem::part(IrcUser *ircUser) {
570   if(!ircUser) {
571     qWarning() << bufferName() << "ChannelBufferItem::part(): unknown User" << ircUser;
572     return;
573   }
574
575   disconnect(ircUser, 0, this, 0);
576   removeUserFromCategory(ircUser);
577   emit dataChanged(2);
578 }
579
580 void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser) {
581   if(!_ircChannel) {
582     // If we parted the channel there might still be some ircUsers connected.
583     // in that case we just ignore the call
584     Q_ASSERT(childCount() == 0);
585     return;
586   }
587
588   UserCategoryItem *categoryItem = 0;
589   for(int i = 0; i < childCount(); i++) {
590     categoryItem = qobject_cast<UserCategoryItem *>(child(i));
591     if(categoryItem->removeUser(ircUser)) {
592       if(categoryItem->childCount() == 0)
593         removeChild(i);
594       break;
595     }
596   }
597 }
598
599 void ChannelBufferItem::userModeChanged(IrcUser *ircUser) {
600   Q_ASSERT(_ircChannel);
601
602   int categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
603   UserCategoryItem *categoryItem = findCategoryItem(categoryId);
604
605   if(categoryItem) {
606     if(categoryItem->findIrcUser(ircUser)) {
607       return; // already in the right category;
608     }
609   } else {
610     categoryItem = new UserCategoryItem(categoryId, this);
611     newChild(categoryItem);
612   }
613
614   // find the item that needs reparenting
615   IrcUserItem *ircUserItem = 0;
616   for(int i = 0; i < childCount(); i++) {
617     UserCategoryItem *oldCategoryItem = qobject_cast<UserCategoryItem *>(child(i));
618     Q_ASSERT(oldCategoryItem);
619     IrcUserItem *userItem = oldCategoryItem->findIrcUser(ircUser);
620     if(userItem) {
621       ircUserItem = userItem;
622       break;
623     }
624   }
625
626   if(!ircUserItem) {
627     qWarning() << "ChannelBufferItem::userModeChanged(IrcUser *): unable to determine old category of" << ircUser;
628     return;
629   }
630   ircUserItem->reParent(categoryItem);
631 }
632
633 /*****************************************
634 *  User Category Items (like @vh etc.)
635 *****************************************/
636 // we hardcode this even though we have PREFIX in network... but that wouldn't help with mapping modes to
637 // category strings anyway.
638 const QList<QChar> UserCategoryItem::categories = QList<QChar>() << 'q' << 'a' << 'o' << 'h' << 'v';
639
640 UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent)
641   : PropertyMapItem(QStringList() << "categoryName", parent),
642     _category(category)
643 {
644   setFlags(Qt::ItemIsEnabled);
645   setTreeItemFlags(AbstractTreeItem::DeleteOnLastChildRemoved);
646   setObjectName(parent->data(0, Qt::DisplayRole).toString() + "/" + QString::number(category));
647 }
648
649 // caching this makes no sense, since we display the user number dynamically
650 QString UserCategoryItem::categoryName() const {
651   int n = childCount();
652   switch(_category) {
653     case 0: return tr("%n Owner(s)", 0, n);
654     case 1: return tr("%n Admin(s)", 0, n);
655     case 2: return tr("%n Operator(s)", 0, n);
656     case 3: return tr("%n Half-Op(s)", 0, n);
657     case 4: return tr("%n Voiced", 0, n);
658     default: return tr("%n User(s)", 0, n);
659   }
660 }
661
662 IrcUserItem *UserCategoryItem::findIrcUser(IrcUser *ircUser) {
663   IrcUserItem *userItem = 0;
664
665   for(int i = 0; i < childCount(); i++) {
666     userItem = qobject_cast<IrcUserItem *>(child(i));
667     if(!userItem)
668       continue;
669     if(userItem->ircUser() == ircUser)
670       return userItem;
671   }
672   return 0;
673 }
674
675 void UserCategoryItem::addUsers(const QList<IrcUser *> &ircUsers) {
676   QList<AbstractTreeItem *> userItems;
677   foreach(IrcUser *ircUser, ircUsers)
678     userItems << new IrcUserItem(ircUser, this);
679   newChilds(userItems);
680   emit dataChanged(0);
681 }
682
683 bool UserCategoryItem::removeUser(IrcUser *ircUser) {
684   IrcUserItem *userItem = findIrcUser(ircUser);
685   bool success = (bool)userItem;
686   if(success) {
687     removeChild(userItem);
688     emit dataChanged(0);
689   }
690   return success;
691 }
692
693 int UserCategoryItem::categoryFromModes(const QString &modes) {
694   for(int i = 0; i < categories.count(); i++) {
695     if(modes.contains(categories[i]))
696       return i;
697   }
698   return categories.count();
699 }
700
701 QVariant UserCategoryItem::data(int column, int role) const {
702   switch(role) {
703   case TreeModel::SortRole:
704     return _category;
705   case NetworkModel::ItemActiveRole:
706     return true;
707   case NetworkModel::ItemTypeRole:
708     return NetworkModel::UserCategoryItemType;
709   case NetworkModel::BufferIdRole:
710     return parent()->data(column, role);
711   case NetworkModel::NetworkIdRole:
712     return parent()->data(column, role);
713   case NetworkModel::BufferInfoRole:
714     return parent()->data(column, role);
715   default:
716     return PropertyMapItem::data(column, role);
717   }
718 }
719
720
721 /*****************************************
722 *  Irc User Items
723 *****************************************/
724 IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent)
725   : PropertyMapItem(QStringList() << "nickName", parent),
726     _ircUser(ircUser)
727 {
728   setObjectName(ircUser->nick());
729   connect(ircUser, SIGNAL(quited()), this, SLOT(ircUserQuited()));
730   connect(ircUser, SIGNAL(nickSet(QString)), this, SIGNAL(dataChanged()));
731   connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
732 }
733
734 QVariant IrcUserItem::data(int column, int role) const {
735   switch(role) {
736   case NetworkModel::ItemActiveRole:
737     return isActive();
738   case NetworkModel::ItemTypeRole:
739     return NetworkModel::IrcUserItemType;
740   case NetworkModel::BufferIdRole:
741     return parent()->data(column, role);
742   case NetworkModel::NetworkIdRole:
743     return parent()->data(column, role);
744   case NetworkModel::BufferInfoRole:
745     return parent()->data(column, role);
746   case NetworkModel::IrcChannelRole:
747     return parent()->data(column, role);
748   case NetworkModel::IrcUserRole:
749     return QVariant::fromValue<QObject *>(_ircUser.data());
750   case NetworkModel::UserAwayRole:
751     return (bool)_ircUser ? _ircUser->isAway() : false;
752   default:
753     return PropertyMapItem::data(column, role);
754   }
755 }
756
757 QString IrcUserItem::toolTip(int column) const {
758   Q_UNUSED(column);
759   QStringList toolTip(QString("<b>%1</b>").arg(nickName()));
760   if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
761   if(_ircUser->isAway()) {
762     toolTip[0].append(" is away");
763     if(!_ircUser->awayMessage().isEmpty())
764       toolTip[0].append(QString(" (%1)").arg(_ircUser->awayMessage()));
765   }
766   if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
767   if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(nickName()).arg(_ircUser->ircOperator()));
768   if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
769   if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
770
771   toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
772
773   if(_ircUser->idleTime().isValid()) {
774     QDateTime now = QDateTime::currentDateTime();
775     QDateTime idle = _ircUser->idleTime();
776     int idleTime = idle.secsTo(now);
777     toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
778   }
779   if(_ircUser->loginTime().isValid()) {
780     toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
781   }
782
783   if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
784
785   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
786 }
787
788 /*****************************************
789  * NetworkModel
790  *****************************************/
791 NetworkModel::NetworkModel(QObject *parent)
792   : TreeModel(NetworkModel::defaultHeader(), parent)
793 {
794   connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
795           this, SLOT(checkForNewBuffers(const QModelIndex &, int, int)));
796   connect(this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
797           this, SLOT(checkForRemovedBuffers(const QModelIndex &, int, int)));
798 }
799
800 QList<QVariant >NetworkModel::defaultHeader() {
801   QList<QVariant> data;
802   data << tr("Buffer") << tr("Topic") << tr("Nick Count");
803   return data;
804 }
805
806 bool NetworkModel::isBufferIndex(const QModelIndex &index) const {
807   return index.data(NetworkModel::ItemTypeRole) == NetworkModel::BufferItemType;
808 }
809
810 int NetworkModel::networkRow(NetworkId networkId) const {
811   NetworkItem *netItem = 0;
812   for(int i = 0; i < rootItem->childCount(); i++) {
813     netItem = qobject_cast<NetworkItem *>(rootItem->child(i));
814     if(!netItem)
815       continue;
816     if(netItem->networkId() == networkId)
817       return i;
818   }
819   return -1;
820 }
821
822 QModelIndex NetworkModel::networkIndex(NetworkId networkId) {
823   int netRow = networkRow(networkId);
824   if(netRow == -1)
825     return QModelIndex();
826   else
827     return indexByItem(qobject_cast<NetworkItem *>(rootItem->child(netRow)));
828 }
829
830 NetworkItem *NetworkModel::findNetworkItem(NetworkId networkId) const {
831   int netRow = networkRow(networkId);
832   if(netRow == -1)
833     return 0;
834   else
835     return qobject_cast<NetworkItem *>(rootItem->child(netRow));
836 }
837
838 NetworkItem *NetworkModel::networkItem(NetworkId networkId) {
839   NetworkItem *netItem = findNetworkItem(networkId);
840
841   if(netItem == 0) {
842     netItem = new NetworkItem(networkId, rootItem);
843     rootItem->newChild(netItem);
844   }
845   return netItem;
846 }
847
848 void NetworkModel::networkRemoved(const NetworkId &networkId) {
849   int netRow = networkRow(networkId);
850   if(netRow != -1) {
851     rootItem->removeChild(netRow);
852   }
853 }
854
855 QModelIndex NetworkModel::bufferIndex(BufferId bufferId) {
856   if(!_bufferItemCache.contains(bufferId))
857     return QModelIndex();
858
859   return indexByItem(_bufferItemCache[bufferId]);
860 }
861
862 BufferItem *NetworkModel::findBufferItem(BufferId bufferId) const {
863   if(_bufferItemCache.contains(bufferId))
864     return _bufferItemCache[bufferId];
865   else
866     return 0;
867 }
868
869 BufferItem *NetworkModel::bufferItem(const BufferInfo &bufferInfo) {
870   if(_bufferItemCache.contains(bufferInfo.bufferId()))
871     return _bufferItemCache[bufferInfo.bufferId()];
872
873   NetworkItem *netItem = networkItem(bufferInfo.networkId());
874   return netItem->bufferItem(bufferInfo);
875 }
876
877 QStringList NetworkModel::mimeTypes() const {
878   // mimetypes we accept for drops
879   QStringList types;
880   // comma separated list of colon separated pairs of networkid and bufferid
881   // example: 0:1,0:2,1:4
882   types << "application/Quassel/BufferItemList";
883   return types;
884 }
885
886 bool NetworkModel::mimeContainsBufferList(const QMimeData *mimeData) {
887   return mimeData->hasFormat("application/Quassel/BufferItemList");
888 }
889
890 QList< QPair<NetworkId, BufferId> > NetworkModel::mimeDataToBufferList(const QMimeData *mimeData) {
891   QList< QPair<NetworkId, BufferId> > bufferList;
892
893   if(!mimeContainsBufferList(mimeData))
894     return bufferList;
895
896   QStringList rawBufferList = QString::fromAscii(mimeData->data("application/Quassel/BufferItemList")).split(",");
897   NetworkId networkId;
898   BufferId bufferUid;
899   foreach(QString rawBuffer, rawBufferList) {
900     if(!rawBuffer.contains(":"))
901       continue;
902     networkId = rawBuffer.section(":", 0, 0).toInt();
903     bufferUid = rawBuffer.section(":", 1, 1).toInt();
904     bufferList.append(qMakePair(networkId, bufferUid));
905   }
906   return bufferList;
907 }
908
909
910 QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const {
911   QMimeData *mimeData = new QMimeData();
912
913   QStringList bufferlist;
914   QString netid, uid, bufferid;
915   foreach(QModelIndex index, indexes) {
916     netid = QString::number(index.data(NetworkIdRole).value<NetworkId>().toInt());
917     uid = QString::number(index.data(BufferIdRole).value<BufferId>().toInt());
918     bufferid = QString("%1:%2").arg(netid).arg(uid);
919     if(!bufferlist.contains(bufferid))
920       bufferlist << bufferid;
921   }
922
923   mimeData->setData("application/Quassel/BufferItemList", bufferlist.join(",").toAscii());
924
925   return mimeData;
926 }
927
928 void NetworkModel::attachNetwork(Network *net) {
929   NetworkItem *netItem = networkItem(net->networkId());
930   netItem->attachNetwork(net);
931 }
932
933 void NetworkModel::bufferUpdated(BufferInfo bufferInfo) {
934   BufferItem *bufItem = bufferItem(bufferInfo);
935   QModelIndex itemindex = indexByItem(bufItem);
936   emit dataChanged(itemindex, itemindex);
937 }
938
939 void NetworkModel::removeBuffer(BufferId bufferId) {
940   BufferItem *buffItem = findBufferItem(bufferId);
941   if(!buffItem)
942     return;
943
944   buffItem->parent()->removeChild(buffItem);
945 }
946
947 MsgId NetworkModel::lastSeenMsgId(BufferId bufferId) const {
948   if(!_bufferItemCache.contains(bufferId))
949     return MsgId();
950
951   return _bufferItemCache[bufferId]->lastSeenMsgId();
952 }
953
954 MsgId NetworkModel::lastSeenMarkerMsgId(BufferId bufferId) const {
955   if(!_bufferItemCache.contains(bufferId))
956     return MsgId();
957
958   return _bufferItemCache[bufferId]->lastSeenMarkerMsgId();
959 }
960
961 void NetworkModel::setLastSeenMsgId(const BufferId &bufferId, const MsgId &msgId) {
962   BufferItem *bufferItem = findBufferItem(bufferId);
963   if(!bufferItem) {
964     qDebug() << "NetworkModel::setLastSeenMsgId(): buffer is unknown:" << bufferId;
965     return;
966   }
967   bufferItem->setLastSeenMsgId(msgId);
968 }
969
970 void NetworkModel::updateBufferActivity(const Message &msg) {
971   BufferItem *item = bufferItem(msg.bufferInfo());
972   item->updateActivityLevel(msg);
973   if(item->isCurrentBuffer())
974     emit setLastSeenMsg(item->bufferId(), msg.msgId());
975 }
976
977 void NetworkModel::setBufferActivity(const BufferId &bufferId, BufferInfo::ActivityLevel level) {
978   BufferItem *bufferItem = findBufferItem(bufferId);
979   if(!bufferItem) {
980     qDebug() << "NetworkModel::setBufferActivity(): buffer is unknown:" << bufferId;
981     return;
982   }
983   bufferItem->setActivityLevel(level);
984 }
985
986 void NetworkModel::clearBufferActivity(const BufferId &bufferId) {
987   BufferItem *bufferItem = findBufferItem(bufferId);
988   if(!bufferItem) {
989     qDebug() << "NetworkModel::clearBufferActivity(): buffer is unknown:" << bufferId;
990     return;
991   }
992   bufferItem->clearActivityLevel();
993 }
994
995 const Network *NetworkModel::networkByIndex(const QModelIndex &index) const {
996   QVariant netVariant = index.data(NetworkIdRole);
997   if(!netVariant.isValid())
998     return 0;
999
1000   NetworkId networkId = netVariant.value<NetworkId>();
1001   return Client::network(networkId);
1002 }
1003
1004 void NetworkModel::checkForRemovedBuffers(const QModelIndex &parent, int start, int end) {
1005   if(parent.data(ItemTypeRole) != NetworkItemType)
1006     return;
1007
1008   for(int row = start; row <= end; row++) {
1009     _bufferItemCache.remove(parent.child(row, 0).data(BufferIdRole).value<BufferId>());
1010   }
1011 }
1012
1013 void NetworkModel::checkForNewBuffers(const QModelIndex &parent, int start, int end) {
1014   if(parent.data(ItemTypeRole) != NetworkItemType)
1015     return;
1016
1017   for(int row = start; row <= end; row++) {
1018     QModelIndex child = parent.child(row, 0);
1019     _bufferItemCache[child.data(BufferIdRole).value<BufferId>()] = static_cast<BufferItem *>(child.internalPointer());
1020   }
1021 }
1022
1023 QString NetworkModel::bufferName(BufferId bufferId) const {
1024   if(!_bufferItemCache.contains(bufferId))
1025     return QString();
1026
1027   return _bufferItemCache[bufferId]->bufferName();
1028 }
1029
1030 BufferInfo::Type NetworkModel::bufferType(BufferId bufferId) const {
1031   if(!_bufferItemCache.contains(bufferId))
1032     return BufferInfo::InvalidBuffer;
1033
1034   return _bufferItemCache[bufferId]->bufferType();
1035 }
1036
1037 BufferInfo NetworkModel::bufferInfo(BufferId bufferId) const {
1038   if(!_bufferItemCache.contains(bufferId))
1039     return BufferInfo();
1040
1041   return _bufferItemCache[bufferId]->bufferInfo();
1042 }
1043
1044 NetworkId NetworkModel::networkId(BufferId bufferId) const {
1045   if(!_bufferItemCache.contains(bufferId))
1046     return NetworkId();
1047
1048   NetworkItem *netItem = qobject_cast<NetworkItem *>(_bufferItemCache[bufferId]->parent());
1049   if(netItem)
1050     return netItem->networkId();
1051   else
1052     return NetworkId();
1053 }
1054
1055 QString NetworkModel::networkName(BufferId bufferId) const {
1056   if(!_bufferItemCache.contains(bufferId))
1057     return QString();
1058
1059   NetworkItem *netItem = qobject_cast<NetworkItem *>(_bufferItemCache[bufferId]->parent());
1060   if(netItem)
1061     return netItem->networkName();
1062   else
1063     return QString();
1064 }
1065
1066 BufferId NetworkModel::bufferId(NetworkId networkId, const QString &bufferName, Qt::CaseSensitivity cs) const {
1067   const NetworkItem *netItem = findNetworkItem(networkId);
1068   if(!netItem)
1069     return BufferId();
1070
1071   for(int i = 0; i < netItem->childCount(); i++) {
1072     BufferItem *bufferItem = qobject_cast<BufferItem *>(netItem->child(i));
1073     if(bufferItem && !bufferItem->bufferName().compare(bufferName, cs))
1074       return bufferItem->bufferId();
1075   }
1076   return BufferId();
1077 }
1078
1079 void NetworkModel::sortBufferIds(QList<BufferId> &bufferIds) const {
1080   QList<BufferItem *> bufferItems;
1081   foreach(BufferId bufferId, bufferIds) {
1082     if(_bufferItemCache.contains(bufferId))
1083       bufferItems << _bufferItemCache[bufferId];
1084   }
1085
1086   qSort(bufferItems.begin(), bufferItems.end(), bufferItemLessThan);
1087
1088   bufferIds.clear();
1089   foreach(BufferItem *bufferItem, bufferItems) {
1090     bufferIds << bufferItem->bufferId();
1091   }
1092 }
1093
1094 QList<BufferId> NetworkModel::allBufferIdsSorted() const {
1095   QList<BufferId> bufferIds = allBufferIds();
1096   sortBufferIds(bufferIds);
1097   return bufferIds;
1098 }
1099
1100 bool NetworkModel::bufferItemLessThan(const BufferItem *left, const BufferItem *right) {
1101   int leftType = left->bufferType();
1102   int rightType = right->bufferType();
1103
1104   if(leftType != rightType)
1105     return leftType < rightType;
1106   else
1107     return QString::compare(left->bufferName(), right->bufferName(), Qt::CaseInsensitive) < 0;
1108 }
1109