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 QVariant tmp = value;
263 if (!tmp.convert(type: aDefault.metaType())) {
264 tmp = aDefault;
265 }
266 return tmp;
267 }
268 case QMetaType::QPoint: {
269 const auto list = asIntList(string: value);
270
271 if (list.count() != 2) {
272 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
273 return aDefault;
274 }
275 return QPoint(list.at(idx: 0), list.at(idx: 1));
276 }
277 case QMetaType::QPointF: {
278 const auto list = asRealList(string: value);
279
280 if (list.count() != 2) {
281 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
282 return aDefault;
283 }
284 return QPointF(list.at(idx: 0), list.at(idx: 1));
285 }
286 case QMetaType::QRect: {
287 const auto list = asIntList(string: value);
288
289 if (list.count() != 4) {
290 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 4, got: list.count());
291 return aDefault;
292 }
293 const QRect rect(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2), list.at(idx: 3));
294 if (!rect.isValid()) {
295 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
296 return aDefault;
297 }
298 return rect;
299 }
300 case QMetaType::QRectF: {
301 const auto list = asRealList(string: value);
302
303 if (list.count() != 4) {
304 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 4, got: list.count());
305 return aDefault;
306 }
307 const QRectF rect(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2), list.at(idx: 3));
308 if (!rect.isValid()) {
309 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
310 return aDefault;
311 }
312 return rect;
313 }
314 case QMetaType::QSize: {
315 const auto list = asIntList(string: value);
316
317 if (list.count() != 2) {
318 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
319 return aDefault;
320 }
321 const QSize size(list.at(idx: 0), list.at(idx: 1));
322 if (!size.isValid()) {
323 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
324 return aDefault;
325 }
326 return size;
327 }
328 case QMetaType::QSizeF: {
329 const auto list = asRealList(string: value);
330
331 if (list.count() != 2) {
332 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 2, got: list.count());
333 return aDefault;
334 }
335 const QSizeF size(list.at(idx: 0), list.at(idx: 1));
336 if (!size.isValid()) {
337 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
338 return aDefault;
339 }
340 return size;
341 }
342 case QMetaType::QDateTime: {
343 const auto list = asRealList(string: value);
344 if (list.count() < 6) {
345 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 6, got: list.count());
346 return aDefault;
347 }
348 const QDate date(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2));
349 const qreal totalSeconds = list.at(idx: 5);
350 qreal seconds;
351 const qreal fractional = modf(x: totalSeconds, iptr: &seconds);
352 const qreal milliseconds = round(x: fractional * 1000.0);
353 const QTime time(list.at(idx: 3), list.at(idx: 4), seconds, milliseconds);
354
355 QDateTime dt(date, time);
356 if (list.count() == 7) { // Then the timezone, which was added later
357 const auto id = value.mid(index: value.lastIndexOf(ch: ',') + 1);
358 dt.setTimeZone(toZone: QTimeZone(id));
359 }
360 if (!dt.isValid()) {
361 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
362 return aDefault;
363 }
364 return dt;
365 }
366 case QMetaType::QDate: {
367 auto list = asIntList(string: value);
368 // list.count == 6 -> don't break config files that stored QDate as QDateTime
369 if (list.count() != 3 && list.count() != 6) {
370 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(expected: 3, got: list.count());
371 return aDefault;
372 }
373 const QDate date(list.at(idx: 0), list.at(idx: 1), list.at(idx: 2));
374 if (!date.isValid()) {
375 qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault);
376 return aDefault;
377 }
378 return date;
379 }
380 case QMetaType::QTime:
381 return QTime::fromString(string: QString::fromUtf8(ba: value));
382 case QMetaType::QColor:
383 case QMetaType::QFont:
384 qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::readEntry was passed GUI type '" << aDefault.typeName()
385 << "' but KConfigGui isn't linked! If it is linked to your program, "
386 "this is a platform bug. Please inform the KDE developers";
387 break;
388 case QMetaType::QUrl:
389 return QUrl(QString::fromUtf8(ba: value));
390
391 default:
392 break;
393 }
394
395 qCWarning(KCONFIG_CORE_LOG) << "unhandled type " << aDefault.typeName();
396 return QVariant();
397}
398
399static bool cleanHomeDirPath(QString &path, const QString &homeDir)
400{
401#ifdef Q_OS_WIN // safer
402 if (!QDir::toNativeSeparators(path).startsWith(QDir::toNativeSeparators(homeDir))) {
403 return false;
404 }
405#else
406 if (!path.startsWith(s: homeDir)) {
407 return false;
408 }
409#endif
410
411 int len = homeDir.length();
412 // replace by "$HOME" if possible
413 if (len && (path.length() == len || path[len] == QLatin1Char('/'))) {
414 path.replace(i: 0, len, QStringLiteral("$HOME"));
415 return true;
416 }
417
418 return false;
419}
420
421static QString translatePath(QString path) // krazy:exclude=passbyvalue
422{
423 if (path.isEmpty()) {
424 return path;
425 }
426
427 // only "our" $HOME should be interpreted
428 path.replace(c: QLatin1Char('$'), after: QLatin1String("$$"));
429
430 const bool startsWithFile = path.startsWith(s: QLatin1String("file:"), cs: Qt::CaseInsensitive);
431 path = startsWithFile ? QUrl(path).toLocalFile() : path;
432
433 if (QDir::isRelativePath(path)) {
434 return path;
435 }
436
437 // Use the same thing as what expandString() will do, to keep data intact
438#ifdef Q_OS_WIN
439 const QString homeDir = QDir::homePath();
440#else
441 const QString homeDir = QFile::decodeName(localFileName: qgetenv(varName: "HOME"));
442#endif
443 (void)cleanHomeDirPath(path, homeDir);
444
445 if (startsWithFile) {
446 path = QUrl::fromLocalFile(localfile: path).toString();
447 }
448
449 return path;
450}
451
452KConfigGroup::KConfigGroup()
453 : d()
454{
455}
456
457bool KConfigGroup::isValid() const
458{
459 return bool(d);
460}
461
462KConfigGroupGui _kde_internal_KConfigGroupGui;
463static inline bool readEntryGui(const QByteArray &data, const char *key, const QVariant &input, QVariant &output)
464{
465 if (_kde_internal_KConfigGroupGui.readEntryGui) {
466 return _kde_internal_KConfigGroupGui.readEntryGui(data, key, input, output);
467 }
468 return false;
469}
470
471static inline bool writeEntryGui(KConfigGroup *cg, const char *key, const QVariant &input, KConfigGroup::WriteConfigFlags flags)
472{
473 if (_kde_internal_KConfigGroupGui.writeEntryGui) {
474 return _kde_internal_KConfigGroupGui.writeEntryGui(cg, key, input, flags);
475 }
476 return false;
477}
478
479KConfigGroup::KConfigGroup(KConfigBase *master, const QString &_group)
480 : d(KConfigGroupPrivate::create(master, name: _group, isImmutable: master->isGroupImmutable(group: _group), isConst: false))
481{
482}
483
484KConfigGroup::KConfigGroup(const KConfigBase *master, const QString &_group)
485 : d(KConfigGroupPrivate::create(master: const_cast<KConfigBase *>(master), name: _group, isImmutable: master->isGroupImmutable(group: _group), isConst: true))
486{
487}
488
489KConfigGroup::KConfigGroup(const KSharedConfigPtr &master, const QString &_group)
490 : d(new KConfigGroupPrivate(master, _group))
491{
492}
493
494KConfigGroup &KConfigGroup::operator=(const KConfigGroup &rhs)
495{
496 d = rhs.d;
497 return *this;
498}
499
500KConfigGroup::KConfigGroup(const KConfigGroup &rhs)
501 : d(rhs.d)
502{
503}
504
505KConfigGroup::~KConfigGroup()
506{
507 d.reset();
508}
509
510KConfigGroup KConfigGroup::groupImpl(const QString &aGroup)
511{
512 Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group");
513 Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group");
514
515 KConfigGroup newGroup;
516
517 newGroup.d = new KConfigGroupPrivate(this, isGroupImmutableImpl(groupName: aGroup), d->bConst, aGroup);
518
519 return newGroup;
520}
521
522const KConfigGroup KConfigGroup::groupImpl(const QString &aGroup) const
523{
524 Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group");
525 Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group");
526
527 KConfigGroup newGroup;
528
529 newGroup.d = new KConfigGroupPrivate(const_cast<KConfigGroup *>(this), isGroupImmutableImpl(groupName: aGroup), true, aGroup);
530
531 return newGroup;
532}
533
534KConfigGroup KConfigGroup::parent() const
535{
536 Q_ASSERT_X(isValid(), "KConfigGroup::parent", "accessing an invalid group");
537
538 KConfigGroup parentGroup;
539
540 if (d->mParent) {
541 parentGroup.d = d->mParent;
542 } else {
543 parentGroup.d = new KConfigGroupPrivate(d->mOwner, d->mOwner->isImmutable(), d->bConst, QString());
544 // make sure we keep the refcount up on the KConfig object
545 parentGroup.d->sOwner = d->sOwner;
546 }
547
548 return parentGroup;
549}
550
551void KConfigGroup::deleteGroup(WriteConfigFlags flags)
552{
553 Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroup", "accessing an invalid group");
554 Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroup", "deleting a read-only group");
555
556 config()->deleteGroup(group: d->fullName(), flags);
557}
558
559QString KConfigGroup::name() const
560{
561 Q_ASSERT_X(isValid(), "KConfigGroup::name", "accessing an invalid group");
562
563 return d->name();
564}
565
566bool KConfigGroup::exists() const
567{
568 Q_ASSERT_X(isValid(), "KConfigGroup::exists", "accessing an invalid group");
569
570 return config()->hasGroup(group: d->fullName());
571}
572
573bool KConfigGroup::sync()
574{
575 Q_ASSERT_X(isValid(), "KConfigGroup::sync", "accessing an invalid group");
576
577 if (!d->bConst) {
578 return config()->sync();
579 }
580
581 return false;
582}
583
584QMap<QString, QString> KConfigGroup::entryMap() const
585{
586 Q_ASSERT_X(isValid(), "KConfigGroup::entryMap", "accessing an invalid group");
587
588 return config()->entryMap(aGroup: d->fullName());
589}
590
591KConfig *KConfigGroup::config()
592{
593 Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group");
594
595 return d->mOwner;
596}
597
598const KConfig *KConfigGroup::config() const
599{
600 Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group");
601
602 return d->mOwner;
603}
604
605bool KConfigGroup::isEntryImmutable(const char *key) const
606{
607 Q_ASSERT_X(isValid(), "KConfigGroup::isEntryImmutable", "accessing an invalid group");
608
609 return (isImmutable() || !config()->d_func()->canWriteEntry(group: d->fullName(), key, isDefault: config()->readDefaults()));
610}
611
612bool KConfigGroup::isEntryImmutable(const QString &key) const
613{
614 return isEntryImmutable(key: key.toUtf8().constData());
615}
616
617QString KConfigGroup::readEntryUntranslated(const QString &pKey, const QString &aDefault) const
618{
619 return readEntryUntranslated(key: pKey.toUtf8().constData(), aDefault);
620}
621
622QString KConfigGroup::readEntryUntranslated(const char *key, const QString &aDefault) const
623{
624 Q_ASSERT_X(isValid(), "KConfigGroup::readEntryUntranslated", "accessing an invalid group");
625
626 QString result = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchFlags(), expand: nullptr);
627 if (result.isNull()) {
628 return aDefault;
629 }
630 return result;
631}
632
633QString KConfigGroup::readEntry(const char *key, const char *aDefault) const
634{
635 return readEntry(key, aDefault: QString::fromUtf8(utf8: aDefault));
636}
637
638QString KConfigGroup::readEntry(const QString &key, const char *aDefault) const
639{
640 return readEntry(key: key.toUtf8().constData(), aDefault);
641}
642
643QString KConfigGroup::readEntry(const char *key, const QString &aDefault) const
644{
645 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
646
647 bool expand = false;
648
649 // read value from the entry map
650 QString aValue = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchLocalized, expand: &expand);
651 if (aValue.isNull()) {
652 aValue = aDefault;
653 }
654
655 if (expand) {
656 return KConfigPrivate::expandString(value: aValue);
657 }
658
659 return aValue;
660}
661
662QString KConfigGroup::readEntry(const QString &key, const QString &aDefault) const
663{
664 return readEntry(key: key.toUtf8().constData(), aDefault);
665}
666
667QStringList KConfigGroup::readEntry(const char *key, const QStringList &aDefault) const
668{
669 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
670
671 const QString data = readEntry(key, aDefault: QString());
672 if (data.isNull()) {
673 return aDefault;
674 }
675
676 return KConfigGroupPrivate::deserializeList(data);
677}
678
679QStringList KConfigGroup::readEntry(const QString &key, const QStringList &aDefault) const
680{
681 return readEntry(key: key.toUtf8().constData(), aDefault);
682}
683
684QVariant KConfigGroup::readEntry(const char *key, const QVariant &aDefault) const
685{
686 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
687
688 const QByteArray data = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchLocalized);
689 if (data.isNull()) {
690 return aDefault;
691 }
692
693 QVariant value;
694 if (!readEntryGui(data, key, input: aDefault, output&: value)) {
695 return convertToQVariant(pKey: key, value: data, aDefault);
696 }
697
698 return value;
699}
700
701QVariant KConfigGroup::readEntry(const QString &key, const QVariant &aDefault) const
702{
703 return readEntry(key: key.toUtf8().constData(), aDefault);
704}
705
706QVariantList KConfigGroup::readEntry(const char *key, const QVariantList &aDefault) const
707{
708 Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
709
710 const QString data = readEntry(key, aDefault: QString());
711 if (data.isNull()) {
712 return aDefault;
713 }
714
715 const auto &list = KConfigGroupPrivate::deserializeList(data);
716
717 QVariantList value;
718 value.reserve(asize: list.count());
719 for (const QString &v : list) {
720 value << v;
721 }
722
723 return value;
724}
725
726QVariantList KConfigGroup::readEntry(const QString &key, const QVariantList &aDefault) const
727{
728 return readEntry(key: key.toUtf8().constData(), aDefault);
729}
730
731QStringList KConfigGroup::readXdgListEntry(const QString &key, const QStringList &aDefault) const
732{
733 return readXdgListEntry(key: key.toUtf8().constData(), aDefault);
734}
735
736QStringList KConfigGroup::readXdgListEntry(const char *key, const QStringList &aDefault) const
737{
738 Q_ASSERT_X(isValid(), "KConfigGroup::readXdgListEntry", "accessing an invalid group");
739
740 const QString data = readEntry(key, aDefault: QString());
741 if (data.isNull()) {
742 return aDefault;
743 }
744
745 QStringList value;
746 QString val;
747 val.reserve(asize: data.size());
748 // XXX List serialization being a separate layer from low-level parsing is
749 // probably a bug. No affected entries are defined, though.
750 bool quoted = false;
751 for (int p = 0; p < data.length(); p++) {
752 if (quoted) {
753 val += data[p];
754 quoted = false;
755 } else if (data[p] == QLatin1Char('\\')) {
756 quoted = true;
757 } else if (data[p] == QLatin1Char(';')) {
758 value.append(t: val);
759 val.clear();
760 val.reserve(asize: data.size() - p);
761 } else {
762 val += data[p];
763 }
764 }
765 if (!val.isEmpty()) {
766 value.append(t: val);
767 }
768 return value;
769}
770
771QString KConfigGroup::readPathEntry(const QString &pKey, const QString &aDefault) const
772{
773 return readPathEntry(key: pKey.toUtf8().constData(), aDefault);
774}
775
776QString KConfigGroup::readPathEntry(const char *key, const QString &aDefault) const
777{
778 Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group");
779
780 bool expand = false;
781
782 QString aValue = config()->d_func()->lookupData(group: d->fullName(), key, flags: KEntryMap::SearchLocalized, expand: &expand);
783 if (aValue.isNull()) {
784 aValue = aDefault;
785 }
786
787 return KConfigPrivate::expandString(value: aValue);
788}
789
790QStringList KConfigGroup::readPathEntry(const QString &pKey, const QStringList &aDefault) const
791{
792 return readPathEntry(key: pKey.toUtf8().constData(), aDefault);
793}
794
795QStringList KConfigGroup::readPathEntry(const char *key, const QStringList &aDefault) const
796{
797 Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group");
798
799 const QString data = readPathEntry(key, aDefault: QString());
800 if (data.isNull()) {
801 return aDefault;
802 }
803
804 return KConfigGroupPrivate::deserializeList(data);
805}
806
807void KConfigGroup::writeEntry(const char *key, const QString &value, WriteConfigFlags flags)
808{
809 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
810 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
811
812 writeEntry(key, value: value.toUtf8(), pFlags: flags);
813}
814
815void KConfigGroup::writeEntry(const QString &key, const QString &value, WriteConfigFlags flags)
816{
817 writeEntry(key: key.toUtf8().constData(), value, flags);
818}
819
820void KConfigGroup::writeEntry(const QString &key, const char *value, WriteConfigFlags pFlags)
821{
822 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
823 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
824
825 writeEntry(key: key.toUtf8().constData(), value: QVariant(QString::fromLatin1(ba: value)), pFlags);
826}
827
828void KConfigGroup::writeEntry(const char *key, const char *value, WriteConfigFlags pFlags)
829{
830 writeEntry(key, value: QVariant(QString::fromLatin1(ba: value)), pFlags);
831}
832
833void KConfigGroup::writeEntry(const char *key, const QByteArray &value, WriteConfigFlags flags)
834{
835 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
836 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
837
838 config()->d_func()->putData(groupName: d->fullName(), key, value: value.isNull() ? QByteArray("") : value, flags);
839}
840
841void KConfigGroup::writeEntry(const QString &key, const QByteArray &value, WriteConfigFlags pFlags)
842{
843 writeEntry(key: key.toUtf8().constData(), value, flags: pFlags);
844}
845
846void KConfigGroup::writeEntry(const char *key, const QStringList &list, WriteConfigFlags flags)
847{
848 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
849 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
850
851 QList<QByteArray> balist;
852 balist.reserve(asize: list.count());
853
854 for (const QString &entry : list) {
855 balist.append(t: entry.toUtf8());
856 }
857
858 writeEntry(key, value: KConfigGroupPrivate::serializeList(list: balist), flags);
859}
860
861void KConfigGroup::writeEntry(const QString &key, const QStringList &list, WriteConfigFlags flags)
862{
863 writeEntry(key: key.toUtf8().constData(), list, flags);
864}
865
866void KConfigGroup::writeEntry(const char *key, const QVariantList &list, WriteConfigFlags flags)
867{
868 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
869 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
870
871 QList<QByteArray> data;
872 data.reserve(asize: list.count());
873
874 for (const QVariant &v : list) {
875 if (v.userType() == QMetaType::QByteArray) {
876 data << v.toByteArray();
877 } else {
878 data << v.toString().toUtf8();
879 }
880 }
881
882 writeEntry(key, value: KConfigGroupPrivate::serializeList(list: data), flags);
883}
884
885void KConfigGroup::writeEntry(const char *key, const QVariant &value, WriteConfigFlags flags)
886{
887 Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
888 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
889
890 if (writeEntryGui(cg: this, key, input: value, flags)) {
891 return; // GUI type that was handled
892 }
893
894 QByteArray data;
895 // if a type handler is added here you must add a QVConversions definition
896 // to kconfigconversioncheck_p.h, or KConfigConversionCheck::to_QVariant will not allow
897 // writeEntry<T> to convert to QVariant.
898 switch (static_cast<QMetaType::Type>(value.userType())) {
899 case QMetaType::UnknownType:
900 data = "";
901 break;
902 case QMetaType::QByteArray:
903 data = value.toByteArray();
904 break;
905 case QMetaType::QString:
906 case QMetaType::Int:
907 case QMetaType::UInt:
908 case QMetaType::Double:
909 case QMetaType::Float:
910 case QMetaType::Bool:
911 case QMetaType::LongLong:
912 case QMetaType::ULongLong:
913 data = value.toString().toUtf8();
914 break;
915 case QMetaType::QVariantList:
916 if (!value.canConvert<QStringList>()) {
917 qCWarning(KCONFIG_CORE_LOG) << "not all types in \"" << key
918 << "\" can convert to QString,"
919 " information will be lost";
920 }
921 Q_FALLTHROUGH();
922 case QMetaType::QStringList:
923 writeEntry(key, list: value.toList(), flags);
924 return;
925 case QMetaType::QPoint: {
926 const QPoint rPoint = value.toPoint();
927
928 const QVariantList list{rPoint.x(), rPoint.y()};
929
930 writeEntry(key, list, flags);
931 return;
932 }
933 case QMetaType::QPointF: {
934 const QPointF point = value.toPointF();
935
936 const QVariantList list{point.x(), point.y()};
937
938 writeEntry(key, list, flags);
939 return;
940 }
941 case QMetaType::QRect: {
942 const QRect rRect = value.toRect();
943
944 const QVariantList list{rRect.left(), rRect.top(), rRect.width(), rRect.height()};
945
946 writeEntry(key, list, flags);
947 return;
948 }
949 case QMetaType::QRectF: {
950 const QRectF rRectF = value.toRectF();
951
952 const QVariantList list{rRectF.left(), rRectF.top(), rRectF.width(), rRectF.height()};
953
954 writeEntry(key, list, flags);
955 return;
956 }
957 case QMetaType::QSize: {
958 const QSize rSize = value.toSize();
959
960 const QVariantList list{rSize.width(), rSize.height()};
961
962 writeEntry(key, list, flags);
963 return;
964 }
965 case QMetaType::QUuid: {
966 writeEntry(key, value: value.toString(), flags);
967 return;
968 }
969 case QMetaType::QSizeF: {
970 const QSizeF rSizeF = value.toSizeF();
971
972 const QVariantList list{rSizeF.width(), rSizeF.height()};
973
974 writeEntry(key, list, flags);
975 return;
976 }
977 case QMetaType::QDate: {
978 const QDate date = value.toDate();
979
980 const QVariantList list{date.year(), date.month(), date.day()};
981
982 writeEntry(key, list, flags);
983 return;
984 }
985 case QMetaType::QTime: {
986 data = value.toTime().toString().toUtf8();
987 break;
988 }
989 case QMetaType::QDateTime: {
990 const QDateTime rDateTime = value.toDateTime();
991
992 const QTime time = rDateTime.time();
993 const QDate date = rDateTime.date();
994
995 QVariantList list{
996 date.year(),
997 date.month(),
998 date.day(),
999
1000 time.hour(),
1001 time.minute(),
1002 time.second() + time.msec() / 1000.0,
1003 };
1004 if (rDateTime.timeRepresentation().timeSpec() != Qt::LocalTime) {
1005 list.append(t: rDateTime.timeZone().id());
1006 }
1007
1008 writeEntry(key, list, flags);
1009 return;
1010 }
1011
1012 case QMetaType::QColor:
1013 case QMetaType::QFont:
1014 qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::writeEntry was passed GUI type '" << value.typeName()
1015 << "' but KConfigGui isn't linked! If it is linked to your program, this is a platform bug. "
1016 "Please inform the KDE developers";
1017 break;
1018 case QMetaType::QUrl:
1019 data = QUrl(value.toUrl()).toString().toUtf8();
1020 break;
1021 default:
1022 qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::writeEntry - unhandled type" << value.typeName() << "in group" << name();
1023 }
1024
1025 writeEntry(key, value: data, flags);
1026}
1027
1028void KConfigGroup::writeEntry(const QString &key, const QVariant &value, WriteConfigFlags flags)
1029{
1030 writeEntry(key: key.toUtf8().constData(), value, flags);
1031}
1032
1033void KConfigGroup::writeEntry(const QString &key, const QVariantList &list, WriteConfigFlags flags)
1034{
1035 writeEntry(key: key.toUtf8().constData(), list, flags);
1036}
1037
1038void KConfigGroup::writeXdgListEntry(const QString &key, const QStringList &value, WriteConfigFlags pFlags)
1039{
1040 writeXdgListEntry(key: key.toUtf8().constData(), value, pFlags);
1041}
1042
1043void KConfigGroup::writeXdgListEntry(const char *key, const QStringList &list, WriteConfigFlags flags)
1044{
1045 Q_ASSERT_X(isValid(), "KConfigGroup::writeXdgListEntry", "accessing an invalid group");
1046 Q_ASSERT_X(!d->bConst, "KConfigGroup::writeXdgListEntry", "writing to a read-only group");
1047
1048 QString value;
1049 value.reserve(asize: 4096);
1050
1051 // XXX List serialization being a separate layer from low-level escaping is
1052 // probably a bug. No affected entries are defined, though.
1053 for (QString val : list) { // clazy:exclude=range-loop
1054 val.replace(c: QLatin1Char('\\'), after: QLatin1String("\\\\")).replace(c: QLatin1Char(';'), after: QLatin1String("\\;"));
1055 value += val + QLatin1Char(';');
1056 }
1057
1058 writeEntry(key, value, flags);
1059}
1060
1061void KConfigGroup::writePathEntry(const QString &pKey, const QString &path, WriteConfigFlags pFlags)
1062{
1063 writePathEntry(Key: pKey.toUtf8().constData(), path, pFlags);
1064}
1065
1066void KConfigGroup::writePathEntry(const char *pKey, const QString &path, WriteConfigFlags pFlags)
1067{
1068 Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group");
1069 Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group");
1070
1071 config()->d_func()->putData(groupName: d->fullName(), key: pKey, value: translatePath(path).toUtf8(), flags: pFlags, expand: true);
1072}
1073
1074void KConfigGroup::writePathEntry(const QString &pKey, const QStringList &value, WriteConfigFlags pFlags)
1075{
1076 writePathEntry(key: pKey.toUtf8().constData(), value, pFlags);
1077}
1078
1079void KConfigGroup::writePathEntry(const char *pKey, const QStringList &value, WriteConfigFlags pFlags)
1080{
1081 Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group");
1082 Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group");
1083
1084 QList<QByteArray> list;
1085 list.reserve(asize: value.length());
1086 for (const QString &path : value) {
1087 list << translatePath(path).toUtf8();
1088 }
1089
1090 config()->d_func()->putData(groupName: d->fullName(), key: pKey, value: KConfigGroupPrivate::serializeList(list), flags: pFlags, expand: true);
1091}
1092
1093void KConfigGroup::deleteEntry(const char *key, WriteConfigFlags flags)
1094{
1095 Q_ASSERT_X(isValid(), "KConfigGroup::deleteEntry", "accessing an invalid group");
1096 Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteEntry", "deleting from a read-only group");
1097
1098 config()->d_func()->putData(groupName: d->fullName(), key, value: QByteArray(), flags);
1099}
1100
1101void KConfigGroup::deleteEntry(const QString &key, WriteConfigFlags flags)
1102{
1103 deleteEntry(key: key.toUtf8().constData(), flags);
1104}
1105
1106void KConfigGroup::revertToDefault(const char *key, WriteConfigFlags flags)
1107{
1108 Q_ASSERT_X(isValid(), "KConfigGroup::revertToDefault", "accessing an invalid group");
1109 Q_ASSERT_X(!d->bConst, "KConfigGroup::revertToDefault", "writing to a read-only group");
1110
1111 config()->d_func()->revertEntry(group: d->fullName(), key, flags);
1112}
1113
1114void KConfigGroup::revertToDefault(const QString &key, WriteConfigFlags flags)
1115{
1116 revertToDefault(key: key.toUtf8().constData(), flags);
1117}
1118
1119bool KConfigGroup::hasDefault(const char *key) const
1120{
1121 Q_ASSERT_X(isValid(), "KConfigGroup::hasDefault", "accessing an invalid group");
1122
1123 KEntryMap::SearchFlags flags = KEntryMap::SearchDefaults | KEntryMap::SearchLocalized;
1124
1125 return !config()->d_func()->lookupData(group: d->fullName(), key, flags).isNull();
1126}
1127
1128bool KConfigGroup::hasDefault(const QString &key) const
1129{
1130 return hasDefault(key: key.toUtf8().constData());
1131}
1132
1133bool KConfigGroup::hasKey(const char *key) const
1134{
1135 Q_ASSERT_X(isValid(), "KConfigGroup::hasKey", "accessing an invalid group");
1136
1137 KEntryMap::SearchFlags flags = KEntryMap::SearchLocalized;
1138 if (config()->readDefaults()) {
1139 flags |= KEntryMap::SearchDefaults;
1140 }
1141
1142 return !config()->d_func()->lookupData(group: d->fullName(), key, flags).isNull();
1143}
1144
1145bool KConfigGroup::hasKey(const QString &key) const
1146{
1147 return hasKey(key: key.toUtf8().constData());
1148}
1149
1150bool KConfigGroup::isImmutable() const
1151{
1152 Q_ASSERT_X(isValid(), "KConfigGroup::isImmutable", "accessing an invalid group");
1153
1154 return d->bImmutable;
1155}
1156
1157QStringList KConfigGroup::groupList() const
1158{
1159 Q_ASSERT_X(isValid(), "KConfigGroup::groupList", "accessing an invalid group");
1160
1161 return config()->d_func()->groupList(groupName: d->fullName());
1162}
1163
1164QStringList KConfigGroup::keyList() const
1165{
1166 Q_ASSERT_X(isValid(), "KConfigGroup::keyList", "accessing an invalid group");
1167
1168 return config()->d_func()->usedKeyList(theGroup: d->fullName());
1169}
1170
1171void KConfigGroup::markAsClean()
1172{
1173 Q_ASSERT_X(isValid(), "KConfigGroup::markAsClean", "accessing an invalid group");
1174
1175 config()->markAsClean();
1176}
1177
1178KConfigGroup::AccessMode KConfigGroup::accessMode() const
1179{
1180 Q_ASSERT_X(isValid(), "KConfigGroup::accessMode", "accessing an invalid group");
1181
1182 return config()->accessMode();
1183}
1184
1185bool KConfigGroup::hasGroupImpl(const QString &b) const
1186{
1187 Q_ASSERT_X(isValid(), "KConfigGroup::hasGroupImpl", "accessing an invalid group");
1188
1189 return config()->hasGroup(group: d->fullName(aGroup: b));
1190}
1191
1192void KConfigGroup::deleteGroupImpl(const QString &b, WriteConfigFlags flags)
1193{
1194 Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroupImpl", "accessing an invalid group");
1195 Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroupImpl", "deleting from a read-only group");
1196
1197 config()->deleteGroup(group: d->fullName(aGroup: b), flags);
1198}
1199
1200bool KConfigGroup::isGroupImmutableImpl(const QString &groupName) const
1201{
1202 Q_ASSERT_X(isValid(), "KConfigGroup::isGroupImmutableImpl", "accessing an invalid group");
1203
1204 if (!hasGroupImpl(b: groupName)) { // group doesn't exist yet
1205 return d->bImmutable; // child groups are immutable if the parent is immutable.
1206 }
1207
1208 return config()->isGroupImmutable(group: d->fullName(aGroup: groupName));
1209}
1210
1211void KConfigGroup::copyTo(KConfigBase *other, WriteConfigFlags pFlags) const
1212{
1213 Q_ASSERT_X(isValid(), "KConfigGroup::copyTo", "accessing an invalid group");
1214 Q_ASSERT(other != nullptr);
1215
1216 if (KConfigGroup *otherGroup = dynamic_cast<KConfigGroup *>(other)) {
1217 config()->d_func()->copyGroup(source: d->fullName(), destination: otherGroup->d->fullName(), otherGroup, flags: pFlags);
1218 } else if (KConfig *otherConfig = dynamic_cast<KConfig *>(other)) {
1219 KConfigGroup newGroup = otherConfig->group(group: d->fullName());
1220 otherConfig->d_func()->copyGroup(source: d->fullName(), destination: d->fullName(), otherGroup: &newGroup, flags: pFlags);
1221 } else {
1222 Q_ASSERT_X(false, "KConfigGroup::copyTo", "unknown type of KConfigBase");
1223 }
1224}
1225
1226void KConfigGroup::reparent(KConfigBase *parent, WriteConfigFlags pFlags)
1227{
1228 Q_ASSERT_X(isValid(), "KConfigGroup::reparent", "accessing an invalid group");
1229 Q_ASSERT_X(!d->bConst, "KConfigGroup::reparent", "reparenting a read-only group");
1230 Q_ASSERT_X(!d->bImmutable, "KConfigGroup::reparent", "reparenting an immutable group");
1231 Q_ASSERT(parent != nullptr);
1232
1233 KConfigGroup oldGroup(*this);
1234
1235 d = KConfigGroupPrivate::create(master: parent, name: d->mName, isImmutable: false, isConst: false);
1236 oldGroup.copyTo(other: this, pFlags);
1237 oldGroup.deleteGroup(); // so that the entries with the old group name are deleted on sync
1238}
1239
1240void KConfigGroup::moveValue(const char *key, KConfigGroup &other, WriteConfigFlags pFlags)
1241{
1242 const QString groupName = d->fullName();
1243 const auto entry = config()->d_ptr->lookupInternalEntry(group: groupName, key, flags: KEntryMap::SearchLocalized);
1244
1245 // Only write the entry if it is not null, if it is a global enry there is no point in moving it
1246 if (!entry.mValue.isNull() && !entry.bGlobal) {
1247 deleteEntry(key, flags: pFlags);
1248 KEntryMap::EntryOptions options = KEntryMap::EntryOption::EntryDirty;
1249 if (entry.bDeleted) {
1250 options |= KEntryMap::EntryDeleted;
1251 }
1252
1253 if (entry.bExpand) {
1254 options |= KEntryMap::EntryExpansion;
1255 }
1256
1257 other.config()->d_ptr->setEntryData(groupName: other.d->fullName(), key, value: entry.mValue, flags: options);
1258 }
1259}
1260
1261void KConfigGroup::moveValuesTo(const QList<const char *> &keys, KConfigGroup &other, WriteConfigFlags pFlags)
1262{
1263 Q_ASSERT(isValid());
1264 Q_ASSERT(other.isValid());
1265
1266 for (const auto key : keys) {
1267 moveValue(key, other, pFlags);
1268 }
1269}
1270
1271void KConfigGroup::moveValuesTo(KConfigGroup &other, WriteConfigFlags pFlags)
1272{
1273 Q_ASSERT(isValid());
1274 Q_ASSERT(other.isValid());
1275
1276 const QStringList keys = keyList();
1277 for (const QString &key : keys) {
1278 moveValue(key: key.toUtf8().constData(), other, pFlags);
1279 }
1280}
1281

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