Fix genversion error in unclean build directories
[quassel.git] / src / uisupport / contextmenuactionprovider.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 <QInputDialog>
22 #include <QMenu>
23 #include <QMessageBox>
24 #include <QMap>
25
26 #include "contextmenuactionprovider.h"
27
28 #include "buffermodel.h"
29 #include "buffersettings.h"
30 #include "iconloader.h"
31 #include "clientidentity.h"
32 #include "network.h"
33 #include "util.h"
34 #include "client.h"
35 #include "clientignorelistmanager.h"
36
37 ContextMenuActionProvider::ContextMenuActionProvider(QObject *parent) : NetworkModelController(parent) {
38   registerAction(NetworkConnect, SmallIcon("network-connect"), tr("Connect"));
39   registerAction(NetworkDisconnect, SmallIcon("network-disconnect"), tr("Disconnect"));
40
41   registerAction(BufferJoin, SmallIcon("irc-join-channel"), tr("Join"));
42   registerAction(BufferPart, SmallIcon("irc-close-channel"), tr("Part"));
43   registerAction(BufferRemove, tr("Delete Chat(s)..."));
44   registerAction(BufferSwitchTo, tr("Go to Chat"));
45
46   registerAction(HideJoin, tr("Joins"), true);
47   registerAction(HidePart, tr("Parts"), true);
48   registerAction(HideQuit, tr("Quits"), true);
49   registerAction(HideNick, tr("Nick Changes"), true);
50   registerAction(HideMode, tr("Mode Changes"), true);
51   registerAction(HideDayChange, tr("Day Changes"), true);
52   registerAction(HideTopic, tr("Topic Changes"), true);
53   registerAction(HideApplyToAll, tr("Set as Default..."));
54   registerAction(HideUseDefaults, tr("Use Defaults..."));
55
56   registerAction(JoinChannel, SmallIcon("irc-join-channel"), tr("Join Channel..."));
57
58   registerAction(NickQuery, tr("Start Query"));
59   registerAction(NickSwitchTo, tr("Show Query"));
60   registerAction(NickWhois, tr("Whois"));
61
62   registerAction(NickCtcpVersion, tr("Version"));
63   registerAction(NickCtcpTime, tr("Time"));
64   registerAction(NickCtcpPing, tr("Ping"));
65   registerAction(NickCtcpFinger, tr("Finger"));
66   registerAction(NickIgnoreCustom, tr("Custom..."));
67
68   // these texts are only dummies! don't think about tr() here!
69   registerAction(NickIgnoreUser, "*!ident@host.domain.tld");
70   registerAction(NickIgnoreHost, "*!*@host.domain.tld");
71   registerAction(NickIgnoreDomain, "*!ident@*.domain.tld");
72   registerAction(NickIgnoreToggleEnabled0, "Enable", true);
73   registerAction(NickIgnoreToggleEnabled1, "Enable", true);
74   registerAction(NickIgnoreToggleEnabled2, "Enable", true);
75   registerAction(NickIgnoreToggleEnabled3, "Enable", true);
76   registerAction(NickIgnoreToggleEnabled4, "Enable", true);
77
78   registerAction(NickOp, SmallIcon("irc-operator"), tr("Give Operator Status"));
79   registerAction(NickDeop, SmallIcon("irc-remove-operator"), tr("Take Operator Status"));
80   registerAction(NickVoice, SmallIcon("irc-voice"), tr("Give Voice"));
81   registerAction(NickDevoice, SmallIcon("irc-unvoice"), tr("Take Voice"));
82   registerAction(NickKick, SmallIcon("im-kick-user"), tr("Kick From Channel"));
83   registerAction(NickBan, SmallIcon("im-ban-user"), tr("Ban From Channel"));
84   registerAction(NickKickBan, SmallIcon("im-ban-kick-user"), tr("Kick && Ban"));
85
86   registerAction(HideBufferTemporarily, tr("Hide Chat(s) Temporarily"));
87   registerAction(HideBufferPermanently, tr("Hide Chat(s) Permanently"));
88   registerAction(ShowChannelList, tr("Show Channel List"));
89   registerAction(ShowIgnoreList, tr("Show Ignore List"));
90
91   QMenu *hideEventsMenu = new QMenu();
92   hideEventsMenu->addAction(action(HideJoin));
93   hideEventsMenu->addAction(action(HidePart));
94   hideEventsMenu->addAction(action(HideQuit));
95   hideEventsMenu->addAction(action(HideNick));
96   hideEventsMenu->addAction(action(HideMode));
97   hideEventsMenu->addAction(action(HideTopic));
98   hideEventsMenu->addAction(action(HideDayChange));
99   hideEventsMenu->addSeparator();
100   hideEventsMenu->addAction(action(HideApplyToAll));
101   hideEventsMenu->addAction(action(HideUseDefaults));
102   _hideEventsMenuAction = new Action(tr("Hide Events"), 0);
103   _hideEventsMenuAction->setMenu(hideEventsMenu);
104
105   QMenu *nickCtcpMenu = new QMenu();
106   nickCtcpMenu->addAction(action(NickCtcpPing));
107   nickCtcpMenu->addAction(action(NickCtcpVersion));
108   nickCtcpMenu->addAction(action(NickCtcpTime));
109   nickCtcpMenu->addAction(action(NickCtcpFinger));
110   _nickCtcpMenuAction = new Action(tr("CTCP"), 0);
111   _nickCtcpMenuAction->setMenu(nickCtcpMenu);
112
113   QMenu *nickModeMenu = new QMenu();
114   nickModeMenu->addAction(action(NickOp));
115   nickModeMenu->addAction(action(NickDeop));
116   nickModeMenu->addAction(action(NickVoice));
117   nickModeMenu->addAction(action(NickDevoice));
118   nickModeMenu->addSeparator();
119   nickModeMenu->addAction(action(NickKick));
120   nickModeMenu->addAction(action(NickBan));
121   nickModeMenu->addAction(action(NickKickBan));
122   _nickModeMenuAction = new Action(tr("Actions"), 0);
123   _nickModeMenuAction->setMenu(nickModeMenu);
124
125   QMenu *ignoreMenu = new QMenu();
126   _nickIgnoreMenuAction = new Action(tr("Ignore"), 0);
127   _nickIgnoreMenuAction->setMenu(ignoreMenu);
128
129   // These are disabled actions used as descriptions
130   // They don't need any of the Action fancyness so we use plain QActions
131   _ignoreDescriptions << new QAction(tr("Add Ignore Rule"), this);
132   _ignoreDescriptions << new QAction(tr("Existing Rules"), this);
133   foreach(QAction *act, _ignoreDescriptions)
134     act->setEnabled(false);
135 }
136
137 ContextMenuActionProvider::~ContextMenuActionProvider() {
138   _hideEventsMenuAction->menu()->deleteLater();
139   _hideEventsMenuAction->deleteLater();
140   _nickCtcpMenuAction->menu()->deleteLater();
141   _nickCtcpMenuAction->deleteLater();
142   _nickModeMenuAction->menu()->deleteLater();
143   _nickModeMenuAction->deleteLater();
144   _nickIgnoreMenuAction->menu()->deleteLater();
145   _nickIgnoreMenuAction->deleteLater();
146   qDeleteAll(_ignoreDescriptions);
147   _ignoreDescriptions.clear();
148 }
149
150 void ContextMenuActionProvider::addActions(QMenu *menu, BufferId bufId, QObject *receiver, const char *method) {
151   if(!bufId.isValid())
152     return;
153   addActions(menu, Client::networkModel()->bufferIndex(bufId), receiver, method);
154 }
155
156 void ContextMenuActionProvider::addActions(QMenu *menu, const QModelIndex &index, QObject *receiver, const char *method, bool isCustomBufferView) {
157   if(!index.isValid())
158     return;
159   addActions(menu, QList<QModelIndex>() << index, 0, QString(), receiver, method, isCustomBufferView);
160 }
161
162 void ContextMenuActionProvider::addActions(QMenu *menu, MessageFilter *filter, BufferId msgBuffer, QObject *receiver, const char *slot) {
163   addActions(menu, filter, msgBuffer, QString(), receiver, slot);
164 }
165
166 void ContextMenuActionProvider::addActions(QMenu *menu, MessageFilter *filter, BufferId msgBuffer, const QString &chanOrNick, QObject *receiver, const char *method) {
167   if(!filter)
168     return;
169   addActions(menu, QList<QModelIndex>() << Client::networkModel()->bufferIndex(msgBuffer), filter, chanOrNick, receiver, method, false);
170 }
171
172 void ContextMenuActionProvider::addActions(QMenu *menu, const QList<QModelIndex> &indexList, QObject *receiver,  const char *method, bool isCustomBufferView) {
173   addActions(menu, indexList, 0, QString(), receiver, method, isCustomBufferView);
174 }
175
176 // add a list of actions sensible for the current item(s)
177 void ContextMenuActionProvider::addActions(QMenu *menu,
178                                             const QList<QModelIndex> &indexList_,
179                                             MessageFilter *filter_,
180                                             const QString &contextItem_,
181                                             QObject *receiver_,
182                                             const char *method_,
183                                             bool isCustomBufferView)
184 {
185   if(!indexList_.count())
186     return;
187
188   setIndexList(indexList_);
189   setMessageFilter(filter_);
190   setContextItem(contextItem_);
191   setSlot(receiver_, method_);
192
193   if(!messageFilter()) {
194     // this means we are in a BufferView (or NickView) rather than a ChatView
195
196     // first index in list determines the menu type (just in case we have both buffers and networks selected, for example)
197     QModelIndex index = indexList().at(0);
198     NetworkModel::ItemType itemType = static_cast<NetworkModel::ItemType>(index.data(NetworkModel::ItemTypeRole).toInt());
199
200     switch(itemType) {
201       case NetworkModel::NetworkItemType:
202         addNetworkItemActions(menu, index);
203         break;
204       case NetworkModel::BufferItemType:
205         addBufferItemActions(menu, index, isCustomBufferView);
206         break;
207       case NetworkModel::IrcUserItemType:
208         addIrcUserActions(menu, index);
209         break;
210       default:
211         return;
212
213     }
214   } else {
215     // ChatView actions
216     if(contextItem().isEmpty()) {
217       // a) query buffer: handle like ircuser
218       // b) general chatview: handle like channel iff it displays a single buffer
219       // NOTE stuff breaks probably with merged buffers, need to rework a lot around here then
220       if(messageFilter()->containedBuffers().count() == 1) {
221         // we can handle this like a single bufferItem
222         QModelIndex index = Client::networkModel()->bufferIndex(messageFilter()->containedBuffers().values().at(0));
223         setIndexList(index);
224         addBufferItemActions(menu, index);
225         return;
226       } else {
227         // TODO: actions for merged buffers... _indexList contains the index of the message we clicked on
228
229       }
230     } else {
231       // context item = chan or nick, _indexList = buf where the msg clicked on originated
232       if(isChannelName(contextItem())) {
233         QModelIndex msgIdx = indexList().at(0);
234         if(!msgIdx.isValid())
235           return;
236         NetworkId networkId = msgIdx.data(NetworkModel::NetworkIdRole).value<NetworkId>();
237         BufferId bufId = Client::networkModel()->bufferId(networkId, contextItem());
238         if(bufId.isValid()) {
239           QModelIndex targetIdx = Client::networkModel()->bufferIndex(bufId);
240           setIndexList(targetIdx);
241           addAction(BufferJoin, menu, targetIdx, InactiveState);
242           addAction(BufferSwitchTo, menu, targetIdx, ActiveState);
243         } else
244           addAction(JoinChannel, menu);
245       } else {
246         // TODO: actions for a nick
247       }
248     }
249   }
250 }
251
252 void ContextMenuActionProvider::addNetworkItemActions(QMenu *menu, const QModelIndex &index) {
253   NetworkId networkId = index.data(NetworkModel::NetworkIdRole).value<NetworkId>();
254   if(!networkId.isValid())
255     return;
256   const Network *network = Client::network(networkId);
257   Q_CHECK_PTR(network);
258   if(!network)
259     return;
260
261   addAction(NetworkConnect, menu, network->connectionState() == Network::Disconnected);
262   addAction(NetworkDisconnect, menu, network->connectionState() != Network::Disconnected);
263   menu->addSeparator();
264   addAction(ShowChannelList, menu, index, ActiveState);
265   addAction(JoinChannel, menu, index, ActiveState);
266
267 }
268
269 void ContextMenuActionProvider::addBufferItemActions(QMenu *menu, const QModelIndex &index, bool isCustomBufferView) {
270   BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
271
272   menu->addSeparator();
273   switch(bufferInfo.type()) {
274     case BufferInfo::ChannelBuffer:
275       addAction(BufferJoin, menu, index, InactiveState);
276       addAction(BufferPart, menu, index, ActiveState);
277       menu->addSeparator();
278       addHideEventsMenu(menu, bufferInfo.bufferId());
279       menu->addSeparator();
280       addAction(HideBufferTemporarily, menu, isCustomBufferView);
281       addAction(HideBufferPermanently, menu, isCustomBufferView);
282       addAction(BufferRemove, menu, index, InactiveState);
283       break;
284
285     case BufferInfo::QueryBuffer:
286     {
287       //IrcUser *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
288       //if(ircUser) {
289         addIrcUserActions(menu, index);
290         menu->addSeparator();
291       //}
292       addHideEventsMenu(menu, bufferInfo.bufferId());
293       menu->addSeparator();
294       addAction(HideBufferTemporarily, menu, isCustomBufferView);
295       addAction(HideBufferPermanently, menu, isCustomBufferView);
296       addAction(BufferRemove, menu, index);
297       break;
298     }
299
300     default:
301       addAction(HideBufferTemporarily, menu, isCustomBufferView);
302       addAction(HideBufferPermanently, menu, isCustomBufferView);
303   }
304 }
305
306 void ContextMenuActionProvider::addIrcUserActions(QMenu *menu, const QModelIndex &index) {
307   // this can be called: a) as a nicklist context menu (index has IrcUserItemType)
308   //                     b) as a query buffer context menu (index has BufferItemType and is a QueryBufferItem)
309   //                     c) right-click in a query chatview (same as b), index will be the corresponding QueryBufferItem)
310   //                     d) right-click on some nickname (_contextItem will be non-null, _filter -> chatview, index -> message buffer)
311
312   if(contextItem().isNull()) {
313     // cases a, b, c
314     bool haveQuery = indexList().count() == 1 && findQueryBuffer(index).isValid();
315     NetworkModel::ItemType itemType = static_cast<NetworkModel::ItemType>(index.data(NetworkModel::ItemTypeRole).toInt());
316     addAction(_nickModeMenuAction, menu, itemType == NetworkModel::IrcUserItemType);
317     addAction(_nickCtcpMenuAction, menu);
318
319     IrcUser *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
320     if(ircUser) {
321       // ignoreliststuff
322       QString bufferName;
323       BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
324       if(bufferInfo.type() == BufferInfo::ChannelBuffer)
325         bufferName = bufferInfo.bufferName();
326       QMap<QString, bool> ignoreMap = Client::ignoreListManager()->matchingRulesForHostmask(ircUser->hostmask(), ircUser->network()->networkName(), bufferName);
327       addIgnoreMenu(menu, ircUser->hostmask(), ignoreMap);
328       // end of ignoreliststuff
329     }
330     menu->addSeparator();
331     addAction(NickQuery, menu, itemType == NetworkModel::IrcUserItemType && !haveQuery && indexList().count() == 1);
332     addAction(NickSwitchTo, menu, itemType == NetworkModel::IrcUserItemType && haveQuery);
333     menu->addSeparator();
334     addAction(NickWhois, menu, true);
335
336   } else if(!contextItem().isEmpty() && messageFilter()) {
337     // case d
338     // TODO
339
340   }
341 }
342
343 Action * ContextMenuActionProvider::addAction(ActionType type , QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
344   return addAction(action(type), menu, checkRequirements(index, requiredActiveState));
345 }
346
347 Action * ContextMenuActionProvider::addAction(Action *action , QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
348   return addAction(action, menu, checkRequirements(index, requiredActiveState));
349 }
350
351 Action * ContextMenuActionProvider::addAction(ActionType type , QMenu *menu, bool condition) {
352   return addAction(action(type), menu, condition);
353 }
354
355 Action * ContextMenuActionProvider::addAction(Action *action , QMenu *menu, bool condition) {
356   if(condition) {
357     menu->addAction(action);
358     action->setVisible(true);
359   } else {
360     action->setVisible(false);
361   }
362   return action;
363 }
364
365 void ContextMenuActionProvider::addHideEventsMenu(QMenu *menu, BufferId bufferId) {
366   if(BufferSettings(bufferId).hasFilter())
367     addHideEventsMenu(menu, BufferSettings(bufferId).messageFilter());
368   else
369     addHideEventsMenu(menu);
370 }
371
372 void ContextMenuActionProvider::addHideEventsMenu(QMenu *menu, MessageFilter *msgFilter) {
373   if(BufferSettings(msgFilter->idString()).hasFilter())
374     addHideEventsMenu(menu, BufferSettings(msgFilter->idString()).messageFilter());
375   else
376     addHideEventsMenu(menu);
377 }
378
379 void ContextMenuActionProvider::addHideEventsMenu(QMenu *menu, int filter) {
380   action(HideApplyToAll)->setEnabled(filter != -1);
381   action(HideUseDefaults)->setEnabled(filter != -1);
382   if(filter == -1)
383     filter = BufferSettings().messageFilter();
384
385   action(HideJoin)->setChecked(filter & Message::Join);
386   action(HidePart)->setChecked(filter & Message::Part);
387   action(HideQuit)->setChecked(filter & Message::Quit);
388   action(HideNick)->setChecked(filter & Message::Nick);
389   action(HideMode)->setChecked(filter & Message::Mode);
390   action(HideDayChange)->setChecked(filter & Message::DayChange);
391   action(HideTopic)->setChecked(filter & Message::Topic);
392
393   menu->addAction(_hideEventsMenuAction);
394 }
395
396 void ContextMenuActionProvider::addIgnoreMenu(QMenu *menu, const QString &hostmask, const QMap<QString, bool> &ignoreMap) {
397   QMenu *ignoreMenu = _nickIgnoreMenuAction->menu();
398   ignoreMenu->clear();
399   QString nick = nickFromMask(hostmask);
400   QString ident = userFromMask(hostmask);
401   QString host = hostFromMask(hostmask);
402   QString domain = host;
403   QRegExp domainRx = QRegExp("(\\.[^.]+\\.\\w+\\D)$");
404   if(domainRx.indexIn(host) != -1)
405     domain = domainRx.cap(1);
406   // we can't rely on who-data
407   // if we don't have the data, we skip actions where we would need it
408   bool haveWhoData = !ident.isEmpty() && !host.isEmpty();
409
410   // add "Add Ignore Rule" description
411   ignoreMenu->addAction(_ignoreDescriptions.at(0));
412
413   if(haveWhoData) {
414     QString text;
415     text = QString("*!%1@%2").arg(ident, host);
416     action(NickIgnoreUser)->setText(text);
417     action(NickIgnoreUser)->setProperty("ignoreRule", text);
418
419     text = QString("*!*@%1").arg(host);
420     action(NickIgnoreHost)->setText(text);
421     action(NickIgnoreHost)->setProperty("ignoreRule", text);
422
423     text = domain.at(0) == '.' ? QString("*!%1@*%2").arg(ident, domain)
424                                : QString("*!%1@%2").arg(ident, domain);
425
426     action(NickIgnoreDomain)->setText(text);
427     action(NickIgnoreDomain)->setProperty("ignoreRule", text);
428
429     if(!ignoreMap.contains(action(NickIgnoreUser)->property("ignoreRule").toString()))
430       ignoreMenu->addAction(action(NickIgnoreUser));
431     if(!ignoreMap.contains(action(NickIgnoreHost)->property("ignoreRule").toString()))
432       ignoreMenu->addAction(action(NickIgnoreHost));
433     // we only add that NickIgnoreDomain if it isn't the same as NickIgnoreUser
434     // as happens with @foobar.com hostmasks and ips
435     if(!ignoreMap.contains(action(NickIgnoreDomain)->property("ignoreRule").toString())
436       && action(NickIgnoreUser)->property("ignoreRule").toString() != action(NickIgnoreDomain)->property("ignoreRule").toString())
437       ignoreMenu->addAction(action(NickIgnoreDomain));
438   }
439
440   action(NickIgnoreCustom)->setProperty("ignoreRule", hostmask);
441   ignoreMenu->addAction(action(NickIgnoreCustom));
442
443   ignoreMenu->addSeparator();
444
445   if(haveWhoData) {
446     QMap<QString, bool>::const_iterator ruleIter = ignoreMap.begin();
447     int counter = 0;
448     if(!ignoreMap.isEmpty())
449       // add "Existing Rules" description
450       ignoreMenu->addAction(_ignoreDescriptions.at(1));
451     while(ruleIter != ignoreMap.constEnd()) {
452       if(counter < 5) {
453         ActionType type = static_cast<ActionType>(NickIgnoreToggleEnabled0 + counter*0x100000);
454         Action *act = action(type);
455         act->setText(ruleIter.key());
456         act->setProperty("ignoreRule", ruleIter.key());
457         act->setChecked(ruleIter.value());
458         ignoreMenu->addAction(act);
459       }
460       counter++;
461       ruleIter++;
462     }
463     if(counter)
464       ignoreMenu->addSeparator();
465   }
466   ignoreMenu->addAction(action(ShowIgnoreList));
467   addAction(_nickIgnoreMenuAction, menu);
468 }