Adding nick context menu actions to NetworkModelActionProvider
[quassel.git] / src / uisupport / networkmodelactionprovider.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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 <QInputDialog>
22 #include <QMenu>
23 #include <QMessageBox>
24
25 #include "networkmodelactionprovider.h"
26
27 #include "buffermodel.h"
28 #include "buffersettings.h"
29 #include "iconloader.h"
30 #include "identity.h"
31 #include "network.h"
32
33 NetworkModelActionProvider::NetworkModelActionProvider(QObject *parent)
34 : AbstractActionProvider(parent),
35   _actionCollection(new ActionCollection(this)),
36   _messageFilter(0),
37   _receiver(0)
38 {
39   registerAction(NetworkConnect, SmallIcon("network-connect"), tr("Connect"));
40   registerAction(NetworkDisconnect, SmallIcon("network-disconnect"), tr("Disconnect"));
41
42   registerAction(BufferJoin, tr("Join"));
43   registerAction(BufferPart, tr("Part"));
44   registerAction(BufferRemove, tr("Delete Buffer..."));
45   registerAction(BufferSwitchTo, tr("Show Buffer"));
46
47   registerAction(HideJoin, tr("Joins"), true);
48   registerAction(HidePart, tr("Parts"), true);
49   registerAction(HideQuit, tr("Quits"), true);
50   registerAction(HideNick, tr("Nick Changes"), true);
51   registerAction(HideMode, tr("Mode Changes"), true);
52   registerAction(HideDayChange, tr("Day Changes"), true);
53   registerAction(HideApplyToAll, tr("Apply to All Chat Views..."));
54
55   registerAction(JoinChannel, tr("Join Channel..."));
56
57   registerAction(NickQuery, tr("Start Query"));
58   registerAction(NickSwitchTo, tr("Show Query"));
59   registerAction(NickWhois, tr("Whois"));
60
61   registerAction(NickCtcpVersion, tr("Version"));
62   registerAction(NickCtcpTime, tr("Time"));
63   registerAction(NickCtcpPing, tr("Ping"));
64   registerAction(NickCtcpFinger, tr("Finger"));
65
66   registerAction(NickOp, tr("Give Operator Status"));
67   registerAction(NickDeop, tr("Take Operator Status"));
68   registerAction(NickVoice, tr("Give Voice"));
69   registerAction(NickDevoice, tr("Take Voice"));
70   registerAction(NickKick, tr("Kick"));
71   registerAction(NickBan, tr("Ban"));
72   registerAction(NickKickBan, tr("Kickban"));
73
74   registerAction(HideBufferTemporarily, tr("Hide Buffer(s) Temporarily"));
75   registerAction(HideBufferPermanently, tr("Hide Buffer(s) Permanently"));
76   registerAction(ShowChannelList, SmallIcon("format-list-unordered"), tr("Show Channel List"));
77   registerAction(ShowIgnoreList, tr("Show Ignore List"));
78
79   connect(_actionCollection, SIGNAL(actionTriggered(QAction *)), SLOT(actionTriggered(QAction *)));
80
81   action(HideApplyToAll)->setDisabled(true);
82
83   QMenu *hideEventsMenu = new QMenu();
84   hideEventsMenu->addAction(action(HideJoin));
85   hideEventsMenu->addAction(action(HidePart));
86   hideEventsMenu->addAction(action(HideQuit));
87   hideEventsMenu->addAction(action(HideNick));
88   hideEventsMenu->addAction(action(HideMode));
89   hideEventsMenu->addAction(action(HideDayChange));
90   hideEventsMenu->addSeparator();
91   hideEventsMenu->addAction(action(HideApplyToAll));
92   _hideEventsMenuAction = new Action(tr("Hide Events"), 0);
93   _hideEventsMenuAction->setMenu(hideEventsMenu);
94
95   QMenu *nickCtcpMenu = new QMenu();
96   nickCtcpMenu->addAction(action(NickCtcpPing));
97   nickCtcpMenu->addAction(action(NickCtcpVersion));
98   nickCtcpMenu->addAction(action(NickCtcpTime));
99   nickCtcpMenu->addAction(action(NickCtcpFinger));
100   _nickCtcpMenuAction = new Action(tr("CTCP"), 0);
101   _nickCtcpMenuAction->setMenu(nickCtcpMenu);
102
103   QMenu *nickModeMenu = new QMenu();
104   nickModeMenu->addAction(action(NickOp));
105   nickModeMenu->addAction(action(NickDeop));
106   nickModeMenu->addAction(action(NickVoice));
107   nickModeMenu->addAction(action(NickDevoice));
108   _nickModeMenuAction = new Action(tr("Modes"), 0);
109   _nickModeMenuAction->setMenu(nickModeMenu);
110 }
111
112 NetworkModelActionProvider::~NetworkModelActionProvider() {
113   _hideEventsMenuAction->menu()->deleteLater();
114   _hideEventsMenuAction->deleteLater();
115   _nickCtcpMenuAction->menu()->deleteLater();
116   _nickCtcpMenuAction->deleteLater();
117   _nickModeMenuAction->menu()->deleteLater();
118   _nickModeMenuAction->deleteLater();
119 }
120
121 void NetworkModelActionProvider::registerAction(ActionType type, const QString &text, bool checkable) {
122   registerAction(type, QPixmap(), text, checkable);
123 }
124
125 void NetworkModelActionProvider::registerAction(ActionType type, const QPixmap &icon, const QString &text, bool checkable) {
126   Action *act;
127   if(icon.isNull())
128     act = new Action(text, this);
129   else
130     act = new Action(icon, text, this);
131
132   act->setCheckable(checkable);
133   act->setData(type);
134
135   _actionCollection->addAction(QString::number(type, 16), act);
136   _actionByType[type] = act;
137 }
138
139 void NetworkModelActionProvider::addActions(QMenu *menu, BufferId bufId, QObject *receiver, const char *method) {
140   if(!bufId.isValid())
141     return;
142   _messageFilter = 0;
143   _contextItem = QString();
144   addActions(menu, Client::networkModel()->bufferIndex(bufId), receiver, method);
145 }
146
147 void NetworkModelActionProvider::addActions(QMenu *menu, const QModelIndex &index, QObject *receiver, const char *method, bool isCustomBufferView) {
148   if(!index.isValid())
149     return;
150   _messageFilter = 0;
151   _contextItem = QString();
152   addActions(menu, QList<QModelIndex>() << index, receiver, method, isCustomBufferView);
153 }
154
155 void NetworkModelActionProvider::addActions(QMenu *menu, MessageFilter *filter, BufferId msgBuffer, QObject *receiver, const char *slot) {
156   addActions(menu, filter, msgBuffer, QString(), receiver, slot);
157 }
158
159 void NetworkModelActionProvider::addActions(QMenu *menu, MessageFilter *filter, BufferId msgBuffer, const QString &chanOrNick, QObject *receiver, const char *method) {
160   if(!filter)
161     return;
162   _messageFilter = filter;
163   _contextItem = chanOrNick;
164   addActions(menu, QList<QModelIndex>() << Client::networkModel()->bufferIndex(msgBuffer), receiver, method);
165 }
166
167 // add a list of actions sensible for the current item(s)
168 void NetworkModelActionProvider::addActions(QMenu *menu,
169                                             const QList<QModelIndex> &indexList,
170                                             QObject *receiver,
171                                             const char *method,
172                                             bool isCustomBufferView)
173 {
174   if(!indexList.count())
175     return;
176
177   _indexList = indexList;
178   _receiver = receiver;
179   _method = method;
180
181   if(!_messageFilter) {
182     // this means we are in a BufferView (or NickView) rather than a ChatView
183
184     // first index in list determines the menu type (just in case we have both buffers and networks selected, for example)
185     QModelIndex index = _indexList.at(0);
186     NetworkModel::ItemType itemType = static_cast<NetworkModel::ItemType>(index.data(NetworkModel::ItemTypeRole).toInt());
187
188     switch(itemType) {
189       case NetworkModel::NetworkItemType:
190         addNetworkItemActions(menu, index);
191         break;
192       case NetworkModel::BufferItemType:
193         addBufferItemActions(menu, index, isCustomBufferView);
194         break;
195       case NetworkModel::IrcUserItemType:
196         addIrcUserActions(menu, index);
197         break;
198       default:
199         return;
200
201     }
202   } else {
203     // ChatView actions
204     if(_contextItem.isEmpty()) {
205       // a) query buffer: handle like ircuser
206       // b) general chatview: only react if _contextItem is set (i.e. we right-clicked on something)
207       // NOTE stuff breaks probably with merged buffers, need to rework a lot around here then
208       // for now, use the item type of a random buffer... assuming we never mix channel and query buffers
209       //if(!_messageFilter->containedBuffers.count())
210       //  return;
211       //BufferId randomBuf = _messageFilter->containedBuffers.values().at(0);
212
213     }
214   }
215 }
216
217 void NetworkModelActionProvider::addNetworkItemActions(QMenu *menu, const QModelIndex &index) {
218   NetworkId networkId = index.data(NetworkModel::NetworkIdRole).value<NetworkId>();
219   if(!networkId.isValid())
220     return;
221   const Network *network = Client::network(networkId);
222   Q_CHECK_PTR(network);
223   if(!network)
224     return;
225
226   addAction(NetworkConnect, menu, network->connectionState() == Network::Disconnected);
227   addAction(NetworkDisconnect, menu, network->connectionState() != Network::Disconnected);
228   menu->addSeparator();
229   addAction(ShowChannelList, menu, index, ActiveState);
230   addAction(JoinChannel, menu, index, ActiveState);
231
232 }
233
234 void NetworkModelActionProvider::addBufferItemActions(QMenu *menu, const QModelIndex &index, bool isCustomBufferView) {
235   BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
236
237   switch(bufferInfo.type()) {
238     case BufferInfo::ChannelBuffer:
239       addAction(BufferJoin, menu, index, InactiveState);
240       addAction(BufferPart, menu, index, ActiveState);
241       menu->addSeparator();
242       addHideEventsMenu(menu, bufferInfo.bufferId());
243       menu->addSeparator();
244       addAction(HideBufferTemporarily, menu, isCustomBufferView);
245       addAction(HideBufferPermanently, menu, isCustomBufferView);
246       addAction(BufferRemove, menu, index);
247       break;
248
249     case BufferInfo::QueryBuffer:
250     {
251       //IrcUser *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
252       //if(ircUser) {
253         addIrcUserActions(menu, index);
254         menu->addSeparator();
255       //}
256       addHideEventsMenu(menu, bufferInfo.bufferId());
257       menu->addSeparator();
258       addAction(HideBufferTemporarily, menu, isCustomBufferView);
259       addAction(HideBufferPermanently, menu, isCustomBufferView);
260       addAction(BufferRemove, menu, index);
261       break;
262     }
263
264     default:
265       addAction(HideBufferTemporarily, menu, isCustomBufferView);
266       addAction(HideBufferPermanently, menu, isCustomBufferView);
267   }
268 }
269
270 void NetworkModelActionProvider::addIrcUserActions(QMenu *menu, const QModelIndex &index) {
271   // this can be called: a) as a nicklist context menu (index has IrcUserItemType)
272   //                     b) as a query buffer context menu (index has BufferItemType and is a QueryBufferItem)
273   //                     c) right-click in a query chatview (same as b), index will be the corresponding QueryBufferItem)
274   //                     d) right-click on some nickname (_contextItem will be non-null, _filter -> chatview, index -> message buffer)
275
276   if(_contextItem.isNull()) {
277     // cases a, b, c
278     bool haveQuery = findQueryBuffer(index).isValid();
279     NetworkModel::ItemType itemType = static_cast<NetworkModel::ItemType>(index.data(NetworkModel::ItemTypeRole).toInt());
280     addAction(_nickModeMenuAction, menu, itemType == NetworkModel::IrcUserItemType);
281     addAction(_nickCtcpMenuAction, menu);
282     menu->addSeparator();
283     addAction(NickQuery, menu, itemType == NetworkModel::IrcUserItemType && !haveQuery);
284     addAction(NickSwitchTo, menu, itemType == NetworkModel::IrcUserItemType && haveQuery);
285     menu->addSeparator();
286     addAction(NickWhois, menu, true);
287
288   } else if(!_contextItem.isEmpty() && _messageFilter) {
289     // case d
290     // TODO
291
292   }
293 }
294
295 /******** Helper Functions ***********************************************************************/
296
297 bool NetworkModelActionProvider::checkRequirements(const QModelIndex &index, ItemActiveStates requiredActiveState) {
298   if(!index.isValid())
299     return false;
300
301   ItemActiveStates isActive = index.data(NetworkModel::ItemActiveRole).toBool()
302   ? ActiveState
303   : InactiveState;
304
305   if(!(isActive & requiredActiveState))
306     return false;
307
308   return true;
309 }
310
311 Action * NetworkModelActionProvider::addAction(ActionType type , QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
312   return addAction(action(type), menu, checkRequirements(index, requiredActiveState));
313 }
314
315 Action * NetworkModelActionProvider::addAction(Action *action , QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
316   return addAction(action, menu, checkRequirements(index, requiredActiveState));
317 }
318
319 Action * NetworkModelActionProvider::addAction(ActionType type , QMenu *menu, bool condition) {
320   return addAction(action(type), menu, condition);
321 }
322
323 Action * NetworkModelActionProvider::addAction(Action *action , QMenu *menu, bool condition) {
324   if(condition) {
325     menu->addAction(action);
326     action->setVisible(true);
327   } else {
328     action->setVisible(false);
329   }
330   return action;
331 }
332
333 void NetworkModelActionProvider::addHideEventsMenu(QMenu *menu, BufferId bufferId) {
334   addHideEventsMenu(menu, BufferSettings(bufferId).messageFilter());
335 }
336
337 void NetworkModelActionProvider::addHideEventsMenu(QMenu *menu, MessageFilter *msgFilter) {
338   addHideEventsMenu(menu, BufferSettings(msgFilter->idString()).messageFilter());
339 }
340
341 void NetworkModelActionProvider::addHideEventsMenu(QMenu *menu, int filter) {
342   action(HideJoin)->setChecked(filter & Message::Join);
343   action(HidePart)->setChecked(filter & Message::Part);
344   action(HideQuit)->setChecked(filter & Message::Quit);
345   action(HideNick)->setChecked(filter & Message::Nick);
346   action(HideMode)->setChecked(filter & Message::Mode);
347   action(HideDayChange)->setChecked(filter & Message::DayChange);
348
349   menu->addAction(_hideEventsMenuAction);
350 }
351
352 QString NetworkModelActionProvider::nickName(const QModelIndex &index) const {
353   IrcUser *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
354   if(ircUser)
355     return ircUser->nick();
356
357   BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
358   if(!bufferInfo.isValid())
359     return QString();
360
361   return bufferInfo.bufferName(); // FIXME this might break with merged queries maybe
362 }
363
364 BufferId NetworkModelActionProvider::findQueryBuffer(const QModelIndex &index, const QString &predefinedNick) const {
365   NetworkId networkId = _indexList.at(0).data(NetworkModel::NetworkIdRole).value<NetworkId>();
366   if(!networkId.isValid())
367     return BufferId();
368
369   QString nick = predefinedNick.isEmpty() ? nickName(index) : predefinedNick;
370   if(nick.isEmpty())
371     return BufferId();
372
373   return findQueryBuffer(networkId, nick);
374 }
375
376 BufferId NetworkModelActionProvider::findQueryBuffer(NetworkId networkId, const QString &nick) const {
377   return Client::networkModel()->bufferId(networkId, nick);
378 }
379
380 void NetworkModelActionProvider::handleExternalAction(ActionType type, QAction *action) {
381   Q_UNUSED(type);
382   if(_receiver && _method) {
383     if(!QMetaObject::invokeMethod(_receiver, _method, Q_ARG(QAction *, action)))
384       qWarning() << "NetworkModelActionProvider::handleExternalAction(): Could not invoke slot" << _receiver << _method;
385   }
386 }
387
388 /******** Handle Actions *************************************************************************/
389
390 void NetworkModelActionProvider::actionTriggered(QAction *action) {
391   ActionType type = (ActionType)action->data().toInt();
392   if(type > 0) {
393     if(type & NetworkMask)
394       handleNetworkAction(type, action);
395     else if(type & BufferMask)
396       handleBufferAction(type, action);
397     else if(type & HideMask)
398       handleHideAction(type, action);
399     else if(type & GeneralMask)
400       handleGeneralAction(type, action);
401     else if(type & NickMask)
402       handleNickAction(type, action);
403     else if(type & ExternalMask)
404       handleExternalAction(type, action);
405     else
406       qWarning() << "NetworkModelActionProvider::actionTriggered(): Unhandled action!";
407   }
408   _indexList.clear();
409   _messageFilter = 0;
410   _receiver = 0;
411 }
412
413 void NetworkModelActionProvider::handleNetworkAction(ActionType type, QAction *) {
414   if(!_indexList.count())
415     return;
416   const Network *network = Client::network(_indexList.at(0).data(NetworkModel::NetworkIdRole).value<NetworkId>());
417   Q_CHECK_PTR(network);
418   if(!network)
419     return;
420
421   switch(type) {
422     case NetworkConnect:
423       network->requestConnect();
424       break;
425     case NetworkDisconnect:
426       network->requestDisconnect();
427       break;
428     default:
429       break;
430   }
431 }
432
433 void NetworkModelActionProvider::handleBufferAction(ActionType type, QAction *) {
434   foreach(QModelIndex index, _indexList) {
435     BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
436     if(!bufferInfo.isValid())
437       continue;
438
439     switch(type) {
440       case BufferJoin:
441         Client::userInput(bufferInfo, QString("/JOIN %1").arg(bufferInfo.bufferName()));
442         break;
443       case BufferPart:
444       {
445         QString reason = Client::identity(Client::network(bufferInfo.networkId())->identity())->partReason();
446         Client::userInput(bufferInfo, QString("/PART %1").arg(reason));
447         break;
448       }
449       case BufferSwitchTo:
450         Client::bufferModel()->switchToBuffer(bufferInfo.bufferId());
451         break;
452       case BufferRemove:
453       {
454         int res = QMessageBox::question(0, tr("Remove buffer permanently?"),
455         tr("Do you want to delete the buffer \"%1\" permanently? This will delete all related data, including all backlog "
456         "data, from the core's database!").arg(bufferInfo.bufferName()),
457         QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
458         if(res == QMessageBox::Yes) {
459           Client::removeBuffer(bufferInfo.bufferId());
460         }
461         break;
462       }
463       default:
464         break;
465     }
466   }
467 }
468
469 void NetworkModelActionProvider::handleHideAction(ActionType type, QAction *action) {
470   Message::Type msgType;
471   switch(type) {
472     case HideJoin:
473       msgType = Message::Join; break;
474     case HidePart:
475       msgType = Message::Part; break;
476     case HideQuit:
477       msgType = Message::Quit; break;
478     case HideNick:
479       msgType = Message::Nick; break;
480     case HideMode:
481       msgType = Message::Mode; break;
482     case HideDayChange:
483       msgType = Message::DayChange; break;
484     case HideApplyToAll:
485       // TODO implement "apply to all" for hiding messages
486       return;
487       break;
488     default:
489       return;
490   }
491
492   if(_messageFilter)
493     BufferSettings(_messageFilter->idString()).filterMessage(msgType, action->isChecked());
494   else {
495     foreach(QModelIndex index, _indexList) {
496       BufferId bufferId = index.data(NetworkModel::BufferIdRole).value<BufferId>();
497       if(!bufferId.isValid())
498         continue;
499       BufferSettings(bufferId).filterMessage(msgType, action->isChecked());
500     }
501   }
502 }
503
504 void NetworkModelActionProvider::handleGeneralAction(ActionType type, QAction *action) {
505   if(!_indexList.count())
506     return;
507   NetworkId networkId = _indexList.at(0).data(NetworkModel::NetworkIdRole).value<NetworkId>();
508   if(!networkId.isValid())
509     return;
510
511   switch(type) {
512     case JoinChannel:
513     {
514     //  FIXME no QInputDialog in Qtopia
515 #   ifndef Q_WS_QWS
516       bool ok;
517       QString channelName = QInputDialog::getText(0, tr("Join Channel"), tr("Input channel name:"), QLineEdit::Normal, QString(), &ok);
518       if(ok && !channelName.isEmpty()) {
519         Client::instance()->userInput(BufferInfo::fakeStatusBuffer(networkId),
520                                       QString("/JOIN %1").arg(channelName));
521       }
522 #   endif
523       break;
524     }
525     case ShowChannelList:
526       emit showChannelList(networkId);
527       break;
528     case ShowIgnoreList:
529       emit showIgnoreList(networkId);
530       break;
531     default:
532       break;
533   }
534 }
535
536 void NetworkModelActionProvider::handleNickAction(ActionType type, QAction *) {
537   if(!_indexList.count())
538     return;
539   NetworkId networkId = _indexList.at(0).data(NetworkModel::NetworkIdRole).value<NetworkId>();
540   if(!networkId.isValid())
541     return;
542   QString nick = nickName(_indexList.at(0));
543   if(nick.isEmpty())
544     return;
545   BufferInfo bufferInfo = _indexList.at(0).data(NetworkModel::BufferInfoRole).value<BufferInfo>();
546   if(!bufferInfo.isValid())
547     return;
548
549   switch(type) {
550     case NickWhois:
551       Client::userInput(bufferInfo, QString("/WHOIS %1 %1").arg(nick));
552       break;
553     case NickCtcpVersion:
554       Client::userInput(bufferInfo, QString("/CTCP %1 VERSION").arg(nick));
555       break;
556     case NickCtcpPing:
557       Client::userInput(bufferInfo, QString("/CTCP %1 PING").arg(nick));
558       break;
559     case NickCtcpTime:
560       Client::userInput(bufferInfo, QString("/CTCP %1 TIME").arg(nick));
561       break;
562     case NickCtcpFinger:
563       Client::userInput(bufferInfo, QString("/CTCP %1 FINGER").arg(nick));
564       break;
565     case NickOp:
566       Client::userInput(bufferInfo, QString("/OP %1").arg(nick));
567       break;
568     case NickDeop:
569       Client::userInput(bufferInfo, QString("/DEOP %1").arg(nick));
570       break;
571     case NickVoice:
572       Client::userInput(bufferInfo, QString("/VOICE %1").arg(nick));
573       break;
574     case NickDevoice:
575       Client::userInput(bufferInfo, QString("/DEVOICE %1").arg(nick));
576       break;
577     case NickKick:
578       Client::userInput(bufferInfo, QString("/KICK %1").arg(nick));
579       break;
580     case NickBan:
581       Client::userInput(bufferInfo, QString("/BAN %1").arg(nick));
582       break;
583     case NickKickBan:
584       Client::userInput(bufferInfo, QString("/BAN %1").arg(nick));
585       Client::userInput(bufferInfo, QString("/KICK %1").arg(nick));
586       break;
587     case NickSwitchTo:
588       Client::bufferModel()->switchToBuffer(findQueryBuffer(networkId, nick));
589       break;
590     case NickQuery:
591       Client::userInput(bufferInfo, QString("/QUERY %1").arg(nick));
592       break;
593     default:
594       qWarning() << "Unhandled nick action";
595   }
596 }