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