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