1 /*
2 SPDX-License-Identifier: GPL-2.0-or-later
3
4 SPDX-FileCopyrightText: 2002 Dario Abatianni <eisfuchs@tigress.com>
5 SPDX-FileCopyrightText: 2005 Ismail Donmez <ismail@kde.org>
6 SPDX-FileCopyrightText: 2005 Peter Simonsson <psn@linux.se>
7 SPDX-FileCopyrightText: 2005 John Tapsell <johnflux@gmail.com>
8 SPDX-FileCopyrightText: 2005-2008 Eike Hein <hein@kde.org>
9 */
10
11 #include "application.h"
12
13 #include "connectionmanager.h"
14 #include "scriptlauncher.h"
15 #include "transfermanager.h"
16 #include "viewcontainer.h"
17 #include "trayicon.h"
18 #include "urlcatcher.h"
19 #include "highlight.h"
20 #include "sound.h"
21 #include "quickconnectdialog.h"
22 #include "dbus.h"
23 #include "servergroupsettings.h"
24 #include "serversettings.h"
25 #include "channel.h"
26 #include "images.h"
27 #include "notificationhandler.h"
28 #include "awaymanager.h"
29 #include "konversation_log.h"
30 #include "config-konversation.h"
31
32 #include <KIO/JobUiDelegate>
33 #include <KIO/OpenUrlJob>
34 #include <KConfig>
35 #include <KShell>
36 #include <KMacroExpander>
37 #include <KWallet>
38 #include <KTextEdit>
39 #include <KSharedConfig>
40 #include <KStartupInfo>
41 #include <KWindowSystem>
42 #include <kwindowsystem_version.h>
43
44 #include <QRegularExpression>
45 #include <QDBusConnection>
46 #include <QNetworkProxy>
47 #include <QStandardItemModel>
48 #include <QFileInfo>
49 #include <QTextCursor>
50 #include <QDesktopServices>
51 #include <QCommandLineParser>
52 #if HAVE_X11
53 #include <QX11Info>
54 #endif
55
56 using namespace Konversation;
57
Application(int & argc,char ** argv)58 Application::Application(int &argc, char **argv)
59 : QApplication(argc, argv)
60 {
61 mainWindow = nullptr;
62 m_restartScheduled = false;
63 m_connectionManager = nullptr;
64 m_awayManager = nullptr;
65 m_scriptLauncher = nullptr;
66 quickConnectDialog = nullptr;
67 m_osd = nullptr;
68 m_wallet = nullptr;
69 m_images = nullptr;
70 m_sound = nullptr;
71 m_dccTransferManager = nullptr;
72 m_notificationHandler = nullptr;
73 m_urlModel = nullptr;
74 dbusObject = nullptr;
75 identDBus = nullptr;
76 m_networkConfigurationManager = nullptr;
77 }
78
~Application()79 Application::~Application()
80 {
81 qCDebug(KONVERSATION_LOG) << __FUNCTION__;
82
83 if (!m_images)
84 return; // Nothing to do, newInstance() has never been called.
85
86 stashQueueRates();
87 Preferences::self()->save(); // FIXME i can't figure out why this isn't in saveOptions --argonel
88 saveOptions(false);
89
90 // Delete m_dccTransferManager here as its destructor depends on the main loop being in tact which it
91 // won't be if if we wait till Qt starts deleting parent pointers.
92 delete m_dccTransferManager;
93
94 delete m_images;
95 delete m_sound;
96 //delete dbusObject;
97 //delete prefsDCOP;
98 //delete identDBus;
99 delete m_osd;
100 m_osd = nullptr;
101 closeWallet();
102
103 delete m_networkConfigurationManager;
104
105 if (m_restartScheduled) implementRestart();
106 }
107
implementRestart()108 void Application::implementRestart()
109 {
110 // Pop off the executable name. May not be the first argument in argv
111 // everywhere, so verify first.
112 if (QFileInfo(m_restartArguments.first()) == QFileInfo(QCoreApplication::applicationFilePath()))
113 m_restartArguments.removeFirst();
114
115 // Don't round-trip --restart.
116 m_restartArguments.removeAll(QStringLiteral("--restart"));
117
118 // Avoid accumulating multiple --startupdelay arguments across multiple
119 // uses of restart().
120 if (m_restartArguments.contains(QLatin1String("--startupdelay")))
121 {
122 int index = m_restartArguments.lastIndexOf(QStringLiteral("--startupdelay"));
123
124 if (index < m_restartArguments.count() - 1 && !m_restartArguments.at(index + 1).startsWith(QLatin1Char('-')))
125 {
126 QString delayArgument = m_restartArguments.at(index + 1);
127
128 bool ok;
129
130 uint delay = delayArgument.toUInt(&ok, 10);
131
132 // If the argument is invalid or too low, raise to at least 2000 msecs.
133 if (!ok || delay < 2000)
134 m_restartArguments.replace(index + 1, QStringLiteral("2000"));
135 }
136 }
137 else
138 m_restartArguments << QStringLiteral("--startupdelay") << QStringLiteral("2000");
139
140 KProcess::startDetached(QCoreApplication::applicationFilePath(), m_restartArguments);
141 }
142
createMainWindow(AutoConnectMode autoConnectMode,WindowRestoreMode restoreMode)143 void Application::createMainWindow(AutoConnectMode autoConnectMode, WindowRestoreMode restoreMode)
144 {
145 connect(this, &Application::aboutToQuit, this, &Application::prepareShutdown);
146
147 m_connectionManager = new ConnectionManager(this);
148
149 m_awayManager = new AwayManager(this);
150
151 connect(m_connectionManager, &ConnectionManager::identityOnline, m_awayManager, &AwayManager::identityOnline);
152 connect(m_connectionManager, &ConnectionManager::identityOffline, m_awayManager, &AwayManager::identityOffline);
153 connect(m_connectionManager, &ConnectionManager::connectionChangedAwayState, m_awayManager, &AwayManager::updateGlobalAwayAction);
154
155 // Silence deprecation warnings as long as there is no known substitute for QNetworkConfigurationManager
156 QT_WARNING_PUSH
157 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
158 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
159 m_networkConfigurationManager = new QNetworkConfigurationManager();
160 connect(m_networkConfigurationManager, &QNetworkConfigurationManager::onlineStateChanged, m_connectionManager, &ConnectionManager::onOnlineStateChanged);
161 QT_WARNING_POP
162
163 m_scriptLauncher = new ScriptLauncher(this);
164
165 // an instance of DccTransferManager needs to be created before GUI class instances' creation.
166 m_dccTransferManager = new DCC::TransferManager(this);
167
168 // make sure all vars are initialized properly
169 quickConnectDialog = nullptr;
170
171 // Sound object used to play sound is created when needed.
172 m_sound = nullptr;
173
174 // initialize OSD display here, so we can read the Preferences::properly
175 m_osd = new OSDWidget(QStringLiteral("Konversation"));
176
177 Preferences::self();
178 readOptions();
179
180 // Images object providing LEDs, NickIcons
181 m_images = new Images();
182
183 m_urlModel = new QStandardItemModel(0, 3, this);
184
185 // Auto-alias scripts. This adds any missing aliases
186 QStringList aliasList(Preferences::self()->aliasList());
187 const QStringList scripts(Preferences::defaultAliasList());
188 bool changed = false;
189 for (const QString& script : scripts) {
190 if (!aliasList.contains(script)) {
191 changed = true;
192 aliasList.append(script);
193 }
194 }
195 if(changed)
196 Preferences::self()->setAliasList(aliasList);
197
198 // open main window
199 mainWindow = new MainWindow();
200
201 connect(mainWindow.data(), &MainWindow::showQuickConnectDialog, this, &Application::openQuickConnectDialog);
202 connect(Preferences::self(), &Preferences::updateTrayIcon, mainWindow.data(), &MainWindow::updateTrayIcon);
203 connect(mainWindow.data(), &MainWindow::endNotification, m_osd, &OSDWidget::hide);
204 // take care of user style changes, setting back colors and stuff
205
206 // apply GUI settings
207 Q_EMIT appearanceChanged();
208
209 if (restoreMode == WindowRestore)
210 mainWindow->restore();
211 else if (Preferences::self()->showTrayIcon() && Preferences::self()->hideToTrayOnStartup())
212 {
213 mainWindow->systemTrayIcon()->hideWindow();
214 KStartupInfo::appStarted();
215 }
216 else
217 mainWindow->show();
218
219 bool openServerList = Preferences::self()->showServerList();
220
221 // handle autoconnect on startup
222 const Konversation::ServerGroupHash serverGroups = Preferences::serverGroupHash();
223
224 if (autoConnectMode == AutoConnect)
225 {
226 QList<ServerGroupSettingsPtr> serversToAutoconnect;
227 for (const auto& server : serverGroups) {
228 if (server->autoConnectEnabled()) {
229 openServerList = false;
230 serversToAutoconnect << server;
231 }
232 }
233
234 std::sort(serversToAutoconnect.begin(), serversToAutoconnect.end(), [] (const ServerGroupSettingsPtr &left, const ServerGroupSettingsPtr &right)
235 {
236 return left->sortIndex() < right->sortIndex();
237 });
238
239 for (const auto& server : qAsConst(serversToAutoconnect)) {
240 m_connectionManager->connectTo(Konversation::CreateNewConnection, server->id());
241 }
242 }
243
244 if (openServerList) mainWindow->openServerList();
245
246 connect(this, &Application::serverGroupsChanged, this, &Application::saveOptions);
247
248 // prepare dbus interface
249 dbusObject = new Konversation::DBus(this);
250 QDBusConnection::sessionBus().registerObject(QStringLiteral("/irc"), dbusObject, QDBusConnection::ExportNonScriptableSlots);
251 identDBus = new Konversation::IdentDBus(this);
252 QDBusConnection::sessionBus().registerObject(QStringLiteral("/identity"), identDBus, QDBusConnection::ExportNonScriptableSlots);
253
254 if (dbusObject)
255 {
256 connect(dbusObject,&DBus::dbusMultiServerRaw,
257 this,&Application::dbusMultiServerRaw );
258 connect(dbusObject,&DBus::dbusRaw,
259 this,&Application::dbusRaw );
260 connect(dbusObject,&DBus::dbusSay,
261 this,&Application::dbusSay );
262 connect(dbusObject,&DBus::dbusInfo,
263 this,&Application::dbusInfo );
264 connect(dbusObject,&DBus::dbusInsertMarkerLine,
265 mainWindow.data(),&MainWindow::insertMarkerLine);
266 connect(dbusObject, &DBus::connectTo,
267 m_connectionManager,
268 QOverload<Konversation::ConnectionFlag, const QString&, const QString&, const QString&, const QString&, const QString&, bool>::of(&ConnectionManager::connectTo));
269 }
270
271 m_notificationHandler = new Konversation::NotificationHandler(this);
272
273 connect(this, &Application::appearanceChanged, this, &Application::updateProxySettings);
274 }
275
newInstance(QCommandLineParser * args)276 void Application::newInstance(QCommandLineParser *args)
277 {
278 QString url;
279 if (!args->positionalArguments().isEmpty())
280 url = args->positionalArguments().at(0);
281
282 if (!mainWindow)
283 {
284 const AutoConnectMode autoConnectMode = (!args->isSet(QStringLiteral("noautoconnect")) && url.isEmpty() && !args->isSet(QStringLiteral("server"))) ? AutoConnect : NoAutoConnect;
285 createMainWindow(autoConnectMode, NoWindowRestore);
286 }
287 else if (args->isSet(QStringLiteral("restart")))
288 {
289 restart();
290
291 return;
292 }
293
294 if (!url.isEmpty())
295 getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection, url);
296 else if (args->isSet(QStringLiteral("server")))
297 {
298 getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection,
299 args->value(QStringLiteral("server")),
300 args->value(QStringLiteral("port")),
301 args->value(QStringLiteral("password")),
302 args->value(QStringLiteral("nick")),
303 args->value(QStringLiteral("channel")),
304 args->isSet(QStringLiteral("ssl")));
305 }
306
307 return;
308 }
309
restoreInstance()310 void Application::restoreInstance()
311 {
312 createMainWindow(AutoConnect, WindowRestore);
313 }
314
instance()315 Application* Application::instance()
316 {
317 return qobject_cast<Application*>(QApplication::instance());
318 }
319
restart()320 void Application::restart()
321 {
322 m_restartScheduled = true;
323
324 mainWindow->quitProgram();
325 }
326
prepareShutdown()327 void Application::prepareShutdown()
328 {
329 if (mainWindow)
330 mainWindow->getViewContainer()->prepareShutdown();
331
332 if (m_awayManager)
333 {
334 m_awayManager->blockSignals(true);
335 delete m_awayManager;
336 m_awayManager = nullptr;
337 }
338
339 if (m_connectionManager)
340 {
341 m_connectionManager->quitServers();
342 m_connectionManager->blockSignals(true);
343 delete m_connectionManager;
344 m_connectionManager = nullptr;
345 }
346 }
347
event(QEvent * event)348 bool Application::event(QEvent* event)
349 {
350 if (event->type() == QEvent::ApplicationPaletteChange
351 || event->type() == QEvent::ApplicationFontChange) {
352 Q_EMIT appearanceChanged();
353 }
354
355 return QApplication::event(event);
356 }
357
showQueueTuner(bool p)358 void Application::showQueueTuner(bool p)
359 {
360 getMainWindow()->getViewContainer()->showQueueTuner(p);
361 }
362
dbusMultiServerRaw(const QString & command)363 void Application::dbusMultiServerRaw(const QString &command)
364 {
365 sendMultiServerCommand(command.section(QLatin1Char(' '), 0,0), command.section(QLatin1Char(' '), 1));
366 }
367
dbusRaw(const QString & connection,const QString & command)368 void Application::dbusRaw(const QString& connection, const QString &command)
369 {
370 Server* server = getConnectionManager()->getServerByName(connection, ConnectionManager::MatchByIdThenName);
371
372 if (server) server->dbusRaw(command);
373 }
374
375
dbusSay(const QString & connection,const QString & target,const QString & command)376 void Application::dbusSay(const QString& connection, const QString& target, const QString& command)
377 {
378 Server* server = getConnectionManager()->getServerByName(connection, ConnectionManager::MatchByIdThenName);
379
380 if (server) server->dbusSay(target, command);
381 }
382
dbusInfo(const QString & string)383 void Application::dbusInfo(const QString& string)
384 {
385 mainWindow->getViewContainer()->appendToFrontmost(i18n("D-Bus"), string, nullptr);
386 }
387
readOptions()388 void Application::readOptions()
389 {
390 // get standard config file
391
392 // read nickname sorting order for channel nick lists
393 KConfigGroup cgSortNicknames(KSharedConfig::openConfig()->group("Sort Nicknames"));
394
395 QString sortOrder=cgSortNicknames.readEntry("SortOrder");
396 QStringList sortOrderList=sortOrder.split(QString());
397 sortOrderList.sort();
398 if (sortOrderList.join(QString())!=QStringLiteral("-hopqv"))
399 {
400 sortOrder=Preferences::defaultNicknameSortingOrder();
401 Preferences::self()->setSortOrder(sortOrder);
402 }
403
404 // Identity list
405 const QStringList identityList=KSharedConfig::openConfig()->groupList().filter(
406 QRegularExpression(QStringLiteral("Identity [0-9]+")));
407 if (!identityList.isEmpty())
408 {
409 Preferences::clearIdentityList();
410
411 for (const QString& identityGroup : identityList) {
412 IdentityPtr newIdentity(new Identity());
413 KConfigGroup cgIdentity(KSharedConfig::openConfig()->group(identityGroup));
414
415 newIdentity->setName(cgIdentity.readEntry("Name"));
416
417 newIdentity->setIdent(cgIdentity.readEntry("Ident"));
418 newIdentity->setRealName(cgIdentity.readEntry("Realname"));
419
420 newIdentity->setNicknameList(cgIdentity.readEntry<QStringList>("Nicknames",QStringList()));
421
422 newIdentity->setAuthType(cgIdentity.readEntry("AuthType", "nickserv"));
423 newIdentity->setAuthPassword(cgIdentity.readEntry("Password"));
424 newIdentity->setNickservNickname(cgIdentity.readEntry("Bot"));
425 newIdentity->setNickservCommand(cgIdentity.readEntry("NickservCommand", "identify"));
426 newIdentity->setSaslAccount(cgIdentity.readEntry("SaslAccount"));
427 newIdentity->setPemClientCertFile(cgIdentity.readEntry<QUrl>("PemClientCertFile", QUrl()));
428
429 newIdentity->setInsertRememberLineOnAway(cgIdentity.readEntry("InsertRememberLineOnAway", false));
430 newIdentity->setRunAwayCommands(cgIdentity.readEntry("ShowAwayMessage", false));
431 newIdentity->setAwayCommand(cgIdentity.readEntry("AwayMessage"));
432 newIdentity->setReturnCommand(cgIdentity.readEntry("ReturnMessage"));
433 newIdentity->setAutomaticAway(cgIdentity.readEntry("AutomaticAway", false));
434 newIdentity->setAwayInactivity(cgIdentity.readEntry("AwayInactivity", 10));
435 newIdentity->setAutomaticUnaway(cgIdentity.readEntry("AutomaticUnaway", false));
436
437 newIdentity->setQuitReason(cgIdentity.readEntry("QuitReason"));
438 newIdentity->setPartReason(cgIdentity.readEntry("PartReason"));
439 newIdentity->setKickReason(cgIdentity.readEntry("KickReason"));
440
441 newIdentity->setShellCommand(cgIdentity.readEntry("PreShellCommand"));
442
443 newIdentity->setCodecName(cgIdentity.readEntry("Codec"));
444
445 newIdentity->setAwayMessage(cgIdentity.readEntry("AwayReason"));
446 newIdentity->setAwayNickname(cgIdentity.readEntry("AwayNick"));
447
448 Preferences::addIdentity(newIdentity);
449
450 }
451
452 }
453
454 m_osd->setEnabled(Preferences::self()->useOSD());
455
456 //How to load the font from the text?
457 m_osd->setFont(Preferences::self()->oSDFont());
458
459 m_osd->setDuration(Preferences::self()->oSDDuration());
460 m_osd->setScreen(Preferences::self()->oSDScreen());
461 m_osd->setShadow(Preferences::self()->oSDDrawShadow());
462
463 m_osd->setOffset(Preferences::self()->oSDOffsetX(), Preferences::self()->oSDOffsetY());
464 m_osd->setAlignment(static_cast<OSDWidget::Alignment>(Preferences::self()->oSDAlignment()));
465
466 if(Preferences::self()->oSDUseCustomColors())
467 {
468 m_osd->setTextColor(Preferences::self()->oSDTextColor());
469 QPalette p = m_osd->palette();
470 p.setColor(m_osd->backgroundRole(), Preferences::self()->oSDBackgroundColor());
471 m_osd->setPalette(p);
472 }
473
474 // Check if there is old server list config //TODO FIXME why are we doing this here?
475 KConfigGroup cgServerList(KSharedConfig::openConfig()->group("Server List"));
476
477 // Read the new server settings
478 const QStringList groups = KSharedConfig::openConfig()->groupList().filter(
479 QRegularExpression(QStringLiteral("ServerGroup [0-9]+")));
480 QMap<int,QStringList> notifyList;
481 QList<int> sgKeys;
482 sgKeys.reserve(groups.size());
483
484 if(!groups.isEmpty())
485 {
486 Konversation::ServerGroupHash serverGroups;
487 Konversation::ChannelList channelHistory;
488 Konversation::ServerSettings server;
489 Konversation::ChannelSettings channel;
490
491 for (const QString& groupName : groups) {
492 KConfigGroup cgServerGroup(KSharedConfig::openConfig()->group(groupName));
493 Konversation::ServerGroupSettingsPtr serverGroup(new Konversation::ServerGroupSettings);
494 serverGroup->setName(cgServerGroup.readEntry("Name"));
495 serverGroup->setSortIndex(groupName.section(QLatin1Char(' '), -1).toInt());
496 serverGroup->setIdentityId(Preferences::identityByName(cgServerGroup.readEntry("Identity"))->id());
497 serverGroup->setConnectCommands(cgServerGroup.readEntry("ConnectCommands"));
498 serverGroup->setAutoConnectEnabled(cgServerGroup.readEntry("AutoConnect", false));
499 serverGroup->setNotificationsEnabled(cgServerGroup.readEntry("EnableNotifications", true));
500 serverGroup->setExpanded(cgServerGroup.readEntry("Expanded", false));
501
502 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
503 notifyList.insert((*serverGroup).id(), cgServerGroup.readEntry("NotifyList", QString()).split(QLatin1Char(' '), QString::SkipEmptyParts));
504 #else
505 notifyList.insert((*serverGroup).id(), cgServerGroup.readEntry("NotifyList", QString()).split(QLatin1Char(' '), Qt::SkipEmptyParts));
506 #endif
507
508 const QStringList serverNames = cgServerGroup.readEntry("ServerList", QStringList());
509 for (const QString& serverName : serverNames) {
510 KConfigGroup cgServer(KSharedConfig::openConfig()->group(serverName));
511 server.setHost(cgServer.readEntry("Server"));
512 server.setPort(cgServer.readEntry<int>("Port", 0));
513 server.setPassword(cgServer.readEntry("Password"));
514 server.setSSLEnabled(cgServer.readEntry("SSLEnabled", false));
515 server.setBypassProxy(cgServer.readEntry("BypassProxy", false));
516 serverGroup->addServer(server);
517 }
518
519 //config->setGroup(groupName);
520 const QStringList autoJoinChannels = cgServerGroup.readEntry("AutoJoinChannels", QStringList());
521
522 for (const QString& channelName : autoJoinChannels) {
523 KConfigGroup cgJoin(KSharedConfig::openConfig()->group(channelName));
524
525 if (!cgJoin.readEntry("Name").isEmpty())
526 {
527 channel.setName(cgJoin.readEntry("Name"));
528 channel.setPassword(cgJoin.readEntry("Password"));
529 serverGroup->addChannel(channel);
530 }
531 }
532
533 //config->setGroup(groupName);
534 const QStringList channelHistoryList = cgServerGroup.readEntry("ChannelHistory", QStringList());
535 channelHistory.clear();
536
537 for (const QString& channelName : channelHistoryList) {
538 KConfigGroup cgChanHistory(KSharedConfig::openConfig()->group(channelName));
539
540 if (!cgChanHistory.readEntry("Name").isEmpty())
541 {
542 channel.setName(cgChanHistory.readEntry("Name"));
543 channel.setPassword(cgChanHistory.readEntry("Password"));
544 channel.setNotificationsEnabled(cgChanHistory.readEntry("EnableNotifications", true));
545 channelHistory.append(channel);
546 }
547 }
548
549 serverGroup->setChannelHistory(channelHistory);
550
551 serverGroups.insert(serverGroup->id(), serverGroup);
552 sgKeys.append(serverGroup->id());
553 }
554
555 Preferences::setServerGroupHash(serverGroups);
556 }
557
558 // Notify Settings and lists. Must follow Server List.
559 Preferences::setNotifyList(notifyList);
560 Preferences::self()->setNotifyDelay(Preferences::self()->notifyDelay());
561 Preferences::self()->setUseNotify(Preferences::self()->useNotify());
562
563 // Quick Buttons List
564
565 // if there are button definitions in the config file, remove default buttons
566 if (KSharedConfig::openConfig()->hasGroup("Button List"))
567 Preferences::clearQuickButtonList();
568
569 KConfigGroup cgQuickButtons(KSharedConfig::openConfig()->group("Button List"));
570 // Read all default buttons
571 QStringList buttonList(Preferences::quickButtonList());
572 // Read all quick buttons
573 int index=0;
574 while (cgQuickButtons.hasKey(QStringLiteral("Button%1").arg(index)))
575 {
576 buttonList.append(cgQuickButtons.readEntry(QStringLiteral("Button%1").arg(index++)));
577 } // while
578 // Put back the changed button list
579 Preferences::setQuickButtonList(buttonList);
580
581 // Autoreplace List
582
583 // if there are autoreplace definitions in the config file, remove default entries
584 if (KSharedConfig::openConfig()->hasGroup("Autoreplace List"))
585 Preferences::clearAutoreplaceList();
586
587 KConfigGroup cgAutoreplace(KSharedConfig::openConfig()->group("Autoreplace List"));
588 // Read all default entries
589 QList<QStringList> autoreplaceList(Preferences::autoreplaceList());
590 // Read all entries
591 index=0;
592 // legacy code for old autoreplace format 4/6/09
593 QString autoReplaceString(QStringLiteral("Autoreplace"));
594 while (cgAutoreplace.hasKey(autoReplaceString + QString::number(index)))
595 {
596 // read entry and get length of the string
597 QString entry=cgAutoreplace.readEntry(autoReplaceString + QString::number(index++));
598 int length=entry.length()-1;
599 // if there's a "#" in the end, strip it (used to preserve blanks at the end of the replacement text)
600 // there should always be one, but older versions did not do it, so we check first
601 if (entry.at(length)==QLatin1Char('#'))
602 entry.truncate(length);
603 QString regex = entry.section(QLatin1Char(','),0,0);
604 QString direction = entry.section(QLatin1Char(','),1,1);
605 QString pattern = entry.section(QLatin1Char(','),2,2);
606 QString replace = entry.section(QLatin1Char(','),3);
607 // add entry to internal list
608 autoreplaceList.append(QStringList { regex, direction, pattern, replace });
609 } // while
610 //end legacy code for old autoreplace format
611 index=0; //new code for autoreplace config
612 QString indexString(QString::number(index));
613 QString regexString(QStringLiteral("Regex"));
614 QString directString(QStringLiteral("Direction"));
615 QString patternString(QStringLiteral("Pattern"));
616 QString replaceString(QStringLiteral("Replace"));
617 while (cgAutoreplace.hasKey(patternString + indexString))
618 {
619 QString pattern = cgAutoreplace.readEntry(patternString + indexString);
620 QString regex = cgAutoreplace.readEntry(regexString + indexString, QStringLiteral("0"));
621 QString direction = cgAutoreplace.readEntry(directString + indexString, QStringLiteral("o"));
622 QString replace = cgAutoreplace.readEntry(replaceString + indexString, QString());
623 if (!replace.isEmpty()) {
624 int repLen=replace.length()-1;
625 if (replace.at(repLen)==QLatin1Char('#'))
626 replace.truncate(repLen);
627 }
628 if (!pattern.isEmpty()) {
629 int patLen=pattern.length()-1;
630 if (pattern.at(patLen)==QLatin1Char('#'))
631 pattern.truncate(patLen);
632 }
633 index++;
634 indexString = QString::number(index);
635 autoreplaceList.append(QStringList { regex, direction, pattern, replace });
636 }
637 // Put back the changed autoreplace list
638 Preferences::setAutoreplaceList(autoreplaceList);
639
640 //TODO FIXME I assume this is in the <default> group, but I have a hunch we just don't care about <1.0.1
641 // Highlight List
642 KConfigGroup cgDefault(KSharedConfig::openConfig()->group("<default>"));
643 if (cgDefault.hasKey("Highlight")) // Stay compatible with versions < 0.14
644 {
645 QString highlight=cgDefault.readEntry("Highlight");
646 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
647 QStringList hiList = highlight.split(QLatin1Char(' '), QString::SkipEmptyParts);
648 #else
649 QStringList hiList = highlight.split(QLatin1Char(' '), Qt::SkipEmptyParts);
650 #endif
651
652 for (int hiIndex=0; hiIndex < hiList.count(); hiIndex+=2)
653 {
654 Preferences::addHighlight(hiList[hiIndex], false, QString(QLatin1Char('#')+hiList[hiIndex+1]), QString(), QString(), QString(), true);
655 }
656
657 cgDefault.deleteEntry("Highlight");
658 }
659 else
660 {
661 int i = 0;
662
663 while (KSharedConfig::openConfig()->hasGroup(QStringLiteral("Highlight%1").arg(i)))
664 {
665 KConfigGroup cgHilight(KSharedConfig::openConfig()->group(QStringLiteral("Highlight%1").arg(i)));
666 Preferences::addHighlight(
667 cgHilight.readEntry("Pattern"),
668 cgHilight.readEntry("RegExp", false),
669 cgHilight.readEntry("Color", QColor(Qt::black)),
670 cgHilight.readPathEntry("Sound", QString()),
671 cgHilight.readEntry("AutoText"),
672 cgHilight.readEntry("ChatWindows"),
673 cgHilight.readEntry("Notify", true)
674 );
675 i++;
676 }
677 }
678
679 // Ignore List
680 KConfigGroup cgIgnoreList(KSharedConfig::openConfig()->group("Ignore List"));
681 // Remove all default entries if there is at least one Ignore in the Preferences::file
682 if (cgIgnoreList.hasKey("Ignore0"))
683 Preferences::clearIgnoreList();
684 // Read all ignores
685 index=0;
686 while (cgIgnoreList.hasKey(QStringLiteral("Ignore%1").arg(index)))
687 {
688 Preferences::addIgnore(cgIgnoreList.readEntry(QStringLiteral("Ignore%1").arg(index++)));
689 }
690
691 // Aliases
692 KConfigGroup cgAliases(KSharedConfig::openConfig()->group("Aliases"));
693 QStringList newList=cgAliases.readEntry("AliasList", QStringList());
694 if (!newList.isEmpty())
695 Preferences::self()->setAliasList(newList);
696
697 // Channel Encodings
698
699 //Legacy channel encodings read in Jun. 29, 2009
700 KConfigGroup cgChannelEncodings(KSharedConfig::openConfig()->group("Channel Encodings"));
701 const QMap<QString,QString> channelEncodingEntries = cgChannelEncodings.entryMap();
702 const QRegularExpression re(QStringLiteral("^(.+) ([^\\s]+)$"));
703
704 for (auto it = channelEncodingEntries.begin(), end = channelEncodingEntries.end(); it != end; ++it) {
705 const QRegularExpressionMatch match = re.match(it.key());
706 if(match.hasMatch())
707 {
708 Preferences::setChannelEncoding(match.captured(1), match.captured(2), it.value());
709 }
710 }
711 //End legacy channel encodings read in Jun 29, 2009
712
713 KConfigGroup cgEncodings(KSharedConfig::openConfig()->group("Encodings"));
714 const QMap<QString,QString> encodingEntries = cgEncodings.entryMap();
715
716 const QRegularExpression reg(QStringLiteral("^([^\\s]+) ([^\\s]+)\\s?([^\\s]*)$"));
717 for (auto it = encodingEntries.begin(), end = encodingEntries.end(); it != end; ++it) {
718 const QRegularExpressionMatch match = reg.match(it.key());
719 if(match.hasMatch())
720 {
721 if(match.captured(1) == QLatin1String("ServerGroup") && !match.captured(3).isEmpty())
722 Preferences::setChannelEncoding(sgKeys.at(match.captured(2).toInt()), match.captured(3), it.value());
723 else
724 Preferences::setChannelEncoding(match.captured(1), match.captured(2), it.value());
725 }
726 }
727
728 // Spell Checking Languages
729 KConfigGroup cgSpellCheckingLanguages(KSharedConfig::openConfig()->group("Spell Checking Languages"));
730 const QMap<QString, QString> spellCheckingLanguageEntries = cgSpellCheckingLanguages.entryMap();
731
732 for (auto it = spellCheckingLanguageEntries.begin(), end = spellCheckingLanguageEntries.end(); it != end; ++it) {
733 const QRegularExpressionMatch match = reg.match(it.key());
734 if (match.hasMatch())
735 {
736 if (match.captured(1) == QLatin1String("ServerGroup") && !match.captured(3).isEmpty())
737 {
738 ServerGroupSettingsPtr serverGroup = Preferences::serverGroupById(sgKeys.at(match.captured(2).toInt()));
739
740 if (serverGroup)
741 Preferences::setSpellCheckingLanguage(serverGroup, match.captured(3), it.value());
742 }
743 else
744 Preferences::setSpellCheckingLanguage(match.captured(1), match.captured(2), it.value());
745 }
746 }
747
748 fetchQueueRates();
749
750 updateProxySettings();
751 }
752
saveOptions(bool updateGUI)753 void Application::saveOptions(bool updateGUI)
754 {
755 // template: KConfigGroup (KSharedConfig::openConfig()->group( ));
756
757 //KConfig* config=KSharedConfig::openConfig();
758
759 // Should be handled in NicklistBehaviorConfigController now
760 // config->setGroup("Sort Nicknames");
761
762 // Clean up identity list
763 const QStringList identities=KSharedConfig::openConfig()->groupList().filter(
764 QRegularExpression(QStringLiteral("Identity [0-9]+")));
765 // remove old identity list from Preferences::file to keep numbering under control
766 for (const QString& identityGroup : identities) {
767 KSharedConfig::openConfig()->deleteGroup(identityGroup);
768 }
769
770 const IdentityList identityList = Preferences::identityList();
771 int index = 0;
772
773 for (const auto& identity : identityList) {
774 KConfigGroup cgIdentity(KSharedConfig::openConfig()->group(QStringLiteral("Identity %1").arg(index)));
775
776 cgIdentity.writeEntry("Name",identity->getName());
777 cgIdentity.writeEntry("Ident",identity->getIdent());
778 cgIdentity.writeEntry("Realname",identity->getRealName());
779 cgIdentity.writeEntry("Nicknames",identity->getNicknameList());
780 cgIdentity.writeEntry("AuthType",identity->getAuthType());
781 cgIdentity.writeEntry("Password",identity->getAuthPassword());
782 cgIdentity.writeEntry("Bot",identity->getNickservNickname());
783 cgIdentity.writeEntry("NickservCommand",identity->getNickservCommand());
784 cgIdentity.writeEntry("SaslAccount",identity->getSaslAccount());
785 cgIdentity.writeEntry("PemClientCertFile", identity->getPemClientCertFile().toString());
786 cgIdentity.writeEntry("InsertRememberLineOnAway", identity->getInsertRememberLineOnAway());
787 cgIdentity.writeEntry("ShowAwayMessage",identity->getRunAwayCommands());
788 cgIdentity.writeEntry("AwayMessage",identity->getAwayCommand());
789 cgIdentity.writeEntry("ReturnMessage",identity->getReturnCommand());
790 cgIdentity.writeEntry("AutomaticAway", identity->getAutomaticAway());
791 cgIdentity.writeEntry("AwayInactivity", identity->getAwayInactivity());
792 cgIdentity.writeEntry("AutomaticUnaway", identity->getAutomaticUnaway());
793 cgIdentity.writeEntry("QuitReason",identity->getQuitReason());
794 cgIdentity.writeEntry("PartReason",identity->getPartReason());
795 cgIdentity.writeEntry("KickReason",identity->getKickReason());
796 cgIdentity.writeEntry("PreShellCommand",identity->getShellCommand());
797 cgIdentity.writeEntry("Codec",identity->getCodecName());
798 cgIdentity.writeEntry("AwayReason",identity->getAwayMessage());
799 cgIdentity.writeEntry("AwayNick", identity->getAwayNickname());
800 index++;
801 } // endfor
802
803 // Remove the old servergroups from the config
804 const QStringList serverGroupGroups =
805 KSharedConfig::openConfig()->groupList().filter(QRegularExpression(QStringLiteral("ServerGroup [0-9]+")));
806
807 for (const QString& serverGroupGroup : serverGroupGroups) {
808 KSharedConfig::openConfig()->deleteGroup(serverGroupGroup);
809 }
810
811 // Remove the old servers from the config
812 const QStringList serverGroups =
813 KSharedConfig::openConfig()->groupList().filter(QRegularExpression(QStringLiteral("Server [0-9]+")));
814
815 for (const QString& serverGroup : serverGroups) {
816 KSharedConfig::openConfig()->deleteGroup(serverGroup);
817 }
818
819 // Remove the old channels from the config
820 const QStringList channelGroups =
821 KSharedConfig::openConfig()->groupList().filter(QRegularExpression(QStringLiteral("Channel [0-9]+")));
822
823 for (const QString& channelGroup : channelGroups) {
824 KSharedConfig::openConfig()->deleteGroup(channelGroup);
825 }
826
827 // Add the new servergroups to the config
828 const Konversation::ServerGroupHash serverGroupHash = Preferences::serverGroupHash();
829
830 QMap<int, Konversation::ServerGroupSettingsPtr> sortedServerGroupMap;
831
832 // Make the indices in the group headers reflect the server list dialog sorting.
833 for (const auto& serverGroup : serverGroupHash) {
834 sortedServerGroupMap.insert(serverGroup->sortIndex(), serverGroup);
835 }
836
837 index = 0;
838 int index2 = 0;
839 int index3 = 0;
840 QStringList servers;
841 QStringList channels;
842 QStringList channelHistory;
843 QHash<int, int> sgKeys;
844
845 for (const auto& serverGroup : qAsConst(sortedServerGroupMap)) {
846 const Konversation::ServerList serverList = serverGroup->serverList();
847 servers.clear();
848 servers.reserve(serverList.size());
849
850 sgKeys.insert(serverGroup->id(), index);
851
852 for (const auto& server : serverList) {
853 const QString groupName = QStringLiteral("Server %1").arg(index2);
854 servers.append(groupName);
855 KConfigGroup cgServer(KSharedConfig::openConfig()->group(groupName));
856 cgServer.writeEntry("Server", server.host());
857 cgServer.writeEntry("Port", server.port());
858 cgServer.writeEntry("Password", server.password());
859 cgServer.writeEntry("SSLEnabled", server.SSLEnabled());
860 cgServer.writeEntry("BypassProxy", server.bypassProxy());
861 index2++;
862 }
863
864 const Konversation::ChannelList channelList = serverGroup->channelList();
865 channels.clear();
866 channels.reserve(channelList.size());
867
868 for (const auto& channel : channelList) {
869 const QString groupName = QStringLiteral("Channel %1").arg(index3);
870 channels.append(groupName);
871 KConfigGroup cgChannel(KSharedConfig::openConfig()->group(groupName));
872 cgChannel.writeEntry("Name", channel.name());
873 cgChannel.writeEntry("Password", channel.password());
874 index3++;
875 }
876
877 const Konversation::ChannelList channelHistoryList = serverGroup->channelHistory();
878 channelHistory.clear();
879 channelHistory.reserve(channelHistoryList.size());
880
881 for (const auto& channel : channelHistoryList) {
882 // TODO FIXME: is it just me or is this broken?
883 const QString groupName = QStringLiteral("Channel %1").arg(index3);
884 channelHistory.append(groupName);
885 KConfigGroup cgChannelHistory(KSharedConfig::openConfig()->group(groupName));
886 cgChannelHistory.writeEntry("Name", channel.name());
887 cgChannelHistory.writeEntry("Password", channel.password());
888 cgChannelHistory.writeEntry("EnableNotifications", channel.enableNotifications());
889 index3++;
890 }
891
892 QString sgn = QStringLiteral("ServerGroup %1").arg(index);
893 KConfigGroup cgServerGroup(KSharedConfig::openConfig()->group(sgn));
894 cgServerGroup.writeEntry("Name", serverGroup->name());
895 cgServerGroup.writeEntry("Identity", serverGroup->identity()->getName());
896 cgServerGroup.writeEntry("ServerList", servers);
897 cgServerGroup.writeEntry("AutoJoinChannels", channels);
898 cgServerGroup.writeEntry("ConnectCommands", serverGroup->connectCommands());
899 cgServerGroup.writeEntry("AutoConnect", serverGroup->autoConnectEnabled());
900 cgServerGroup.writeEntry("ChannelHistory", channelHistory);
901 cgServerGroup.writeEntry("EnableNotifications", serverGroup->enableNotifications());
902 cgServerGroup.writeEntry("Expanded", serverGroup->expanded());
903 cgServerGroup.writeEntry("NotifyList",Preferences::notifyStringByGroupId(serverGroup->id()));
904 index++;
905 }
906
907 KSharedConfig::openConfig()->deleteGroup("Server List");
908
909 // Ignore List
910 KSharedConfig::openConfig()->deleteGroup("Ignore List");
911 KConfigGroup cgIgnoreList(KSharedConfig::openConfig()->group("Ignore List"));
912 QList<Ignore*> ignoreList=Preferences::ignoreList();
913 for (int i = 0; i < ignoreList.size(); ++i) {
914 cgIgnoreList.writeEntry(QStringLiteral("Ignore%1").arg(i), QStringLiteral("%1,%2").arg(ignoreList.at(i)->getName()).arg(ignoreList.at(i)->getFlags()));
915 }
916
917 // Channel Encodings
918 // remove all entries once
919 KSharedConfig::openConfig()->deleteGroup("Channel Encodings"); // legacy Jun 29, 2009
920 KSharedConfig::openConfig()->deleteGroup("Encodings");
921 KConfigGroup cgEncoding(KSharedConfig::openConfig()->group("Encodings"));
922 const QList<int> encServers = Preferences::channelEncodingsServerGroupIdList();
923 //i have no idea these would need to be sorted //encServers.sort();
924 for (int encServer : encServers) {
925 Konversation::ServerGroupSettingsPtr sgsp = Preferences::serverGroupById(encServer);
926
927 if ( sgsp ) // sgsp == 0 when the entry is of QuickConnect or something?
928 {
929 const QStringList encChannels = Preferences::channelEncodingsChannelList(encServer);
930 //ditto //encChannels.sort();
931 for (const QString& encChannel : encChannels) {
932 QString enc = Preferences::channelEncoding(encServer, encChannel);
933 QString key = QLatin1Char(' ') + encChannel;
934 if (sgKeys.contains(encServer))
935 key.prepend(QStringLiteral("ServerGroup ") + QString::number(sgKeys.value(encServer)));
936 else
937 key.prepend(sgsp->name());
938 cgEncoding.writeEntry(key, enc);
939 }
940 }
941 }
942
943 // Spell Checking Languages
944 KSharedConfig::openConfig()->deleteGroup("Spell Checking Languages");
945 KConfigGroup cgSpellCheckingLanguages(KSharedConfig::openConfig()->group("Spell Checking Languages"));
946
947 QHashIterator<Konversation::ServerGroupSettingsPtr, QHash<QString, QString> > i(Preferences::serverGroupSpellCheckingLanguages());
948
949 while (i.hasNext())
950 {
951 i.next();
952
953 QHashIterator<QString, QString> i2(i.value());
954
955 while (i2.hasNext())
956 {
957 i2.next();
958
959 if (sgKeys.contains(i.key()->id()))
960 cgSpellCheckingLanguages.writeEntry(QStringLiteral("ServerGroup ") + QString::number(sgKeys.value(i.key()->id())) + QLatin1Char(' ') + i2.key(), i2.value());
961 }
962 }
963
964 QHashIterator<QString, QHash<QString, QString> > i3(Preferences::serverSpellCheckingLanguages());
965
966 while (i3.hasNext())
967 {
968 i3.next();
969
970 QHashIterator<QString, QString> i4(i3.value());
971
972 while (i4.hasNext())
973 {
974 i4.next();
975
976 cgSpellCheckingLanguages.writeEntry(i3.key() + QLatin1Char(' ') + i4.key(), i4.value());
977 }
978 }
979
980 KSharedConfig::openConfig()->sync();
981
982 if(updateGUI)
983 Q_EMIT appearanceChanged();
984 }
985
fetchQueueRates()986 void Application::fetchQueueRates()
987 {
988 //The following rate was found in the rc for all queues, which were deliberately bad numbers chosen for debugging.
989 //Its possible that the static array was constructed or deconstructed at the wrong time, and so those values saved
990 //in the rc. When reading the values out of the rc, we must check to see if they're this specific value,
991 //and if so, reset to defaults. --argonel
992 IRCQueue::EmptyingRate shit(6, 50000, IRCQueue::EmptyingRate::Lines);
993 int bad = 0;
994 for (int i=0; i <= countOfQueues(); i++)
995 {
996 QList<int> r = Preferences::self()->queueRate(i);
997 staticrates[i] = IRCQueue::EmptyingRate(r[0], r[1]*1000, IRCQueue::EmptyingRate::RateType(r[2]));
998 if (staticrates[i] == shit)
999 bad++;
1000 }
1001 if (bad == 3)
1002 resetQueueRates();
1003 }
1004
stashQueueRates()1005 void Application::stashQueueRates()
1006 {
1007 for (int i=0; i <= countOfQueues(); i++)
1008 {
1009 const QList<int> r {
1010 staticrates[i].m_rate,
1011 staticrates[i].m_interval / 1000,
1012 static_cast<int>(staticrates[i].m_type),
1013 };
1014 Preferences::self()->setQueueRate(i, r);
1015 }
1016 }
1017
resetQueueRates()1018 void Application::resetQueueRates()
1019 {
1020 for (int i=0; i <= countOfQueues(); i++)
1021 {
1022 Preferences::self()->queueRateItem(i)->setDefault();
1023 QList<int> r=Preferences::self()->queueRate(i);
1024 staticrates[i]=IRCQueue::EmptyingRate(r[0], r[1]*1000, IRCQueue::EmptyingRate::RateType(r[2]));
1025 }
1026 }
1027
storeUrl(const QString & origin,const QString & newUrl,const QDateTime & dateTime)1028 void Application::storeUrl(const QString& origin, const QString& newUrl, const QDateTime& dateTime)
1029 {
1030 QString url(newUrl);
1031
1032 url.replace(QStringLiteral("&"), QStringLiteral("&"));
1033
1034 const QList<QStandardItem*> existing = m_urlModel->findItems(url, Qt::MatchExactly, 1);
1035
1036 for (QStandardItem* item : existing) {
1037 if (m_urlModel->item(item->row(), 0)->data(Qt::DisplayRole).toString() == origin)
1038 m_urlModel->removeRow(item->row());
1039 }
1040
1041 m_urlModel->insertRow(0);
1042 m_urlModel->setData(m_urlModel->index(0, 0), origin, Qt::DisplayRole);
1043 m_urlModel->setData(m_urlModel->index(0, 1), url, Qt::DisplayRole);
1044
1045 auto* dateItem = new UrlDateItem(dateTime);
1046 m_urlModel->setItem(0, 2, dateItem);
1047 }
1048
openQuickConnectDialog()1049 void Application::openQuickConnectDialog()
1050 {
1051 quickConnectDialog = new QuickConnectDialog(mainWindow);
1052 connect(quickConnectDialog, &QuickConnectDialog::connectClicked,
1053 m_connectionManager, QOverload<Konversation::ConnectionFlag, const QString&, const QString&, const QString&, const QString&, const QString&, bool>::of(&ConnectionManager::connectTo));
1054 quickConnectDialog->show();
1055 }
1056
sendMultiServerCommand(const QString & command,const QString & parameter)1057 void Application::sendMultiServerCommand(const QString& command, const QString& parameter)
1058 {
1059 const QList<Server*> serverList = getConnectionManager()->getServerList();
1060
1061 for (Server* server : serverList)
1062 server->executeMultiServerCommand(command, parameter);
1063 }
1064
splitNick_Server(const QString & nick_server,QString & ircnick,QString & serverOrGroup)1065 void Application::splitNick_Server(const QString& nick_server, QString &ircnick, QString &serverOrGroup)
1066 {
1067 //kaddresbook uses the utf separator 0xE120, so treat that as a separator as well
1068 QString nickServer = nick_server;
1069 nickServer.replace(QChar(0xE120), QStringLiteral("@"));
1070 ircnick = nickServer.section(QLatin1Char('@'),0,0);
1071 serverOrGroup = nickServer.section(QLatin1Char('@'),1);
1072 }
1073
getNickInfo(const QString & ircnick,const QString & serverOrGroup)1074 NickInfoPtr Application::getNickInfo(const QString &ircnick, const QString &serverOrGroup)
1075 {
1076 const QList<Server*> serverList = getConnectionManager()->getServerList();
1077 NickInfoPtr nickInfo;
1078 QString lserverOrGroup = serverOrGroup.toLower();
1079 for (Server* lookServer : serverList) {
1080 if (lserverOrGroup.isEmpty()
1081 || lookServer->getServerName().toLower()==lserverOrGroup
1082 || lookServer->getDisplayName().toLower()==lserverOrGroup)
1083 {
1084 nickInfo = lookServer->getNickInfo(ircnick);
1085 if (nickInfo)
1086 return nickInfo; //If we found one
1087 }
1088 }
1089 return static_cast<NickInfoPtr>(nullptr);
1090 }
1091
1092 // auto replace on input/output
doAutoreplace(const QString & text,bool output,int cursorPos) const1093 QPair<QString, int> Application::doAutoreplace(const QString& text, bool output, int cursorPos) const
1094 {
1095 // get autoreplace list
1096 const QList<QStringList> autoreplaceList = Preferences::autoreplaceList();
1097 // working copy
1098 QString line=text;
1099
1100 // loop through the list of replacement patterns
1101 for (const QStringList& definition : autoreplaceList) {
1102 // split definition in parts
1103 QString regex=definition.at(0);
1104 QString direction=definition.at(1);
1105 QString pattern=definition.at(2);
1106 QString replacement=definition.at(3);
1107
1108 QString isDirection=output ? QStringLiteral("o") : QStringLiteral("i");
1109
1110 // only replace if this pattern is for the specific direction or both directions
1111 if (direction==isDirection || direction==QStringLiteral("io"))
1112 {
1113 // regular expression pattern?
1114 if (regex== QLatin1Char('1'))
1115 {
1116 // create regex from pattern
1117 const QRegularExpression needleReg(pattern);
1118 int index = 0;
1119 int newIndex = index;
1120
1121 do {
1122 QRegularExpressionMatch rmatch;
1123 // find matches
1124 index = line.indexOf(needleReg, index, &rmatch);
1125
1126 if (index != -1)
1127 {
1128 // remember captured patterns
1129 const QStringList captures = rmatch.capturedTexts();
1130 QString replaceWith = replacement;
1131
1132 replaceWith.replace(QStringLiteral("%%"),QStringLiteral("%\x01")); // escape double %
1133 // replace %0-9 in regex groups
1134 for (int capture=0;capture<captures.count();capture++)
1135 {
1136 QString search = QStringLiteral("%%1").arg(capture);
1137 replaceWith.replace(search, captures[capture]);
1138 }
1139 //Explanation why this is important so we don't forget:
1140 //If somebody has a regex that say has a replacement of url.com/%1/%2 and the
1141 //regex can either match one or two patterns, if the 2nd pattern match is left,
1142 //the url is invalid (url.com/match/%2). This is expected regex behavior I'd assume.
1143 replaceWith.remove(QRegularExpression(QStringLiteral("%[0-9]")));
1144
1145 replaceWith.replace(QStringLiteral("%\x01"),QStringLiteral("%")); // return escaped % to normal
1146 // allow for var expansion in autoreplace
1147 replaceWith = Konversation::doVarExpansion(replaceWith);
1148 // replace input with replacement
1149 line.replace(index, captures[0].length(), replaceWith);
1150
1151 newIndex = index + replaceWith.length();
1152
1153 if (cursorPos > -1 && cursorPos >= index)
1154 {
1155 if (cursorPos < index + captures[0].length())
1156 cursorPos = newIndex;
1157 else
1158 {
1159 if (captures[0].length() > replaceWith.length())
1160 cursorPos -= captures[0].length() - replaceWith.length();
1161 else
1162 cursorPos += replaceWith.length() - captures[0].length();
1163 }
1164 }
1165
1166 index = newIndex;
1167 }
1168 } while (index >= 0 && index < line.length());
1169 }
1170 else
1171 {
1172 int index = line.indexOf(pattern);
1173 while (index>=0)
1174 {
1175 int length,nextLength,patLen,repLen;
1176 patLen=pattern.length();
1177
1178 repLen=replacement.length();
1179 length=index;
1180 length+=patLen;
1181 nextLength=length;
1182 //nextlength is used to account for the replacement taking up less space
1183 QChar before,after;
1184 if (index!=0) before = line.at(index-1);
1185 if (line.length() > length) after = line.at(length);
1186
1187 if (index==0 || before.isSpace() || before.isPunct())
1188 {
1189 if (line.length() == length || after.isSpace() || after.isPunct())
1190 {
1191 // allow for var expansion in autoreplace
1192 replacement = Konversation::doVarExpansion(replacement);
1193 line.replace(index,patLen,replacement);
1194 nextLength = index+repLen;
1195 }
1196 }
1197
1198 if (cursorPos > -1 && cursorPos >= index)
1199 {
1200 if (cursorPos < length)
1201 cursorPos = nextLength;
1202 else
1203 {
1204 if (patLen > repLen)
1205 cursorPos -= patLen - repLen;
1206 else
1207 cursorPos += repLen - patLen;
1208 }
1209 }
1210
1211 index = line.indexOf(pattern, nextLength);
1212 }
1213 }
1214 }
1215 }
1216
1217 return QPair<QString, int>(line, cursorPos);
1218 }
1219
doInlineAutoreplace(KTextEdit * textEdit) const1220 void Application::doInlineAutoreplace(KTextEdit* textEdit) const
1221 {
1222 QTextCursor cursor(textEdit->document());
1223
1224 cursor.beginEditBlock();
1225 const QPair<QString, int>& replace = Application::instance()->doAutoreplace(textEdit->toPlainText(), true, textEdit->textCursor().position());
1226 cursor.select(QTextCursor::Document);
1227 cursor.insertText(replace.first);
1228 cursor.setPosition(replace.second);
1229 cursor.endEditBlock();
1230
1231 textEdit->setTextCursor(cursor);
1232 }
1233
1234
openUrl(const QString & url)1235 void Application::openUrl(const QString& url)
1236 {
1237 if (!Preferences::self()->useCustomBrowser()
1238 || url.startsWith(QLatin1String("mailto:"))
1239 || url.startsWith(QLatin1String("amarok:"))) {
1240 if (url.startsWith(QLatin1String("irc://")) || url.startsWith(QLatin1String("ircs://"))) {
1241 Application::instance()->getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection, url);
1242 return;
1243 }
1244
1245 #ifdef Q_OS_WIN
1246 QDesktopServices::openUrl(QUrl::fromUserInput(url));
1247 #else
1248 auto *job = new KIO::OpenUrlJob(QUrl(url));
1249 job->setFollowRedirections(false);
1250 job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, Application::instance()->getMainWindow()));
1251 job->start();
1252 #endif
1253 return;
1254 }
1255
1256 // Use custom browser
1257 QHash<QChar,QString> map;
1258 map.insert(QLatin1Char('u'), url);
1259 const QString cmd = KMacroExpander::expandMacrosShellQuote(Preferences::webBrowserCmd(), map);
1260 const QStringList args = KShell::splitArgs(cmd);
1261
1262 if (!args.isEmpty()) {
1263 KProcess::startDetached(args);
1264 }
1265 }
1266
sound() const1267 Konversation::Sound* Application::sound() const
1268 {
1269 if (!m_sound)
1270 m_sound = new Konversation::Sound;
1271
1272 return m_sound;
1273 }
1274
osd() const1275 OSDWidget* Application::osd() const
1276 {
1277 return m_osd;
1278 }
1279
updateProxySettings()1280 void Application::updateProxySettings()
1281 {
1282 if (Preferences::self()->proxyEnabled())
1283 {
1284 QNetworkProxy proxy;
1285
1286 if (Preferences::self()->proxyType() == Preferences::Socksv5Proxy)
1287 {
1288 proxy.setType(QNetworkProxy::Socks5Proxy);
1289 }
1290 else
1291 {
1292 proxy.setType(QNetworkProxy::HttpProxy);
1293 }
1294
1295 proxy.setHostName(Preferences::self()->proxyAddress());
1296 proxy.setPort(static_cast<quint16>(Preferences::self()->proxyPort()));
1297 proxy.setUser(Preferences::self()->proxyUsername());
1298 QString password;
1299
1300 if(wallet())
1301 {
1302 int ret = wallet()->readPassword(QStringLiteral("ProxyPassword"), password);
1303
1304 if(ret != 0)
1305 {
1306 qCCritical(KONVERSATION_LOG) << "Failed to read the proxy password from the wallet, error code:" << ret;
1307 }
1308 }
1309
1310 proxy.setPassword(password);
1311 QNetworkProxy::setApplicationProxy(proxy);
1312 }
1313 else
1314 {
1315 QNetworkProxy::setApplicationProxy(QNetworkProxy::DefaultProxy);
1316 }
1317 }
1318
wallet()1319 KWallet::Wallet* Application::wallet()
1320 {
1321 if(!m_wallet)
1322 {
1323 WId winid = 0;
1324
1325 if(mainWindow)
1326 winid = mainWindow->winId();
1327
1328 m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), winid);
1329
1330 if(!m_wallet)
1331 return nullptr;
1332
1333 connect(m_wallet, &KWallet::Wallet::walletClosed, this, &Application::closeWallet);
1334
1335 if(!m_wallet->hasFolder(QStringLiteral("Konversation")))
1336 {
1337 if(!m_wallet->createFolder(QStringLiteral("Konversation")))
1338 {
1339 qCCritical(KONVERSATION_LOG) << "Failed to create folder Konversation in the network wallet.";
1340 closeWallet();
1341 return nullptr;
1342 }
1343 }
1344
1345 if(!m_wallet->setFolder(QStringLiteral("Konversation")))
1346 {
1347 qCCritical(KONVERSATION_LOG) << "Failed to set active folder to Konversation in the network wallet.";
1348 closeWallet();
1349 return nullptr;
1350 }
1351 }
1352
1353 return m_wallet;
1354 }
1355
closeWallet()1356 void Application::closeWallet()
1357 {
1358 delete m_wallet;
1359 m_wallet = nullptr;
1360 }
1361
handleActivate(const QStringList & arguments)1362 void Application::handleActivate(const QStringList& arguments)
1363 {
1364 m_commandLineParser->parse(arguments.isEmpty()? QStringList(applicationFilePath()) : arguments);
1365
1366 if(m_commandLineParser->isSet(QStringLiteral("restart")))
1367 {
1368 m_restartArguments = arguments;
1369 }
1370
1371 newInstance(m_commandLineParser);
1372
1373 mainWindow->show();
1374 #if HAVE_X11
1375 if (KWindowSystem::isPlatformX11()) {
1376 KStartupInfo::setNewStartupId(mainWindow->windowHandle(), QX11Info::nextStartupId());
1377 } else
1378 #endif
1379 #if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 83, 0)
1380 if (KWindowSystem::isPlatformWayland()) {
1381 const QString token = qEnvironmentVariable("XDG_ACTIVATION_TOKEN");
1382 if (!token.isEmpty()) {
1383 KWindowSystem::setCurrentXdgActivationToken(token);
1384 qunsetenv("XDG_ACTIVATION_TOKEN");
1385 }
1386 }
1387 #endif
1388 mainWindow->activateAndRaiseWindow();
1389 }
1390
1391
1392
1393 // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on;
1394 // vim: set et sw=4 ts=4 cino=l1,cs,U1:
1395