1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
4 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
5 SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kconfiggroup.h"
11#include "kconfiggroup_p.h"
12
13#include "kconfig.h"
14#include "kconfig_core_log_settings.h"
15#include "kconfig_p.h"
16#include "kconfigdata_p.h"
17#include "ksharedconfig.h"
18
19#include <QDate>
20#include <QDir>
21#include <QFile>
22#include <QPoint>
23#include <QRect>
24#include <QSharedData>
25#include <QString>
26#include <QTextStream>
27#include <QUrl>
28#include <QUuid>
29
30#include <algorithm>
31#include <array>
32#include <math.h>
33#include <stdlib.h>
34
35class KConfigGroupPrivate : public QSharedData
36{
37public:
38 KConfigGroupPrivate(KConfig *owner, bool isImmutable, bool isConst, const QString &name)
39 : mOwner(owner)
40 , mName(name)
41 , bImmutable(isImmutable)
42 , bConst(isConst)
43 {
44 if (Q_UNLIKELY(!mOwner->name().isEmpty() && mOwner->accessMode() == KConfigBase::NoAccess)) {
45 qCWarning(KCONFIG_CORE_LOG) << "Created a KConfigGroup on an inaccessible config location" << mOwner->name() << name;
46 }
47 }
48
49 KConfigGroupPrivate(const KSharedConfigPtr &owner, const QString &name)
50 : sOwner(owner)
51 , mOwner(sOwner.data())
52 , mName(name)
53 , bImmutable(name.isEmpty() ? owner->isImmutable() : owner->isGroupImmutable(group: name))
54 , bConst(false)
55 {
56 if (Q_UNLIKELY(!mOwner->name().isEmpty() && mOwner->accessMode() == KConfigBase::NoAccess)) {
57 qCWarning(KCONFIG_CORE_LOG) << "Created a KConfigGroup on an inaccessible config location" << mOwner->name() << name;
58 }
59 }
60
61 KConfigGroupPrivate(KConfigGroup *parent, bool isImmutable, bool isConst, const QString &name)
62 : sOwner(parent->d->sOwner)
63 , mOwner(parent->d->mOwner)
64 , mName(name)
65 , bImmutable(isImmutable)
66 , bConst(isConst)
67 {
68 if (!parent->d->mName.isEmpty()) {
69 mParent = parent->d;
70 }
71 }
72
73 KConfigGroupPrivate(const KConfigGroupPrivate *other, bool isImmutable, const QString &name)
74 : sOwner(other->sOwner)
75 , mOwner(other->mOwner)
76 , mName(name)
77 , bImmutable(isImmutable)
78 , bConst(other->bConst)
79 {
80 if (!other->mName.isEmpty()) {
81 mParent = const_cast<KConfigGroupPrivate *>(other);
82 }
83 }
84
85 KSharedConfig::Ptr sOwner;
86 KConfig *mOwner;
87 QExplicitlySharedDataPointer<KConfigGroupPrivate> mParent;
88 QString mName;
89
90 /* bitfield */
91 const bool bImmutable : 1; // is this group immutable?
92 const bool bConst : 1; // is this group read-only?
93
94 QString fullName() const
95 {
96 if (!mParent) {
97 return name();
98 }
99 return mParent->fullName(aGroup: mName);
100 }
101
102 QString name() const
103 {
104 if (mName.isEmpty()) {
105 return QStringLiteral("<default>");
106 }
107 return mName;
108 }
109
110 QString fullName(const QString &aGroup) const
111 {
112 if (mName.isEmpty()) {
113 return aGroup;
114 }
115 return fullName() + QLatin1Char('\x1d') + aGroup;
116 }
117
118 static QExplicitlySharedDataPointer<KConfigGroupPrivate> create(KConfigBase *master, const QString &name, bool isImmutable, bool isConst)
119 {
120 QExplicitlySharedDataPointer<KConfigGroupPrivate> data;
121 if (dynamic_cast<KConfigGroup *>(master)) {
122 data = new KConfigGroupPrivate(static_cast<KConfigGroup *>(master), isImmutable, isConst, name);
123 } else {
124 data = new KConfigGroupPrivate(dynamic_cast<KConfig *>(master), isImmutable, isConst, name);
125 }
126 return data;
127 }
128
129 static QByteArray serializeList(const QList<QByteArray> &list);
130 static QStringList deserializeList(const QString &data);
131};
132
133QByteArray KConfigGroupPrivate::serializeList(const QList<QByteArray> &list)
134{
135 QByteArray value;
136
137 if (!list.isEmpty()) {
138 auto it = list.cbegin();
139 const auto end = list.cend();
140
141 value = QByteArray(*it).replace(before: '\\', QByteArrayLiteral("\\\\")).replace(before: ',', QByteArrayLiteral("\\,"));
142
143 while (++it != end) {
144 // In the loop, so it is not done when there is only one element.
145 // Doing it repeatedly is a pretty cheap operation.
146 value.reserve(asize: 4096);
147
148 value += ',';
149 value += QByteArray(*it).replace(before: '\\', QByteArrayLiteral("\\\\")).replace(before: ',', QByteArrayLiteral("\\,"));
150 }
151
152 // To be able to distinguish an empty list from a list with one empty element.
153 if (value.isEmpty()) {
154 value = QByteArrayLiteral("\\0");
155 }
156 }
157
158 return value;
159}
160
161QStringList KConfigGroupPrivate::deserializeList(const QString &data)
162{
163 if (data.isEmpty()) {
164 return QStringList();
165 }
166 if (data == QLatin1String("\\0")) {
167 return QStringList(QString());
168 }
169 QStringList value;
170 QString val;
171 val.reserve(asize: data.size());
172 bool quoted = false;
173 for (int p = 0; p < data.length(); p++) {
174 if (quoted) {
175 val += data[p];
176 quoted = false;
177 } else if (data[p].unicode() == '\\') {
178 quoted = true;
179 } else if (data[p].unicode() == ',') {
180 val.squeeze(); // release any unused memory
181 value.append(t: val);
182 val.clear();
183 val.reserve(asize: data.size() - p);
184 } else {
185 val += data[p];
186 }
187 }
188 value.append(t: val);
189 return value;
190}
191
192static QVarLengthArray<int, 8> asIntList(QByteArrayView string)
193{
194 int start = 0;
195 int next = start;
196 QVarLengthArray<int, 8> ret;
197 while ((next = string.indexOf(ch: ',', from: start)) != -1) {
198 ret.push_back(t: string.sliced(pos: start, n: next - start).toInt());
199 start = next + 1;
200 }
201 ret.push_back(t: string.sliced(pos: start, n: string.size() - start).toInt());
202 return ret;
203}
204
205static QVarLengthArray<qreal, 8> asRealList(QByteArrayView string)
206{
207 int start = 0;
208 int next = start;
209 QVarLengthArray<qreal, 8> ret;
210 while ((next = string.indexOf(ch: ',', from: start)) != -1) {
211 ret.push_back(t: string.sliced(pos: start, n: next - start).toDouble());
212 start = next + 1;
213 }
214 ret.push_back(t: string.sliced(pos: start, n: string.size() - start).toDouble());
215 return ret;
216}
217
218static QString errString(const char *pKey, const QByteArray &value, const QVariant &aDefault)
219{
220 return QStringLiteral("\"%1\" - conversion of \"%3\" to %2 failed")
221 .arg(args: QString::fromLatin1(ba: pKey), args: QString::fromLatin1(ba: aDefault.typeName()), args: QString::fromLatin1(ba: value));
222}
223
224static QString formatError(int expected, int got)
225{
226 return QStringLiteral(" (wrong format: expected %1 items, got %2)").arg(a: expected).arg(a: got);
227}
228
229QVariant KConfigGroup::convertToQVariant(const char *pKey, const QByteArray &value, const QVariant &aDefault)
230{
231 // if a type handler is added here you must add a QVConversions definition
232 // to kconfigconversioncheck_p.h, or KConfigConversionCheck::to_QVariant will not allow
233 // readEntry<T> to convert to QVariant.
234 switch (static_cast<QMetaType::Type>(aDefault.userType())) {
235 case QMetaType::UnknownType:
236 return QVariant();
237 case QMetaType::QString:
238 // this should return the raw string not the dollar expanded string.
239 // imho if processed string is wanted should call
240 // readEntry(key, QString) not readEntry(key, QVariant)
241 return QString::fromUtf8(ba: value);
242 case QMetaType::QUuid:
243 return QUuid::fromString(string: value);
244 case QMetaType::QVariantList:
245 case QMetaType::QStringList:
246 return KConfigGroupPrivate::deserializeList(data: QString::fromUtf8(ba: value));
247 case QMetaType::QByteArray:
248 return value;
249 case QMetaType::Bool: {
250 static const std::array<const char *, 4> negatives = {"false", "no", "off", "0"};
251
252 return std::all_of(first: negatives.begin(), last: negatives.end(), pred: [value](const char *negativeString) {
253 return value.compare(a: negativeString, cs: Qt::CaseInsensitive) != 0;
254 });
255 }
256 case QMetaType::Double:
257 case QMetaType::Float:
258 case QMetaType::Int:
259 case QMetaType::UInt:
260 case QMetaType::LongLong:
261 case QMetaType::ULongLong:
262 case QMetaType::Long:
263 case QMetaType::ULong: {
264 QVariant tmp = value;
265 if (!tmp.convert(type: aDefault.metaType())) {
266 tmp = aDefault;
267 }
268 return tmp;
269 }
270 case QMetaType::QPoint: {
271 const auto list = asIntList(string: value);
272
273 if (list.count() != 2) {
274 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
275 return aDefault;
276 }
277 return QPoint(list.at(idx: 0), list.at(idx: 1));
278 }
279 case QMetaType::QPointF: {
280 const auto list = asRealList(string: value);
281
282 if (list.count() != 2) {
283 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
284 return aDefault;
285 }
286 return QPointF(list.at(idx: 0), list.at(idx: 1));
287 }
288 case QMetaType::QRect: {
289 const auto list = asIntList(string: value);
290
291 if (list.count() != 4) {
292 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 4, got: list.count());
293 return aDefault;
294 }
295 const QRect rect(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2), list.at(idx: 3));
296 if (!rect.isValid()) {
297 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
298 return aDefault;
299 }
300 return rect;
301 }
302 case QMetaType::QRectF: {
303 const auto list = asRealList(string: value);
304
305 if (list.count() != 4) {
306 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 4, got: list.count());
307 return aDefault;
308 }
309 const QRectF rect(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2), list.at(idx: 3));
310 if (!rect.isValid()) {
311 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
312 return aDefault;
313 }
314 return rect;
315 }
316 case QMetaType::QSize: {
317 const auto list = asIntList(string: value);
318
319 if (list.count() != 2) {
320 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
321 return aDefault;
322 }
323 const QSize size(list.at(idx: 0), list.at(idx: 1));
324 if (!size.isValid()) {
325 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
326 return aDefault;
327 }
328 return size;
329 }
330 case QMetaType::QSizeF: {
331 const auto list = asRealList(string: value);
332
333 if (list.count() != 2) {
334 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
335 return aDefault;
336 }
337 const QSizeF size(list.at(idx: 0), list.at(idx: 1));
338 if (!size.isValid()) {
339 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
340 return aDefault;
341 }
342 return size;
343 }
344 case QMetaType::QDateTime: {
345 const auto list = asRealList(string: value);
346 if (list.count() < 6) {
347 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 6, got: list.count());
348 return aDefault;
349 }
350 const QDate date(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2));
351 const qreal totalSeconds = list.at(idx: 5);
352 qreal seconds;
353 const qreal fractional = modf(x: totalSeconds, iptr: &seconds);
354 const qreal milliseconds = round(x: fractional * 1000.0);
355 const QTime time(list.at(idx: 3), list.at(idx: 4), seconds, milliseconds);
356
357 QDateTime dt(date, time);
358 if (list.count() == 7) { // Then the timezone, which was added later
359 const auto id = value.mid(index: value.lastIndexOf(ch: ',') + 1);
360 dt.setTimeZone(toZone: QTimeZone(id));
361 }
362 if (!dt.isValid()) {
363 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
364 return aDefault;
365 }
366 return dt;
367 }
368 case QMetaType::QDate: {
369 auto list = asIntList(string: value);
370 // list.count == 6 -> don't break config files that stored QDate as QDateTime
371 if (list.count() != 3 && list.count() != 6) {
372 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 3, got: list.count());
373 return aDefault;
374 }
375 const QDate date(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2));
376 if (!date.isValid()) {
377 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
378 return aDefault;
379 }
380 return date;
381 }
382 case QMetaType::QTime:
383 return QTime::fromString(string: QString::fromUtf8(ba: value));
384 case QMetaType::QColor:
385 case QMetaType::QFont:
386 qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::readEntry was passed GUI type '" << aDefault.typeName()
387 << "' but KConfigGui isn't linked! If it is linked to your program, "
388 "this is a platform bug. Please inform the KDE developers";
389 break;
390 case QMetaType::QUrl:
391 return QUrl(QString::fromUtf8(ba: value));
392
393 default:
394 break;
395 }
396
397 qCWarning(KCONFIG_CORE_LOG) << "unhandled type " << aDefault.typeName();
398 return QVariant();
399}
400
401static bool cleanHomeDirPath(QString &path, const QString &homeDir)
402{
403#ifdef Q_OS_WIN // safer
404 if (!QDir::toNativeSeparators(path).startsWith(QDir::toNativeSeparators(homeDir))) {
405 return false;
406 }
407#else
408 if (!path.startsWith(s: homeDir)) {
409 return false;
410 }
411#endif
412
413 int len = homeDir.length();
414 // replace by "$HOME" if possible
415 if (len && (path.length() == len || path[len] == QLatin1Char('/'))) {
416 path.replace(i: 0, len, QStringLiteral("$HOME"));
417 return true;
418 }
419
420 return false;
421}
422
423static QString translatePath(QString path) // krazy:exclude=passbyvalue
424{
425 if (path.isEmpty()) {
426 return path;
427 }
428
429 // only "our" $HOME should be interpreted
430 path.replace(c: QLatin1Char('$'), after: QLatin1String("$$"));
431
432 const bool startsWithFile = path.startsWith(s: QLatin1String("file:"), cs: Qt::CaseInsensitive);
433 path = startsWithFile ? QUrl(path).toLocalFile() : path;
434
435 if (QDir::isRelativePath(path)) {
436 return path;
437 }
438
439 // Use the same thing as what expandString() will do, to keep data intact
440#ifdef Q_OS_WIN
441 const QString homeDir = QDir::homePath();
442#else
443 const QString homeDir = QFile::decodeName(localFileName: qgetenv(varName: "HOME"));
444#endif
445 (void)cleanHomeDirPath(path, homeDir);
446
447 if (startsWithFile) {
448 path = QUrl::fromLocalFile(localfile: path).toString();
449 }
450
451 return path;
452}
453
454KConfigGroup::KConfigGroup()
455 : d()
456{
457}
458
459bool KConfigGroup::isValid() const
460{
461 return bool(d);
462}
463
464KConfigGroupGui _kde_internal_KConfigGroupGui;
465static inline bool readEntryGui(const QByteArray &data, const char *key, const QVariant &input, QVariant &output)
466{
467 if (_kde_internal_KConfigGroupGui.readEntryGui) {
468 return _kde_internal_KConfigGroupGui.readEntryGui(data, key, input, output);
469 }
470 return false;
471}
472
473static inline bool writeEntryGui(KConfigGroup *cg, const char *key, const QVariant &input, KConfigGroup::WriteConfigFlags flags)
474{
475 if (_kde_internal_KConfigGroupGui.writeEntryGui) {
476 return _kde_internal_KConfigGroupGui.writeEntryGui(cg, key, input, flags);
477 }
478 return false;
479}
480
481KConfigGroup::KConfigGroup(KConfigBase *master, const QString &_group)
482 : d(KConfigGroupPrivate::create(master, name: _group, isImmutable: master->isGroupImmutable(group: _group), isConst: false))
483{
484}
485
486KConfigGroup::KConfigGroup(const KConfigBase *master, const QString &_group)
487 : d(KConfigGroupPrivate::create(master: const_cast<KConfigBase *>(master), name: _group, isImmutable: master->isGroupImmutable(group: _group), isConst: true))
488{
489}
490
491KConfigGroup::KConfigGroup(const KSharedConfigPtr &master, const QString &_group)
492 : d(new KConfigGroupPrivate(master, _group))
493{
494}
495
496KConfigGroup &KConfigGroup::operator=(const KConfigGroup &rhs)
497{
498 d = rhs.d;
499 return *this;
500}
501
502KConfigGroup::KConfigGroup(const KConfigGroup &rhs)
503 : d(rhs.d)
504{
505}
506
507KConfigGroup::~KConfigGroup()
508{
509 d.reset();
510}
511
512KConfigGroup KConfigGroup::groupImpl(const QString &aGroup)
513{
514 Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group");
515 Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group");
516
517 KConfigGroup newGroup;
518
519 newGroup.d = new KConfigGroupPrivate(this, isGroupImmutableImpl(groupName: aGroup), d->bConst, aGroup);
520
521 return newGroup;
522}
523
524const KConfigGroup KConfigGroup::groupImpl(const QString &aGroup) const
525{
526 Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group");
527 Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group");
528
529 KConfigGroup newGroup;
530
531 newGroup.d = new KConfigGroupPrivate(const_cast<KConfigGroup *>(this), isGroupImmutableImpl(groupName: aGroup), true, aGroup);
532
533 return newGroup;
534}
535
536KConfigGroup KConfigGroup::parent() const
537{
538 Q_ASSERT_X(isValid(), "KConfigGroup::parent", "accessing an invalid group");
539
540 KConfigGroup parentGroup;
541
542 if (d->mParent) {
543 parentGroup.d = d->mParent;
544 } else {
545 parentGroup.d = new KConfigGroupPrivate(d->mOwner, d->mOwner->isImmutable(), d->bConst, QString());
546 // make sure we keep the refcount up on the KConfig object
547 parentGroup.d->sOwner = d->sOwner;
548 }
549
550 return parentGroup;
551}
552
553void KConfigGroup::deleteGroup(WriteConfigFlags flags)
554{
555 Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroup", "accessing an invalid group");
556 Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroup", "deleting a read-only group");
557
558 config()->deleteGroup(group: d->fullName(), flags);
559}
560
561QString KConfigGroup::name() const
562{
563 Q_ASSERT_X(isValid(), "KConfigGroup::name", "accessing an invalid group");
564
565 return d->name();
566}
567
568bool KConfigGroup::exists() const
569{
570 Q_ASSERT_X(isValid(), "KConfigGroup::exists", "accessing an invalid group");
571
572 return config()->hasGroup(group: d->fullName());
573}
574
575bool KConfigGroup::sync()
576{
577 Q_ASSERT_X(isValid(), "KConfigGroup::sync", "accessing an invalid group");
578
579 if (!d->bConst) {
580 return config()->sync();
581 }
582
583 return false;
584}
585
586QMap<QString, QString> KConfigGroup::entryMap() const
587{
588 Q_ASSERT_X(isValid(), "KConfigGroup::entryMap", "accessing an invalid group");
589
590 return config()->entryMap(aGroup: d->fullName());
591}
592
593KConfig *KConfigGroup::config()
594{
595 Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group");
596
597 return d->mOwner;
598}
599
600const KConfig *KConfigGroup::config() const
601{
602 Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group");
603
604 return d->mOwner;
605}
606
607bool KConfigGroup::isEntryImmutable(const char *key) const
608{
609 Q_ASSERT_X(isValid(), "KConfigGroup::isEntryImmutable", "accessing an invalid group");
610
611 return (isImmutable() || !config()->d_func()->canWriteEntry(group: d->fullName(), key, isDefault: config()->readDefaults()));
612}
613
614bool KConfigGroup::isEntryImmutable(const QString &key) const
615{
616 return isEntryImmutable(key: key.toUtf8().constData());
617}
618
619QString KConfigGroup::readEntryUntranslated(const QString &pKey, const QString &aDefault) const
620{
621 return readEntryUntranslated(key: pKey.toUtf8().constData(), aDefault);
622}
623
624QString KConfigGroup::readEntryUntranslated(const char *key, const QString &aDefault) const
625{
626 Q_ASSERT_X(isValid(), "KConfigGroup::readEntryUntranslated", "accessing an invalid group");
627
628 QString result = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchFlags(), expand: nullptr);
629 if (result.isNull()) {
630 return aDefault;
631 }
632 return result;
633}
634
635QString KConfigGroup::readEntry(const char *key, const char *aDefault) const
636{
637 return readEntry(key, aDefault: QString::fromUtf8(utf8: aDefault));
638}
639
640QString KConfigGroup::readEntry(const QString &key, const char *aDefault) const
641{
642 return readEntry(key: key.toUtf8().constData(), aDefault);
643}
644
645QString KConfigGroup::readEntry(const char *key, const QString &aDefault) const
646{
647 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
648
649 bool expand = false;
650
651 // read value from the entry map
652 QString aValue = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchLocalized, expand: &expand);
653 if (aValue.isNull()) {
654 aValue = aDefault;
655 }
656
657 if (expand) {
658 return KConfigPrivate::expandString(value: aValue);
659 }
660
661 return aValue;
662}
663
664QString KConfigGroup::readEntry(const QString &key, const QString &aDefault) const
665{
666 return readEntry(key: key.toUtf8().constData(), aDefault);
667}
668
669QStringList KConfigGroup::readEntry(const char *key, const QStringList &aDefault) const
670{
671 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
672
673 const QString data = readEntry(key, aDefault: QString());
674 if (data.isNull()) {
675 return aDefault;
676 }
677
678 return KConfigGroupPrivate::deserializeList(data);
679}
680
681QStringList KConfigGroup::readEntry(const QString &key, const QStringList &aDefault) const
682{
683 return readEntry(key: key.toUtf8().constData(), aDefault);
684}
685
686QVariant KConfigGroup::readEntry(const char *key, const QVariant &aDefault) const
687{
688 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
689
690 const QByteArray data = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchLocalized);
691 if (data.isNull()) {
692 return aDefault;
693 }
694
695 QVariant value;
696 if (!readEntryGui(data, key, input: aDefault, output&: value)) {
697 return convertToQVariant(pKey: key, value: data, aDefault);
698 }
699
700 return value;
701}
702
703QVariant KConfigGroup::readEntry(const QString &key, const QVariant &aDefault) const
704{
705 return readEntry(key: key.toUtf8().constData(), aDefault);
706}
707
708QVariantList KConfigGroup::readEntry(const char *key, const QVariantList &aDefault) const
709{
710 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
711
712 const QString data = readEntry(key, aDefault: QString());
713 if (data.isNull()) {
714 return aDefault;
715 }
716
717 const auto &list = KConfigGroupPrivate::deserializeList(data);
718
719 QVariantList value;
720 value.reserve(asize: list.count());
721 for (const QString &v : list) {
722 value << v;
723 }
724
725 return value;
726}
727
728QVariantList KConfigGroup::readEntry(const QString &key, const QVariantList &aDefault) const
729{
730 return readEntry(key: key.toUtf8().constData(), aDefault);
731}
732
733QStringList KConfigGroup::readXdgListEntry(const QString &key, const QStringList &aDefault) const
734{
735 return readXdgListEntry(key: key.toUtf8().constData(), aDefault);
736}
737
738QStringList KConfigGroup::readXdgListEntry(const char *key, const QStringList &aDefault) const
739{
740 Q_ASSERT_X(isValid(), "KConfigGroup::readXdgListEntry", "accessing an invalid group");
741
742 const QString data = readEntry(key, aDefault: QString());
743 if (data.isNull()) {
744 return aDefault;
745 }
746
747 QStringList value;
748 QString val;
749 val.reserve(asize: data.size());
750 // XXX List serialization being a separate layer from low-level parsing is
751 // probably a bug. No affected entries are defined, though.
752 bool quoted = false;
753 for (int p = 0; p < data.length(); p++) {
754 if (quoted) {
755 val += data[p];
756 quoted = false;
757 } else if (data[p] == QLatin1Char('\\')) {
758 quoted = true;
759 } else if (data[p] == QLatin1Char(';')) {
760 value.append(t: val);
761 val.clear();
762 val.reserve(asize: data.size() - p);
763 } else {
764 val += data[p];
765 }
766 }
767 if (!val.isEmpty()) {
768 value.append(t: val);
769 }
770 return value;
771}
772
773QString KConfigGroup::readPathEntry(const QString &pKey, const QString &aDefault) const
774{
775 return readPathEntry(key: pKey.toUtf8().constData(), aDefault);
776}
777
778QString KConfigGroup::readPathEntry(const char *key, const QString &aDefault) const
779{
780 Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group");
781
782 bool expand = false;
783
784 QString aValue = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchLocalized, expand: &expand);
785 if (aValue.isNull()) {
786 aValue = aDefault;
787 }
788
789 return KConfigPrivate::expandString(value: aValue);
790}
791
792QStringList KConfigGroup::readPathEntry(const QString &pKey, const QStringList &aDefault) const
793{
794 return readPathEntry(key: pKey.toUtf8().constData(), aDefault);
795}
796
797QStringList KConfigGroup::readPathEntry(const char *key, const QStringList &aDefault) const
798{
799 Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group");
800
801 const QString data = readPathEntry(key, aDefault: QString());
802 if (data.isNull()) {
803 return aDefault;
804 }
805
806 return KConfigGroupPrivate::deserializeList(data);
807}
808
809void KConfigGroup::writeEntry(const char *key, const QString &value, WriteConfigFlags flags)
810{
811 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
812 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
813
814 writeEntry(key, value: value.toUtf8(), pFlags: flags);
815}
816
817void KConfigGroup::writeEntry(const QString &key, const QString &value, WriteConfigFlags flags)
818{
819 writeEntry(key: key.toUtf8().constData(), value, flags);
820}
821
822void KConfigGroup::writeEntry(const QString &key, const char *value, WriteConfigFlags pFlags)
823{
824 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
825 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
826
827 writeEntry(key: key.toUtf8().constData(), value: QVariant(QString::fromLatin1(ba: value)), pFlags);
828}
829
830void KConfigGroup::writeEntry(const char *key, const char *value, WriteConfigFlags pFlags)
831{
832 writeEntry(key, value: QVariant(QString::fromLatin1(ba: value)), pFlags);
833}
834
835void KConfigGroup::writeEntry(const char *key, const QByteArray &value, WriteConfigFlags flags)
836{
837 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
838 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
839
840 config()->d_func()->putData(groupName: d->fullName(), key, value: value.isNull() ? QByteArray("") : value, flags);
841}
842
843void KConfigGroup::writeEntry(const QString &key, const QByteArray &value, WriteConfigFlags pFlags)
844{
845 writeEntry(key: key.toUtf8().constData(), value, flags: pFlags);
846}
847
848void KConfigGroup::writeEntry(const char *key, const QStringList &list, WriteConfigFlags flags)
849{
850 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
851 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
852
853 QList<QByteArray> balist;
854 balist.reserve(asize: list.count());
855
856 for (const QString &entry : list) {
857 balist.append(t: entry.toUtf8());
858 }
859
860 writeEntry(key, value: KConfigGroupPrivate::serializeList(list: balist), flags);
861}
862
863void KConfigGroup::writeEntry(const QString &key, const QStringList &list, WriteConfigFlags flags)
864{
865 writeEntry(key: key.toUtf8().constData(), list, flags);
866}
867
868void KConfigGroup::writeEntry(const char *key, const QVariantList &list, WriteConfigFlags flags)
869{
870 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
871 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
872
873 QList<QByteArray> data;
874 data.reserve(asize: list.count());
875
876 for (const QVariant &v : list) {
877 if (v.userType() == QMetaType::QByteArray) {
878 data << v.toByteArray();
879 } else {
880 data << v.toString().toUtf8();
881 }
882 }
883
884 writeEntry(key, value: KConfigGroupPrivate::serializeList(list: data), flags);
885}
886
887void KConfigGroup::writeEntry(const char *key, const QVariant &value, WriteConfigFlags flags)
888{
889 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
890 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
891
892 if (writeEntryGui(cg: this, key, input: value, flags)) {
893 return; // GUI type that was handled
894 }
895
896 QByteArray data;
897 // if a type handler is added here you must add a QVConversions definition
898 // to kconfigconversioncheck_p.h, or KConfigConversionCheck::to_QVariant will not allow
899 // writeEntry<T> to convert to QVariant.
900 switch (static_cast<QMetaType::Type>(value.userType())) {
901 case QMetaType::UnknownType:
902 data = "";
903 break;
904 case QMetaType::QByteArray:
905 data = value.toByteArray();
906 break;
907 case QMetaType::QString:
908 case QMetaType::Int:
909 case QMetaType::UInt:
910 case QMetaType::Double:
911 case QMetaType::Float:
912 case QMetaType::Bool:
913 case QMetaType::LongLong:
914 case QMetaType::ULongLong:
915 case QMetaType::Long:
916 case QMetaType::ULong:
917 data = value.toString().toUtf8();
918 break;
919 case QMetaType::QVariantList:
920 if (!value.canConvert<QStringList>()) {
921 qCWarning(KCONFIG_CORE_LOG) << "not all types in \"" << key
922 << "\" can convert to QString,"
923 " information will be lost";
924 }
925 Q_FALLTHROUGH();
926 case QMetaType::QStringList:
927 writeEntry(key, list: value.toList(), flags);
928 return;
929 case QMetaType::QPoint: {
930 const QPoint rPoint = value.toPoint();
931
932 const QVariantList list{rPoint.x(), rPoint.y()};
933
934 writeEntry(key, list, flags);
935 return;
936 }
937 case QMetaType::QPointF: {
938 const QPointF point = value.toPointF();
939
940 const QVariantList list{point.x(), point.y()};
941
942 writeEntry(key, list, flags);
943 return;
944 }
945 case QMetaType::QRect: {
946 const QRect rRect = value.toRect();
947
948 const QVariantList list{rRect.left(), rRect.top(), rRect.width(), rRect.height()};
949
950 writeEntry(key, list, flags);
951 return;
952 }
953 case QMetaType::QRectF: {
954 const QRectF rRectF = value.toRectF();
955
956 const QVariantList list{rRectF.left(), rRectF.top(), rRectF.width(), rRectF.height()};
957
958 writeEntry(key, list, flags);
959 return;
960 }
961 case QMetaType::QSize: {
962 const QSize rSize = value.toSize();
963
964 const QVariantList list{rSize.width(), rSize.height()};
965
966 writeEntry(key, list, flags);
967 return;
968 }
969 case QMetaType::QUuid: {
970 writeEntry(key, value: value.toString(), flags);
971 return;
972 }
973 case QMetaType::QSizeF: {
974 const QSizeF rSizeF = value.toSizeF();
975
976 const QVariantList list{rSizeF.width(), rSizeF.height()};
977
978 writeEntry(key, list, flags);
979 return;
980 }
981 case QMetaType::QDate: {
982 const QDate date = value.toDate();
983
984 const QVariantList list{date.year(), date.month(), date.day()};
985
986 writeEntry(key, list, flags);
987 return;
988 }
989 case QMetaType::QTime: {
990 data = value.toTime().toString().toUtf8();
991 break;
992 }
993 case QMetaType::QDateTime: {
994 const QDateTime rDateTime = value.toDateTime();
995
996 const QTime time = rDateTime.time();
997 const QDate date = rDateTime.date();
998
999 QVariantList list{
1000 date.year(),
1001 date.month(),
1002 date.day(),
1003
1004 time.hour(),
1005 time.minute(),
1006 time.second() + time.msec() / 1000.0,
1007 };
1008 if (rDateTime.timeRepresentation().timeSpec() != Qt::LocalTime) {
1009 list.append(t: rDateTime.timeZone().id());
1010 }
1011
1012 writeEntry(key, list, flags);
1013 return;
1014 }
1015
1016 case QMetaType::QColor:
1017 case QMetaType::QFont:
1018 qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::writeEntry was passed GUI type '" << value.typeName()
1019 << "' but KConfigGui isn't linked! If it is linked to your program, this is a platform bug. "
1020 "Please inform the KDE developers";
1021 break;
1022 case QMetaType::QUrl:
1023 data = QUrl(value.toUrl()).toString().toUtf8();
1024 break;
1025 default:
1026 qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::writeEntry - unhandled type" << value.typeName() << "in group" << name();
1027 }
1028
1029 writeEntry(key, value: data, flags);
1030}
1031
1032void KConfigGroup::writeEntry(const QString &key, const QVariant &value, WriteConfigFlags flags)
1033{
1034 writeEntry(key: key.toUtf8().constData(), value, flags);
1035}
1036
1037void KConfigGroup::writeEntry(const QString &key, const QVariantList &list, WriteConfigFlags flags)
1038{
1039 writeEntry(key: key.toUtf8().constData(), list, flags);
1040}
1041
1042void KConfigGroup::writeXdgListEntry(const QString &key, const QStringList &value, WriteConfigFlags pFlags)
1043{
1044 writeXdgListEntry(key: key.toUtf8().constData(), value, pFlags);
1045}
1046
1047void KConfigGroup::writeXdgListEntry(const char *key, const QStringList &list, WriteConfigFlags flags)
1048{
1049 Q_ASSERT_X(isValid(), "KConfigGroup::writeXdgListEntry", "accessing an invalid group");
1050 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeXdgListEntry", "writing to a read-only group");
1051
1052 QString value;
1053 value.reserve(asize: 4096);
1054
1055 // XXX List serialization being a separate layer from low-level escaping is
1056 // probably a bug. No affected entries are defined, though.
1057 for (QString val : list) { // clazy:exclude=range-loop
1058 val.replace(c: QLatin1Char('\\'), after: QLatin1String("\\\\")).replace(c: QLatin1Char(';'), after: QLatin1String("\\;"));
1059 value += val + QLatin1Char(';');
1060 }
1061
1062 writeEntry(key, value, flags);
1063}
1064
1065void KConfigGroup::writePathEntry(const QString &pKey, const QString &path, WriteConfigFlags pFlags)
1066{
1067 writePathEntry(Key: pKey.toUtf8().constData(), path, pFlags);
1068}
1069
1070void KConfigGroup::writePathEntry(const char *pKey, const QString &path, WriteConfigFlags pFlags)
1071{
1072 Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group");
1073 Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group");
1074
1075 config()->d_func()->putData(groupName: d->fullName(), key: pKey, value: translatePath(path).toUtf8(), flags: pFlags, expand: true);
1076}
1077
1078void KConfigGroup::writePathEntry(const QString &pKey, const QStringList &value, WriteConfigFlags pFlags)
1079{
1080 writePathEntry(key: pKey.toUtf8().constData(), value, pFlags);
1081}
1082
1083void KConfigGroup::writePathEntry(const char *pKey, const QStringList &value, WriteConfigFlags pFlags)
1084{
1085 Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group");
1086 Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group");
1087
1088 QList<QByteArray> list;
1089 list.reserve(asize: value.length());
1090 for (const QString &path : value) {
1091 list << translatePath(path).toUtf8();
1092 }
1093
1094 config()->d_func()->putData(groupName: d->fullName(), key: pKey, value: KConfigGroupPrivate::serializeList(list), flags: pFlags, expand: true);
1095}
1096
1097void KConfigGroup::deleteEntry(const char *key, WriteConfigFlags flags)
1098{
1099 Q_ASSERT_X(isValid(), "KConfigGroup::deleteEntry", "accessing an invalid group");
1100 Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteEntry", "deleting from a read-only group");
1101
1102 config()->d_func()->putData(groupName: d->fullName(), key, value: QByteArray(), flags);
1103}
1104
1105void KConfigGroup::deleteEntry(const QString &key, WriteConfigFlags flags)
1106{
1107 deleteEntry(key: key.toUtf8().constData(), flags);
1108}
1109
1110void KConfigGroup::revertToDefault(const char *key, WriteConfigFlags flags)
1111{
1112 Q_ASSERT_X(isValid(), "KConfigGroup::revertToDefault", "accessing an invalid group");
1113 Q_ASSERT_X(!d->bConst, "KConfigGroup::revertToDefault", "writing to a read-only group");
1114
1115 config()->d_func()->revertEntry(group: d->fullName(), key, flags);
1116}
1117
1118void KConfigGroup::revertToDefault(const QString &key, WriteConfigFlags flags)
1119{
1120 revertToDefault(key: key.toUtf8().constData(), flags);
1121}
1122
1123bool KConfigGroup::hasDefault(const char *key) const
1124{
1125 Q_ASSERT_X(isValid(), "KConfigGroup::hasDefault", "accessing an invalid group");
1126
1127 KEntryMap::SearchFlags flags = KEntryMap::SearchDefaults | KEntryMap::SearchLocalized;
1128
1129 return !config()->d_func()->lookupData(group: d->fullName(), key, flags).isNull();
1130}
1131
1132bool KConfigGroup::hasDefault(const QString &key) const
1133{
1134 return hasDefault(key: key.toUtf8().constData());
1135}
1136
1137bool KConfigGroup::hasKey(const char *key) const
1138{
1139 Q_ASSERT_X(isValid(), "KConfigGroup::hasKey", "accessing an invalid group");
1140
1141 KEntryMap::SearchFlags flags = KEntryMap::SearchLocalized;
1142 if (config()->readDefaults()) {
1143 flags |= KEntryMap::SearchDefaults;
1144 }
1145
1146 return !config()->d_func()->lookupData(group: d->fullName(), key, flags).isNull();
1147}
1148
1149bool KConfigGroup::hasKey(const QString &key) const
1150{
1151 return hasKey(key: key.toUtf8().constData());
1152}
1153
1154bool KConfigGroup::isImmutable() const
1155{
1156 Q_ASSERT_X(isValid(), "KConfigGroup::isImmutable", "accessing an invalid group");
1157
1158 return d->bImmutable;
1159}
1160
1161QStringList KConfigGroup::groupList() const
1162{
1163 Q_ASSERT_X(isValid(), "KConfigGroup::groupList", "accessing an invalid group");
1164
1165 return config()->d_func()->groupList(groupName: d->fullName());
1166}
1167
1168QStringList KConfigGroup::keyList() const
1169{
1170 Q_ASSERT_X(isValid(), "KConfigGroup::keyList", "accessing an invalid group");
1171
1172 return config()->d_func()->usedKeyList(theGroup: d->fullName());
1173}
1174
1175void KConfigGroup::markAsClean()
1176{
1177 Q_ASSERT_X(isValid(), "KConfigGroup::markAsClean", "accessing an invalid group");
1178
1179 config()->markAsClean();
1180}
1181
1182KConfigGroup::AccessMode KConfigGroup::accessMode() const
1183{
1184 Q_ASSERT_X(isValid(), "KConfigGroup::accessMode", "accessing an invalid group");
1185
1186 return config()->accessMode();
1187}
1188
1189bool KConfigGroup::hasGroupImpl(const QString &b) const
1190{
1191 Q_ASSERT_X(isValid(), "KConfigGroup::hasGroupImpl", "accessing an invalid group");
1192
1193 return config()->hasGroup(group: d->fullName(aGroup: b));
1194}
1195
1196void KConfigGroup::deleteGroupImpl(const QString &b, WriteConfigFlags flags)
1197{
1198 Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroupImpl", "accessing an invalid group");
1199 Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroupImpl", "deleting from a read-only group");
1200
1201 config()->deleteGroup(group: d->fullName(aGroup: b), flags);
1202}
1203
1204bool KConfigGroup::isGroupImmutableImpl(const QString &groupName) const
1205{
1206 Q_ASSERT_X(isValid(), "KConfigGroup::isGroupImmutableImpl", "accessing an invalid group");
1207
1208 if (!hasGroupImpl(b: groupName)) { // group doesn't exist yet
1209 return d->bImmutable; // child groups are immutable if the parent is immutable.
1210 }
1211
1212 return config()->isGroupImmutable(group: d->fullName(aGroup: groupName));
1213}
1214
1215void KConfigGroup::copyTo(KConfigBase *other, WriteConfigFlags pFlags) const
1216{
1217 Q_ASSERT_X(isValid(), "KConfigGroup::copyTo", "accessing an invalid group");
1218 Q_ASSERT(other != nullptr);
1219
1220 if (KConfigGroup *otherGroup = dynamic_cast<KConfigGroup *>(other)) {
1221 config()->d_func()->copyGroup(source: d->fullName(), destination: otherGroup->d->fullName(), otherGroup, flags: pFlags);
1222 } else if (KConfig *otherConfig = dynamic_cast<KConfig *>(other)) {
1223 KConfigGroup newGroup = otherConfig->group(group: d->fullName());
1224 otherConfig->d_func()->copyGroup(source: d->fullName(), destination: d->fullName(), otherGroup: &newGroup, flags: pFlags);
1225 } else {
1226 Q_ASSERT_X(false, "KConfigGroup::copyTo", "unknown type of KConfigBase");
1227 }
1228}
1229
1230void KConfigGroup::reparent(KConfigBase *parent, WriteConfigFlags pFlags)
1231{
1232 Q_ASSERT_X(isValid(), "KConfigGroup::reparent", "accessing an invalid group");
1233 Q_ASSERT_X(!d->bConst, "KConfigGroup::reparent", "reparenting a read-only group");
1234 Q_ASSERT_X(!d->bImmutable, "KConfigGroup::reparent", "reparenting an immutable group");
1235 Q_ASSERT(parent != nullptr);
1236
1237 KConfigGroup oldGroup(*this);
1238
1239 d = KConfigGroupPrivate::create(master: parent, name: d->mName, isImmutable: false, isConst: false);
1240 oldGroup.copyTo(other: this, pFlags);
1241 oldGroup.deleteGroup(); // so that the entries with the old group name are deleted on sync
1242}
1243
1244void KConfigGroup::moveValue(const char *key, KConfigGroup &other, WriteConfigFlags pFlags)
1245{
1246 const QString groupName = d->fullName();
1247 const auto entry = config()->d_ptr->lookupInternalEntry(group: groupName, key, flags: KEntryMap::SearchLocalized);
1248
1249 // Only write the entry if it is not null, if it is a global enry there is no point in moving it
1250 if (!entry.mValue.isNull() && !entry.bGlobal) {
1251 deleteEntry(key, flags: pFlags);
1252 KEntryMap::EntryOptions options = KEntryMap::EntryOption::EntryDirty;
1253 if (entry.bDeleted) {
1254 options |= KEntryMap::EntryDeleted;
1255 }
1256
1257 if (entry.bExpand) {
1258 options |= KEntryMap::EntryExpansion;
1259 }
1260
1261 other.config()->d_ptr->setEntryData(groupName: other.d->fullName(), key, value: entry.mValue, flags: options);
1262 }
1263}
1264
1265void KConfigGroup::moveValuesTo(const QList<const char *> &keys, KConfigGroup &other, WriteConfigFlags pFlags)
1266{
1267 Q_ASSERT(isValid());
1268 Q_ASSERT(other.isValid());
1269
1270 for (const auto key : keys) {
1271 moveValue(key, other, pFlags);
1272 }
1273}
1274
1275void KConfigGroup::moveValuesTo(KConfigGroup &other, WriteConfigFlags pFlags)
1276{
1277 Q_ASSERT(isValid());
1278 Q_ASSERT(other.isValid());
1279
1280 const QStringList keys = keyList();
1281 for (const QString &key : keys) {
1282 moveValue(key: key.toUtf8().constData(), other, pFlags);
1283 }
1284}
1285

source code of kconfig/src/core/kconfiggroup.cpp