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