0ca781149d137cd3aba4476c92060814f84af8b0
[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 "client.h"
28 #include "signalproxy.h"
29 #include "network.h"
30 #include "ircchannel.h"
31
32 #include "buffersettings.h"
33
34 #include "util.h" // get rid of this (needed for isChannelName)
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 BufferItem *NetworkItem::findBufferItem(BufferId bufferId) {
74   BufferItem *bufferItem = 0;
75
76   for(int i = 0; i < childCount(); i++) {
77     bufferItem = qobject_cast<BufferItem *>(child(i));
78     if(!bufferItem)
79       continue;
80     if(bufferItem->bufferId() == bufferId)
81       return bufferItem;
82   }
83   return 0;
84 }
85
86
87 BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo) {
88   BufferItem *bufferItem = findBufferItem(bufferInfo);
89   if(bufferItem)
90     return bufferItem;
91
92   switch(bufferInfo.type()) {
93   case BufferInfo::StatusBuffer:
94     _statusBufferItem = new StatusBufferItem(bufferInfo, this);
95     bufferItem = _statusBufferItem;
96     disconnect(this, SIGNAL(networkDataChanged(int)), this, SIGNAL(dataChanged(int)));
97     connect(this, SIGNAL(networkDataChanged(int)), bufferItem, SIGNAL(dataChanged(int)));
98     connect(bufferItem, SIGNAL(dataChanged(int)), this, SIGNAL(dataChanged(int)));
99     break;
100   case BufferInfo::ChannelBuffer:
101     bufferItem = new ChannelBufferItem(bufferInfo, this);
102     break;
103   case BufferInfo::QueryBuffer:
104     bufferItem = new QueryBufferItem(bufferInfo, this);
105     break;
106   default:
107     bufferItem = new BufferItem(bufferInfo, this);
108   }
109
110   newChild(bufferItem);
111
112   // postprocess... this is necessary because Qt doesn't seem to like adding childs which already have childs on their own
113   switch(bufferInfo.type()) {
114   case BufferInfo::ChannelBuffer:
115     {
116       ChannelBufferItem *channelBufferItem = static_cast<ChannelBufferItem *>(bufferItem);
117       if(_network) {
118         IrcChannel *ircChannel = _network->ircChannel(bufferInfo.bufferName());
119         if(ircChannel)
120           channelBufferItem->attachIrcChannel(ircChannel);
121       }
122     }
123     break;
124   default:
125     break;
126   }
127
128   return bufferItem;
129 }
130
131 void NetworkItem::attachNetwork(Network *network) {
132   if(!network)
133     return;
134
135   _network = network;
136
137   connect(network, SIGNAL(networkNameSet(QString)),
138           this, SLOT(setNetworkName(QString)));
139   connect(network, SIGNAL(currentServerSet(QString)),
140           this, SLOT(setCurrentServer(QString)));
141   connect(network, SIGNAL(ircChannelAdded(IrcChannel *)),
142           this, SLOT(attachIrcChannel(IrcChannel *)));
143   connect(network, SIGNAL(ircUserAdded(IrcUser *)),
144           this, SLOT(attachIrcUser(IrcUser *)));
145   connect(network, SIGNAL(connectedSet(bool)),
146           this, SIGNAL(networkDataChanged()));
147   connect(network, SIGNAL(destroyed()),
148           this, SIGNAL(networkDataChanged()));
149
150   emit networkDataChanged();
151 }
152
153 void NetworkItem::attachIrcChannel(IrcChannel *ircChannel) {
154   ChannelBufferItem *channelItem;
155   for(int i = 0; i < childCount(); i++) {
156     channelItem = qobject_cast<ChannelBufferItem *>(child(i));
157     if(!channelItem)
158       continue;
159
160     if(channelItem->bufferName().toLower() == ircChannel->name().toLower()) {
161       channelItem->attachIrcChannel(ircChannel);
162       return;
163     }
164   }
165 }
166
167 void NetworkItem::attachIrcUser(IrcUser *ircUser) {
168   QueryBufferItem *queryItem = 0;
169   for(int i = 0; i < childCount(); i++) {
170     queryItem = qobject_cast<QueryBufferItem *>(child(i));
171     if(!queryItem)
172       continue;
173
174     if(queryItem->bufferName().toLower() == ircUser->nick().toLower()) {
175       queryItem->setIrcUser(ircUser);
176       break;
177     }
178   }
179 }
180
181 void NetworkItem::setNetworkName(const QString &networkName) {
182   Q_UNUSED(networkName);
183   emit networkDataChanged(0);
184 }
185
186 void NetworkItem::setCurrentServer(const QString &serverName) {
187   Q_UNUSED(serverName);
188   emit networkDataChanged(1);
189 }
190
191
192 QString NetworkItem::toolTip(int column) const {
193   Q_UNUSED(column);
194
195   QStringList toolTip(QString("<b>%1</b>").arg(Qt::escape(networkName())));
196   toolTip.append(tr("Server: %1").arg(Qt::escape(currentServer())));
197   toolTip.append(tr("Users: %1").arg(nickCount()));
198
199   if(_network) {
200     toolTip.append(tr("Lag: %1 msecs").arg(_network->latency()));
201   }
202
203   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
204 }
205
206 void NetworkItem::onBeginRemoveChilds(int start, int end) {
207   for(int i = start; i <= end; i++) {
208     StatusBufferItem *statusBufferItem = qobject_cast<StatusBufferItem *>(child(i));
209     if(statusBufferItem) {
210       _statusBufferItem = 0;
211       break;
212     }
213   }
214 }
215
216 /*****************************************
217 *  Fancy Buffer Items
218 *****************************************/
219 BufferItem::BufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
220   : PropertyMapItem(QStringList() << "bufferName" << "topic" << "nickCount", parent),
221     _bufferInfo(bufferInfo),
222     _activity(BufferInfo::NoActivity)
223 {
224   setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
225 }
226
227 void BufferItem::setActivityLevel(BufferInfo::ActivityLevel level) {
228   if(_activity != level) {
229     _activity = level;
230     emit dataChanged();
231   }
232 }
233
234 void BufferItem::clearActivityLevel() {
235   _activity = BufferInfo::NoActivity;
236   _lastSeenMarkerMsgId = _lastSeenMsgId;
237   emit dataChanged();
238 }
239
240 void BufferItem::updateActivityLevel(const Message &msg) {
241   if(isCurrentBuffer()) {
242     return;
243   }
244
245   if(msg.flags() & Message::Self)       // don't update activity for our own messages
246     return;
247
248   if(lastSeenMsgId() >= msg.msgId())
249     return;
250
251   BufferInfo::ActivityLevel oldLevel = activityLevel();
252
253   _activity |= BufferInfo::OtherActivity;
254   if(msg.type() & (Message::Plain | Message::Notice | Message::Action))
255     _activity |= BufferInfo::NewMessage;
256
257   if(msg.flags() & Message::Highlight)
258     _activity |= BufferInfo::Highlight;
259
260   if(oldLevel != _activity)
261     emit dataChanged();
262 }
263
264 QVariant BufferItem::data(int column, int role) const {
265   switch(role) {
266   case NetworkModel::ItemTypeRole:
267     return NetworkModel::BufferItemType;
268   case NetworkModel::BufferIdRole:
269     return qVariantFromValue(bufferInfo().bufferId());
270   case NetworkModel::NetworkIdRole:
271     return qVariantFromValue(bufferInfo().networkId());
272   case NetworkModel::BufferInfoRole:
273     return qVariantFromValue(bufferInfo());
274   case NetworkModel::BufferTypeRole:
275     return int(bufferType());
276   case NetworkModel::ItemActiveRole:
277     return isActive();
278   case NetworkModel::BufferActivityRole:
279     return (int)activityLevel();
280   default:
281     return PropertyMapItem::data(column, role);
282   }
283 }
284
285 bool BufferItem::setData(int column, const QVariant &value, int role) {
286   switch(role) {
287   case NetworkModel::BufferActivityRole:
288     setActivityLevel((BufferInfo::ActivityLevel)value.toInt());
289     return true;
290   default:
291     return PropertyMapItem::setData(column, value, role);
292   }
293   return true;
294 }
295
296 void BufferItem::setBufferName(const QString &name) {
297   _bufferInfo = BufferInfo(_bufferInfo.bufferId(), _bufferInfo.networkId(), _bufferInfo.type(), _bufferInfo.groupId(), name);
298   emit dataChanged(0);
299 }
300
301 void BufferItem::setLastSeenMsgId(const MsgId &msgId) {
302   _lastSeenMsgId = msgId;
303   if(!isCurrentBuffer()) {
304     _lastSeenMarkerMsgId = msgId;
305   }
306   setActivityLevel(BufferInfo::NoActivity);
307 }
308
309 bool BufferItem::isCurrentBuffer() const {
310   return _bufferInfo.bufferId() == Client::bufferModel()->currentIndex().data(NetworkModel::BufferIdRole).value<BufferId>();
311 }
312
313 QString BufferItem::toolTip(int column) const {
314   Q_UNUSED(column);
315   return tr("<p> %1 - %2 </p>").arg(bufferInfo().bufferId().toInt()).arg(bufferName());
316 }
317
318 /*****************************************
319 *  StatusBufferItem
320 *****************************************/
321 StatusBufferItem::StatusBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
322   : BufferItem(bufferInfo, parent)
323 {
324 }
325
326 QString StatusBufferItem::toolTip(int column) const {
327   NetworkItem *networkItem = qobject_cast<NetworkItem *>(parent());
328   if(networkItem)
329     return networkItem->toolTip(column);
330   else
331     return QString();
332 }
333
334 /*****************************************
335 *  QueryBufferItem
336 *****************************************/
337 QueryBufferItem::QueryBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
338   : BufferItem(bufferInfo, parent),
339     _ircUser(0)
340 {
341   setFlags(flags() | Qt::ItemIsDropEnabled | Qt::ItemIsEditable);
342
343   const Network *net = Client::network(bufferInfo.networkId());
344   if(!net)
345     return;
346
347   IrcUser *ircUser = net->ircUser(bufferInfo.bufferName());
348   setIrcUser(ircUser);
349 }
350
351 QVariant QueryBufferItem::data(int column, int role) const {
352   switch(role) {
353   case Qt::EditRole:
354     return BufferItem::data(column, Qt::DisplayRole);
355   case NetworkModel::IrcUserRole:
356     return QVariant::fromValue<QObject *>(_ircUser);
357   case NetworkModel::UserAwayRole:
358     return (bool)_ircUser ? _ircUser->isAway() : false;
359   default:
360     return BufferItem::data(column, role);
361   }
362 }
363
364 bool QueryBufferItem::setData(int column, const QVariant &value, int role) {
365   if(column != 0)
366     return BufferItem::setData(column, value, role);
367
368   switch(role) {
369   case Qt::EditRole:
370     {
371       QString newName = value.toString();
372       if(!newName.isEmpty()) {
373         Client::renameBuffer(bufferId(), newName);
374         return true;
375       } else {
376         return false;
377       }
378     }
379     break;
380   default:
381     return BufferItem::setData(column, value, role);
382   }
383 }
384
385 void QueryBufferItem::setBufferName(const QString &name) {
386   BufferItem::setBufferName(name);
387   NetworkId netId = data(0, NetworkModel::NetworkIdRole).value<NetworkId>();
388   const Network *net = Client::network(netId);
389   if(net)
390     setIrcUser(net->ircUser(name));
391 }
392
393 QString QueryBufferItem::toolTip(int column) const {
394   // pretty much code duplication of IrcUserItem::toolTip() but inheritance won't solve this...
395   Q_UNUSED(column);
396   QStringList toolTip;
397
398   toolTip.append(tr("<b>Query with %1</b>").arg(bufferName()));
399
400   if(_ircUser) {
401     if(_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
402     if(_ircUser->isAway()) {
403       toolTip[0].append(QString(" (away%1)").arg(!_ircUser->awayMessage().isEmpty() ? (QString(" ") + _ircUser->awayMessage()) : QString()));
404     }
405     if(!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
406     if(!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(_ircUser->nick()).arg(_ircUser->ircOperator()));
407     if(!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
408     if(!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
409
410     toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
411
412     if(_ircUser->idleTime().isValid()) {
413       QDateTime now = QDateTime::currentDateTime();
414       QDateTime idle = _ircUser->idleTime();
415       int idleTime = idle.secsTo(now);
416       toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
417     }
418     if(_ircUser->loginTime().isValid()) {
419       toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
420     }
421
422     if(!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
423   }
424
425   return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
426 }
427
428 void QueryBufferItem::setIrcUser(IrcUser *ircUser) {
429   if(_ircUser == ircUser)
430     return;
431
432   if(_ircUser) {
433     disconnect(_ircUser, 0, this, 0);
434   }
435
436   if(ircUser) {
437     connect(ircUser, SIGNAL(quited()), this, SLOT(removeIrcUser()));
438     connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
439   }
440
441   _ircUser = ircUser;
442   emit dataChanged();
443 }
444
445 void QueryBufferItem::removeIrcUser() {
446   _ircUser = 0;
447   emit dataChanged();
448 }
449
450 /*****************************************
451 *  ChannelBufferItem
452 *****************************************/
453 ChannelBufferItem::ChannelBufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
454   : BufferItem(bufferInfo, parent),
455     _ircChannel(0)
456 {
457 }
458
459 QVariant ChannelBufferItem::data(int column, int role) const {
460   switch(role) {
461     case NetworkModel::IrcChannelRole:
462       return QVariant::fromValue<QObject *>(_ircChannel);
463     default:
464       return BufferItem::data(column, role);
465   }
466 }
467
468 QString ChannelBufferItem::toolTip(int column) const {
469   Q_UNUSED(column);
470   QStringList toolTip;
471
472   toolTip.append(tr("<b>Channel %1</b>").arg(Qt::escape(bufferName())));
473   if(isActive()) {
474     //TODO: add channel modes
475     toolTip.append(tr("<b>Users:</b> %1").arg(nickCount()));
476     if(_ircChannel) {
477       QString channelMode = _ircChannel->channelModeString(); // channelModeString is compiled on the fly -> thus cache the result
478       if(!channelMode.isEmpty())
479         toolTip.append(tr("<b>Mode:</b> %1").arg(channelMode));
480     }
481
482     BufferSettings s;
483     bool showTopic = s.value("DisplayTopicInTooltip", QVariant(false)).toBool();
484     if(showTopic) {
485       QString _topic = topic();
486       if(_topic != "") {
487         _topic = stripFormatCodes(_topic);
488         _topic = Qt::escape(_topic);
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 }