1 /*
2     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
3 
4     SPDX-FileCopyrightText: 2010 Eike Hein <hein@kde.org>
5 */
6 
7 #include "irccontextmenus.h"
8 #include "application.h"
9 #include "nick.h"
10 #include "server.h"
11 #include "ircinput.h"
12 
13 #include <QClipboard>
14 
15 #include <KActionCollection>
16 #include <KAuthorized>
17 #include <KBookmarkDialog>
18 #include <KBookmarkManager>
19 #include <QFileDialog>
20 #include <KIO/CopyJob>
21 #include <KIO/ApplicationLauncherJob>
22 #include <KIO/JobUiDelegate>
23 #include <QMenu>
24 #include <KMessageBox>
25 #include <KStandardAction>
26 #include <KStringHandler>
27 #include <KToggleAction>
28 
29 // For the Web Shortcuts context menu sub-menu.
30 #include <KIO/CommandLauncherJob>
31 #include <KUriFilter>
32 
33 
34 class IrcContextMenusPrivate
35 {
36     public:
37         IrcContextMenusPrivate();
38         ~IrcContextMenusPrivate();
39 
40         IrcContextMenus instance;
41 };
42 
Q_GLOBAL_STATIC(IrcContextMenusPrivate,s_ircContextMenusPrivate)43 Q_GLOBAL_STATIC(IrcContextMenusPrivate, s_ircContextMenusPrivate)
44 
45 IrcContextMenusPrivate::IrcContextMenusPrivate()
46 {
47 }
48 
~IrcContextMenusPrivate()49 IrcContextMenusPrivate::~IrcContextMenusPrivate()
50 {
51 }
52 
IrcContextMenus()53 IrcContextMenus::IrcContextMenus() : QObject()
54 {
55 }
56 
setupUi(QWidget * parent)57 void IrcContextMenus::setupUi(QWidget* parent)
58 {
59     self()->doSetupUi(parent);
60 }
61 
doSetupUi(QWidget * parent)62 void IrcContextMenus::doSetupUi(QWidget* parent)
63 {
64     m_parent = parent;
65     createSharedBasicNickActions();
66     createSharedNickSettingsActions();
67     createSharedDccActions();
68 
69     setupQuickButtonMenu();
70     setupNickMenu();
71     setupTextMenu(); // creates m_textCopyAction needed by the channel menu
72     setupChannelMenu();
73     setupTopicHistoryMenu();
74 
75     updateQuickButtonMenu();
76 }
77 
self()78 IrcContextMenus* IrcContextMenus::self()
79 {
80     return &s_ircContextMenusPrivate->instance;
81 }
82 
setupQuickButtonMenu()83 void IrcContextMenus::setupQuickButtonMenu()
84 {
85     //NOTE: if we depend on m_nickMenu we get an we an cyclic initialising
86     m_quickButtonMenu = new QMenu();
87     m_quickButtonMenu->setTitle(i18n("Quick Buttons"));
88     connect(Application::instance(), &Application::appearanceChanged, this, &IrcContextMenus::updateQuickButtonMenu);
89 }
90 
shouldShowQuickButtonMenu()91 bool IrcContextMenus::shouldShowQuickButtonMenu()
92 {
93     return Preferences::self()->showQuickButtonsInContextMenu() && !m_quickButtonMenu->isEmpty();
94 }
95 
updateQuickButtonMenu()96 void IrcContextMenus::updateQuickButtonMenu()
97 {
98     m_quickButtonMenu->clear();
99 
100     QAction * action;
101     QString pattern;
102 
103     const auto buttons = Preferences::quickButtonList();
104     for (const QString& button : buttons) {
105         pattern = button.section(QLatin1Char(','), 1);
106 
107         if (pattern.contains(QLatin1String("%u")))
108         {
109             action = new QAction(button.section(QLatin1Char(','), 0, 0), m_quickButtonMenu);
110             action->setData(pattern);
111             m_quickButtonMenu->addAction(action);
112         }
113     }
114 }
115 
processQuickButtonAction(QAction * action,Server * server,const QString & context,const QStringList & nicks)116 void IrcContextMenus::processQuickButtonAction(QAction* action, Server* server, const QString& context, const QStringList &nicks)
117 {
118     ChatWindow* chatWindow = server->getChannelOrQueryByName(context);
119     QString line = server->parseWildcards(action->data().toString(), chatWindow, nicks);
120 
121     if (line.contains(QLatin1Char('\n')))
122         chatWindow->sendText(line);
123     else
124     {
125         if (chatWindow->getInputBar())
126             chatWindow->getInputBar()->setText(line, true);
127     }
128 }
129 
setupTextMenu()130 void IrcContextMenus::setupTextMenu()
131 {
132     m_textMenu = new QMenu(m_parent);
133 
134     m_textMenu->addSeparator();
135 
136     m_linkActions << createAction(m_textMenu, LinkCopy, QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link Address"));
137     // Not using KStandardAction is intentional here since the Ctrl+B
138     // shortcut it would show in the menu is already used by our IRC-
139     // wide bookmarking feature.
140     m_linkActions << createAction(m_textMenu, LinkBookmark, QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add to Bookmarks"));
141     m_linkActions << createAction(m_textMenu, LinkOpenWith, i18n("Open With..."));
142     m_linkActions << createAction(m_textMenu, LinkSaveAs, QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save Link As..."));
143 
144     m_textMenu->addSeparator();
145 
146     m_textCopyAction = KStandardAction::copy(nullptr, nullptr, this);
147     m_textCopyAction->setData(TextCopy);
148     m_textMenu->addAction(m_textCopyAction);
149     m_textCopyAction->setEnabled(false);
150 
151     QAction* action = KStandardAction::selectAll(nullptr, nullptr, this);
152     action->setData(TextSelectAll);
153     m_textMenu->addAction(action);
154 
155     m_webShortcutsMenu = new QMenu(m_parent);
156     m_webShortcutsMenu->menuAction()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
157     m_webShortcutsMenu->menuAction()->setVisible(false);
158     m_textMenu->addMenu(m_webShortcutsMenu);
159 
160     m_textActionsSeparator = m_textMenu->addSeparator();
161 
162     for (QAction* action : qAsConst(m_sharedBasicNickActions))
163         m_textMenu->addAction(action);
164 
165     m_textMenu->addSeparator();
166 
167     m_textMenu->addMenu(m_quickButtonMenu);
168 
169     m_textMenu->addSeparator();
170 
171     for (QAction* action : qAsConst(m_sharedNickSettingsActions))
172         m_textMenu->addAction(action);
173 
174     m_textMenu->addSeparator();
175 
176     for (QAction* action : qAsConst(m_sharedDccActions))
177         m_textMenu->addAction(action);
178 
179     m_textMenu->addSeparator();
180 }
181 
textMenu(const QPoint & pos,MenuOptions options,Server * server,const QString & selectedText,const QString & link,const QString & nick)182 int IrcContextMenus::textMenu(const QPoint& pos, MenuOptions options, Server* server,
183     const QString& selectedText, const QString& link, const QString& nick)
184 {
185     QMenu* textMenu = self()->m_textMenu;
186 
187     KActionCollection* actionCollection = Application::instance()->getMainWindow()->actionCollection();
188 
189     auto* toggleMenuBarAction = qobject_cast<KToggleAction*>(actionCollection->action(QStringLiteral("options_show_menubar")));
190 
191     if (toggleMenuBarAction && !toggleMenuBarAction->isChecked())
192         textMenu->insertAction(textMenu->actions().first(), toggleMenuBarAction);
193 
194     bool showLinkActions = options.testFlag(ShowLinkActions);
195 
196     for (QAction* action : qAsConst(self()->m_linkActions))
197         action->setVisible(showLinkActions);
198 
199     self()->m_textCopyAction->setEnabled(!selectedText.isEmpty());
200 
201     self()->updateWebShortcutsMenu(selectedText);
202 
203     bool showNickActions = options.testFlag(ShowNickActions);
204 
205     for (QAction* action : qAsConst(self()->m_sharedBasicNickActions))
206         action->setVisible(showNickActions);
207 
208     self()->m_quickButtonMenu->menuAction()->setVisible(showNickActions && self()->shouldShowQuickButtonMenu());
209 
210     if (showNickActions)
211     {
212         bool connected = server->isConnected();
213 
214         for (QAction* action : qAsConst(self()->m_sharedBasicNickActions))
215             action->setEnabled(connected);
216 
217         updateSharedNickSettingsActions(server, QStringList { nick });
218 
219         for (QAction* action : qAsConst(self()->m_sharedDccActions))
220             action->setEnabled(connected);
221     }
222     else
223     {
224         for (QAction* action : qAsConst(self()->m_sharedNickSettingsActions))
225             action->setVisible(false);
226     }
227 
228     for (QAction* action : qAsConst(self()->m_sharedDccActions))
229         action->setVisible(showNickActions);
230 
231     if (options.testFlag(ShowFindAction))
232         textMenu->insertAction(self()->m_textActionsSeparator, actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find))));
233 
234     if (options.testFlag(ShowLogAction))
235         textMenu->addAction(actionCollection->action(QStringLiteral("open_logfile")));
236 
237     if (options.testFlag(ShowChannelActions))
238         textMenu->addAction(actionCollection->action(QStringLiteral("channel_settings")));
239 
240     QAction* action = textMenu->exec(pos);
241 
242     int actionId = extractActionId(action);
243 
244     if (showLinkActions)
245         processLinkAction(actionId, link);
246 
247     if (self()->m_quickButtonMenu->actions().contains(action))
248         processQuickButtonAction(action, server, nick, QStringList { nick });
249 
250     textMenu->removeAction(toggleMenuBarAction);
251     textMenu->removeAction(actionCollection->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Find))));
252     textMenu->removeAction(actionCollection->action(QStringLiteral("open_logfile")));
253     textMenu->removeAction(actionCollection->action(QStringLiteral("channel_settings")));
254 
255     return actionId;
256 }
257 
updateWebShortcutsMenu(const QString & selectedText)258 void IrcContextMenus::updateWebShortcutsMenu(const QString& selectedText)
259 {
260     m_webShortcutsMenu->menuAction()->setVisible(false);
261     m_webShortcutsMenu->clear();
262 
263     if (selectedText.isEmpty())
264         return;
265 
266     QString searchText = selectedText;
267     searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified();
268 
269     if (searchText.isEmpty())
270         return;
271 
272     KUriFilterData filterData(searchText);
273 
274     filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);
275 
276     if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter))
277     {
278         const QStringList searchProviders = filterData.preferredSearchProviders();
279 
280         if (!searchProviders.isEmpty())
281         {
282             m_webShortcutsMenu->setTitle(i18n("Search for '%1' with",  KStringHandler::rsqueeze(searchText, 21)));
283 
284             QAction * action = nullptr;
285 
286             for (const QString& searchProvider : searchProviders) {
287                 action = new QAction(searchProvider, m_webShortcutsMenu);
288                 action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
289                 action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
290                 connect(action, &QAction::triggered, this, &IrcContextMenus::processWebShortcutAction);
291                 m_webShortcutsMenu->addAction(action);
292             }
293 
294             m_webShortcutsMenu->addSeparator();
295 
296             action = new QAction(i18n("Configure Web Shortcuts..."), m_webShortcutsMenu);
297             action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
298             connect(action, &QAction::triggered, this, &IrcContextMenus::configureWebShortcuts);
299             m_webShortcutsMenu->addAction(action);
300 
301             m_webShortcutsMenu->menuAction()->setVisible(true);
302         }
303     }
304 }
305 
processWebShortcutAction()306 void IrcContextMenus::processWebShortcutAction()
307 {
308     auto * action = qobject_cast<QAction*>(sender());
309 
310     if (action)
311     {
312         KUriFilterData filterData(action->data().toString());
313 
314         if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter))
315             Application::openUrl(filterData.uri().url());
316     }
317 }
318 
configureWebShortcuts()319 void IrcContextMenus::configureWebShortcuts()
320 {
321     auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), {QStringLiteral("webshortcuts")});
322     job->start();
323 }
324 
setupChannelMenu()325 void IrcContextMenus::setupChannelMenu()
326 {
327     m_channelMenu = new QMenu(m_parent);
328 
329     QAction* defaultAction = createAction(m_channelMenu, Join, QIcon::fromTheme(QStringLiteral("irc-join-channel")), i18n("&Join Channel..."));
330     m_channelMenu->setDefaultAction(defaultAction);
331 
332     createAction(m_channelMenu, Topic, i18n("Get &topic"));
333     createAction(m_channelMenu, Names, i18n("Get &user list"));
334 
335     m_channelMenu->addAction(m_textCopyAction);
336 }
337 
channelMenu(const QPoint & pos,Server * server,const QString & channel)338 void IrcContextMenus::channelMenu(const QPoint& pos, Server* server, const QString& channel)
339 {
340     QMenu* channelMenu = self()->m_channelMenu;
341 
342     if (!channel.isEmpty())
343         channelMenu->setTitle(KStringHandler::rsqueeze(channel, 15));
344 
345     bool connected = server->isConnected();
346 
347     const auto channelMenuActions = channelMenu->actions();
348     for (QAction* action : channelMenuActions)
349         action->setEnabled(connected);
350 
351     QAction* action = channelMenu->exec(pos);
352 
353     self()->m_textCopyAction->setEnabled(false);
354 
355     switch (extractActionId(action))
356     {
357         case Join:
358             commandToServer(server, QLatin1String("join ") + channel);
359             break;
360         case Topic:
361             server->requestTopic(channel);
362             break;
363         case Names:
364             commandToServer(server, QLatin1String("names ") + channel);
365             break;
366         case TextCopy:
367             qApp->clipboard()->setText(channel, QClipboard::Clipboard);
368             break;
369         default:
370             break;
371     }
372 }
373 
setupNickMenu()374 void IrcContextMenus::setupNickMenu()
375 {
376     m_nickMenu = new QMenu(m_parent);
377 
378     QAction* defaultAction = createAction(m_nickMenu, OpenQuery, i18n("Open Query"));
379     m_nickMenu->setDefaultAction(defaultAction);
380 
381     m_nickMenu->addSeparator();
382 
383     for (QAction* action : qAsConst(m_sharedBasicNickActions))
384         m_nickMenu->addAction(action);
385 
386     m_nickMenu->addSeparator();
387 
388     m_modesMenu = new QMenu(m_parent);
389     m_nickMenu->addMenu(m_modesMenu);
390     m_modesMenu->setTitle(i18n("Modes"));
391     createAction(m_modesMenu, GiveOp, QIcon::fromTheme(QStringLiteral("irc-operator")), i18n("Give Op"));
392     createAction(m_modesMenu, TakeOp, QIcon::fromTheme(QStringLiteral("irc-remove-operator")), i18n("Take Op"));
393     createAction(m_modesMenu, GiveHalfOp, i18n("Give HalfOp"));
394     createAction(m_modesMenu, TakeHalfOp, i18n("Take HalfOp"));
395     createAction(m_modesMenu, GiveVoice, QIcon::fromTheme(QStringLiteral("irc-voice")), i18n("Give Voice"));
396     createAction(m_modesMenu, TakeVoice, QIcon::fromTheme(QStringLiteral("irc-unvoice")), i18n("Take Voice"));
397 
398     m_kickBanMenu = new QMenu(m_parent);
399     m_nickMenu->addMenu(m_kickBanMenu);
400     m_kickBanMenu->setTitle(i18n("Kick / Ban"));
401     createAction(m_kickBanMenu, Kick, i18n("Kick"));
402     createAction(m_kickBanMenu, KickBan, i18n("Kickban"));
403     createAction(m_kickBanMenu, BanNick, i18n("Ban Nickname"));
404     m_kickBanMenu->addSeparator();
405     createAction(m_kickBanMenu, BanHost, i18n("Ban *!*@*.host"));
406     createAction(m_kickBanMenu, BanDomain, i18n("Ban *!*@domain"));
407     createAction(m_kickBanMenu, BanUserHost, i18n("Ban *!user@*.host"));
408     createAction(m_kickBanMenu, BanUserDomain, i18n("Ban *!user@domain"));
409     m_kickBanMenu->addSeparator();
410     createAction(m_kickBanMenu, KickBanHost, i18n("Kickban *!*@*.host"));
411     createAction(m_kickBanMenu, KickBanDomain, i18n("Kickban *!*@domain"));
412     createAction(m_kickBanMenu, KickBanUserHost, i18n("Kickban *!user@*.host"));
413     createAction(m_kickBanMenu, KickBanUserDomain, i18n("Kickban *!user@domain"));
414 
415     m_nickMenu->addMenu(m_quickButtonMenu);
416 
417     m_nickMenu->addSeparator();
418 
419     for (QAction* action : qAsConst(m_sharedNickSettingsActions))
420         m_nickMenu->addAction(action);
421 
422     m_nickMenu->addSeparator();
423 
424     for (QAction* action : qAsConst(m_sharedDccActions))
425         m_nickMenu->addAction(action);
426 }
427 
createSharedBasicNickActions()428 void IrcContextMenus::createSharedBasicNickActions()
429 {
430     m_sharedBasicNickActions << createAction(Whois, i18n("&Whois"));
431     m_sharedBasicNickActions << createAction(Version, i18n("&Version"));
432     m_sharedBasicNickActions << createAction(Ping, i18n("&Ping"));
433 }
434 
createSharedNickSettingsActions()435 void IrcContextMenus::createSharedNickSettingsActions()
436 {
437     m_ignoreAction = createAction(IgnoreNick, i18n("Ignore"));
438     m_sharedNickSettingsActions << m_ignoreAction;
439     m_unignoreAction = createAction(UnignoreNick, i18n("Unignore"));
440     m_sharedNickSettingsActions << m_unignoreAction;
441 
442     m_addNotifyAction = createAction(AddNotify, QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("Add to Watched Nicks"));
443     m_sharedNickSettingsActions << m_addNotifyAction;
444     m_removeNotifyAction = createAction(RemoveNotify, QIcon::fromTheme(QStringLiteral("list-remove-user")), i18n("Remove From Watched Nicks"));
445     m_sharedNickSettingsActions << m_removeNotifyAction;
446 }
447 
createSharedDccActions()448 void IrcContextMenus::createSharedDccActions()
449 {
450     if (KAuthorized::authorizeAction(QStringLiteral("allow_downloading")))
451         m_sharedDccActions << createAction(DccSend, QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("Send &File..."));
452 
453     m_sharedDccActions << createAction(StartDccChat, i18n("Open DCC Chat"));
454     m_sharedDccActions << createAction(StartDccWhiteboard, i18n("Open DCC Whiteboard"));
455 }
456 
nickMenu(const QPoint & pos,MenuOptions options,Server * server,const QStringList & nicks,const QString & context)457 void IrcContextMenus::nickMenu(const QPoint& pos, MenuOptions options, Server* server,
458     const QStringList& nicks, const QString& context)
459 {
460     QMenu* nickMenu = self()->m_nickMenu;
461 
462     if (options.testFlag(ShowTitle) && nicks.count() == 1)
463         nickMenu->setTitle(KStringHandler::rsqueeze(nicks.first(), 15));
464 
465     const auto nickMenuActions = nickMenu->actions();
466     for (QAction* action : nickMenuActions)
467         action->setVisible(true);
468 
469     self()->m_modesMenu->menuAction()->setVisible(options.testFlag(ShowChannelActions));
470     self()->m_kickBanMenu->menuAction()->setVisible(options.testFlag(ShowChannelActions));
471     self()->m_quickButtonMenu->menuAction()->setVisible(self()->shouldShowQuickButtonMenu());
472 
473     bool connected = server->isConnected();
474 
475     for (QAction* action : qAsConst(self()->m_sharedBasicNickActions))
476         action->setEnabled(connected);
477 
478     for (QAction* action : qAsConst(self()->m_sharedDccActions))
479         action->setEnabled(connected);
480 
481     self()->m_modesMenu->menuAction()->setEnabled(connected);
482     self()->m_kickBanMenu->menuAction()->setEnabled(connected);
483 
484     updateSharedNickSettingsActions(server, nicks);
485 
486     QAction* action = nickMenu->exec(pos);
487 
488     if (self()->m_quickButtonMenu->actions().contains(action))
489         processQuickButtonAction(action, server, context, nicks);
490     else
491         processNickAction(extractActionId(action), server, nicks, context);
492 }
493 
processNickAction(int actionId,Server * server,const QStringList & nicks,const QString & context)494 void IrcContextMenus::processNickAction(int actionId, Server* server, const QStringList& nicks,
495     const QString& context)
496 {
497     QString channel;
498 
499     if (server->getChannelByName(context))
500         channel = context;
501 
502     QString pattern;
503     QChar mode;
504 
505     switch (actionId)
506     {
507         case OpenQuery:
508             commandToServer(server, QStringLiteral("query %1"), nicks);
509             break;
510         case Whois:
511             commandToServer(server, QStringLiteral("whois %1 %1"), nicks);
512             break;
513         case Version:
514             commandToServer(server, QStringLiteral("ctcp %1 VERSION"), nicks);
515             break;
516         case Ping:
517             commandToServer(server, QStringLiteral("ctcp %1 PING"), nicks);
518             break;
519         case GiveOp:
520             if (channel.isEmpty()) break;
521             pattern = QStringLiteral("MODE %c +%m %l");
522             mode = QLatin1Char('o');
523             break;
524         case TakeOp:
525             if (channel.isEmpty()) break;
526             pattern = QStringLiteral("MODE %c -%m %l");
527             mode = QLatin1Char('o');
528             break;
529         case GiveHalfOp:
530             if (channel.isEmpty()) break;
531             pattern = QStringLiteral("MODE %c +%m %l");
532             mode = QLatin1Char('h');
533             break;
534         case TakeHalfOp:
535             if (channel.isEmpty()) break;
536             pattern = QStringLiteral("MODE %c -%m %l");
537             mode = QLatin1Char('h');
538             break;
539         case GiveVoice:
540             if (channel.isEmpty()) break;
541             pattern = QStringLiteral("MODE %c +%m %l");
542             mode = QLatin1Char('v');
543             break;
544         case TakeVoice:
545             if (channel.isEmpty()) break;
546             pattern = QStringLiteral("MODE %c -%m %l");
547             mode = QLatin1Char('v');
548             break;
549         case Kick:
550             commandToServer(server, QStringLiteral("kick %1"), nicks, channel);
551             break;
552         case KickBan:
553             commandToServer(server, QStringLiteral("kickban %1"), nicks, channel);
554             break;
555         case BanNick:
556             commandToServer(server, QStringLiteral("ban %1"), nicks, channel);
557             break;
558         case BanHost:
559             commandToServer(server, QStringLiteral("ban -HOST %1"), nicks, channel);
560             break;
561         case BanDomain:
562             commandToServer(server, QStringLiteral("ban -DOMAIN %1"), nicks, channel);
563             break;
564         case BanUserHost:
565             commandToServer(server, QStringLiteral("ban -USERHOST %1"), nicks, channel);
566             break;
567         case BanUserDomain:
568             commandToServer(server, QStringLiteral("ban -USERDOMAIN %1"), nicks, channel);
569             break;
570         case KickBanHost:
571             commandToServer(server, QStringLiteral("kickban -HOST %1"), nicks, channel);
572             break;
573         case KickBanDomain:
574             commandToServer(server, QStringLiteral("kickban -DOMAIN %1"), nicks, channel);
575             break;
576         case KickBanUserHost:
577             commandToServer(server, QStringLiteral("kickban -USERHOST %1"), nicks, channel);
578             break;
579         case KickBanUserDomain:
580             commandToServer(server, QStringLiteral("kickban -USERDOMAIN %1"), nicks, channel);
581             break;
582         case IgnoreNick:
583         {
584             QString question;
585 
586             if (nicks.size() == 1)
587                 question = i18n("Do you want to ignore %1?", nicks.first());
588             else
589                 question = i18n("Do you want to ignore the selected users?");
590 
591             if (KMessageBox::warningContinueCancel(
592                 Application::instance()->getMainWindow(),
593                 question,
594                 i18n("Ignore"),
595                 KGuiItem(i18n("Ignore")),
596                 KStandardGuiItem::cancel(),
597                 QStringLiteral("IgnoreNick")
598                 ) ==
599                 KMessageBox::Continue)
600             {
601                 commandToServer(server, QLatin1String("ignore -ALL ") + nicks.join(QLatin1Char(' ')));
602             }
603 
604             break;
605         }
606         case UnignoreNick:
607         {
608             QString question;
609             QStringList selectedIgnoredNicks;
610 
611             for (const QString& nick : nicks) {
612                 if (Preferences::isIgnored(nick))
613                     selectedIgnoredNicks << nick;
614             }
615 
616             if (selectedIgnoredNicks.count() == 1)
617                 question = i18n("Do you want to stop ignoring %1?", selectedIgnoredNicks.first());
618             else
619                 question = i18n("Do you want to stop ignoring the selected users?");
620 
621             if (KMessageBox::warningContinueCancel(
622                 Application::instance()->getMainWindow(),
623                 question,
624                 i18n("Unignore"),
625                 KGuiItem(i18n("Unignore")),
626                 KStandardGuiItem::cancel(),
627                 QStringLiteral("UnignoreNick")) ==
628                 KMessageBox::Continue)
629             {
630                 commandToServer(server, QLatin1String("unignore ") + selectedIgnoredNicks.join(QLatin1Char(' ')));
631             }
632 
633             break;
634         }
635         case AddNotify:
636         {
637             if (!server->getServerGroup()) break;
638 
639             for(const QString& nick : nicks)
640                 Preferences::addNotify(server->getServerGroup()->id(), nick);
641 
642             break;
643         }
644         case RemoveNotify:
645         {
646             if (!server->getServerGroup()) break;
647 
648             for(const QString& nick : nicks)
649                 Preferences::removeNotify(server->getServerGroup()->id(), nick);
650 
651             break;
652         }
653         case DccSend:
654             commandToServer(server, QStringLiteral("dcc send %1"), nicks);
655             break;
656         case StartDccChat:
657             commandToServer(server, QStringLiteral("dcc chat %1"), nicks);
658             break;
659         case StartDccWhiteboard:
660             commandToServer(server, QStringLiteral("dcc whiteboard %1"), nicks);
661             break;
662         default:
663             break;
664     }
665 
666     if (!pattern.isEmpty())
667     {
668         pattern.replace(QLatin1String("%c"), channel);
669 
670         QString command;
671         QStringList partialList;
672         int modesCount = server->getModesCount();
673 
674         for (int index = 0; index < nicks.count(); index += modesCount)
675         {
676             command = pattern;
677             partialList = nicks.mid(index, modesCount);
678             command.replace(QLatin1String("%l"), partialList.join(QLatin1Char(' ')));
679             const QString repeatedMode = QString(partialList.count(), mode);
680 
681             command.replace(QLatin1String("%m"), repeatedMode);
682 
683             server->queue(command);
684         }
685     }
686 }
687 
updateSharedNickSettingsActions(Server * server,const QStringList & nicks)688 void IrcContextMenus::updateSharedNickSettingsActions(Server* server, const QStringList& nicks)
689 {
690     int ignoreCounter = 0;
691     int unignoreCounter = 0;
692     int addNotifyCounter = 0;
693     int removeNotifyCounter = 0;
694 
695     int serverGroupId = -1;
696 
697     if (server->getServerGroup())
698         serverGroupId = server->getServerGroup()->id();
699 
700     for (const QString& nick : nicks) {
701         if (Preferences::isIgnored(nick))
702             ++unignoreCounter;
703         else
704             ++ignoreCounter;
705 
706         if (serverGroupId != -1)
707         {
708             if (Preferences::isNotify(serverGroupId, nick))
709                 ++removeNotifyCounter;
710             else
711                 ++addNotifyCounter;
712         }
713     }
714 
715     self()->m_ignoreAction->setVisible(ignoreCounter);
716     self()->m_unignoreAction->setVisible(unignoreCounter);
717 
718     self()->m_addNotifyAction->setVisible(serverGroupId == -1 || addNotifyCounter);
719     self()->m_addNotifyAction->setEnabled(serverGroupId != -1);
720     self()->m_removeNotifyAction->setVisible(removeNotifyCounter);
721 }
722 
processLinkAction(int actionId,const QString & link)723 void IrcContextMenus::processLinkAction(int  actionId, const QString& link)
724 {
725    if (actionId == -1 || link.isEmpty())
726        return;
727 
728     switch (actionId)
729     {
730         case LinkCopy:
731         {
732             QClipboard* clipboard = qApp->clipboard();
733             clipboard->setText(link, QClipboard::Selection);
734             clipboard->setText(link, QClipboard::Clipboard);
735 
736             break;
737         }
738         case LinkBookmark:
739         {
740             KBookmarkManager* manager = KBookmarkManager::userBookmarksManager();
741             auto* dialog = new KBookmarkDialog(manager, Application::instance()->getMainWindow());
742 
743             dialog->addBookmark(link, QUrl(link), QString());
744 
745             delete dialog;
746 
747             break;
748         }
749         case LinkOpenWith:
750         {
751             // ApplicationLauncherJob ctor without args will invoke the open-with dialog
752             auto *job = new KIO::ApplicationLauncherJob();
753             job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, Application::instance()->getMainWindow()));
754             job->setUrls({ QUrl(link) });
755             job->start();
756 
757             break;
758         }
759         case LinkSaveAs:
760         {
761             QUrl srcUrl(link);
762 
763             QUrl saveUrl = QFileDialog::getSaveFileUrl(Application::instance()->getMainWindow(), i18n("Save link as"), QUrl::fromLocalFile(srcUrl.fileName()));
764 
765             if (saveUrl.isEmpty() || !saveUrl.isValid())
766                 break;
767 
768             KIO::copy(srcUrl, saveUrl);
769             break;
770         }
771         default:
772             break;
773     }
774 }
775 
setupTopicHistoryMenu()776 void IrcContextMenus::setupTopicHistoryMenu()
777 {
778     m_topicHistoryMenu = new QMenu(m_parent);
779 
780     m_topicHistoryMenu->addAction(m_textCopyAction);
781 
782     m_queryTopicAuthorAction = createAction(m_topicHistoryMenu, OpenQuery, i18n("Query author"));
783 }
784 
topicHistoryMenu(const QPoint & pos,Server * server,const QString & text,const QString & author)785 void IrcContextMenus::topicHistoryMenu(const QPoint& pos, Server* server, const QString& text, const QString& author)
786 {
787     QMenu* topicHistoryMenu = self()->m_topicHistoryMenu;
788 
789     self()->m_textCopyAction->setEnabled(true);
790     self()->m_queryTopicAuthorAction->setEnabled(!author.isEmpty());
791 
792     QAction* action = topicHistoryMenu->exec(pos);
793 
794     switch (extractActionId(action))
795     {
796         case TextCopy:
797             qApp->clipboard()->setText(text, QClipboard::Clipboard);
798             break;
799         case OpenQuery:
800             commandToServer(server, QStringLiteral("query %1").arg(author));
801             break;
802         default:
803             break;
804     }
805 }
806 
createAction(ActionId id,const QString & text)807 QAction* IrcContextMenus::createAction(ActionId id, const QString& text)
808 {
809     auto* action = new QAction(text, m_parent);
810 
811     action->setData(id);
812 
813     return action;
814 }
815 
createAction(ActionId id,const QIcon & icon)816 QAction* IrcContextMenus::createAction(ActionId id, const QIcon& icon)
817 {
818     auto* action = new QAction(m_parent);
819 
820     action->setData(id);
821     action->setIcon(icon);
822 
823     return action;
824 }
825 
createAction(ActionId id,const QIcon & icon,const QString & text)826 QAction* IrcContextMenus::createAction(ActionId id, const QIcon& icon, const QString& text)
827 {
828     auto* action = new QAction(icon, text, m_parent);
829 
830     action->setData(id);
831 
832     return action;
833 }
834 
createAction(QMenu * menu,ActionId id,const QString & text)835 QAction* IrcContextMenus::createAction(QMenu* menu, ActionId id, const QString& text)
836 {
837     QAction* action = createAction(id, text);
838 
839     menu->addAction(action);
840 
841     return action;
842 }
843 
createAction(QMenu * menu,ActionId id,const QIcon & icon,const QString & text)844 QAction* IrcContextMenus::createAction(QMenu* menu, ActionId id, const QIcon& icon, const QString& text)
845 {
846     QAction* action = createAction(id, text);
847 
848     action->setIcon(icon);
849 
850     menu->addAction(action);
851 
852     return action;
853 }
854 
extractActionId(QAction * action)855 int IrcContextMenus::extractActionId(QAction* action)
856 {
857     if (action)
858     {
859         bool ok = false;
860 
861         int actionId = action->data().toInt(&ok);
862 
863         if (ok)
864             return actionId;
865     }
866 
867     return -1;
868 }
869 
commandToServer(Server * server,const QString & command,const QString & destination)870 void IrcContextMenus::commandToServer(Server* server, const QString& command, const QString& destination)
871 {
872     const Konversation::OutputFilterResult result = server->getOutputFilter()->parse(QString(), Preferences::self()->commandChar() + command, destination);
873 
874     server->queue(result.toServer);
875 
876     if (!result.output.isEmpty())
877         server->appendMessageToFrontmost(result.typeString, result.output);
878     else if (!result.outputList.isEmpty())
879     {
880         for (const QString& output : result.outputList)
881             server->appendMessageToFrontmost(result.typeString, output);
882     }
883 }
884 
commandToServer(Server * server,const QString & command,const QStringList & arguments,const QString & destination)885 void IrcContextMenus::commandToServer(Server* server, const QString& command,
886     const QStringList& arguments, const QString& destination)
887 {
888     for (const QString& argument : arguments)
889         commandToServer(server, command.arg(argument), destination);
890 }
891