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 "config/preferences.h"
12 #include "ignore.h"
13 #include "highlight.h"
14 
15 #include <QHashIterator>
16 #include <QHeaderView>
17 #include <QTreeView>
18 
19 #include <KLocalizedString>
20 #include <KUser>
21 #include <KSharedConfig>
22 #include <QStandardPaths>
23 
24 struct PreferencesSingleton
25 {
26     Preferences prefs;
27 };
28 
Q_GLOBAL_STATIC(PreferencesSingleton,s_prefs)29 Q_GLOBAL_STATIC(PreferencesSingleton, s_prefs)
30 
31 Preferences *Preferences::self()
32 {
33   if ( !s_prefs.exists() ) {
34     s_prefs->prefs.load();
35   }
36 
37   return &s_prefs->prefs;
38 }
39 
40 
Preferences()41 Preferences::Preferences()
42 {
43     // create default identity
44     mIdentity=new Identity();
45     mIdentity->setName(i18n("Default Identity"));
46     mIdentityList.append(mIdentity);
47 
48     KUser user(KUser::UseRealUserID);
49     mIdentity->setIdent(user.loginName());
50     mIdentity->setRealName(user.property(KUser::FullName).toString());
51 
52     const QStringList nickList = {
53         user.loginName(),
54         user.loginName() + QLatin1Char('_'),
55         user.loginName() + QStringLiteral("__"),
56     };
57     mIdentity->setNicknameList(nickList);
58 
59     Konversation::ServerGroupSettingsPtr serverGroup(new Konversation::ServerGroupSettings);
60     serverGroup->setName(QStringLiteral("libera"));
61     Konversation::ServerSettings server;
62     server.setHost(QStringLiteral("irc.libera.chat"));
63     server.setPort(6697);
64     server.setSSLEnabled(true);
65     serverGroup->addServer(server);
66     serverGroup->setIdentityId(mIdentity->id());
67     Konversation::ChannelSettings channel;
68     channel.setName(QStringLiteral("#konversation"));
69     serverGroup->addChannel(channel);
70     serverGroup->setExpanded(false);
71     mServerGroupHash.insert(0, serverGroup);
72     mQuickButtonList = defaultQuickButtonList();
73     mAutoreplaceList = defaultAutoreplaceList();
74 }
75 
~Preferences()76 Preferences::~Preferences()
77 {
78     mIdentityList.clear();
79     qDeleteAll(mIgnoreList);
80     qDeleteAll(mHighlightList);
81 }
serverGroupHash()82 const Konversation::ServerGroupHash Preferences::serverGroupHash()
83 {
84     return self()->mServerGroupHash;
85 }
86 
defaultQuickButtonList()87 const QStringList Preferences::defaultQuickButtonList()
88 {
89     return QStringList {
90         QStringLiteral("Op,/OP %u%n"),
91         QStringLiteral("DeOp,/DEOP %u%n"),
92         QStringLiteral("WhoIs,/WHOIS %s,%%u%n"),
93         QStringLiteral("Version,/CTCP %s,%%u VERSION%n"),
94         QStringLiteral("Kick,/KICK %u%n"),
95         QStringLiteral("Ban,/BAN %u%n"),
96         QStringLiteral("Part,/PART %c Leaving...%n"),
97         QStringLiteral("Quit,/QUIT Leaving...%n"),
98     };
99 }
100 
quickButtonList()101 const QStringList Preferences::quickButtonList()
102 {
103   return self()->mQuickButtonList;
104 }
105 
setQuickButtonList(const QStringList & newList)106 void Preferences::setQuickButtonList(const QStringList& newList)
107 {
108   self()->mQuickButtonList=newList;
109 }
110 
clearQuickButtonList()111 void Preferences::clearQuickButtonList()
112 {
113   self()->mQuickButtonList.clear();
114 }
115 
116 // --------------------------- AutoReplace ---------------------------
117 
defaultAutoreplaceList()118 const QList<QStringList> Preferences::defaultAutoreplaceList()
119 {
120     return QList<QStringList> {
121         { QStringLiteral("1"), QStringLiteral("o"), QStringLiteral("\\[\\[([^\\s]+)\\]\\]"), QStringLiteral("http://en.wikipedia.org/wiki/Special:Search?go=Go&search=%1") },
122         { QStringLiteral("1"), QStringLiteral("o"), QStringLiteral("(BUG:|bug:)([0-9]+)"), QStringLiteral("https://bugs.kde.org/show_bug.cgi?id=%2") },
123     };
124 }
125 
autoreplaceList()126 const QList<QStringList> Preferences::autoreplaceList()
127 {
128   return self()->mAutoreplaceList;
129 }
130 
setAutoreplaceList(const QList<QStringList> & newList)131 void Preferences::setAutoreplaceList(const QList<QStringList> &newList)
132 {
133   self()->mAutoreplaceList=newList;
134 }
135 
clearAutoreplaceList()136 void Preferences::clearAutoreplaceList()
137 {
138   self()->mAutoreplaceList.clear();
139 }
140 
141 // --------------------------- AutoReplace ---------------------------
142 
setServerGroupHash(const Konversation::ServerGroupHash & hash)143 void Preferences::setServerGroupHash(const Konversation::ServerGroupHash& hash)
144 {
145     self()->mServerGroupHash.clear();
146     self()->mServerGroupHash = hash;
147 }
148 
addServerGroup(Konversation::ServerGroupSettingsPtr serverGroup)149 void Preferences::addServerGroup(Konversation::ServerGroupSettingsPtr serverGroup)
150 {
151     Konversation::ServerGroupHash hash = self()->mServerGroupHash;
152     hash.insert(serverGroup->id(), serverGroup);
153     self()->mServerGroupHash = hash;
154 }
155 
serverGroupById(int id)156 const Konversation::ServerGroupSettingsPtr Preferences::serverGroupById(int id)
157 {
158     return  self()->mServerGroupHash.value(id);
159 }
160 
serverGroupsByServer(const QString & server)161 const QList<Konversation::ServerGroupSettingsPtr> Preferences::serverGroupsByServer(const QString& server)
162 {
163     QList<Konversation::ServerGroupSettingsPtr> serverGroups;
164     const Konversation::ServerGroupHash& serverGroupHash = self()->mServerGroupHash;
165     for (const auto& serverGroupSettings : serverGroupHash) {
166         const auto serverList = serverGroupSettings->serverList();
167         for (const auto& serverSertting : serverList) {
168             if (serverSertting.host().toLower() == server)
169                 serverGroups.append(serverGroupSettings);
170         }
171     }
172     return serverGroups;
173 }
174 
serverGroupIdsByName(const QString & serverGroup)175 QList<int> Preferences::serverGroupIdsByName(const QString& serverGroup)
176 {
177     QList<int> serverIds;
178     QHashIterator<int, Konversation::ServerGroupSettingsPtr> it(self()->mServerGroupHash);
179     while(it.hasNext())
180     {
181         if(it.next().value()->name().toLower() == serverGroup.toLower())
182             serverIds.append(it.key());
183     }
184     if (serverIds.isEmpty())
185         serverIds.append(-1);
186 
187     return serverIds;
188 }
189 
isServerGroup(const QString & server)190 bool Preferences::isServerGroup(const QString& server)
191 {
192     const Konversation::ServerGroupHash& serverGroupHash = self()->mServerGroupHash;
193     for (const auto& serverGroupSettings : serverGroupHash) {
194         if (serverGroupSettings->name().toLower() == server.toLower())
195             return true;
196     }
197     return false;
198 }
199 
removeServerGroup(int id)200 void Preferences::removeServerGroup(int id)
201 {
202     self()->mServerGroupHash.remove(id);
203 }
204 
205 
highlightList()206 const QList<Highlight*> Preferences::highlightList()
207 {
208     return self()->mHighlightList;
209 }
210 
setHighlightList(const QList<Highlight * > & newList)211 void Preferences::setHighlightList(const QList<Highlight*> &newList)
212 {
213     qDeleteAll(self()->mHighlightList);
214     self()->mHighlightList.clear();
215     self()->mHighlightList=newList;
216 }
217 
addHighlight(const QString & highlight,bool regExp,const QColor & color,const QString & soundURL,const QString & autoText,const QString & chatWindows,bool notify)218 void Preferences::addHighlight(const QString& highlight, bool regExp, const QColor& color,
219     const QString& soundURL, const QString& autoText, const QString& chatWindows, bool notify)
220 {
221     self()->mHighlightList.append(new Highlight(highlight, regExp, color,
222         QUrl(soundURL), autoText, chatWindows, notify));
223 }
224 
setIgnoreList(const QList<Ignore * > & newList)225 void Preferences::setIgnoreList(const QList<Ignore*> &newList)
226 {
227     clearIgnoreList();
228     self()->mIgnoreList=newList;
229 }
230 
addIgnore(const QString & newIgnore)231 void Preferences::addIgnore(const QString &newIgnore)
232 {
233     QStringList ignore = newIgnore.split(QLatin1Char(','));
234     self()->mIgnoreList.append(new Ignore(ignore[0],ignore[1].toInt()));
235 }
236 
removeIgnore(const QString & oldIgnore)237 bool Preferences::removeIgnore(const QString &oldIgnore)
238 {
239     for (Ignore *ignore : qAsConst(self()->mIgnoreList)) {
240         if (ignore->getName().toLower() == oldIgnore.toLower())
241         {
242             self()->mIgnoreList.removeOne(ignore);
243             delete ignore;
244             return true;
245         }
246     }
247 
248     return false;
249 }
250 
isIgnored(const QString & nickname)251 bool Preferences::isIgnored(const QString &nickname)
252 {
253     for (Ignore *ignore : qAsConst(self()->mIgnoreList)) {
254         if (ignore->getName().section(QLatin1Char('!'),0,0).toLower()==nickname.toLower())
255         {
256             return true;
257         }
258     }
259 
260     return false;
261 }
262 
setNotifyList(const QMap<int,QStringList> & newList)263 void Preferences::setNotifyList(const QMap<int, QStringList> &newList)
264 { self()->mNotifyList=newList; }
265 
notifyList()266 const QMap<int, QStringList> Preferences::notifyList() { return self()->mNotifyList; }
267 
notifyListByGroupId(int serverGroupId)268 const QStringList Preferences::notifyListByGroupId(int serverGroupId)
269 {
270     return self()->mNotifyList.value(serverGroupId);
271 }
272 
notifyStringByGroupId(int serverGroupId)273 const QString Preferences::notifyStringByGroupId(int serverGroupId)
274 {
275     return notifyListByGroupId(serverGroupId).join(QLatin1Char(' '));
276 }
277 
addNotify(int serverGroupId,const QString & newPattern)278 bool Preferences::addNotify(int serverGroupId, const QString& newPattern)
279 {
280     QStringList& list = self()->mNotifyList[serverGroupId];
281 
282     if (!list.contains(newPattern, Qt::CaseInsensitive))
283     {
284         list.append(newPattern);
285 
286         if (list.size() == 1)
287             Q_EMIT self()->notifyListStarted(serverGroupId);
288 
289         return true;
290     }
291 
292     return false;
293 }
294 
removeNotify(int serverGroupId,const QString & pattern)295 bool Preferences::removeNotify(int serverGroupId, const QString& pattern)
296 {
297     if (self()->mNotifyList.contains(serverGroupId))
298     {
299         QString lowered = pattern.toLower();
300         const QStringList& oldList = self()->mNotifyList[serverGroupId];
301         QStringList newList;
302 
303         for (const QString& nick : oldList) {
304             if (nick.toLower() != lowered)
305                 newList << nick;
306         }
307 
308         if (newList.size() < oldList.size())
309         {
310             if (newList.isEmpty())
311                 self()->mNotifyList.remove(serverGroupId);
312             else
313                 self()->mNotifyList[serverGroupId] = newList;
314 
315             return true;
316         }
317     }
318 
319     return false;
320 }
321 
isNotify(int serverGroupId,const QString & pattern)322 bool Preferences::isNotify(int serverGroupId, const QString& pattern)
323 {
324     if (self()->mNotifyList.contains(serverGroupId))
325         if (self()->mNotifyList.value(serverGroupId).contains(pattern, Qt::CaseInsensitive))
326             return true;
327 
328     return false;
329 }
330 
331 // Default identity functions
addIdentity(const IdentityPtr & identity)332 void Preferences::addIdentity(const IdentityPtr &identity) { self()->mIdentityList.append(identity); }
removeIdentity(const IdentityPtr & identity)333 void Preferences::removeIdentity(const IdentityPtr &identity) { self()->mIdentityList.removeOne(identity); }
334 
clearIdentityList()335 void Preferences::clearIdentityList()
336 {
337     self()->mIdentityList.clear();
338 }
339 
identityList()340 const IdentityList Preferences::identityList() { return self()->mIdentityList; }
341 
setIdentityList(const IdentityList & list)342 void Preferences::setIdentityList(const IdentityList& list)
343 {
344     self()->mIdentityList.clear();
345     self()->mIdentityList = list;
346 }
347 
identityByName(const QString & name)348 const IdentityPtr Preferences::identityByName(const QString& name)
349 {
350     const QList<IdentityPtr> identities = identityList();
351     for (const auto& identity : identities) {
352         if (identity->getName() == name) {
353             return identity;
354         }
355     }
356 
357     // no self()->matching identity found, return default identity
358     return identities.first();
359 }
360 
identityById(int id)361 const IdentityPtr Preferences::identityById(int id)
362 {
363     const QList<IdentityPtr> identities = identityList();
364     for (const auto& identity : identities) {
365         if (identity->id() == id) {
366             return identity;
367         }
368     }
369 
370     return identities.first();
371 }
372 
defaultAliasList()373 QStringList Preferences::defaultAliasList()
374 {
375     // Auto-alias scripts
376     const QStringList scriptDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("scripts"), QStandardPaths::LocateDirectory);
377     QSet<QString> scripts;
378 
379     for (const QString &dir : scriptDirs) {
380 
381         const QStringList scriptFiles = QDir(dir).entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Executable);
382 
383         for (const QString &script : scriptFiles) {
384             scripts << script;
385         }
386     }
387 
388     QStringList aliasList;
389 
390     for (const QString& script : qAsConst(scripts)) {
391         aliasList.append(QStringLiteral("%1 /exec %1").arg(script));
392 
393         // FIXME: Historically, defaultAliasList() is primarily used to dynamically
394         // compile a list of installed scripts and generate appropriate aliases for
395         // them. It's not only used when the alias preferences are reset or initia-
396         // lized, but also on application start. The following crudely adds two
397         // aliases when the 'media' script is found, to provide easy access to its
398         // capability to differentiate  between audio and video media. This method
399         // needs at the very least to be split up in two, or scripts may in the
400         // future determine what aliases they want to add.
401         if (script == QLatin1String("media"))
402         {
403             aliasList.append(QStringLiteral("audio /exec media audio"));
404             aliasList.append(QStringLiteral("video /exec media video"));
405         }
406     }
407 
408     return aliasList;
409 }
410 
clearIgnoreList()411 void Preferences::clearIgnoreList() { qDeleteAll(self()->mIgnoreList); self()->mIgnoreList.clear(); }
ignoreList()412 const QList<Ignore*> Preferences::ignoreList() { return self()->mIgnoreList; }
413 
setShowTrayIcon(bool state)414 void Preferences::setShowTrayIcon(bool state)
415 {
416     self()->PreferencesBase::setShowTrayIcon(state);
417     Q_EMIT self()->updateTrayIcon();
418 }
419 
setTrayNotify(bool state)420 void Preferences::setTrayNotify(bool state)
421 {
422     self()->PreferencesBase::setTrayNotify(state);
423     Q_EMIT self()->updateTrayIcon();
424 }
425 
426 
setAutoUserhost(bool state)427 void Preferences::setAutoUserhost(bool state)
428 {
429     self()->PreferencesBase::setAutoUserhost(state);
430 }
431 
dialogFlag(const QString & flagName)432 bool Preferences::dialogFlag(const QString& flagName)
433 {
434     KConfigGroup config(KSharedConfig::openConfig()->group("Notification self()->Messages"));
435 
436     if (!config.readEntry(flagName).isEmpty())
437         return false;
438     else
439         return true;
440 }
441 
setDialogFlag(const QString & flagName,bool state)442 void Preferences::setDialogFlag(const QString& flagName,bool state)
443 {
444     KConfigGroup config(KSharedConfig::openConfig()->group("Notification self()->Messages"));
445 
446     if (state)
447         config.deleteEntry(flagName);
448     else
449     {
450         if (config.readEntry(flagName).isEmpty())
451             config.writeEntry(flagName,"no");
452     }
453 
454     config.sync();
455 }
456 
457 
458 // Channel Encodings
channelEncoding(const QString & server,const QString & channel)459 const QString Preferences::channelEncoding(const QString& server,const QString& channel)
460 {
461     //check all matching server's encodings
462     //TODO when we rewrite dbus, allow for multiple encodings to be returned
463     //for true 'duplicity compatibility'
464     const QList<int> serverIds = serverGroupIdsByName(server);
465     if(serverIds.count() > 1)
466     {
467         for (int serverId : serverIds) {
468             const QString codec = channelEncoding(serverId, channel);
469             if(!codec.isEmpty())
470                 return codec;
471         }
472         return QString();
473     }
474     else
475         return channelEncoding(serverIds.first(),channel);
476 }
477 
channelEncoding(int serverGroupId,const QString & channel)478 const QString Preferences::channelEncoding(int serverGroupId,const QString& channel)
479 {
480     if(self()->mChannelEncodingsMap.contains(serverGroupId))
481         if(self()->mChannelEncodingsMap[serverGroupId].contains(channel.toLower()))
482             return self()->mChannelEncodingsMap[serverGroupId][channel.toLower()];
483     return QString();
484 }
485 
setChannelEncoding(const QString & server,const QString & channel,const QString & encoding)486 void Preferences::setChannelEncoding(const QString& server,const QString& channel,const QString& encoding)
487 {
488     //set channel encoding for ALL matching servergroups
489     const QList<int> serverIds = serverGroupIdsByName(server);
490     if(serverIds.count() > 1)
491     {
492         for (int serverId : serverIds)
493             setChannelEncoding(serverId, channel, encoding);
494     }
495     else
496         setChannelEncoding(serverIds.first(),channel,encoding);
497 }
498 
setChannelEncoding(int serverGroupId,const QString & channel,const QString & encoding)499 void Preferences::setChannelEncoding(int serverGroupId,const QString& channel,const QString& encoding)
500 {
501     self()->mChannelEncodingsMap[serverGroupId][channel.toLower()]=encoding;
502 }
503 
channelEncodingsServerGroupIdList()504 const QList<int> Preferences::channelEncodingsServerGroupIdList()
505 {
506     return self()->mChannelEncodingsMap.keys();
507 }
508 
channelEncodingsChannelList(int serverGroupId)509 const QStringList Preferences::channelEncodingsChannelList(int serverGroupId)
510 {
511     return self()->mChannelEncodingsMap[serverGroupId].keys();
512 }
513 
spellCheckingLanguage(const Konversation::ServerGroupSettingsPtr & serverGroup,const QString & key)514 const QString Preferences::spellCheckingLanguage(const Konversation::ServerGroupSettingsPtr &serverGroup, const QString& key)
515 {
516     if (self()->mServerGroupSpellCheckingLanguages.contains(serverGroup))
517         return self()->mServerGroupSpellCheckingLanguages.value(serverGroup).value(key);
518 
519     return QString();
520 }
521 
spellCheckingLanguage(const QString & server,const QString & key)522 const QString Preferences::spellCheckingLanguage(const QString& server, const QString& key)
523 {
524     if (self()->mServerSpellCheckingLanguages.contains(server))
525         return self()->mServerSpellCheckingLanguages.value(server).value(key);
526 
527     return QString();
528 }
529 
setSpellCheckingLanguage(const Konversation::ServerGroupSettingsPtr & serverGroup,const QString & key,const QString & language)530 void Preferences::setSpellCheckingLanguage(const Konversation::ServerGroupSettingsPtr &serverGroup, const QString& key, const QString& language)
531 {
532     QHash<QString, QString> languageHash;
533 
534     if (self()->mServerGroupSpellCheckingLanguages.contains(serverGroup))
535         languageHash = self()->mServerGroupSpellCheckingLanguages.value(serverGroup);
536 
537     languageHash.insert(key, language);
538 
539     self()->mServerGroupSpellCheckingLanguages.insert(serverGroup, languageHash);
540 }
541 
setSpellCheckingLanguage(const QString & server,const QString & key,const QString & language)542 void Preferences::setSpellCheckingLanguage(const QString& server, const QString& key, const QString& language)
543 {
544     QHash<QString, QString> languageHash;
545 
546     if (self()->mServerSpellCheckingLanguages.contains(server))
547         languageHash = self()->mServerSpellCheckingLanguages.value(server);
548 
549     languageHash.insert(key, language);
550 
551     self()->mServerSpellCheckingLanguages.insert(server, languageHash);
552 }
553 
serverGroupSpellCheckingLanguages()554 const QHash< Konversation::ServerGroupSettingsPtr, QHash< QString, QString > > Preferences::serverGroupSpellCheckingLanguages()
555 {
556     return self()->mServerGroupSpellCheckingLanguages;
557 }
558 
serverSpellCheckingLanguages()559 const QHash< QString, QHash< QString, QString > > Preferences::serverSpellCheckingLanguages()
560 {
561     return self()->mServerSpellCheckingLanguages;
562 }
563 
defaultNicknameSortingOrder()564 const QString Preferences::defaultNicknameSortingOrder()
565 {
566   return QStringLiteral("qpohv-");
567 }
568 
569 // override to add %u if needed
webBrowserCmd()570 QString Preferences::webBrowserCmd()
571 {
572   // add %u to command if it's not in there
573   QString cmd=self()->mWebBrowserCmd;
574   if (!cmd.contains(QLatin1String("%u")))
575       cmd += QStringLiteral(" %u");
576   return cmd;
577 }
578 
saveColumnState(QTreeView * treeView,const QString & name)579 void Preferences::saveColumnState(QTreeView *treeView, const QString &name)
580 {
581     KConfigGroup group(KSharedConfig::openConfig(), name);
582 
583     QList<int> columnWidths;
584     const int columnCount = treeView->header()->count();
585     columnWidths.reserve(columnCount);
586     for (int i = 0; i < columnCount; ++i)
587         columnWidths.append(treeView->columnWidth(i));
588     // save column widths
589     group.writeEntry("ColumnWidths", columnWidths);
590     group.writeEntry("ColumnSorted", treeView->header()->sortIndicatorSection());
591     group.writeEntry("ColumnSortDescending", treeView->header()->sortIndicatorOrder() == Qt::DescendingOrder ? true : false );
592 }
593 
restoreColumnState(QTreeView * treeView,const QString & name,int defaultColumn,Qt::SortOrder defaultSortOrder)594 void Preferences::restoreColumnState(QTreeView* treeView, const QString &name, int defaultColumn , Qt::SortOrder defaultSortOrder)
595 {
596     KConfigGroup group(KSharedConfig::openConfig(), name);
597 
598     QList<int> columnWidths = group.readEntry("ColumnWidths", QList<int>());
599     for (int i = 0; i < columnWidths.count(); ++i)
600         if (columnWidths.at(i))
601             treeView->setColumnWidth(i, columnWidths.at(i));
602 
603     if (group.readEntry("ColumnSortDescending", (defaultSortOrder == Qt::DescendingOrder)))
604         treeView->header()->setSortIndicator(group.readEntry("ColumnSorted", defaultColumn), Qt::DescendingOrder);
605     else
606         treeView->header()->setSortIndicator(group.readEntry("ColumnSorted", defaultColumn), Qt::AscendingOrder);
607 }
608 
slotSetUseOSD(bool use)609 void Preferences::slotSetUseOSD(bool use)
610 {
611     self()->setUseOSD(use);
612 }
613 
614 
615