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