1 /***************************************************************************
2 * Copyright (C) 2005-09 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include <QInputDialog>
23 #include <QMessageBox>
26 #include "contextmenuactionprovider.h"
28 #include "buffermodel.h"
29 #include "buffersettings.h"
30 #include "iconloader.h"
31 #include "clientidentity.h"
35 #include "clientignorelistmanager.h"
37 ContextMenuActionProvider::ContextMenuActionProvider(QObject *parent) : NetworkModelController(parent) {
38 registerAction(NetworkConnect, SmallIcon("network-connect"), tr("Connect"));
39 registerAction(NetworkDisconnect, SmallIcon("network-disconnect"), tr("Disconnect"));
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"));
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..."));
56 registerAction(JoinChannel, SmallIcon("irc-join-channel"), tr("Join Channel..."));
58 registerAction(NickQuery, tr("Start Query"));
59 registerAction(NickSwitchTo, tr("Show Query"));
60 registerAction(NickWhois, tr("Whois"));
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..."));
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);
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"));
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"));
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);
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);
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);
125 QMenu *ignoreMenu = new QMenu();
126 _nickIgnoreMenuAction = new Action(tr("Ignore"), 0);
127 _nickIgnoreMenuAction->setMenu(ignoreMenu);
130 ContextMenuActionProvider::~ContextMenuActionProvider() {
131 _hideEventsMenuAction->menu()->deleteLater();
132 _hideEventsMenuAction->deleteLater();
133 _nickCtcpMenuAction->menu()->deleteLater();
134 _nickCtcpMenuAction->deleteLater();
135 _nickModeMenuAction->menu()->deleteLater();
136 _nickModeMenuAction->deleteLater();
137 _nickIgnoreMenuAction->menu()->deleteLater();
138 _nickIgnoreMenuAction->deleteLater();
141 void ContextMenuActionProvider::addActions(QMenu *menu, BufferId bufId, QObject *receiver, const char *method) {
144 addActions(menu, Client::networkModel()->bufferIndex(bufId), receiver, method);
147 void ContextMenuActionProvider::addActions(QMenu *menu, const QModelIndex &index, QObject *receiver, const char *method, bool isCustomBufferView) {
150 addActions(menu, QList<QModelIndex>() << index, 0, QString(), receiver, method, isCustomBufferView);
153 void ContextMenuActionProvider::addActions(QMenu *menu, MessageFilter *filter, BufferId msgBuffer, QObject *receiver, const char *slot) {
154 addActions(menu, filter, msgBuffer, QString(), receiver, slot);
157 void ContextMenuActionProvider::addActions(QMenu *menu, MessageFilter *filter, BufferId msgBuffer, const QString &chanOrNick, QObject *receiver, const char *method) {
160 addActions(menu, QList<QModelIndex>() << Client::networkModel()->bufferIndex(msgBuffer), filter, chanOrNick, receiver, method, false);
163 void ContextMenuActionProvider::addActions(QMenu *menu, const QList<QModelIndex> &indexList, QObject *receiver, const char *method, bool isCustomBufferView) {
164 addActions(menu, indexList, 0, QString(), receiver, method, isCustomBufferView);
167 // add a list of actions sensible for the current item(s)
168 void ContextMenuActionProvider::addActions(QMenu *menu,
169 const QList<QModelIndex> &indexList_,
170 MessageFilter *filter_,
171 const QString &contextItem_,
174 bool isCustomBufferView)
176 if(!indexList_.count())
179 setIndexList(indexList_);
180 setMessageFilter(filter_);
181 setContextItem(contextItem_);
182 setSlot(receiver_, method_);
184 if(!messageFilter()) {
185 // this means we are in a BufferView (or NickView) rather than a ChatView
187 // first index in list determines the menu type (just in case we have both buffers and networks selected, for example)
188 QModelIndex index = indexList().at(0);
189 NetworkModel::ItemType itemType = static_cast<NetworkModel::ItemType>(index.data(NetworkModel::ItemTypeRole).toInt());
192 case NetworkModel::NetworkItemType:
193 addNetworkItemActions(menu, index);
195 case NetworkModel::BufferItemType:
196 addBufferItemActions(menu, index, isCustomBufferView);
198 case NetworkModel::IrcUserItemType:
199 addIrcUserActions(menu, index);
207 if(contextItem().isEmpty()) {
208 // a) query buffer: handle like ircuser
209 // b) general chatview: handle like channel iff it displays a single buffer
210 // NOTE stuff breaks probably with merged buffers, need to rework a lot around here then
211 if(messageFilter()->containedBuffers().count() == 1) {
212 // we can handle this like a single bufferItem
213 QModelIndex index = Client::networkModel()->bufferIndex(messageFilter()->containedBuffers().values().at(0));
215 addBufferItemActions(menu, index);
218 // TODO: actions for merged buffers... _indexList contains the index of the message we clicked on
222 // context item = chan or nick, _indexList = buf where the msg clicked on originated
223 if(isChannelName(contextItem())) {
224 QModelIndex msgIdx = indexList().at(0);
225 if(!msgIdx.isValid())
227 NetworkId networkId = msgIdx.data(NetworkModel::NetworkIdRole).value<NetworkId>();
228 BufferId bufId = Client::networkModel()->bufferId(networkId, contextItem());
229 if(bufId.isValid()) {
230 QModelIndex targetIdx = Client::networkModel()->bufferIndex(bufId);
231 setIndexList(targetIdx);
232 addAction(BufferJoin, menu, targetIdx, InactiveState);
233 addAction(BufferSwitchTo, menu, targetIdx, ActiveState);
235 addAction(JoinChannel, menu);
237 // TODO: actions for a nick
243 void ContextMenuActionProvider::addNetworkItemActions(QMenu *menu, const QModelIndex &index) {
244 NetworkId networkId = index.data(NetworkModel::NetworkIdRole).value<NetworkId>();
245 if(!networkId.isValid())
247 const Network *network = Client::network(networkId);
248 Q_CHECK_PTR(network);
252 addAction(NetworkConnect, menu, network->connectionState() == Network::Disconnected);
253 addAction(NetworkDisconnect, menu, network->connectionState() != Network::Disconnected);
254 menu->addSeparator();
255 addAction(ShowChannelList, menu, index, ActiveState);
256 addAction(JoinChannel, menu, index, ActiveState);
260 void ContextMenuActionProvider::addBufferItemActions(QMenu *menu, const QModelIndex &index, bool isCustomBufferView) {
261 BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
263 menu->addSeparator();
264 switch(bufferInfo.type()) {
265 case BufferInfo::ChannelBuffer:
266 addAction(BufferJoin, menu, index, InactiveState);
267 addAction(BufferPart, menu, index, ActiveState);
268 menu->addSeparator();
269 addHideEventsMenu(menu, bufferInfo.bufferId());
270 menu->addSeparator();
271 addAction(HideBufferTemporarily, menu, isCustomBufferView);
272 addAction(HideBufferPermanently, menu, isCustomBufferView);
273 addAction(BufferRemove, menu, index, InactiveState);
276 case BufferInfo::QueryBuffer:
278 //IrcUser *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
280 addIrcUserActions(menu, index);
281 menu->addSeparator();
283 addHideEventsMenu(menu, bufferInfo.bufferId());
284 menu->addSeparator();
285 addAction(HideBufferTemporarily, menu, isCustomBufferView);
286 addAction(HideBufferPermanently, menu, isCustomBufferView);
287 addAction(BufferRemove, menu, index);
292 addAction(HideBufferTemporarily, menu, isCustomBufferView);
293 addAction(HideBufferPermanently, menu, isCustomBufferView);
297 void ContextMenuActionProvider::addIrcUserActions(QMenu *menu, const QModelIndex &index) {
298 // this can be called: a) as a nicklist context menu (index has IrcUserItemType)
299 // b) as a query buffer context menu (index has BufferItemType and is a QueryBufferItem)
300 // c) right-click in a query chatview (same as b), index will be the corresponding QueryBufferItem)
301 // d) right-click on some nickname (_contextItem will be non-null, _filter -> chatview, index -> message buffer)
303 if(contextItem().isNull()) {
305 bool haveQuery = indexList().count() == 1 && findQueryBuffer(index).isValid();
306 NetworkModel::ItemType itemType = static_cast<NetworkModel::ItemType>(index.data(NetworkModel::ItemTypeRole).toInt());
307 addAction(_nickModeMenuAction, menu, itemType == NetworkModel::IrcUserItemType);
308 addAction(_nickCtcpMenuAction, menu);
310 IrcUser *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
314 BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
315 if(bufferInfo.type() == BufferInfo::ChannelBuffer)
316 bufferName = bufferInfo.bufferName();
317 QMap<QString, bool> ignoreMap = Client::ignoreListManager()->matchingRulesForHostmask(ircUser->hostmask(), ircUser->network()->networkName(), bufferName);
318 addIgnoreMenu(menu, ircUser->hostmask(), ignoreMap);
319 // end of ignoreliststuff
321 menu->addSeparator();
322 addAction(NickQuery, menu, itemType == NetworkModel::IrcUserItemType && !haveQuery && indexList().count() == 1);
323 addAction(NickSwitchTo, menu, itemType == NetworkModel::IrcUserItemType && haveQuery);
324 menu->addSeparator();
325 addAction(NickWhois, menu, true);
327 } else if(!contextItem().isEmpty() && messageFilter()) {
334 Action * ContextMenuActionProvider::addAction(ActionType type , QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
335 return addAction(action(type), menu, checkRequirements(index, requiredActiveState));
338 Action * ContextMenuActionProvider::addAction(Action *action , QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
339 return addAction(action, menu, checkRequirements(index, requiredActiveState));
342 Action * ContextMenuActionProvider::addAction(ActionType type , QMenu *menu, bool condition) {
343 return addAction(action(type), menu, condition);
346 Action * ContextMenuActionProvider::addAction(Action *action , QMenu *menu, bool condition) {
348 menu->addAction(action);
349 action->setVisible(true);
351 action->setVisible(false);
356 void ContextMenuActionProvider::addHideEventsMenu(QMenu *menu, BufferId bufferId) {
357 if(BufferSettings(bufferId).hasFilter())
358 addHideEventsMenu(menu, BufferSettings(bufferId).messageFilter());
360 addHideEventsMenu(menu);
363 void ContextMenuActionProvider::addHideEventsMenu(QMenu *menu, MessageFilter *msgFilter) {
364 if(BufferSettings(msgFilter->idString()).hasFilter())
365 addHideEventsMenu(menu, BufferSettings(msgFilter->idString()).messageFilter());
367 addHideEventsMenu(menu);
370 void ContextMenuActionProvider::addHideEventsMenu(QMenu *menu, int filter) {
371 action(HideApplyToAll)->setEnabled(filter != -1);
372 action(HideUseDefaults)->setEnabled(filter != -1);
374 filter = BufferSettings().messageFilter();
376 action(HideJoin)->setChecked(filter & Message::Join);
377 action(HidePart)->setChecked(filter & Message::Part);
378 action(HideQuit)->setChecked(filter & Message::Quit);
379 action(HideNick)->setChecked(filter & Message::Nick);
380 action(HideMode)->setChecked(filter & Message::Mode);
381 action(HideDayChange)->setChecked(filter & Message::DayChange);
382 action(HideTopic)->setChecked(filter & Message::Topic);
384 menu->addAction(_hideEventsMenuAction);
387 void ContextMenuActionProvider::addIgnoreMenu(QMenu *menu, const QString &hostmask, const QMap<QString, bool> &ignoreMap) {
388 QMenu *ignoreMenu = _nickIgnoreMenuAction->menu();
390 QString nick = nickFromMask(hostmask);
391 QString ident = userFromMask(hostmask);
392 QString host = hostFromMask(hostmask);
393 QString domain = host;
394 QRegExp domainRx = QRegExp("(\\.[^.]+\\.\\w+)$");
395 if(domainRx.indexIn(host) != -1)
396 domain = domainRx.cap(1);
397 // we can't rely on who-data
398 // if we don't have the data, we skip actions where we would need it
399 bool haveWhoData = !ident.isEmpty() && !host.isEmpty();
402 ignoreMenu->addAction(tr("Add Ignore Rule"))->setEnabled(false);
405 action(NickIgnoreUser)->setText(QString("*!%1@%2").arg(ident, host));
406 action(NickIgnoreHost)->setText(QString("*!*@%1").arg(host));
407 action(NickIgnoreDomain)->setText(domain.at(0) == '.' ? QString("*!%1@*%2").arg(ident, domain)
408 : QString("*!%1@%2").arg(ident, domain));
410 if(!ignoreMap.contains(action(NickIgnoreUser)->text()))
411 ignoreMenu->addAction(action(NickIgnoreUser));
412 if(!ignoreMap.contains(action(NickIgnoreHost)->text()))
413 ignoreMenu->addAction(action(NickIgnoreHost));
414 if(!ignoreMap.contains(action(NickIgnoreDomain)->text()))
415 ignoreMenu->addAction(action(NickIgnoreDomain));
417 ignoreMenu->addAction(action(NickIgnoreCustom));
418 ignoreMenu->addSeparator();
421 QMap<QString, bool>::const_iterator ruleIter = ignoreMap.begin();
423 if(!ignoreMap.isEmpty())
424 ignoreMenu->addAction(tr("Existing Rules"))->setEnabled(false);
425 while(ruleIter != ignoreMap.constEnd()) {
427 ActionType type = static_cast<ActionType>(NickIgnoreToggleEnabled0 + counter*0x100000);
428 Action *act = action(type);
429 act->setText(ruleIter.key());
430 act->setChecked(ruleIter.value());
431 ignoreMenu->addAction(act);
437 ignoreMenu->addSeparator();
439 ignoreMenu->addAction(action(ShowIgnoreList));
440 addAction(_nickIgnoreMenuAction, menu);