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