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