modernize: Remove old-style slot usage in NetworkModelController
[quassel.git] / src / common / settings.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QStringList>
22
23 #include "settings.h"
24
25 const int VERSION = 1;              /// Settings version for backwords/forwards incompatible changes
26
27 // This is used if no VersionMinor key exists, e.g. upgrading from a Quassel version before this
28 // change.  This shouldn't be increased from 1; instead, change the logic in Core::Core() and
29 // QtUiApplication::init() to handle upgrading and downgrading.
30 const int VERSION_MINOR_INITIAL = 1; /// Initial settings version for compatible changes
31
32 QHash<QString, QVariant> Settings::_settingsCache;
33 QHash<QString, bool> Settings::_settingsKeyPersistedCache;
34 QHash<QString, std::shared_ptr<SettingsChangeNotifier>> Settings::_settingsChangeNotifier;
35
36 #ifdef Q_OS_MAC
37 #  define create_qsettings QSettings s(QCoreApplication::organizationDomain(), _appName)
38 #else
39 #  define create_qsettings QSettings s(fileName(), format())
40 #endif
41
42 Settings::Settings(QString group, QString appName)
43     : _group(std::move(group)), _appName(std::move(appName))
44 {}
45
46
47 void Settings::setGroup(QString group) {
48     _group = std::move(group);
49 }
50
51
52 void Settings::notify(const QString &key, QObject *receiver, const char *slot) const
53 {
54     QObject::connect(notifier(normalizedKey(_group, key)), SIGNAL(valueChanged(const QVariant &)),
55         receiver, slot);
56 }
57
58
59 void Settings::initAndNotify(const QString &key, QObject *receiver, const char *slot, const QVariant &defaultValue) const
60 {
61     notify(key, receiver, slot);
62     emit notifier(normalizedKey(_group, key))->valueChanged(localValue(key, defaultValue));
63 }
64
65
66 uint Settings::version() const
67 {
68     // we don't cache this value, and we ignore the group
69     create_qsettings;
70     uint ver = s.value("Config/Version", 0).toUInt();
71     if (!ver) {
72         // No version, so create one
73         s.setValue("Config/Version", VERSION);
74         return VERSION;
75     }
76     return ver;
77 }
78
79
80 uint Settings::versionMinor() const
81 {
82     // Don't cache this value; ignore the group
83     create_qsettings;
84     // '0' means new configuration, anything else indicates an existing configuration.  Application
85     // initialization should check this value and manage upgrades/downgrades, e.g. in Core::Core()
86     // and QtUiApplication::init().
87     uint verMinor = s.value("Config/VersionMinor", 0).toUInt();
88
89     // As previous Quassel versions didn't implement this, we need to check if any settings other
90     // than Config/Version exist.  If so, assume it's version 1.
91     if (verMinor == 0 && s.allKeys().count() > 1) {
92         // More than 1 key exists, but version's never been set.  Assume and set version 1.
93         // const_cast is ok, because setVersionMinor() doesn't actually change this instance
94         const_cast<Settings*>(this)->setVersionMinor(VERSION_MINOR_INITIAL);
95         return VERSION_MINOR_INITIAL;
96     } else {
97         return verMinor;
98     }
99 }
100
101
102 void Settings::setVersionMinor(const uint versionMinor)
103 {
104     // Don't cache this value; ignore the group
105     create_qsettings;
106     // Set the value directly.
107     s.setValue("Config/VersionMinor", versionMinor);
108 }
109
110
111 QSettings::Format Settings::format() const
112 {
113 #ifdef Q_OS_WIN
114     return QSettings::IniFormat;
115 #else
116     return QSettings::NativeFormat;
117 #endif
118 }
119
120
121 bool Settings::sync()
122 {
123     create_qsettings;
124     s.sync();
125     switch (s.status()) {
126         case QSettings::NoError:
127             return true;
128         default:
129             return false;
130     }
131 }
132
133
134 bool Settings::isWritable() const
135 {
136     create_qsettings;
137     return s.isWritable();
138 }
139
140
141 QStringList Settings::allLocalKeys() const
142 {
143     create_qsettings;
144     s.beginGroup(_group);
145     QStringList res = s.allKeys();
146     s.endGroup();
147     return res;
148 }
149
150
151 QStringList Settings::localChildKeys(const QString &rootkey) const
152 {
153     QString g;
154     if (rootkey.isEmpty())
155         g = _group;
156     else
157         g = QString("%1/%2").arg(_group, rootkey);
158
159     create_qsettings;
160     s.beginGroup(g);
161     QStringList res = s.childKeys();
162     s.endGroup();
163     return res;
164 }
165
166
167 QStringList Settings::localChildGroups(const QString &rootkey) const
168 {
169     QString g;
170     if (rootkey.isEmpty())
171         g = _group;
172     else
173         g = QString("%1/%2").arg(_group, rootkey);
174
175     create_qsettings;
176     s.beginGroup(g);
177     QStringList res = s.childGroups();
178     s.endGroup();
179     return res;
180 }
181
182
183 void Settings::setLocalValue(const QString &key, const QVariant &data)
184 {
185     QString normKey = normalizedKey(_group, key);
186     create_qsettings;
187     s.setValue(normKey, data);
188     setCacheKeyPersisted(normKey, true);
189     setCacheValue(normKey, data);
190     if (hasNotifier(normKey)) {
191         emit notifier(normKey)->valueChanged(data);
192     }
193 }
194
195
196 QVariant Settings::localValue(const QString &key, const QVariant &def) const
197 {
198     QString normKey = normalizedKey(_group, key);
199     if (!isCached(normKey)) {
200         create_qsettings;
201         // Since we're loading from settings anyways, cache whether or not the key exists on disk
202         setCacheKeyPersisted(normKey, s.contains(normKey));
203         // Cache key value
204         setCacheValue(normKey, s.value(normKey, def));
205     }
206     if (cacheKeyPersisted(normKey)) {
207         return cacheValue(normKey);
208     }
209     // Don't return possibly wrong cached values
210     // A key gets cached with the first default value requested and never changes afterwards
211     return def;
212 }
213
214
215 bool Settings::localKeyExists(const QString &key) const
216 {
217     QString normKey = normalizedKey(_group, key);
218     if (!isKeyPersistedCached(normKey)) {
219         create_qsettings;
220         // Cache whether or not key exists on disk
221         // We can't cache key value as we don't know the default
222         setCacheKeyPersisted(normKey, s.contains(normKey));
223     }
224
225     return cacheKeyPersisted(normKey);
226 }
227
228
229 void Settings::removeLocalKey(const QString &key)
230 {
231     create_qsettings;
232     s.beginGroup(_group);
233     s.remove(key);
234     s.endGroup();
235     QString normKey = normalizedKey(_group, key);
236     if (isCached(normKey)) {
237         _settingsCache.remove(normKey);
238     }
239     if (isKeyPersistedCached(normKey)) {
240         _settingsKeyPersistedCache.remove(normKey);
241     }
242     if (hasNotifier(normKey)) {
243         emit notifier(normKey)->valueChanged({});
244     }
245 }
246
247
248 QString Settings::fileName() const
249 {
250     return Quassel::configDirPath() + _appName
251            + ((format() == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
252 }
253
254
255 QString Settings::normalizedKey(const QString &group, const QString &key) const
256 {
257     if (group.isEmpty())
258         return key;
259     return group + '/' + key;
260 }
261
262
263 void Settings::setCacheKeyPersisted(const QString &normKey, bool exists) const
264 {
265     _settingsKeyPersistedCache[normKey] = exists;
266 }
267
268
269 bool Settings::cacheKeyPersisted(const QString &normKey) const
270 {
271     return _settingsKeyPersistedCache[normKey];
272 }
273
274
275 bool Settings::isKeyPersistedCached(const QString &normKey) const
276 {
277     return _settingsKeyPersistedCache.contains(normKey);
278 }
279
280
281 void Settings::setCacheValue(const QString &normKey, const QVariant &data) const
282 {
283     _settingsCache[normKey] = data;
284 }
285
286
287 QVariant Settings::cacheValue(const QString &normKey) const
288 {
289     return _settingsCache[normKey];
290 }
291
292
293 bool Settings::isCached(const QString &normKey) const
294 {
295     return _settingsCache.contains(normKey);
296 }
297
298
299 SettingsChangeNotifier *Settings::notifier(const QString &normKey) const
300 {
301     if (!hasNotifier(normKey))
302         _settingsChangeNotifier[normKey] = std::make_shared<SettingsChangeNotifier>();
303     return _settingsChangeNotifier[normKey].get();
304 }
305
306
307 bool Settings::hasNotifier(const QString &normKey) const
308 {
309     return _settingsChangeNotifier.contains(normKey);
310 }