2fad8d652e09a791024cce22293bf26be5eebf9a
[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 Qt::EditRole:
335     return BufferItem::data(column, Qt::DisplayRole);
336   case NetworkModel::IrcUserRole:
337     return QVariant::fromValue<QObject *>(_ircUser);
338   case NetworkModel::UserAwayRole:
339     return (bool)_ircUser ? _ircUser->isAway() : false;
340   default:
341     return BufferItem::data(column, role);
342   }
343 }
344
345 bool QueryBufferItem::setData(int column, const QVariant &value, int role) {
346   if(column != 0)
347     return BufferItem::setData(column, value, role);
348
349   switch(role) {
350   case Qt::EditRole:
351     {
352       QString newName = value.toString();
353       if(!newName.isEmpty()) {
354         Client::renameBuffer(bufferId(), newName);
355         return true;
356       } else {
357         return false;
358       }
359     }
360     break;
361   default:
362     return BufferItem::setData(column, value, role);
363   }
364 }
365
366 void QueryBufferItem::setBufferName(const QString &name) {
367   BufferItem::setBufferName(name);
368   NetworkId netId = data(0, NetworkModel::NetworkIdRole).value<NetworkId>();
369   const Network *net = Client::network(netId);
370   if(net)
371     setIrcUser(net->ircUser(name));
372 }
373
374 QString QueryBufferItem::toolTip(int column) const {
375   // pretty much code duplication of IrcUserItem::toolTip() but inheritance won't solve this...
376   Q_UNUSED(column);
377   QStringList toolTip;
378
379   toolTip.append(tr("<b>Query with %1</b>").arg(bufferName()));
380
381   if(_ircUser) {
382     if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
383     if(_ircUser->isAway()) {
384       toolTip[0].append(QString(" (away%1)").arg(!_ircUser->awayMessage().isEmpty() ? (QString(" ") + _ircUser->awayMessage()) : QString()));
385     }
386     if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
387     if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(_ircUser->nick()).arg(_ircUser->ircOperator()));
388     if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
389     if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
390
391     toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
392
393     if(_ircUser->idleTime().isValid()) {
394       QDateTime now = QDateTime::currentDateTime();
395       QDateTime idle = _ircUser->idleTime();
396       int idleTime = idle.secsTo(now);
397       toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
398     }
399     if(_ircUser->loginTime().isValid()) {
400       toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
401     }
402
403     if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
404   }
405
406   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
407 }
408
409 void QueryBufferItem::setIrcUser(IrcUser *ircUser) {
410   if(_ircUser == ircUser)
411     return;
412
413   if(_ircUser) {
414     disconnect(_ircUser, 0, this, 0);
415   }
416
417   if(ircUser) {
418     connect(ircUser, SIGNAL(quited()), this, SLOT(removeIrcUser()));
419     connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
420   }
421
422   _ircUser = ircUser;
423   emit dataChanged();
424 }
425
426 void QueryBufferItem::removeIrcUser() {
427   _ircUser = 0;
428   emit dataChanged();
429 }
430
431 /*****************************************
432 *  ChannelBufferItem
433 *****************************************/
434 ChannelBufferItem::ChannelBufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
435   : BufferItem(bufferInfo, parent),
436     _ircChannel(0)
437 {
438   const Network *net = Client::network(bufferInfo.networkId());
439   if(!net)
440     return;
441
442   IrcChannel *ircChannel = net->ircChannel(bufferInfo.bufferName());
443   if(ircChannel)
444     attachIrcChannel(ircChannel);
445 }
446
447 QVariant ChannelBufferItem::data(int column, int role) const {
448   switch(role) {
449     case NetworkModel::IrcChannelRole:
450       return QVariant::fromValue<QObject *>(_ircChannel);
451     default:
452       return BufferItem::data(column, role);
453   }
454 }
455
456 QString ChannelBufferItem::toolTip(int column) const {
457   Q_UNUSED(column);
458   QStringList toolTip;
459
460   toolTip.append(tr("<b>Channel %1</b>").arg(bufferName()));
461   if(isActive()) {
462     //TODO: add channel modes
463     toolTip.append(tr("<b>Users:</b> %1").arg(nickCount()));
464     if(_ircChannel) {
465       QString channelMode = _ircChannel->channelModeString(); // channelModeString is compiled on the fly -> thus cache the result
466       if(!channelMode.isEmpty())
467         toolTip.append(tr("<b>Mode:</b> %1").arg(channelMode));
468     }
469
470     BufferSettings s;
471     bool showTopic = s.value("DisplayTopicInTooltip", QVariant(false)).toBool();
472     if(showTopic) {
473       QString _topic = topic();
474       if(_topic != "") {
475         _topic = stripFormatCodes(_topic);
476         _topic.replace(QString("<"), QString("&lt;"));
477         _topic.replace(QString(">"), QString("&gt;"));
478         toolTip.append(QString("<font size='-2'>&nbsp;</font>"));
479         toolTip.append(tr("<b>Topic:</b> %1").arg(_topic));
480       }
481     }
482   } else {
483     toolTip.append(tr("Not active <br /> Double-click to join"));
484   }
485
486   return tr("<p> %1 </p>").arg(toolTip.join("<br />"));
487 }
488
489 void ChannelBufferItem::attachIrcChannel(IrcChannel *ircChannel) {
490   Q_ASSERT(!_ircChannel && ircChannel);
491
492   _ircChannel = ircChannel;
493
494   connect(ircChannel, SIGNAL(topicSet(QString)),
495           this, SLOT(setTopic(QString)));
496   connect(ircChannel, SIGNAL(ircUsersJoined(QList<IrcUser *>)),
497           this, SLOT(join(QList<IrcUser *>)));
498   connect(ircChannel, SIGNAL(ircUserParted(IrcUser *)),
499           this, SLOT(part(IrcUser *)));
500   connect(ircChannel, SIGNAL(parted()),
501           this, SLOT(ircChannelParted()));
502   connect(ircChannel, SIGNAL(ircUserModesSet(IrcUser *, QString)),
503           this, SLOT(userModeChanged(IrcUser *)));
504   connect(ircChannel, SIGNAL(ircUserModeAdded(IrcUser *, QString)),
505           this, SLOT(userModeChanged(IrcUser *)));
506   connect(ircChannel, SIGNAL(ircUserModeRemoved(IrcUser *, QString)),
507           this, SLOT(userModeChanged(IrcUser *)));
508
509   if(!ircChannel->ircUsers().isEmpty())
510     join(ircChannel->ircUsers());
511
512   emit dataChanged();
513 }
514
515 void ChannelBufferItem::ircChannelParted() {
516   Q_CHECK_PTR(_ircChannel);
517   disconnect(_ircChannel, 0, this, 0);
518   _ircChannel = 0;
519   emit dataChanged();
520   removeAllChilds();
521 }
522
523 void ChannelBufferItem::join(const QList<IrcUser *> &ircUsers) {
524   addUsersToCategory(ircUsers);
525   emit dataChanged(2);
526 }
527
528 UserCategoryItem *ChannelBufferItem::findCategoryItem(int categoryId) {
529   UserCategoryItem *categoryItem = 0;
530
531   for(int i = 0; i < childCount(); i++) {
532     categoryItem = qobject_cast<UserCategoryItem *>(child(i));
533     if(!categoryItem)
534       continue;
535     if(categoryItem->categoryId() == categoryId)
536       return categoryItem;
537   }
538   return 0;
539 }
540
541 void ChannelBufferItem::addUserToCategory(IrcUser *ircUser) {
542   addUsersToCategory(QList<IrcUser *>() << ircUser);
543 }
544
545 void ChannelBufferItem::addUsersToCategory(const QList<IrcUser *> &ircUsers) {
546   Q_ASSERT(_ircChannel);
547
548   QHash<UserCategoryItem *, QList<IrcUser *> > categories;
549
550   int categoryId = -1;
551   UserCategoryItem *categoryItem = 0;
552
553   foreach(IrcUser *ircUser, ircUsers) {
554     categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
555     categoryItem = findCategoryItem(categoryId);
556     if(!categoryItem) {
557       categoryItem = new UserCategoryItem(categoryId, this);
558       categories[categoryItem] = QList<IrcUser *>();
559       newChild(categoryItem);
560     }
561     categories[categoryItem] << ircUser;
562   }
563
564   QHash<UserCategoryItem *, QList<IrcUser *> >::const_iterator catIter = categories.constBegin();
565   while(catIter != categories.constEnd()) {
566     catIter.key()->addUsers(catIter.value());
567     catIter++;
568   }
569 }
570
571 void ChannelBufferItem::part(IrcUser *ircUser) {
572   if(!ircUser) {
573     qWarning() << bufferName() << "ChannelBufferItem::part(): unknown User" << ircUser;
574     return;
575   }
576
577   disconnect(ircUser, 0, this, 0);
578   removeUserFromCategory(ircUser);
579   emit dataChanged(2);
580 }
581
582 void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser) {
583   if(!_ircChannel) {
584     // If we parted the channel there might still be some ircUsers connected.
585     // in that case we just ignore the call
586     Q_ASSERT(childCount() == 0);
587     return;
588   }
589
590   UserCategoryItem *categoryItem = 0;
591   for(int i = 0; i < childCount(); i++) {
592     categoryItem = qobject_cast<UserCategoryItem *>(child(i));
593     if(categoryItem->removeUser(ircUser)) {
594       if(categoryItem->childCount() == 0)
595         removeChild(i);
596       break;
597     }
598   }
599 }
600
601 void ChannelBufferItem::userModeChanged(IrcUser *ircUser) {
602   Q_ASSERT(_ircChannel);
603
604   int categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
605   UserCategoryItem *categoryItem = findCategoryItem(categoryId);
606
607   if(categoryItem) {
608     if(categoryItem->findIrcUser(ircUser)) {
609       return; // already in the right category;
610     }
611   } else {
612     categoryItem = new UserCategoryItem(categoryId, this);
613     newChild(categoryItem);
614   }
615
616   // find the item that needs reparenting
617   IrcUserItem *ircUserItem = 0;
618   for(int i = 0; i < childCount(); i++) {
619     UserCategoryItem *oldCategoryItem = qobject_cast<UserCategoryItem *>(child(i));
620     Q_ASSERT(oldCategoryItem);
621     IrcUserItem *userItem = oldCategoryItem->findIrcUser(ircUser);
622     if(userItem) {
623       ircUserItem = userItem;
624       break;
625     }
626   }
627
628   if(!ircUserItem) {
629     qWarning() << "ChannelBufferItem::userModeChanged(IrcUser *): unable to determine old category of" << ircUser;
630     return;
631   }
632   ircUserItem->reParent(categoryItem);
633 }
634
635 /*****************************************
636 *  User Category Items (like @vh etc.)
637 *****************************************/
638 // we hardcode this even though we have PREFIX in network... but that wouldn't help with mapping modes to
639 // category strings anyway.
640 const QList<QChar> UserCategoryItem::categories = QList<QChar>() << 'q' << 'a' << 'o' << 'h' << 'v';
641
642 UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent)
643   : PropertyMapItem(QStringList() << "categoryName", parent),
644     _category(category)
645 {
646   setFlags(Qt::ItemIsEnabled);
647   setTreeItemFlags(AbstractTreeItem::DeleteOnLastChildRemoved);
648   setObjectName(parent->data(0, Qt::DisplayRole).toString() + "/" + QString::number(category));
649 }
650
651 // caching this makes no sense, since we display the user number dynamically
652 QString UserCategoryItem::categoryName() const {
653   int n = childCount();
654   switch(_category) {
655     case 0: return tr("%n Owner(s)", 0, n);
656     case 1: return tr("%n Admin(s)", 0, n);
657     case 2: return tr("%n Operator(s)", 0, n);
658     case 3: return tr("%n Half-Op(s)", 0, n);
659     case 4: return tr("%n Voiced", 0, n);
660     default: return tr("%n User(s)", 0, n);
661   }
662 }
663
664 IrcUserItem *UserCategoryItem::findIrcUser(IrcUser *ircUser) {
665   IrcUserItem *userItem = 0;
666
667   for(int i = 0; i < childCount(); i++) {
668     userItem = qobject_cast<IrcUserItem *>(child(i));
669     if(!userItem)
670       continue;
671     if(userItem->ircUser() == ircUser)
672       return userItem;
673   }
674   return 0;
675 }
676
677 void UserCategoryItem::addUsers(const QList<IrcUser *> &ircUsers) {
678   QList<AbstractTreeItem *> userItems;
679   foreach(IrcUser *ircUser, ircUsers)
680     userItems << new IrcUserItem(ircUser, this);
681   newChilds(userItems);
682   emit dataChanged(0);
683 }
684
685 bool UserCategoryItem::removeUser(IrcUser *ircUser) {
686   IrcUserItem *userItem = findIrcUser(ircUser);
687   bool success = (bool)userItem;
688   if(success) {
689     removeChild(userItem);
690     emit dataChanged(0);
691   }
692   return success;
693 }
694
695 int UserCategoryItem::categoryFromModes(const QString &modes) {
696   for(int i = 0; i < categories.count(); i++) {
697     if(modes.contains(categories[i]))
698       return i;
699   }
700   return categories.count();
701 }
702
703 QVariant UserCategoryItem::data(int column, int role) const {
704   switch(role) {
705   case TreeModel::SortRole:
706     return _category;
707   case NetworkModel::ItemActiveRole:
708     return true;
709   case NetworkModel::ItemTypeRole:
710     return NetworkModel::UserCategoryItemType;
711   case NetworkModel::BufferIdRole:
712     return parent()->data(column, role);
713   case NetworkModel::NetworkIdRole:
714     return parent()->data(column, role);
715   case NetworkModel::BufferInfoRole:
716     return parent()->data(column, role);
717   default:
718     return PropertyMapItem::data(column, role);
719   }
720 }
721
722
723 /*****************************************
724 *  Irc User Items
725 *****************************************/
726 IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent)
727   : PropertyMapItem(QStringList() << "nickName", parent),
728     _ircUser(ircUser)
729 {
730   setObjectName(ircUser->nick());
731   connect(ircUser, SIGNAL(quited()), this, SLOT(ircUserQuited()));
732   connect(ircUser, SIGNAL(nickSet(QString)), this, SIGNAL(dataChanged()));
733   connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
734 }
735
736 QVariant IrcUserItem::data(int column, int role) const {
737   switch(role) {
738   case NetworkModel::ItemActiveRole:
739     return isActive();
740   case NetworkModel::ItemTypeRole:
741     return NetworkModel::IrcUserItemType;
742   case NetworkModel::BufferIdRole:
743     return parent()->data(column, role);
744   case NetworkModel::NetworkIdRole:
745     return parent()->data(column, role);
746   case NetworkModel::BufferInfoRole:
747     return parent()->data(column, role);
748   case NetworkModel::IrcChannelRole:
749     return parent()->data(column, role);
750   case NetworkModel::IrcUserRole:
751     return QVariant::fromValue<QObject *>(_ircUser.data());
752   case NetworkModel::UserAwayRole:
753     return (bool)_ircUser ? _ircUser->isAway() : false;
754   default:
755     return PropertyMapItem::data(column, role);
756   }
757 }
758
759 QString IrcUserItem::toolTip(int column) const {
760   Q_UNUSED(column);
761   QStringList toolTip(QString("<b>%1</b>").arg(nickName()));
762   if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
763   if(_ircUser->isAway()) {
764     toolTip[0].append(" is away");
765     if(!_ircUser->awayMessage().isEmpty())
766       toolTip[0].append(QString(" (%1)").arg(_ircUser->awayMessage()));
767   }
768   if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
769   if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(nickName()).arg(_ircUser->ircOperator()));
770   if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
771   if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
772
773   toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
774
775   if(_ircUser->idleTime().isValid()) {
776     QDateTime now = QDateTime::currentDateTime();
777     QDateTime idle = _ircUser->idleTime();
778     int idleTime = idle.secsTo(now);
779     toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
780   }
781   if(_ircUser->loginTime().isValid()) {
782     toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
783   }
784
785   if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
786
787   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
788 }
789
790 /*****************************************
791  * NetworkModel
792  *****************************************/
793 NetworkModel::NetworkModel(QObject *parent)
794   : TreeModel(NetworkModel::defaultHeader(), parent)
795 {
796   connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
797           this, SLOT(checkForNewBuffers(const QModelIndex &, int, int)));
798   connect(this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
799           this, SLOT(checkForRemovedBuffers(const QModelIndex &, int, int)));
800 }
801
802 QList<QVariant >NetworkModel::defaultHeader() {
803   QList<QVariant> data;
804   data << tr("Buffer") << tr("Topic") << tr("Nick Count");
805   return data;
806 }
807
808 bool NetworkModel::isBufferIndex(const QModelIndex &index) const {
809   return index.data(NetworkModel::ItemTypeRole) == NetworkModel::BufferItemType;
810 }
811
812 int NetworkModel::networkRow(NetworkId networkId) const {
813   NetworkItem *netItem = 0;
814   for(int i = 0; i < rootItem->childCount(); i++) {
815     netItem = qobject_cast<NetworkItem *>(rootItem->child(i));
816     if(!netItem)
817       continue;
818     if(netItem->networkId() == networkId)
819       return i;
820   }
821   return -1;
822 }
823
824 QModelIndex NetworkModel::networkIndex(NetworkId networkId) {
825   int netRow = networkRow(networkId);
826   if(netRow == -1)
827     return QModelIndex();
828   else
829     return indexByItem(qobject_cast<NetworkItem *>(rootItem->child(netRow)));
830 }
831
832 NetworkItem *NetworkModel::findNetworkItem(NetworkId networkId) const {
833   int netRow = networkRow(networkId);
834   if(netRow == -1)
835     return 0;
836   else
837     return qobject_cast<NetworkItem *>(rootItem->child(netRow));
838 }
839
840 NetworkItem *NetworkModel::networkItem(NetworkId networkId) {
841   NetworkItem *netItem = findNetworkItem(networkId);
842
843   if(netItem == 0) {
844     netItem = new NetworkItem(networkId, rootItem);
845     rootItem->newChild(netItem);
846   }
847   return netItem;
848 }
849
850 void NetworkModel::networkRemoved(const NetworkId &networkId) {
851   int netRow = networkRow(networkId);
852   if(netRow != -1) {
853     rootItem->removeChild(netRow);
854   }
855 }
856
857 QModelIndex NetworkModel::bufferIndex(BufferId bufferId) {
858   if(!_bufferItemCache.contains(bufferId))
859     return QModelIndex();
860
861   return indexByItem(_bufferItemCache[bufferId]);
862 }
863
864 BufferItem *NetworkModel::findBufferItem(BufferId bufferId) const {
865   if(_bufferItemCache.contains(bufferId))
866     return _bufferItemCache[bufferId];
867   else
868     return 0;
869 }
870
871 BufferItem *NetworkModel::bufferItem(const BufferInfo &bufferInfo) {
872   if(_bufferItemCache.contains(bufferInfo.bufferId()))
873     return _bufferItemCache[bufferInfo.bufferId()];
874
875   NetworkItem *netItem = networkItem(bufferInfo.networkId());
876   return netItem->bufferItem(bufferInfo);
877 }
878
879 QStringList NetworkModel::mimeTypes() const {
880   // mimetypes we accept for drops
881   QStringList types;
882   // comma separated list of colon separated pairs of networkid and bufferid
883   // example: 0:1,0:2,1:4
884   types << "application/Quassel/BufferItemList";
885   return types;
886 }
887
888 bool NetworkModel::mimeContainsBufferList(const QMimeData *mimeData) {
889   return mimeData->hasFormat("application/Quassel/BufferItemList");
890 }
891
892 QList< QPair<NetworkId, BufferId> > NetworkModel::mimeDataToBufferList(const QMimeData *mimeData) {
893   QList< QPair<NetworkId, BufferId> > bufferList;
894
895   if(!mimeContainsBufferList(mimeData))
896     return bufferList;
897
898   QStringList rawBufferList = QString::fromAscii(mimeData->data("application/Quassel/BufferItemList")).split(",");
899   NetworkId networkId;
900   BufferId bufferUid;
901   foreach(QString rawBuffer, rawBufferList) {
902     if(!rawBuffer.contains(":"))
903       continue;
904     networkId = rawBuffer.section(":", 0, 0).toInt();
905     bufferUid = rawBuffer.section(":", 1, 1).toInt();
906     bufferList.append(qMakePair(networkId, bufferUid));
907   }
908   return bufferList;
909 }
910
911
912 QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const {
913   QMimeData *mimeData = new QMimeData();
914
915   QStringList bufferlist;
916   QString netid, uid, bufferid;
917   foreach(QModelIndex index, indexes) {
918     netid = QString::number(index.data(NetworkIdRole).value<NetworkId>().toInt());
919     uid = QString::number(index.data(BufferIdRole).value<BufferId>().toInt());
920     bufferid = QString("%1:%2").arg(netid).arg(uid);
921     if(!bufferlist.contains(bufferid))
922       bufferlist << bufferid;
923   }
924
925   mimeData->setData("application/Quassel/BufferItemList", bufferlist.join(",").toAscii());
926
927   return mimeData;
928 }
929
930 void NetworkModel::attachNetwork(Network *net) {
931   NetworkItem *netItem = networkItem(net->networkId());
932   netItem->attachNetwork(net);
933 }
934
935 void NetworkModel::bufferUpdated(BufferInfo bufferInfo) {
936   BufferItem *bufItem = bufferItem(bufferInfo);
937   QModelIndex itemindex = indexByItem(bufItem);
938   emit dataChanged(itemindex, itemindex);
939 }
940
941 void NetworkModel::removeBuffer(BufferId bufferId) {
942   BufferItem *buffItem = findBufferItem(bufferId);
943   if(!buffItem)
944     return;
945
946   buffItem->parent()->removeChild(buffItem);
947 }
948
949 MsgId NetworkModel::lastSeenMsgId(BufferId bufferId) const {
950   if(!_bufferItemCache.contains(bufferId))
951     return MsgId();
952
953   return _bufferItemCache[bufferId]->lastSeenMsgId();
954 }
955
956 MsgId NetworkModel::lastSeenMarkerMsgId(BufferId bufferId) const {
957   if(!_bufferItemCache.contains(bufferId))
958     return MsgId();
959
960   return _bufferItemCache[bufferId]->lastSeenMarkerMsgId();
961 }
962
963 void NetworkModel::setLastSeenMsgId(const BufferId &bufferId, const MsgId &msgId) {
964   BufferItem *bufferItem = findBufferItem(bufferId);
965   if(!bufferItem) {
966     qDebug() << "NetworkModel::setLastSeenMsgId(): buffer is unknown:" << bufferId;
967     return;
968   }
969   bufferItem->setLastSeenMsgId(msgId);
970 }
971
972 void NetworkModel::updateBufferActivity(const Message &msg) {
973   BufferItem *item = bufferItem(msg.bufferInfo());
974   item->updateActivityLevel(msg);
975   if(item->isCurrentBuffer())
976     emit setLastSeenMsg(item->bufferId(), msg.msgId());
977 }
978
979 void NetworkModel::setBufferActivity(const BufferId &bufferId, BufferInfo::ActivityLevel level) {
980   BufferItem *bufferItem = findBufferItem(bufferId);
981   if(!bufferItem) {
982     qDebug() << "NetworkModel::setBufferActivity(): buffer is unknown:" << bufferId;
983     return;
984   }
985   bufferItem->setActivityLevel(level);
986 }
987
988 void NetworkModel::clearBufferActivity(const BufferId &bufferId) {
989   BufferItem *bufferItem = findBufferItem(bufferId);
990   if(!bufferItem) {
991     qDebug() << "NetworkModel::clearBufferActivity(): buffer is unknown:" << bufferId;
992     return;
993   }
994   bufferItem->clearActivityLevel();
995 }
996
997 const Network *NetworkModel::networkByIndex(const QModelIndex &index) const {
998   QVariant netVariant = index.data(NetworkIdRole);
999   if(!netVariant.isValid())
1000     return 0;
1001
1002   NetworkId networkId = netVariant.value<NetworkId>();
1003   return Client::network(networkId);
1004 }
1005
1006 void NetworkModel::checkForRemovedBuffers(const QModelIndex &parent, int start, int end) {
1007   if(parent.data(ItemTypeRole) != NetworkItemType)
1008     return;
1009
1010   for(int row = start; row <= end; row++) {
1011     _bufferItemCache.remove(parent.child(row, 0).data(BufferIdRole).value<BufferId>());
1012   }
1013 }
1014
1015 void NetworkModel::checkForNewBuffers(const QModelIndex &parent, int start, int end) {
1016   if(parent.data(ItemTypeRole) != NetworkItemType)
1017     return;
1018
1019   for(int row = start; row <= end; row++) {
1020     QModelIndex child = parent.child(row, 0);
1021     _bufferItemCache[child.data(BufferIdRole).value<BufferId>()] = static_cast<BufferItem *>(child.internalPointer());
1022   }
1023 }
1024
1025 QString NetworkModel::bufferName(BufferId bufferId) const {
1026   if(!_bufferItemCache.contains(bufferId))
1027     return QString();
1028
1029   return _bufferItemCache[bufferId]->bufferName();
1030 }
1031
1032 BufferInfo::Type NetworkModel::bufferType(BufferId bufferId) const {
1033   if(!_bufferItemCache.contains(bufferId))
1034     return BufferInfo::InvalidBuffer;
1035
1036   return _bufferItemCache[bufferId]->bufferType();
1037 }
1038
1039 BufferInfo NetworkModel::bufferInfo(BufferId bufferId) const {
1040   if(!_bufferItemCache.contains(bufferId))
1041     return BufferInfo();
1042
1043   return _bufferItemCache[bufferId]->bufferInfo();
1044 }
1045
1046 NetworkId NetworkModel::networkId(BufferId bufferId) const {
1047   if(!_bufferItemCache.contains(bufferId))
1048     return NetworkId();
1049
1050   NetworkItem *netItem = qobject_cast<NetworkItem *>(_bufferItemCache[bufferId]->parent());
1051   if(netItem)
1052     return netItem->networkId();
1053   else
1054     return NetworkId();
1055 }
1056
1057 QString NetworkModel::networkName(BufferId bufferId) const {
1058   if(!_bufferItemCache.contains(bufferId))
1059     return QString();
1060
1061   NetworkItem *netItem = qobject_cast<NetworkItem *>(_bufferItemCache[bufferId]->parent());
1062   if(netItem)
1063     return netItem->networkName();
1064   else
1065     return QString();
1066 }
1067
1068 BufferId NetworkModel::bufferId(NetworkId networkId, const QString &bufferName, Qt::CaseSensitivity cs) const {
1069   const NetworkItem *netItem = findNetworkItem(networkId);
1070   if(!netItem)
1071     return BufferId();
1072
1073   for(int i = 0; i < netItem->childCount(); i++) {
1074     BufferItem *bufferItem = qobject_cast<BufferItem *>(netItem->child(i));
1075     if(bufferItem && !bufferItem->bufferName().compare(bufferName, cs))
1076       return bufferItem->bufferId();
1077   }
1078   return BufferId();
1079 }
1080
1081 void NetworkModel::sortBufferIds(QList<BufferId> &bufferIds) const {
1082   QList<BufferItem *> bufferItems;
1083   foreach(BufferId bufferId, bufferIds) {
1084     if(_bufferItemCache.contains(bufferId))
1085       bufferItems << _bufferItemCache[bufferId];
1086   }
1087
1088   qSort(bufferItems.begin(), bufferItems.end(), bufferItemLessThan);
1089
1090   bufferIds.clear();
1091   foreach(BufferItem *bufferItem, bufferItems) {
1092     bufferIds << bufferItem->bufferId();
1093   }
1094 }
1095
1096 QList<BufferId> NetworkModel::allBufferIdsSorted() const {
1097   QList<BufferId> bufferIds = allBufferIds();
1098   sortBufferIds(bufferIds);
1099   return bufferIds;
1100 }
1101
1102 bool NetworkModel::bufferItemLessThan(const BufferItem *left, const BufferItem *right) {
1103   int leftType = left->bufferType();
1104   int rightType = right->bufferType();
1105
1106   if(leftType != rightType)
1107     return leftType < rightType;
1108   else
1109     return QString::compare(left->bufferName(), right->bufferName(), Qt::CaseInsensitive) < 0;
1110 }
1111