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("&amp;"), 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