diff --git a/autotests/kconfigtest.cpp b/autotests/kconfigtest.cpp index 32d3eef..410b5b8 100644 --- a/autotests/kconfigtest.cpp +++ b/autotests/kconfigtest.cpp @@ -1,1951 +1,1960 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Qt5 TODO: re-enable. No point in doing it before, it breaks on QString::fromUtf8(QByteArray), which exists in qt5. #undef QT_NO_CAST_FROM_BYTEARRAY #include "kconfigtest.h" #include "helper.h" #include "config-kconfig.h" #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #ifndef Q_OS_WIN #include // gethostname #endif KCONFIGGROUP_DECLARE_ENUM_QOBJECT(KConfigTest, Testing) KCONFIGGROUP_DECLARE_FLAGS_QOBJECT(KConfigTest, Flags) QTEST_MAIN(KConfigTest) Q_DECLARE_METATYPE(KConfigGroup) static QString homePath() { #ifdef Q_OS_WIN return QDir::homePath(); #else // Don't use QDir::homePath() on Unix, it removes any trailing slash, while KConfig uses $HOME. return QString::fromLocal8Bit(qgetenv("HOME")); #endif } #define BOOLENTRY1 true #define BOOLENTRY2 false #define STRINGENTRY1 "hello" #define STRINGENTRY2 " hello" #define STRINGENTRY3 "hello " #define STRINGENTRY4 " hello " #define STRINGENTRY5 " " #define STRINGENTRY6 "" #define UTF8BITENTRY "Hello äöü" #define TRANSLATEDSTRINGENTRY1 "bonjour" #define BYTEARRAYENTRY QByteArray( "\x00\xff\x7f\x3c abc\x00\x00", 10 ) #define ESCAPEKEY " []\0017[]==]" #define ESCAPEENTRY "[]\170[]]=3=]\\] " #define DOUBLEENTRY 123456.78912345 #define FLOATENTRY 123.567f #define POINTENTRY QPoint( 4351, 1235 ) #define SIZEENTRY QSize( 10, 20 ) #define RECTENTRY QRect( 10, 23, 5321, 13 ) #define DATETIMEENTRY QDateTime( QDate( 2002, 06, 23 ), QTime( 12, 55, 40 ) ) #define STRINGLISTENTRY (QStringList( "Hello," ) << " World") #define STRINGLISTEMPTYENTRY QStringList() #define STRINGLISTJUSTEMPTYELEMENT QStringList(QString()) #define STRINGLISTEMPTYTRAINLINGELEMENT (QStringList( "Hi" ) << QString()) #define STRINGLISTESCAPEODDENTRY (QStringList( "Hello\\\\\\" ) << "World") #define STRINGLISTESCAPEEVENENTRY (QStringList( "Hello\\\\\\\\" ) << "World") #define STRINGLISTESCAPECOMMAENTRY (QStringList( "Hel\\\\\\,\\\\,\\,\\\\\\\\,lo" ) << "World") #define INTLISTENTRY1 QList() << 1 << 2 << 3 << 4 #define BYTEARRAYLISTENTRY1 QList() << "" << "1,2" << "end" #define VARIANTLISTENTRY (QVariantList() << true << false << QString("joe") << 10023) #define VARIANTLISTENTRY2 (QVariantList() << POINTENTRY << SIZEENTRY) #define HOMEPATH QString(homePath()+"/foo") #define HOMEPATHESCAPE QString(homePath()+"/foo/$HOME") #define DOLLARGROUP "$i" #define TEST_SUBDIR "kconfigtest_subdir/" static inline QString testConfigDir() { return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" TEST_SUBDIR; } static inline QString kdeGlobalsPath() { return QDir::cleanPath(testConfigDir() + "..") + "/kdeglobals"; } void KConfigTest::initTestCase() { // ensure we don't use files in the real config directory QStandardPaths::setTestModeEnabled(true); qRegisterMetaType(); // to make sure all files from a previous failed run are deleted cleanupTestCase(); KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); mainConfig->group("Main").writeEntry("Key", "Value"); mainConfig->sync(); KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cg(&sc, "AAA"); cg.writeEntry("stringEntry1", STRINGENTRY1, KConfig::Persistent | KConfig::Global); cg.deleteEntry("stringEntry2", KConfig::Global); cg = KConfigGroup(&sc, "Hello"); cg.writeEntry("boolEntry1", BOOLENTRY1); cg.writeEntry("boolEntry2", BOOLENTRY2); QByteArray data(UTF8BITENTRY); QCOMPARE(data.size(), 12); // the source file is in utf8 QCOMPARE(QString::fromUtf8(data).length(), 9); cg.writeEntry("Test", data); cg.writeEntry("bytearrayEntry", BYTEARRAYENTRY); cg.writeEntry(ESCAPEKEY, ESCAPEENTRY); cg.writeEntry("emptyEntry", ""); cg.writeEntry("stringEntry1", STRINGENTRY1); cg.writeEntry("stringEntry2", STRINGENTRY2); cg.writeEntry("stringEntry3", STRINGENTRY3); cg.writeEntry("stringEntry4", STRINGENTRY4); cg.writeEntry("stringEntry5", STRINGENTRY5); cg.writeEntry("urlEntry1", QUrl(QStringLiteral("http://qt-project.org"))); cg.writeEntry("keywith=equalsign", STRINGENTRY1); cg.deleteEntry("stringEntry5"); cg.deleteEntry("stringEntry6"); // deleting a nonexistent entry cg.writeEntry("byteArrayEntry1", QByteArray(STRINGENTRY1), KConfig::Global | KConfig::Persistent); cg.writeEntry("doubleEntry1", DOUBLEENTRY); cg.writeEntry("floatEntry1", FLOATENTRY); sc.deleteGroup("deleteMe"); // deleting a nonexistent group cg = KConfigGroup(&sc, "Complex Types"); cg.writeEntry("rectEntry", RECTENTRY); cg.writeEntry("pointEntry", POINTENTRY); cg.writeEntry("sizeEntry", SIZEENTRY); cg.writeEntry("dateTimeEntry", DATETIMEENTRY); cg.writeEntry("dateEntry", DATETIMEENTRY.date()); KConfigGroup ct = cg; cg = KConfigGroup(&ct, "Nested Group 1"); cg.writeEntry("stringentry1", STRINGENTRY1); cg = KConfigGroup(&ct, "Nested Group 2"); cg.writeEntry("stringEntry2", STRINGENTRY2); cg = KConfigGroup(&cg, "Nested Group 2.1"); cg.writeEntry("stringEntry3", STRINGENTRY3); cg = KConfigGroup(&ct, "Nested Group 3"); cg.writeEntry("stringEntry3", STRINGENTRY3); cg = KConfigGroup(&sc, "List Types"); cg.writeEntry("listOfIntsEntry1", INTLISTENTRY1); cg.writeEntry("listOfByteArraysEntry1", BYTEARRAYLISTENTRY1); cg.writeEntry("stringListEntry", STRINGLISTENTRY); cg.writeEntry("stringListEmptyEntry", STRINGLISTEMPTYENTRY); cg.writeEntry("stringListJustEmptyElement", STRINGLISTJUSTEMPTYELEMENT); cg.writeEntry("stringListEmptyTrailingElement", STRINGLISTEMPTYTRAINLINGELEMENT); cg.writeEntry("stringListEscapeOddEntry", STRINGLISTESCAPEODDENTRY); cg.writeEntry("stringListEscapeEvenEntry", STRINGLISTESCAPEEVENENTRY); cg.writeEntry("stringListEscapeCommaEntry", STRINGLISTESCAPECOMMAENTRY); cg.writeEntry("variantListEntry", VARIANTLISTENTRY); cg = KConfigGroup(&sc, "Path Type"); cg.writePathEntry("homepath", HOMEPATH); cg.writePathEntry("homepathescape", HOMEPATHESCAPE); cg = KConfigGroup(&sc, "Enum Types"); #if defined(_MSC_VER) && _MSC_VER == 1600 cg.writeEntry("dummy", 42); #else //Visual C++ 2010 throws an Internal Compiler Error here cg.writeEntry("enum-10", Tens); cg.writeEntry("enum-100", Hundreds); cg.writeEntry("flags-bit0", Flags(bit0)); cg.writeEntry("flags-bit0-bit1", Flags(bit0 | bit1)); #endif cg = KConfigGroup(&sc, "ParentGroup"); KConfigGroup cg1(&cg, "SubGroup1"); cg1.writeEntry("somestring", "somevalue"); cg.writeEntry("parentgrpstring", "somevalue"); KConfigGroup cg2(&cg, "SubGroup2"); cg2.writeEntry("substring", "somevalue"); KConfigGroup cg3(&cg, "SubGroup/3"); cg3.writeEntry("sub3string", "somevalue"); QVERIFY(sc.isDirty()); QVERIFY(sc.sync()); QVERIFY(!sc.isDirty()); QVERIFY2(QFile::exists(testConfigDir() + QStringLiteral("/kconfigtest")), qPrintable(testConfigDir() + QStringLiteral("/kconfigtest must exist"))); QVERIFY2(QFile::exists(kdeGlobalsPath()), qPrintable(kdeGlobalsPath() + QStringLiteral(" must exist"))); KConfig sc1(TEST_SUBDIR "kdebugrc", KConfig::SimpleConfig); KConfigGroup sg0(&sc1, "0"); sg0.writeEntry("AbortFatal", false); sg0.writeEntry("WarnOutput", 0); sg0.writeEntry("FatalOutput", 0); QVERIFY(sc1.sync()); //Setup stuff to test KConfig::addConfigSources() KConfig devcfg(TEST_SUBDIR "specificrc"); KConfigGroup devonlygrp(&devcfg, "Specific Only Group"); devonlygrp.writeEntry("ExistingEntry", "DevValue"); KConfigGroup devandbasegrp(&devcfg, "Shared Group"); devandbasegrp.writeEntry("SomeSharedEntry", "DevValue"); devandbasegrp.writeEntry("SomeSpecificOnlyEntry", "DevValue"); QVERIFY(devcfg.sync()); KConfig basecfg(TEST_SUBDIR "baserc"); KConfigGroup basegrp(&basecfg, "Base Only Group"); basegrp.writeEntry("ExistingEntry", "BaseValue"); KConfigGroup baseanddevgrp(&basecfg, "Shared Group"); baseanddevgrp.writeEntry("SomeSharedEntry", "BaseValue"); baseanddevgrp.writeEntry("SomeBaseOnlyEntry", "BaseValue"); QVERIFY(basecfg.sync()); KConfig gecfg(TEST_SUBDIR "groupescapetest", KConfig::SimpleConfig); cg = KConfigGroup(&gecfg, DOLLARGROUP); cg.writeEntry("entry", "doesntmatter"); } void KConfigTest::cleanupTestCase() { //ensure we don't delete the real directory QDir localConfig(testConfigDir()); //qDebug() << "Erasing" << localConfig; if (localConfig.exists()) { QVERIFY(localConfig.removeRecursively()); } QVERIFY(!localConfig.exists()); if (QFile::exists(kdeGlobalsPath())) { QVERIFY(QFile::remove(kdeGlobalsPath())); } } static QList readLinesFrom(const QString &path) { QFile file(path); const bool opened = file.open(QIODevice::ReadOnly | QIODevice::Text); QList lines; if (!opened) { QWARN(qPrintable(QLatin1String("Failed to open ") + path)); return lines; } QByteArray line; do { line = file.readLine(); if (!line.isEmpty()) { lines.append(line); } } while (!line.isEmpty()); return lines; } static QList readLines(const char *fileName = TEST_SUBDIR "kconfigtest") { const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); Q_ASSERT(!path.isEmpty()); return readLinesFrom(path + '/' + fileName); } // see also testDefaults, which tests reverting with a defaults (global) file available void KConfigTest::testDirtyAfterRevert() { KConfig sc(TEST_SUBDIR "kconfigtest_revert"); KConfigGroup cg(&sc, "Hello"); cg.revertToDefault("does_not_exist"); QVERIFY(!sc.isDirty()); cg.writeEntry("Test", "Correct"); QVERIFY(sc.isDirty()); sc.sync(); QVERIFY(!sc.isDirty()); cg.revertToDefault("Test"); QVERIFY(sc.isDirty()); QVERIFY(sc.sync()); QVERIFY(!sc.isDirty()); cg.revertToDefault("Test"); QVERIFY(!sc.isDirty()); } void KConfigTest::testRevertAllEntries() { // this tests the case were we revert (delete) all entries in a file, // leaving a blank file { KConfig sc(TEST_SUBDIR "konfigtest2", KConfig::SimpleConfig); KConfigGroup cg(&sc, "Hello"); cg.writeEntry("Test", "Correct"); } { KConfig sc(TEST_SUBDIR "konfigtest2", KConfig::SimpleConfig); KConfigGroup cg(&sc, "Hello"); QCOMPARE(cg.readEntry("Test", "Default"), QString("Correct")); cg.revertToDefault("Test"); } KConfig sc(TEST_SUBDIR "konfigtest2", KConfig::SimpleConfig); KConfigGroup cg(&sc, "Hello"); QCOMPARE(cg.readEntry("Test", "Default"), QString("Default")); } void KConfigTest::testSimple() { // kdeglobals (which was created in initTestCase) must be found this way: const QStringList kdeglobals = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals")); QVERIFY(!kdeglobals.isEmpty()); KConfig sc2(TEST_SUBDIR "kconfigtest"); QCOMPARE(sc2.name(), QString(TEST_SUBDIR "kconfigtest")); // make sure groupList() isn't returning something it shouldn't const QStringList lstGroup = sc2.groupList(); for (const QString &group : lstGroup) { QVERIFY(!group.isEmpty() && group != ""); QVERIFY(!group.contains(QChar(0x1d))); } KConfigGroup sc3(&sc2, "AAA"); QVERIFY(sc3.hasKey("stringEntry1")); // from kdeglobals QVERIFY(!sc3.isEntryImmutable("stringEntry1")); QCOMPARE(sc3.readEntry("stringEntry1"), QString(STRINGENTRY1)); QVERIFY(!sc3.hasKey("stringEntry2")); QCOMPARE(sc3.readEntry("stringEntry2", QString("bla")), QString("bla")); QVERIFY(!sc3.hasDefault("stringEntry1")); sc3 = KConfigGroup(&sc2, "Hello"); QCOMPARE(sc3.readEntry("Test", QByteArray()), QByteArray(UTF8BITENTRY)); QCOMPARE(sc3.readEntry("bytearrayEntry", QByteArray()), BYTEARRAYENTRY); QCOMPARE(sc3.readEntry(ESCAPEKEY), QString(ESCAPEENTRY)); QCOMPARE(sc3.readEntry("Test", QString()), QString::fromUtf8(UTF8BITENTRY)); QCOMPARE(sc3.readEntry("emptyEntry"/*, QString("Fietsbel")*/), QLatin1String("")); QCOMPARE(sc3.readEntry("emptyEntry", QString("Fietsbel")).isEmpty(), true); QCOMPARE(sc3.readEntry("stringEntry1"), QString(STRINGENTRY1)); QCOMPARE(sc3.readEntry("stringEntry2"), QString(STRINGENTRY2)); QCOMPARE(sc3.readEntry("stringEntry3"), QString(STRINGENTRY3)); QCOMPARE(sc3.readEntry("stringEntry4"), QString(STRINGENTRY4)); QVERIFY(!sc3.hasKey("stringEntry5")); QCOMPARE(sc3.readEntry("stringEntry5", QString("test")), QString("test")); QVERIFY(!sc3.hasKey("stringEntry6")); QCOMPARE(sc3.readEntry("stringEntry6", QString("foo")), QString("foo")); QCOMPARE(sc3.readEntry("urlEntry1", QUrl()), QUrl("http://qt-project.org")); QCOMPARE(sc3.readEntry("boolEntry1", BOOLENTRY1), BOOLENTRY1); QCOMPARE(sc3.readEntry("boolEntry2", false), BOOLENTRY2); QCOMPARE(sc3.readEntry("keywith=equalsign", QString("wrong")), QString(STRINGENTRY1)); QCOMPARE(sc3.readEntry("byteArrayEntry1", QByteArray()), QByteArray(STRINGENTRY1)); QCOMPARE(sc3.readEntry("doubleEntry1", 0.0), DOUBLEENTRY); QCOMPARE(sc3.readEntry("floatEntry1", 0.0f), FLOATENTRY); } void KConfigTest::testDefaults() { KConfig config(TEST_SUBDIR "defaulttest", KConfig::NoGlobals); const QString defaultsFile = TEST_SUBDIR "defaulttest.defaults"; KConfig defaults(defaultsFile, KConfig::SimpleConfig); const QString Default(QStringLiteral("Default")); const QString NotDefault(QStringLiteral("Not Default")); const QString Value1(STRINGENTRY1); const QString Value2(STRINGENTRY2); KConfigGroup group = defaults.group("any group"); group.writeEntry("entry1", Default); QVERIFY(group.sync()); group = config.group("any group"); group.writeEntry("entry1", Value1); group.writeEntry("entry2", Value2); QVERIFY(group.sync()); config.addConfigSources(QStringList() << QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + '/' + defaultsFile); config.setReadDefaults(true); QCOMPARE(group.readEntry("entry1", QString()), Default); QCOMPARE(group.readEntry("entry2", NotDefault), NotDefault); // no default for entry2 config.setReadDefaults(false); QCOMPARE(group.readEntry("entry1", Default), Value1); QCOMPARE(group.readEntry("entry2", NotDefault), Value2); group.revertToDefault("entry1"); QCOMPARE(group.readEntry("entry1", QString()), Default); group.revertToDefault("entry2"); QCOMPARE(group.readEntry("entry2", QString()), QString()); // TODO test reverting localized entries Q_ASSERT(config.isDirty()); group.sync(); // Check that everything is OK on disk, too KConfig reader(TEST_SUBDIR "defaulttest", KConfig::NoGlobals); reader.addConfigSources(QStringList() << QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + '/' + defaultsFile); KConfigGroup readerGroup = reader.group("any group"); QCOMPARE(readerGroup.readEntry("entry1", QString()), Default); QCOMPARE(readerGroup.readEntry("entry2", QString()), QString()); } void KConfigTest::testLocale() { KConfig config(TEST_SUBDIR "kconfigtest.locales", KConfig::SimpleConfig); const QString Translated(TRANSLATEDSTRINGENTRY1); const QString Untranslated(STRINGENTRY1); KConfigGroup group = config.group("Hello"); group.writeEntry("stringEntry1", Untranslated); config.setLocale(QStringLiteral("fr")); group.writeEntry("stringEntry1", Translated, KConfig::Localized | KConfig::Persistent); QVERIFY(config.sync()); QCOMPARE(group.readEntry("stringEntry1", QString()), Translated); QCOMPARE(group.readEntryUntranslated("stringEntry1"), Untranslated); config.setLocale(QStringLiteral("C")); // strings written in the "C" locale are written as nonlocalized group.writeEntry("stringEntry1", Untranslated, KConfig::Localized | KConfig::Persistent); QVERIFY(config.sync()); QCOMPARE(group.readEntry("stringEntry1", QString()), Untranslated); } void KConfigTest::testEncoding() { QString groupstr = QString::fromUtf8("UTF-8:\xc3\xb6l"); KConfig c(TEST_SUBDIR "kconfigtestencodings"); KConfigGroup cg(&c, groupstr); cg.writeEntry("key", "value"); QVERIFY(c.sync()); QList lines = readLines(TEST_SUBDIR "kconfigtestencodings"); QCOMPARE(lines.count(), 2); QCOMPARE(lines.first(), QByteArray("[UTF-8:\xc3\xb6l]\n")); KConfig c2(TEST_SUBDIR "kconfigtestencodings"); KConfigGroup cg2(&c2, groupstr); QVERIFY(cg2.readEntry("key") == QByteArray("value")); QVERIFY(c2.groupList().contains(groupstr)); } void KConfigTest::testLists() { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "List Types"); QCOMPARE(sc3.readEntry(QString("stringListEntry"), QStringList()), STRINGLISTENTRY); QCOMPARE(sc3.readEntry(QString("stringListEmptyEntry"), QStringList("wrong")), STRINGLISTEMPTYENTRY); QCOMPARE(sc3.readEntry(QString("stringListJustEmptyElement"), QStringList()), STRINGLISTJUSTEMPTYELEMENT); QCOMPARE(sc3.readEntry(QString("stringListEmptyTrailingElement"), QStringList()), STRINGLISTEMPTYTRAINLINGELEMENT); QCOMPARE(sc3.readEntry(QString("stringListEscapeOddEntry"), QStringList()), STRINGLISTESCAPEODDENTRY); QCOMPARE(sc3.readEntry(QString("stringListEscapeEvenEntry"), QStringList()), STRINGLISTESCAPEEVENENTRY); QCOMPARE(sc3.readEntry(QString("stringListEscapeCommaEntry"), QStringList()), STRINGLISTESCAPECOMMAENTRY); QCOMPARE(sc3.readEntry("listOfIntsEntry1"), QString::fromLatin1("1,2,3,4")); QList expectedIntList = INTLISTENTRY1; QVERIFY(sc3.readEntry("listOfIntsEntry1", QList()) == expectedIntList); QCOMPARE(QVariant(sc3.readEntry("variantListEntry", VARIANTLISTENTRY)).toStringList(), QVariant(VARIANTLISTENTRY).toStringList()); QCOMPARE(sc3.readEntry("listOfByteArraysEntry1", QList()), BYTEARRAYLISTENTRY1); } void KConfigTest::testPath() { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "Path Type"); QCOMPARE(sc3.readPathEntry("homepath", QString()), HOMEPATH); QCOMPARE(sc3.readPathEntry("homepathescape", QString()), HOMEPATHESCAPE); QCOMPARE(sc3.entryMap()["homepath"], HOMEPATH); qputenv("WITHSLASH", "/a/"); { QFile file(testConfigDir() + "/pathtest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[Test Group]" << endl << "homePath=$HOME/foo" << endl << "homePath2=file://$HOME/foo" << endl << "withSlash=$WITHSLASH/foo" << endl << "withSlash2=$WITHSLASH" << endl << "withBraces[$e]=file://${HOME}/foo" << endl << "URL[$e]=file://${HOME}/foo" << endl << "hostname[$e]=$(hostname)" << endl << "escapes=aaa,bb/b,ccc\\,ccc" << endl << "noeol=foo" // no EOL ; } KConfig cf2(TEST_SUBDIR "pathtest"); KConfigGroup group = cf2.group("Test Group"); QVERIFY(group.hasKey("homePath")); QCOMPARE(group.readPathEntry("homePath", QString()), HOMEPATH); QVERIFY(group.hasKey("homePath2")); QCOMPARE(group.readPathEntry("homePath2", QString()), QString("file://" + HOMEPATH)); QVERIFY(group.hasKey("withSlash")); QCOMPARE(group.readPathEntry("withSlash", QString()), QStringLiteral("/a//foo")); QVERIFY(group.hasKey("withSlash2")); QCOMPARE(group.readPathEntry("withSlash2", QString()), QStringLiteral("/a/")); QVERIFY(group.hasKey("withBraces")); QCOMPARE(group.readPathEntry("withBraces", QString()), QString("file://" + HOMEPATH)); QVERIFY(group.hasKey("URL")); QCOMPARE(group.readEntry("URL", QString()), QString("file://" + HOMEPATH)); #if !defined(Q_OS_WIN32) && !defined(Q_OS_MAC) // I don't know if this will work on windows // This test hangs on OS X QVERIFY(group.hasKey("hostname")); char hostname[256]; QVERIFY(::gethostname(hostname, sizeof(hostname)) == 0); QCOMPARE(group.readEntry("hostname", QString()), QString::fromLatin1(hostname)); #endif QVERIFY(group.hasKey("noeol")); QCOMPARE(group.readEntry("noeol", QString()), QString("foo")); const auto val = QStringList { QStringLiteral("aaa"), QStringLiteral("bb/b"), QStringLiteral("ccc,ccc")}; QCOMPARE(group.readPathEntry("escapes", QStringList()), val); } void KConfigTest::testPersistenceOfExpandFlagForPath() { // This test checks that a path entry starting with $HOME is still flagged // with the expand flag after the config was altered without rewriting the // path entry. // 1st step: Open the config, add a new dummy entry and then sync the config // back to the storage. { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "Path Type"); sc3.writeEntry("dummy", "dummy"); QVERIFY(sc2.sync()); } // 2nd step: Call testPath() again. Rewriting the config must not break // the testPath() test. testPath(); } void KConfigTest::testPathQtHome() { { QFile file(testConfigDir() + "/pathtest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[Test Group]" << endl << "dataDir[$e]=$QT_DATA_HOME/kconfigtest" << endl << "cacheDir[$e]=$QT_CACHE_HOME/kconfigtest" << endl << "configDir[$e]=$QT_CONFIG_HOME/kconfigtest" << endl; } KConfig cf2(TEST_SUBDIR "pathtest"); KConfigGroup group = cf2.group("Test Group"); qunsetenv("QT_DATA_HOME"); qunsetenv("QT_CACHE_HOME"); qunsetenv("QT_CONFIG_HOME"); QVERIFY(group.hasKey("dataDir")); QCOMPARE(group.readEntry("dataDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).append(QStringLiteral("/kconfigtest"))); QVERIFY(group.hasKey("cacheDir")); QCOMPARE(group.readEntry("cacheDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).append(QStringLiteral("/kconfigtest"))); QVERIFY(group.hasKey("configDir")); QCOMPARE(group.readEntry("configDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).append(QStringLiteral("/kconfigtest"))); qputenv("QT_DATA_HOME","/1"); qputenv("QT_CACHE_HOME","/2"); qputenv("QT_CONFIG_HOME","/3"); QVERIFY(group.hasKey("dataDir")); QCOMPARE(group.readEntry("dataDir", QString()), QStringLiteral("/1/kconfigtest")); QVERIFY(group.hasKey("cacheDir")); QCOMPARE(group.readEntry("cacheDir", QString()), QStringLiteral("/2/kconfigtest")); QVERIFY(group.hasKey("configDir")); QCOMPARE(group.readEntry("configDir", QString()), QStringLiteral("/3/kconfigtest")); } void KConfigTest::testComplex() { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "Complex Types"); QCOMPARE(sc3.readEntry("pointEntry", QPoint()), POINTENTRY); QCOMPARE(sc3.readEntry("sizeEntry", SIZEENTRY), SIZEENTRY); QCOMPARE(sc3.readEntry("rectEntry", QRect(1, 2, 3, 4)), RECTENTRY); QCOMPARE(sc3.readEntry("dateTimeEntry", QDateTime()).toString(Qt::ISODate), DATETIMEENTRY.toString(Qt::ISODate)); QCOMPARE(sc3.readEntry("dateEntry", QDate()).toString(Qt::ISODate), DATETIMEENTRY.date().toString(Qt::ISODate)); QCOMPARE(sc3.readEntry("dateTimeEntry", QDate()), DATETIMEENTRY.date()); } void KConfigTest::testEnums() { //Visual C++ 2010 (compiler version 16.0) throws an Internal Compiler Error //when compiling the code in initTestCase that creates these KConfig entries, //so we can't run this test #if defined(_MSC_VER) && _MSC_VER == 1600 QSKIP("Visual C++ 2010 can't compile this test"); #endif KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc, "Enum Types"); QCOMPARE(sc3.readEntry("enum-10"), QString("Tens")); QVERIFY(sc3.readEntry("enum-100", Ones) != Ones); QVERIFY(sc3.readEntry("enum-100", Ones) != Tens); QCOMPARE(sc3.readEntry("flags-bit0"), QString("bit0")); QVERIFY(sc3.readEntry("flags-bit0", Flags()) == bit0); int eid = staticMetaObject.indexOfEnumerator("Flags"); QVERIFY(eid != -1); QMetaEnum me = staticMetaObject.enumerator(eid); Flags bitfield = bit0 | bit1; QCOMPARE(sc3.readEntry("flags-bit0-bit1"), QString(me.valueToKeys(bitfield))); QVERIFY(sc3.readEntry("flags-bit0-bit1", Flags()) == bitfield); } void KConfigTest::testEntryMap() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cg(&sc, "Hello"); QMap entryMap = cg.entryMap(); qDebug() << entryMap.keys(); QCOMPARE(entryMap.value("stringEntry1"), QString(STRINGENTRY1)); QCOMPARE(entryMap.value("stringEntry2"), QString(STRINGENTRY2)); QCOMPARE(entryMap.value("stringEntry3"), QString(STRINGENTRY3)); QCOMPARE(entryMap.value("stringEntry4"), QString(STRINGENTRY4)); QVERIFY(!entryMap.contains("stringEntry5")); QVERIFY(!entryMap.contains("stringEntry6")); QCOMPARE(entryMap.value("Test"), QString::fromUtf8(UTF8BITENTRY)); QCOMPARE(entryMap.value("bytearrayEntry"), QString::fromUtf8(BYTEARRAYENTRY.constData())); QCOMPARE(entryMap.value("emptyEntry"), QString()); QVERIFY(entryMap.contains("emptyEntry")); QCOMPARE(entryMap.value("boolEntry1"), QString(BOOLENTRY1 ? "true" : "false")); QCOMPARE(entryMap.value("boolEntry2"), QString(BOOLENTRY2 ? "true" : "false")); QCOMPARE(entryMap.value("keywith=equalsign"), QString(STRINGENTRY1)); QCOMPARE(entryMap.value("byteArrayEntry1"), QString(STRINGENTRY1)); QCOMPARE(entryMap.value("doubleEntry1").toDouble(), DOUBLEENTRY); QCOMPARE(entryMap.value("floatEntry1").toFloat(), FLOATENTRY); } void KConfigTest::testInvalid() { KConfig sc(TEST_SUBDIR "kconfigtest"); // all of these should print a message to the kdebug.dbg file KConfigGroup sc3(&sc, "Invalid Types"); sc3.writeEntry("badList", VARIANTLISTENTRY2); QList list; // 1 element list list << 1; sc3.writeEntry(QStringLiteral("badList"), list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 2 element list list << 2; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 3 element list list << 303; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); // out of bounds QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 4 element list list << 4; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 5 element list list[2] = 3; list << 5; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 6 element list list << 6; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); } void KConfigTest::testChangeGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc, "Hello"); QCOMPARE(sc3.name(), QString("Hello")); KConfigGroup newGroup(sc3); #ifndef KDE_NO_DEPRECATED newGroup.changeGroup("FooBar"); // deprecated! QCOMPARE(newGroup.name(), QString("FooBar")); QCOMPARE(sc3.name(), QString("Hello")); // unchanged // Write into the "changed group" and check that it works newGroup.writeEntry("InFooBar", "FB"); QCOMPARE(KConfigGroup(&sc, "FooBar").entryMap().value("InFooBar"), QString("FB")); QCOMPARE(KConfigGroup(&sc, "Hello").entryMap().value("InFooBar"), QString()); #endif KConfigGroup rootGroup(sc.group("")); QCOMPARE(rootGroup.name(), QString("")); KConfigGroup sc32(rootGroup.group("Hello")); QCOMPARE(sc32.name(), QString("Hello")); KConfigGroup newGroup2(sc32); #ifndef KDE_NO_DEPRECATED newGroup2.changeGroup("FooBar"); // deprecated! QCOMPARE(newGroup2.name(), QString("FooBar")); QCOMPARE(sc32.name(), QString("Hello")); // unchanged #endif } // Simple test for deleteEntry void KConfigTest::testDeleteEntry() { const char *configFile = TEST_SUBDIR "kconfigdeletetest"; { KConfig conf(configFile); conf.group("Hello").writeEntry("DelKey", "ToBeDeleted"); } const QList lines = readLines(configFile); Q_ASSERT(lines.contains("[Hello]\n")); Q_ASSERT(lines.contains("DelKey=ToBeDeleted\n")); KConfig sc(configFile); KConfigGroup group(&sc, "Hello"); group.deleteEntry("DelKey"); QCOMPARE(group.readEntry("DelKey", QString("Fietsbel")), QString("Fietsbel")); QVERIFY(group.sync()); Q_ASSERT(!readLines(configFile).contains("DelKey=ToBeDeleted\n")); QCOMPARE(group.readEntry("DelKey", QString("still deleted")), QString("still deleted")); } void KConfigTest::testDelete() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup ct(&sc, "Complex Types"); // First delete a nested group KConfigGroup delgr(&ct, "Nested Group 3"); QVERIFY(delgr.exists()); QVERIFY(ct.hasGroup("Nested Group 3")); delgr.deleteGroup(); QVERIFY(!delgr.exists()); QVERIFY(!ct.hasGroup("Nested Group 3")); QVERIFY(ct.groupList().contains("Nested Group 3")); KConfigGroup ng(&ct, "Nested Group 2"); QVERIFY(sc.hasGroup("Complex Types")); QVERIFY(!sc.hasGroup("Does not exist")); sc.deleteGroup("Complex Types"); QCOMPARE(sc.group("Complex Types").keyList().count(), 0); QVERIFY(!sc.hasGroup("Complex Types")); // #192266 QVERIFY(!sc.group("Complex Types").exists()); QVERIFY(!ct.hasGroup("Nested Group 1")); QCOMPARE(ct.group("Nested Group 1").keyList().count(), 0); QCOMPARE(ct.group("Nested Group 2").keyList().count(), 0); QCOMPARE(ng.group("Nested Group 2.1").keyList().count(), 0); KConfigGroup cg(&sc, "AAA"); cg.deleteGroup(); QVERIFY(sc.entryMap("Complex Types").isEmpty()); QVERIFY(sc.entryMap("AAA").isEmpty()); QVERIFY(!sc.entryMap("Hello").isEmpty()); //not deleted group QVERIFY(sc.entryMap("FooBar").isEmpty()); //inexistant group QVERIFY(cg.sync()); // Check what happens on disk const QList lines = readLines(); //qDebug() << lines; QVERIFY(!lines.contains("[Complex Types]\n")); QVERIFY(!lines.contains("[Complex Types][Nested Group 1]\n")); QVERIFY(!lines.contains("[Complex Types][Nested Group 2]\n")); QVERIFY(!lines.contains("[Complex Types][Nested Group 2.1]\n")); QVERIFY(!lines.contains("[AAA]\n")); QVERIFY(lines.contains("[Hello]\n")); // a group that was not deleted // test for entries that are marked as deleted when there is no default KConfig cf(TEST_SUBDIR "kconfigtest", KConfig::SimpleConfig); // make sure there are no defaults cg = cf.group("Portable Devices"); cg.writeEntry("devices|manual|(null)", "whatever"); cg.writeEntry("devices|manual|/mnt/ipod", "/mnt/ipod"); QVERIFY(cf.sync()); int count = 0; const QList listLines = readLines(); for (const QByteArray &item : listLines) if (item.startsWith("devices|")) { // krazy:exclude=strings count++; } QCOMPARE(count, 2); cg.deleteEntry("devices|manual|/mnt/ipod"); QVERIFY(cf.sync()); const QList listLines2 = readLines(); for (const QByteArray &item : listLines2) { QVERIFY(!item.contains("ipod")); } } void KConfigTest::testDefaultGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup defaultGroup(&sc, ""); QCOMPARE(defaultGroup.name(), QString("")); QVERIFY(!defaultGroup.exists()); defaultGroup.writeEntry("TestKey", "defaultGroup"); QVERIFY(defaultGroup.exists()); QCOMPARE(defaultGroup.readEntry("TestKey", QString()), QString("defaultGroup")); QVERIFY(sc.sync()); { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup defaultGroup2(&sc2, ""); QCOMPARE(defaultGroup2.name(), QString("")); QVERIFY(defaultGroup2.exists()); QCOMPARE(defaultGroup2.readEntry("TestKey", QString()), QString("defaultGroup")); } { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup emptyGroup(&sc2, ""); QCOMPARE(emptyGroup.name(), QString("")); QVERIFY(emptyGroup.exists()); QCOMPARE(emptyGroup.readEntry("TestKey", QString()), QString("defaultGroup")); } QList lines = readLines(); QVERIFY(!lines.contains("[]\n")); QCOMPARE(lines.first(), QByteArray("TestKey=defaultGroup\n")); // Now that the group exists make sure it isn't returned from groupList() const QStringList groupList = sc.groupList(); for (const QString &group : groupList) { QVERIFY(!group.isEmpty() && group != ""); } defaultGroup.deleteGroup(); QVERIFY(sc.sync()); // Test if deleteGroup worked lines = readLines(); QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); } void KConfigTest::testEmptyGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup emptyGroup(&sc, ""); QCOMPARE(emptyGroup.name(), QString("")); // confusing, heh? QVERIFY(!emptyGroup.exists()); emptyGroup.writeEntry("TestKey", "emptyGroup"); QVERIFY(emptyGroup.exists()); QCOMPARE(emptyGroup.readEntry("TestKey", QString()), QString("emptyGroup")); QVERIFY(sc.sync()); { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup defaultGroup(&sc2, ""); QCOMPARE(defaultGroup.name(), QString("")); QVERIFY(defaultGroup.exists()); QCOMPARE(defaultGroup.readEntry("TestKey", QString()), QString("emptyGroup")); } { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup emptyGroup2(&sc2, ""); QCOMPARE(emptyGroup2.name(), QString("")); QVERIFY(emptyGroup2.exists()); QCOMPARE(emptyGroup2.readEntry("TestKey", QString()), QString("emptyGroup")); } QList lines = readLines(); QVERIFY(!lines.contains("[]\n")); // there's no support for the [] group, in fact. QCOMPARE(lines.first(), QByteArray("TestKey=emptyGroup\n")); // Now that the group exists make sure it isn't returned from groupList() const QStringList groupList = sc.groupList(); for (const QString &group : groupList) { QVERIFY(!group.isEmpty() && group != ""); } emptyGroup.deleteGroup(); QVERIFY(sc.sync()); // Test if deleteGroup worked lines = readLines(); QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); } #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_BLACKBERRY) && !defined(Q_OS_ANDROID) #define Q_XDG_PLATFORM #endif void KConfigTest::testCascadingWithLocale() { // This test relies on XDG_CONFIG_DIRS, which only has effect on Unix. // Cascading (more than two levels) isn't available at all on Windows. #ifdef Q_XDG_PLATFORM QTemporaryDir middleDir; QTemporaryDir globalDir; qputenv("XDG_CONFIG_DIRS", qPrintable(middleDir.path() + QString(":") + globalDir.path())); const QString globalConfigDir = globalDir.path() + "/" TEST_SUBDIR; QVERIFY(QDir().mkpath(globalConfigDir)); QFile global(globalConfigDir + "foo.desktop"); QVERIFY(global.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream globalOut(&global); globalOut << "[Group]" << endl << "FromGlobal=true" << endl << "FromGlobal[fr]=vrai" << endl << "Name=Testing" << endl << "Name[fr]=FR" << endl << "Other=Global" << endl << "Other[fr]=Global_FR" << endl; global.close(); const QString middleConfigDir = middleDir.path() + "/" TEST_SUBDIR; QVERIFY(QDir().mkpath(middleConfigDir)); QFile local(middleConfigDir + "foo.desktop"); QVERIFY(local.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&local); out << "[Group]" << endl << "FromLocal=true" << endl << "FromLocal[fr]=vrai" << endl << "Name=Local Testing" << endl << "Name[fr]=FR" << endl << "Other=English Only" << endl; local.close(); KConfig config(TEST_SUBDIR "foo.desktop"); KConfigGroup group = config.group("Group"); QCOMPARE(group.readEntry("FromGlobal"), QString("true")); QCOMPARE(group.readEntry("FromLocal"), QString("true")); QCOMPARE(group.readEntry("Name"), QString("Local Testing")); config.setLocale(QStringLiteral("fr")); QCOMPARE(group.readEntry("FromGlobal"), QString("vrai")); QCOMPARE(group.readEntry("FromLocal"), QString("vrai")); QCOMPARE(group.readEntry("Name"), QString("FR")); QCOMPARE(group.readEntry("Other"), QString("English Only")); // Global_FR is locally overriden #endif } void KConfigTest::testMerge() { DefaultLocale defaultLocale; QLocale::setDefault(QLocale::c()); KConfig config(TEST_SUBDIR "mergetest", KConfig::SimpleConfig); KConfigGroup cg = config.group("some group"); cg.writeEntry("entry", " random entry"); cg.writeEntry("another entry", "blah blah blah"); { // simulate writing by another process QFile file(testConfigDir() + "/mergetest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[Merged Group]" << endl << "entry1=Testing" << endl << "entry2=More Testing" << endl << "[some group]" << endl << "entry[fr]=French" << endl << "entry[es]=Spanish" << endl << "entry[de]=German" << endl; } QVERIFY(config.sync()); { QList lines; // this is what the file should look like lines << "[Merged Group]\n" << "entry1=Testing\n" << "entry2=More Testing\n" << "\n" << "[some group]\n" << "another entry=blah blah blah\n" << "entry=\\srandom entry\n" << "entry[de]=German\n" << "entry[es]=Spanish\n" << "entry[fr]=French\n"; QFile file(testConfigDir() + "/mergetest"); file.open(QIODevice::ReadOnly | QIODevice::Text); for (const QByteArray &line : qAsConst(lines)) { QCOMPARE(line, file.readLine()); } } } void KConfigTest::testImmutable() { { QFile file(testConfigDir() + "/immutabletest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[$i]" << endl << "entry1=Testing" << endl << "[group][$i]" << endl << "[group][subgroup][$i]" << endl; } KConfig config(TEST_SUBDIR "immutabletest", KConfig::SimpleConfig); QVERIFY(config.isGroupImmutable(QByteArray())); KConfigGroup cg = config.group(QByteArray()); QVERIFY(cg.isEntryImmutable("entry1")); KConfigGroup cg1 = config.group("group"); QVERIFY(cg1.isImmutable()); KConfigGroup cg1a = cg.group("group"); QVERIFY(cg1a.isImmutable()); KConfigGroup cg2 = cg1.group("subgroup"); QVERIFY(cg2.isImmutable()); } void KConfigTest::testOptionOrder() { { QFile file(testConfigDir() + "/doubleattrtest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[group3]" << endl << "entry2=unlocalized" << endl << "entry2[$i][de_DE]=t2" << endl; } KConfig config(TEST_SUBDIR "doubleattrtest", KConfig::SimpleConfig); config.setLocale(QStringLiteral("de_DE")); KConfigGroup cg3 = config.group("group3"); QVERIFY(!cg3.isImmutable()); QCOMPARE(cg3.readEntry("entry2", ""), QString("t2")); QVERIFY(cg3.isEntryImmutable("entry2")); config.setLocale(QStringLiteral("C")); QCOMPARE(cg3.readEntry("entry2", ""), QString("unlocalized")); QVERIFY(!cg3.isEntryImmutable("entry2")); cg3.writeEntry("entry2", "modified"); QVERIFY(config.sync()); { QList lines; // this is what the file should look like lines << "[group3]\n" << "entry2=modified\n" << "entry2[de_DE][$i]=t2\n"; QFile file(testConfigDir() + "/doubleattrtest"); file.open(QIODevice::ReadOnly | QIODevice::Text); for (const QByteArray &line : qAsConst(lines)) { QCOMPARE(line, file.readLine()); } } } void KConfigTest::testGroupEscape() { KConfig config(TEST_SUBDIR "groupescapetest", KConfig::SimpleConfig); QVERIFY(config.group(DOLLARGROUP).exists()); } void KConfigTest::testSubGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cg(&sc, "ParentGroup"); QCOMPARE(cg.readEntry("parentgrpstring", ""), QString("somevalue")); KConfigGroup subcg1(&cg, "SubGroup1"); QCOMPARE(subcg1.name(), QString("SubGroup1")); QCOMPARE(subcg1.readEntry("somestring", ""), QString("somevalue")); KConfigGroup subcg2(&cg, "SubGroup2"); QCOMPARE(subcg2.name(), QString("SubGroup2")); QCOMPARE(subcg2.readEntry("substring", ""), QString("somevalue")); KConfigGroup subcg3(&cg, "SubGroup/3"); QCOMPARE(subcg3.readEntry("sub3string", ""), QString("somevalue")); QCOMPARE(subcg3.name(), QString("SubGroup/3")); KConfigGroup rcg(&sc, ""); KConfigGroup srcg(&rcg, "ParentGroup"); QCOMPARE(srcg.readEntry("parentgrpstring", ""), QString("somevalue")); QStringList groupList = cg.groupList(); groupList.sort(); // comes from QSet, so order is undefined QCOMPARE(groupList, (QStringList() << "SubGroup/3" << "SubGroup1" << "SubGroup2")); const QStringList expectedSubgroup3Keys = (QStringList() << QStringLiteral("sub3string")); QCOMPARE(subcg3.keyList(), expectedSubgroup3Keys); const QStringList expectedParentGroupKeys(QStringList() << QStringLiteral("parentgrpstring")); QCOMPARE(cg.keyList(), expectedParentGroupKeys); QCOMPARE(QStringList(cg.entryMap().keys()), expectedParentGroupKeys); QCOMPARE(QStringList(subcg3.entryMap().keys()), expectedSubgroup3Keys); // Create A group containing only other groups. We want to make sure it // shows up in groupList of sc KConfigGroup neg(&sc, "NoEntryGroup"); KConfigGroup negsub1(&neg, "NEG Child1"); negsub1.writeEntry("entry", "somevalue"); KConfigGroup negsub2(&neg, "NEG Child2"); KConfigGroup negsub3(&neg, "NEG Child3"); KConfigGroup negsub31(&negsub3, "NEG Child3-1"); KConfigGroup negsub4(&neg, "NEG Child4"); KConfigGroup negsub41(&negsub4, "NEG Child4-1"); negsub41.writeEntry("entry", "somevalue"); // A group exists if it has content QVERIFY(negsub1.exists()); // But it doesn't exist if it has no content // Ossi and David say: this is how it's supposed to work. // However you could add a dummy entry for now, or we could add a "Persist" feature to kconfig groups // which would make it written out, much like "immutable" already makes them persistent. QVERIFY(!negsub2.exists()); // A subgroup does not qualify as content if it is also empty QVERIFY(!negsub3.exists()); // A subgroup with content is ok QVERIFY(negsub4.exists()); // Only subgroups with content show up in groupList() //QEXPECT_FAIL("", "Empty subgroups do not show up in groupList()", Continue); //QCOMPARE(neg.groupList(), QStringList() << "NEG Child1" << "NEG Child2" << "NEG Child3" << "NEG Child4"); // This is what happens QStringList groups = neg.groupList(); groups.sort(); // Qt5 made the ordering unreliable, due to QHash QCOMPARE(groups, QStringList() << "NEG Child1" << "NEG Child4"); // make sure groupList() isn't returning something it shouldn't const QStringList listGroup = sc.groupList(); for (const QString &group : listGroup) { QVERIFY(!group.isEmpty() && group != ""); QVERIFY(!group.contains(QChar(0x1d))); QVERIFY(!group.contains("subgroup")); QVERIFY(!group.contains("SubGroup")); } QVERIFY(sc.sync()); // Check that the empty groups are not written out. const QList lines = readLines(); QVERIFY(lines.contains("[NoEntryGroup][NEG Child1]\n")); QVERIFY(!lines.contains("[NoEntryGroup][NEG Child2]\n")); QVERIFY(!lines.contains("[NoEntryGroup][NEG Child3]\n")); QVERIFY(!lines.contains("[NoEntryGroup][NEG Child4]\n")); // implicit group, not written out QVERIFY(lines.contains("[NoEntryGroup][NEG Child4][NEG Child4-1]\n")); } void KConfigTest::testAddConfigSources() { KConfig cf(TEST_SUBDIR "specificrc"); cf.addConfigSources(QStringList() << testConfigDir() + "/baserc"); cf.reparseConfiguration(); KConfigGroup specificgrp(&cf, "Specific Only Group"); QCOMPARE(specificgrp.readEntry("ExistingEntry", ""), QString("DevValue")); KConfigGroup sharedgrp(&cf, "Shared Group"); QCOMPARE(sharedgrp.readEntry("SomeSpecificOnlyEntry", ""), QString("DevValue")); QCOMPARE(sharedgrp.readEntry("SomeBaseOnlyEntry", ""), QString("BaseValue")); QCOMPARE(sharedgrp.readEntry("SomeSharedEntry", ""), QString("DevValue")); KConfigGroup basegrp(&cf, "Base Only Group"); QCOMPARE(basegrp.readEntry("ExistingEntry", ""), QString("BaseValue")); basegrp.writeEntry("New Entry Base Only", "SomeValue"); KConfigGroup newgrp(&cf, "New Group"); newgrp.writeEntry("New Entry", "SomeValue"); QVERIFY(cf.sync()); KConfig plaincfg(TEST_SUBDIR "specificrc"); KConfigGroup newgrp2(&plaincfg, "New Group"); QCOMPARE(newgrp2.readEntry("New Entry", ""), QString("SomeValue")); KConfigGroup basegrp2(&plaincfg, "Base Only Group"); QCOMPARE(basegrp2.readEntry("New Entry Base Only", ""), QString("SomeValue")); } void KConfigTest::testGroupCopyTo() { KConfig cf1(TEST_SUBDIR "kconfigtest"); KConfigGroup original = cf1.group("Enum Types"); KConfigGroup copy = cf1.group("Enum Types Copy"); original.copyTo(©); // copy from one group to another QCOMPARE(copy.entryMap(), original.entryMap()); KConfig cf2(TEST_SUBDIR "copy_of_kconfigtest", KConfig::SimpleConfig); QVERIFY(!cf2.hasGroup(original.name())); QVERIFY(!cf2.hasGroup(copy.name())); KConfigGroup newGroup = cf2.group(original.name()); original.copyTo(&newGroup); // copy from one file to another QVERIFY(cf2.hasGroup(original.name())); QVERIFY(!cf2.hasGroup(copy.name())); // make sure we didn't copy more than we wanted QCOMPARE(newGroup.entryMap(), original.entryMap()); } void KConfigTest::testConfigCopyToSync() { KConfig cf1(TEST_SUBDIR "kconfigtest"); // Prepare source file KConfigGroup group(&cf1, "CopyToTest"); group.writeEntry("Type", "Test"); QVERIFY(cf1.sync()); // Copy to "destination" const QString destination = testConfigDir() + "/kconfigcopytotest"; QFile::remove(destination); KConfig cf2(TEST_SUBDIR "kconfigcopytotest"); KConfigGroup group2(&cf2, "CopyToTest"); group.copyTo(&group2); QString testVal = group2.readEntry("Type"); QCOMPARE(testVal, QString("Test")); // should write to disk the copied data from group QVERIFY(cf2.sync()); QVERIFY(QFile::exists(destination)); } void KConfigTest::testConfigCopyTo() { KConfig cf1(TEST_SUBDIR "kconfigtest"); { // Prepare source file KConfigGroup group(&cf1, "CopyToTest"); group.writeEntry("Type", "Test"); QVERIFY(cf1.sync()); } { // Copy to "destination" const QString destination = testConfigDir() + "/kconfigcopytotest"; QFile::remove(destination); KConfig cf2; cf1.copyTo(destination, &cf2); KConfigGroup group2(&cf2, "CopyToTest"); QString testVal = group2.readEntry("Type"); QCOMPARE(testVal, QString("Test")); QVERIFY(cf2.sync()); QVERIFY(QFile::exists(destination)); } // Check copied config file on disk KConfig cf3(TEST_SUBDIR "kconfigcopytotest"); KConfigGroup group3(&cf3, "CopyToTest"); QString testVal = group3.readEntry("Type"); QCOMPARE(testVal, QString("Test")); } void KConfigTest::testReparent() { KConfig cf(TEST_SUBDIR "kconfigtest"); const QString name(QStringLiteral("Enum Types")); KConfigGroup group = cf.group(name); const QMap originalMap = group.entryMap(); KConfigGroup parent = cf.group("Parent Group"); QVERIFY(!parent.hasGroup(name)); QVERIFY(group.entryMap() == originalMap); group.reparent(&parent); // see if it can be made a sub-group of another group QVERIFY(parent.hasGroup(name)); QCOMPARE(group.entryMap(), originalMap); group.reparent(&cf); // see if it can make it a top-level group again // QVERIFY(!parent.hasGroup(name)); QCOMPARE(group.entryMap(), originalMap); } static void ageTimeStamp(const QString &path, int nsec) { #ifdef Q_OS_UNIX QDateTime mtime = QFileInfo(path).lastModified().addSecs(-nsec); struct utimbuf utbuf; utbuf.actime = mtime.toTime_t(); utbuf.modtime = utbuf.actime; utime(QFile::encodeName(path), &utbuf); #else QTest::qSleep(nsec * 1000); #endif } void KConfigTest::testWriteOnSync() { QDateTime oldStamp, newStamp; KConfig sc(TEST_SUBDIR "kconfigtest", KConfig::IncludeGlobals); // Age the timestamp of global config file a few sec, and collect it. QString globFile = kdeGlobalsPath(); ageTimeStamp(globFile, 2); // age 2 sec oldStamp = QFileInfo(globFile).lastModified(); // Add a local entry and sync the config. // Should not rewrite the global config file. KConfigGroup cgLocal(&sc, "Locals"); cgLocal.writeEntry("someLocalString", "whatever"); QVERIFY(sc.sync()); // Verify that the timestamp of global config file didn't change. newStamp = QFileInfo(globFile).lastModified(); QCOMPARE(newStamp, oldStamp); // Age the timestamp of local config file a few sec, and collect it. QString locFile = testConfigDir() + "/kconfigtest"; ageTimeStamp(locFile, 2); // age 2 sec oldStamp = QFileInfo(locFile).lastModified(); // Add a global entry and sync the config. // Should not rewrite the local config file. KConfigGroup cgGlobal(&sc, "Globals"); cgGlobal.writeEntry("someGlobalString", "whatever", KConfig::Persistent | KConfig::Global); QVERIFY(sc.sync()); // Verify that the timestamp of local config file didn't change. newStamp = QFileInfo(locFile).lastModified(); QCOMPARE(newStamp, oldStamp); } void KConfigTest::testFailOnReadOnlyFileSync() { KConfig sc(TEST_SUBDIR "kconfigfailonreadonlytest"); KConfigGroup cgLocal(&sc, "Locals"); cgLocal.writeEntry("someLocalString", "whatever"); QVERIFY(cgLocal.sync()); QFile f(testConfigDir() + "kconfigfailonreadonlytest"); QVERIFY(f.exists()); QVERIFY(f.setPermissions(QFileDevice::ReadOwner)); #ifndef Q_OS_WIN if (::getuid() == 0) QSKIP("Root can write to read-only files"); #endif cgLocal.writeEntry("someLocalString", "whatever2"); QVERIFY(!cgLocal.sync()); QVERIFY(f.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)); QVERIFY(f.remove()); } void KConfigTest::testDirtyOnEqual() { QDateTime oldStamp, newStamp; KConfig sc(TEST_SUBDIR "kconfigtest"); // Initialize value KConfigGroup cgLocal(&sc, "random"); cgLocal.writeEntry("theKey", "whatever"); QVERIFY(sc.sync()); // Age the timestamp of local config file a few sec, and collect it. QString locFile = testConfigDir() + "/kconfigtest"; ageTimeStamp(locFile, 2); // age 2 sec oldStamp = QFileInfo(locFile).lastModified(); // Write exactly the same again cgLocal.writeEntry("theKey", "whatever"); // This should be a no-op QVERIFY(sc.sync()); // Verify that the timestamp of local config file didn't change. newStamp = QFileInfo(locFile).lastModified(); QCOMPARE(newStamp, oldStamp); } void KConfigTest::testDirtyOnEqualOverdo() { QByteArray val1("\0""one", 4); QByteArray val2("\0""two", 4); QByteArray defvalr; KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cgLocal(&sc, "random"); cgLocal.writeEntry("someKey", val1); QCOMPARE(cgLocal.readEntry("someKey", defvalr), val1); cgLocal.writeEntry("someKey", val2); QCOMPARE(cgLocal.readEntry("someKey", defvalr), val2); } void KConfigTest::testCreateDir() { // Test auto-creating the parent directory when needed (KConfigIniBackend::createEnclosing) QString kdehome = QDir::home().canonicalPath() + "/.kde-unit-test"; QString subdir = kdehome + "/newsubdir"; QString file = subdir + "/foo.desktop"; QFile::remove(file); QDir().rmdir(subdir); QVERIFY(!QDir().exists(subdir)); KDesktopFile desktopFile(file); desktopFile.desktopGroup().writeEntry("key", "value"); QVERIFY(desktopFile.sync()); QVERIFY(QFile::exists(file)); // Cleanup QFile::remove(file); QDir().rmdir(subdir); } void KConfigTest::testSyncOnExit() { // Often, the KGlobalPrivate global static's destructor ends up calling ~KConfig -> // KConfig::sync ... and if that code triggers KGlobal code again then things could crash. // So here's a test for modifying KSharedConfig::openConfig() and not syncing, the process exit will sync. KConfigGroup grp(KSharedConfig::openConfig(TEST_SUBDIR "syncOnExitRc"), "syncOnExit"); grp.writeEntry("key", "value"); } void KConfigTest::testSharedConfig() { // Can I use a KConfigGroup even after the KSharedConfigPtr goes out of scope? KConfigGroup myConfigGroup; { KSharedConfigPtr config = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest"); myConfigGroup = KConfigGroup(config, "Hello"); } QCOMPARE(myConfigGroup.readEntry("stringEntry1"), QString(STRINGENTRY1)); // Get the main config KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); KConfigGroup mainGroup(mainConfig, "Main"); QCOMPARE(mainGroup.readEntry("Key", QString()), QString("Value")); } void KConfigTest::testLocaleConfig() { // Initialize the testdata QDir dir; QString subdir = testConfigDir(); dir.mkpath(subdir); QString file = subdir + "/localized.test"; QFile::remove(file); QFile f(file); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream ts(&f); ts << "[Test_Wrong]\n"; ts << "foo[ca]=5\n"; ts << "foostring[ca]=nice\n"; ts << "foobool[ca]=true\n"; ts << "[Test_Right]\n"; ts << "foo=5\n"; ts << "foo[ca]=5\n"; ts << "foostring=primary\n"; ts << "foostring[ca]=nice\n"; ts << "foobool=primary\n"; ts << "foobool[ca]=true\n"; f.close(); // Load the testdata QVERIFY(QFile::exists(file)); KConfig config(file); config.setLocale(QStringLiteral("ca")); // This group has only localized values. That is not supported. The values // should be dropped on loading. KConfigGroup cg(&config, "Test_Wrong"); QEXPECT_FAIL("", "The localized values are not dropped", Continue); QVERIFY(!cg.hasKey("foo")); QEXPECT_FAIL("", "The localized values are not dropped", Continue); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "The localized values are not dropped", Continue); QVERIFY(!cg.hasKey("foobool")); // Now check the correct config group KConfigGroup cg2(&config, "Test_Right"); QCOMPARE(cg2.readEntry("foo"), QString("5")); QCOMPARE(cg2.readEntry("foo", 3), 5); QCOMPARE(cg2.readEntry("foostring"), QString("nice")); QCOMPARE(cg2.readEntry("foostring", "ugly"), QString("nice")); QCOMPARE(cg2.readEntry("foobool"), QString("true")); QCOMPARE(cg2.readEntry("foobool", false), true); // Clean up after the testcase QFile::remove(file); } void KConfigTest::testDeleteWhenLocalized() { // Initialize the testdata QDir dir; QString subdir = QDir::home().canonicalPath() + "/.kde-unit-test/"; dir.mkpath(subdir); QString file = subdir + "/localized_delete.test"; QFile::remove(file); QFile f(file); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream ts(&f); ts << "[Test4711]\n"; ts << "foo=3\n"; ts << "foo[ca]=5\n"; ts << "foo[de]=7\n"; ts << "foostring=ugly\n"; ts << "foostring[ca]=nice\n"; ts << "foostring[de]=schoen\n"; ts << "foobool=false\n"; ts << "foobool[ca]=true\n"; ts << "foobool[de]=true\n"; f.close(); // Load the testdata. We start in locale "ca". QVERIFY(QFile::exists(file)); KConfig config(file); config.setLocale(QStringLiteral("ca")); KConfigGroup cg(&config, "Test4711"); // Delete a value. Once with localized, once with Normal cg.deleteEntry("foostring", KConfigBase::Persistent | KConfigBase::Localized); cg.deleteEntry("foobool"); QVERIFY(config.sync()); // The value is now gone. The others are still there. Everything correct // here. QVERIFY(!cg.hasKey("foostring")); QVERIFY(!cg.hasKey("foobool")); QVERIFY(cg.hasKey("foo")); // The current state is: (Just return before this comment.) // [...] // foobool[ca]=true // foobool[de]=wahr // foostring=ugly // foostring[de]=schoen // Now switch the locale to "de" and repeat the checks. Results should be // the same. But they currently are not. The localized value are // independent of each other. All values are still there in "de". config.setLocale(QStringLiteral("de")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); QVERIFY(cg.hasKey("foo")); // Check where the wrong values come from. // We get the "de" value. QCOMPARE(cg.readEntry("foostring", "nothing"), QString("schoen")); // We get the "de" value. QCOMPARE(cg.readEntry("foobool", false), true); // Now switch the locale back "ca" and repeat the checks. Results are // again different. config.setLocale(QStringLiteral("ca")); // This line worked above. But now it fails. QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foostring")); // This line worked above too. QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); QVERIFY(cg.hasKey("foo")); // Check where the wrong values come from. // We get the primary value because the "ca" value was deleted. QCOMPARE(cg.readEntry("foostring", "nothing"), QString("ugly")); // We get the "ca" value. QCOMPARE(cg.readEntry("foobool", false), true); // Now test the deletion of a group. cg.deleteGroup(); QVERIFY(config.sync()); // Current state: [ca] and [de] entries left... oops. //qDebug() << readLinesFrom(file); // Bug: The group still exists [because of the localized entries]... QVERIFY(cg.exists()); QVERIFY(!cg.hasKey("foo")); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); // Now switch the locale to "de" and repeat the checks. All values // still here because only the primary values are deleted. config.setLocale(QStringLiteral("de")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foo")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); // Check where the wrong values come from. // We get the "de" value. QCOMPARE(cg.readEntry("foostring", "nothing"), QString("schoen")); // We get the "de" value. QCOMPARE(cg.readEntry("foobool", false), true); // We get the "de" value. QCOMPARE(cg.readEntry("foo", 0), 7); // Now switch the locale to "ca" and repeat the checks // "foostring" is now really gone because both the primary value and the // "ca" value are deleted. config.setLocale(QStringLiteral("ca")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foo")); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); // Check where the wrong values come from. // We get the "ca" value. QCOMPARE(cg.readEntry("foobool", false), true); // We get the "ca" value. QCOMPARE(cg.readEntry("foo", 0), 5); // Cleanup QFile::remove(file); } void KConfigTest::testKdeGlobals() { { KConfig glob(QStringLiteral("kdeglobals")); KConfigGroup general(&glob, "General"); general.writeEntry("testKG", "1"); QVERIFY(glob.sync()); } KConfig globRead(QStringLiteral("kdeglobals")); const KConfigGroup general(&globRead, "General"); QCOMPARE(general.readEntry("testKG"), QString("1")); // Check we wrote into kdeglobals const QList lines = readLines("kdeglobals"); QVERIFY(lines.contains("[General]\n")); QVERIFY(lines.contains("testKG=1\n")); // Writing using NoGlobals { KConfig glob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup general(&glob, "General"); general.writeEntry("testKG", "2"); QVERIFY(glob.sync()); } globRead.reparseConfiguration(); QCOMPARE(general.readEntry("testKG"), QString("2")); // Reading using NoGlobals { KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); const KConfigGroup generalNoGlob(&globReadNoGlob, "General"); QCOMPARE(generalNoGlob.readEntry("testKG"), QString("2")); } // TODO now use kconfigtest and writeEntry(,Global) -> should go into kdeglobals } void KConfigTest::testAnonymousConfig() { KConfig anonConfig(QString(), KConfig::SimpleConfig); KConfigGroup general(&anonConfig, "General"); QCOMPARE(general.readEntry("testKG"), QString()); // no kdeglobals merging general.writeEntry("Foo", "Bar"); QCOMPARE(general.readEntry("Foo"), QString("Bar")); } void KConfigTest::testQByteArrayUtf8() { QTemporaryFile file; QVERIFY(file.open()); KConfig config(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&config, "General"); QByteArray bytes(256, '\0'); for (int i = 0; i < 256; i++) { bytes[i] = i; } general.writeEntry("Utf8", bytes); config.sync(); file.flush(); file.close(); QFile readFile(file.fileName()); QVERIFY(readFile.open(QFile::ReadOnly)); #define VALUE "Utf8=\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff" const QByteArray fileBytes = readFile.readAll(); #ifndef Q_OS_WIN QCOMPARE(fileBytes, QByteArrayLiteral("[General]\n" VALUE "\n")); #else QCOMPARE(fileBytes, QByteArrayLiteral("[General]\r\n" VALUE "\r\n")); #endif #undef VALUE // check that reading works KConfig config2(file.fileName(), KConfig::SimpleConfig); KConfigGroup general2(&config2, "General"); QCOMPARE(bytes, general2.readEntry("Utf8", QByteArray())); } void KConfigTest::testQStringUtf8_data() { QTest::addColumn("data"); QTest::newRow("1") << QByteArray("Téléchargements\tTéléchargements"); QTest::newRow("2") << QByteArray("$¢ह€𐍈\t$¢ह€𐍈"); QTest::newRow("3") << QByteArray("\xc2\xe0\xa4\xf0\x90\x8d\t\\xc2\\xe0\\xa4\\xf0\\x90\\x8d"); // 2 byte overlong QTest::newRow("4") << QByteArray("\xc1\xbf\t\\xc1\\xbf"); // 3 byte overlong QTest::newRow("5") << QByteArray("\xe0\x9f\xbf\t\\xe0\\x9f\\xbf"); // 4 byte overlong QTest::newRow("6") << QByteArray("\xf0\x8f\xbf\xbf\t\\xf0\\x8f\\xbf\\xbf"); // outside unicode range QTest::newRow("7") << QByteArray("\xf4\x90\x80\x80\t\\xf4\\x90\\x80\\x80"); // just within range QTest::newRow("8") << QByteArray("\xc2\x80\t\xc2\x80"); QTest::newRow("9") << QByteArray("\xe0\xa0\x80\t\xe0\xa0\x80"); QTest::newRow("10") << QByteArray("\xf0\x90\x80\x80\t\xf0\x90\x80\x80"); QTest::newRow("11") << QByteArray("\xf4\x8f\xbf\xbf\t\xf4\x8f\xbf\xbf"); } void KConfigTest::testQStringUtf8() { QFETCH(QByteArray, data); const QList d = data.split('\t'); const QByteArray value = d[0]; const QByteArray serialized = d[1]; QTemporaryFile file; QVERIFY(file.open()); KConfig config(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&config, "General"); general.writeEntry("key", value); config.sync(); file.flush(); file.close(); QFile readFile(file.fileName()); QVERIFY(readFile.open(QFile::ReadOnly)); QByteArray fileBytes = readFile.readAll(); #ifdef Q_OS_WIN fileBytes.replace("\r\n", "\n"); #endif QCOMPARE(fileBytes, QByteArrayLiteral("[General]\nkey=") + serialized + QByteArrayLiteral("\n")); // check that reading works KConfig config2(file.fileName(), KConfig::SimpleConfig); KConfigGroup general2(&config2, "General"); QCOMPARE(value, general2.readEntry("key", QByteArray())); } void KConfigTest::testNewlines() { // test that kconfig always uses the native line endings QTemporaryFile file; QVERIFY(file.open()); KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&anonConfig, "General"); general.writeEntry("Foo", "Bar"); general.writeEntry("Bar", "Foo"); anonConfig.sync(); file.flush(); file.close(); QFile readFile(file.fileName()); QVERIFY(readFile.open(QFile::ReadOnly)); #ifndef Q_OS_WIN QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\nBar=Foo\nFoo=Bar\n")); #else QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\r\nBar=Foo\r\nFoo=Bar\r\n")); #endif } void KConfigTest::testXdgListEntry() { QTemporaryFile file; QVERIFY(file.open()); QTextStream out(&file); out << "[General]" << endl << "Key1=" << endl // empty list // emtpty entries << "Key2=;" << endl << "Key3=;;" << endl << "Key4=;;;" << endl << "Key5=\\;" << endl << "Key6=1;2\\;3;;" << endl; out.flush(); file.close(); KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); KConfigGroup grp = anonConfig.group("General"); QStringList invalidList; // use this as a default when an empty list is expected invalidList << QStringLiteral("Error! Default value read!"); QCOMPARE(grp.readXdgListEntry("Key1", invalidList), QStringList()); QCOMPARE(grp.readXdgListEntry("Key2", invalidList), QStringList() << QString()); QCOMPARE(grp.readXdgListEntry("Key3", invalidList), QStringList() << QString() << QString()); QCOMPARE(grp.readXdgListEntry("Key4", invalidList), QStringList()<< QString() << QString() << QString()); QCOMPARE(grp.readXdgListEntry("Key5", invalidList), QStringList() << ";"); QCOMPARE(grp.readXdgListEntry("Key6", invalidList), QStringList() << "1" << "2;3" << QString()); } #include #include // To find multithreading bugs: valgrind --tool=helgrind --track-lockorders=no ./kconfigtest testThreads void KConfigTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(6); QList > futures; // Run in parallel some tests that work on different config files, // otherwise unexpected things might indeed happen. futures << QtConcurrent::run(this, &KConfigTest::testAddConfigSources); futures << QtConcurrent::run(this, &KConfigTest::testSimple); futures << QtConcurrent::run(this, &KConfigTest::testDefaults); futures << QtConcurrent::run(this, &KConfigTest::testSharedConfig); futures << QtConcurrent::run(this, &KConfigTest::testSharedConfig); // QEXPECT_FAIL triggers race conditions, it should be fixed to use QThreadStorage... //futures << QtConcurrent::run(this, &KConfigTest::testDeleteWhenLocalized); //futures << QtConcurrent::run(this, &KConfigTest::testEntryMap); for (QFuture f : qAsConst(futures)) { // krazy:exclude=foreach f.waitForFinished(); } } void KConfigTest::testNotify() { #if !KCONFIG_USE_DBUS QSKIP("KConfig notification requires DBus"); #endif KConfig config(TEST_SUBDIR "kconfigtest"); auto myConfigGroup = KConfigGroup(&config, "TopLevelGroup"); //mimics a config in another process, which is watching for events auto remoteConfig = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest"); KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig); //some random config that shouldn't be changing when kconfigtest changes, only on kdeglobals auto otherRemoteConfig = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest2"); KConfigWatcher::Ptr otherWatcher = KConfigWatcher::create(otherRemoteConfig); QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged); QSignalSpy otherWatcherSpy(otherWatcher.data(), &KConfigWatcher::configChanged); //write entries in a group and subgroup myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify); auto subGroup = myConfigGroup.group("aSubGroup"); subGroup.writeEntry("entry1", "foo", KConfig::Persistent | KConfig::Notify); subGroup.writeEntry("entry2", "foo", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 2); std::sort(watcherSpy.begin(), watcherSpy.end(), [] (QList a, QList b) { return a[0].value().name() < b[0].value().name(); }); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entryA"})); QCOMPARE(watcherSpy[1][0].value().name(), QStringLiteral("aSubGroup")); QCOMPARE(watcherSpy[1][0].value().parent().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[1][1].value(), QByteArrayList({"entry1", "entry2"})); //delete an entry watcherSpy.clear(); myConfigGroup.deleteEntry("entryA", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entryA"})); + //revert to default an entry + watcherSpy.clear(); + myConfigGroup.revertToDefault("entryA", KConfig::Persistent | KConfig::Notify); + config.sync(); + watcherSpy.wait(); + QCOMPARE(watcherSpy.count(), 1); + QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); + QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entryA"})); + //deleting a group, should notify that every entry in that group has changed watcherSpy.clear(); myConfigGroup.deleteGroup("aSubGroup", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("aSubGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entry1", "entry2"})); //global write still triggers our notification watcherSpy.clear(); myConfigGroup.writeEntry("someGlobalEntry", "foo", KConfig::Persistent | KConfig::Notify | KConfig::Global); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"someGlobalEntry"})); //watching another file should have only triggered from the kdeglobals change QCOMPARE(otherWatcherSpy.count(), 1); QCOMPARE(otherWatcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(otherWatcherSpy[0][1].value(), QByteArrayList({"someGlobalEntry"})); } diff --git a/autotests/kentrymaptest.cpp b/autotests/kentrymaptest.cpp index 5e39df5..090e497 100644 --- a/autotests/kentrymaptest.cpp +++ b/autotests/kentrymaptest.cpp @@ -1,201 +1,201 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Thomas Braxton (kde.braxton@gmail.com) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kentrymaptest.h" #include #include "kconfigdata.h" const QByteArray group1("A Group"); const QByteArray key1("A Key"); const QByteArray key2("Another Key"); const QByteArray value1("A value"); const QByteArray value2("A different value"); QTEST_MAIN(KEntryMapTest) void KEntryMapTest::testKeyOrder() { const KEntryKey groupMarker(group1); const KEntryKey entry(group1, key1); const KEntryKey localized(group1, key1, true, false); const KEntryKey localizedDefault(group1, key1, true, true); const KEntryKey defaultEntry(group1, key1, false, true); // group marker should come before all entries QVERIFY(groupMarker < entry); QVERIFY(groupMarker < defaultEntry); QVERIFY(groupMarker < localized); QVERIFY(groupMarker < localizedDefault); // localized should come before entry QVERIFY(localized < entry); // localized-default should come after localized entry QVERIFY(localized < localizedDefault); // localized-default should come before non-localized entry QVERIFY(localizedDefault < entry); // default should come after entry QVERIFY(entry < defaultEntry); } void KEntryMapTest::testSimple() { KEntryMap map; map.setEntry(group1, key1, value1, EntryOptions()); QCOMPARE(map.size(), 2); // the group marker & 1 key map.setEntry(group1, key2, value2, EntryOptions()); QCOMPARE(map.size(), 3); // the group marker & 2 keys QVERIFY(map.findEntry(group1) != map.end()); QVERIFY(map.findEntry(group1.toLower()) == map.end()); QVERIFY(map.findEntry(group1, key1) != map.end()); QVERIFY(map.findEntry(group1, key1.toLower()) == map.end()); QVERIFY(map.findEntry(group1, key2) != map.end()); QVERIFY(map.findEntry(group1, key2.toUpper()) == map.end()); QByteArray found = map.findEntry(group1, key1)->mValue; QVERIFY(found == value1); QVERIFY(found != value2); found = map.findEntry(group1, key2)->mValue; QVERIFY(found != value1); QVERIFY(found == value2); } void KEntryMapTest::testDirty() { KEntryMap map; bool ret = map.setEntry(group1, key1, value1, EntryDefault); QCOMPARE(ret, true); ret = map.setEntry(group1, key1, value1, EntryDefault); QCOMPARE(ret, false); ret = map.setEntry(group1, key2, value2, EntryOptions()); QCOMPARE(ret, true); ret = map.setEntry(group1, key2, value2, EntryOptions()); QCOMPARE(ret, false); } void KEntryMapTest::testDefault() { KEntryMap map; map.setEntry(group1, key1, value1, EntryDefault); QCOMPARE(map.size(), 3); // group marker, default, entry map.setEntry(group1, key2, value2, EntryOptions()); QCOMPARE(map.size(), 4); // group marker, default1, entry1, entry2 const KEntryMap::ConstIterator defaultEntry(map.findEntry(group1, key1, SearchDefaults)); const KEntryMap::ConstIterator entry1(map.findEntry(group1, key1)); const KEntryMap::ConstIterator entry2(map.findEntry(group1, key2)); // default set for entry1 QVERIFY(defaultEntry != map.constEnd()); QCOMPARE(defaultEntry->mValue, entry1->mValue); // no default set for entry2 QVERIFY(map.findEntry(group1, key2, SearchDefaults) == map.end()); // change from default map.setEntry(group1, key1, value2, EntryOptions()); QVERIFY(defaultEntry->mValue != entry1->mValue); QVERIFY(entry1 != entry2); QCOMPARE(entry1->mValue, entry2->mValue); // revert entry1 - map.revertEntry(group1, key1); + map.revertEntry(group1, key1, EntryOptions()); QCOMPARE(defaultEntry->mValue, entry1->mValue); // revert entry2, no default --> should be marked as deleted - map.revertEntry(group1, key2); + map.revertEntry(group1, key2, EntryOptions()); QCOMPARE(entry2->mValue, QByteArray()); QVERIFY(entry2->bDirty); QVERIFY(entry2->bReverted); } void KEntryMapTest::testDelete() { KEntryMap map; map.setEntry(group1, key1, value1, EntryDefault); map.setEntry(group1, key2, value2, EntryDefault); QCOMPARE(map.size(), 5); map.setEntry(group1, key2, QByteArray(), EntryDeleted | EntryDirty); QCOMPARE(map.size(), 5); // entry should still be in map, so it can override merged entries later QCOMPARE(map.findEntry(group1, key2)->mValue, QByteArray()); } void KEntryMapTest::testGlobal() { KEntryMap map; map.setEntry(group1, key1, value1, EntryGlobal); QCOMPARE(map.findEntry(group1, key1)->bGlobal, true); // this should create a new key that is not "global" map.setEntry(group1, key1, value2, EntryOptions()); QVERIFY(!map.findEntry(group1, key1)->bGlobal); } void KEntryMapTest::testImmutable() { KEntryMap map; map.setEntry(group1, key1, value1, EntryImmutable); QCOMPARE(map.findEntry(group1, key1)->bImmutable, true); // verify the immutable bit was set map.setEntry(group1, key1, value2, EntryOptions()); QCOMPARE(map.findEntry(group1, key1)->mValue, value1); // verify the value didn't change map.clear(); map.setEntry(group1, QByteArray(), QByteArray(), EntryImmutable); QCOMPARE(map.findEntry(group1)->bImmutable, true); // verify the group is immutable map.setEntry(group1, key1, value1, EntryOptions()); // should be ignored since the group is immutable QVERIFY(map.findEntry(group1, key1) == map.end()); } void KEntryMapTest::testLocale() { const QByteArray translatedDefault("hola"); const QByteArray translated("bonjour"); const QByteArray untranslated("hello"); KEntryMap map; map.setEntry(group1, key1, untranslated, EntryDefault); QCOMPARE(map.findEntry(group1, key1)->mValue, untranslated); QCOMPARE(map.findEntry(group1, key1, SearchLocalized)->mValue, untranslated); // no localized value yet map.setEntry(group1, key1, translated, EntryLocalized); QCOMPARE(map.findEntry(group1, key1, SearchLocalized)->mValue, translated); // has localized value now QVERIFY(map.findEntry(group1, key1, SearchLocalized)->mValue != map.findEntry(group1, key1)->mValue); QCOMPARE(map.findEntry(group1, key1, SearchDefaults | SearchLocalized)->mValue, untranslated); // default should still be untranslated map.setEntry(group1, key1, translatedDefault, EntryDefault | EntryLocalized); QCOMPARE(map.findEntry(group1, key1, SearchLocalized)->mValue, translatedDefault); map.setEntry(group1, key1, translated, EntryLocalized); // set the translated entry to a different locale QCOMPARE(map.findEntry(group1, key1, SearchLocalized)->mValue, translated); } diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp index b78196d..e1b11ed 100644 --- a/src/core/kconfig.cpp +++ b/src/core/kconfig.cpp @@ -1,1053 +1,1055 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997-1999 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfig.h" #include "kconfig_p.h" #include "config-kconfig.h" #include #include #ifdef _MSC_VER static inline FILE *popen(const char *cmd, const char *mode) { return _popen(cmd, mode); } static inline int pclose(FILE *stream) { return _pclose(stream); } #else #include #endif #include "kconfigbackend_p.h" #include "kconfiggroup.h" #include #include #include #include #include #include #include #include #include #include #include #if KCONFIG_USE_DBUS #include #include #include #endif bool KConfigPrivate::mappingsRegistered = false; Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes. static QBasicMutex s_globalFilesMutex; Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals"))) #ifndef Q_OS_WIN static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive; #else static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive; #endif KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType) : openFlags(flags), resourceType(resourceType), mBackend(nullptr), bDynamicBackend(true), bDirty(false), bReadDefaults(false), bFileImmutable(false), bForceGlobal(false), bSuppressGlobal(false), configState(KConfigBase::NoAccess) { static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1); if (use_etc_kderc.load() < 0) { use_etc_kderc.store( !qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests } if (use_etc_kderc.load()) { etc_kderc = #ifdef Q_OS_WIN QFile::decodeName(qgetenv("WINDIR") + "/kde5rc"); #else QStringLiteral("/etc/kde5rc"); #endif if (!QFileInfo(etc_kderc).isReadable()) { use_etc_kderc.store(false); etc_kderc.clear(); } } // if (!mappingsRegistered) { // KEntryMap tmp; // if (!etc_kderc.isEmpty()) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(etc_kderc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults); // } // const QString kde5rc(QDir::home().filePath(".kde5rc")); // if (KStandardDirs::checkAccess(kde5rc, R_OK)) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(kde5rc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions()); // } // KConfigBackend::registerMappings(tmp); // mappingsRegistered = true; // } setLocale(QLocale().name()); } bool KConfigPrivate::lockLocal() { if (mBackend) { return mBackend->lock(); } // anonymous object - pretend we locked it return true; } void KConfigPrivate::copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const { KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap; const int len = source.length(); const bool sameName = (destination == source); // we keep this bool outside the foreach loop so that if // the group is empty, we don't end up marking the other config // as dirty erroneously bool dirtied = false; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const QByteArray &group = entryMapIt.key().mGroup; if (!group.startsWith(source)) { // nothing to do continue; } // don't copy groups that start with the same prefix, but are not sub-groups if (group.length() > len && group[len] != '\x1d') { continue; } KEntryKey newKey = entryMapIt.key(); if (flags & KConfigBase::Localized) { newKey.bLocal = true; } if (!sameName) { newKey.mGroup.replace(0, len, destination); } KEntry entry = entryMap[ entryMapIt.key() ]; dirtied = entry.bDirty = flags & KConfigBase::Persistent; if (flags & KConfigBase::Global) { entry.bGlobal = true; } otherMap[newKey] = entry; } if (dirtied) { otherGroup->config()->d_ptr->bDirty = true; } } QString KConfigPrivate::expandString(const QString &value) { QString aValue = value; // check for environment variables and make necessary translations int nDollarPos = aValue.indexOf(QLatin1Char('$')); while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) { // there is at least one $ if (aValue[nDollarPos + 1] == QLatin1Char('(')) { int nEndPos = nDollarPos + 1; // the next character is not $ while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char(')'))) { nEndPos++; } nEndPos++; QString cmd = aValue.mid(nDollarPos + 2, nEndPos - nDollarPos - 3); QString result; // FIXME: wince does not have pipes #ifndef _WIN32_WCE FILE *fs = popen(QFile::encodeName(cmd).data(), "r"); if (fs) { QTextStream ts(fs, QIODevice::ReadOnly); result = ts.readAll().trimmed(); pclose(fs); } #endif aValue.replace(nDollarPos, nEndPos - nDollarPos, result); nDollarPos += result.length(); } else if (aValue[nDollarPos + 1] != QLatin1Char('$')) { int nEndPos = nDollarPos + 1; // the next character is not $ QStringRef aVarName; if (aValue[nEndPos] == QLatin1Char('{')) { while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) { nEndPos++; } nEndPos++; aVarName = aValue.midRef(nDollarPos + 2, nEndPos - nDollarPos - 3); } else { while (nEndPos <= aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) { nEndPos++; } aVarName = aValue.midRef(nDollarPos + 1, nEndPos - nDollarPos - 1); } QString env; if (!aVarName.isEmpty()) { #ifdef Q_OS_WIN if (aVarName == QLatin1String("HOME")) { env = QDir::homePath(); } else #endif { QByteArray pEnv = qgetenv(aVarName.toLatin1().constData()); if (!pEnv.isEmpty()) { env = QString::fromLocal8Bit(pEnv.constData()); } else { if (aVarName == QStringLiteral("QT_DATA_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); } else if (aVarName == QStringLiteral("QT_CONFIG_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); } else if (aVarName == QStringLiteral("QT_CACHE_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); } } } aValue.replace(nDollarPos, nEndPos - nDollarPos, env); nDollarPos += env.length(); } else { aValue.remove(nDollarPos, nEndPos - nDollarPos); } } else { // remove one of the dollar signs aValue.remove(nDollarPos, 1); nDollarPos++; } nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos); } return aValue; } KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(mode, resourceType)) { d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(SimpleConfig, resourceType)) { d_ptr->mBackend = KConfigBackend::create(file, backend); d_ptr->bDynamicBackend = false; d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(KConfigPrivate &d) : d_ptr(&d) { } KConfig::~KConfig() { Q_D(KConfig); if (d->bDirty && (d->mBackend && d->mBackend->ref.load() == 1)) { sync(); } delete d; } QStringList KConfig::groupList() const { Q_D(const KConfig); QSet groups; for (KEntryMap::ConstIterator entryMapIt(d->entryMap.constBegin()); entryMapIt != d->entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); const QByteArray group = key.mGroup; if (key.mKey.isNull() && !group.isEmpty() && group != "" && group != "$Version") { const QString groupname = QString::fromUtf8(group); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } QStringList KConfigPrivate::groupList(const QByteArray &group) const { QByteArray theGroup = group + '\x1d'; QSet groups; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && key.mGroup.startsWith(theGroup)) { const QString groupname = QString::fromUtf8(key.mGroup.mid(theGroup.length())); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } static bool isGroupOrSubGroupMatch(const QByteArray &potentialGroup, const QByteArray &group) { if (!potentialGroup.startsWith(group)) { return false; } return potentialGroup.length() == group.length() || potentialGroup[group.length()] == '\x1d'; } // List all sub groups, including subsubgroups QSet KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const { QSet groups; for (KEntryMap::const_iterator entryMapIt = entryMap.begin(); entryMapIt != entryMap.end(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && isGroupOrSubGroupMatch(key.mGroup, parentGroup)) { groups << key.mGroup; } } return groups; } bool KConfigPrivate::hasNonDeletedEntries(const QByteArray &group) const { for (KEntryMap::const_iterator it = entryMap.begin(); it != entryMap.end(); ++it) { const KEntryKey &key = it.key(); // Check for any non-deleted entry if (isGroupOrSubGroupMatch(key.mGroup, group) && !key.mKey.isNull() && !it->bDeleted) { return true; } } return false; } QStringList KConfigPrivate::keyListImpl(const QByteArray &theGroup) const { QStringList keys; const KEntryMapConstIterator theEnd = entryMap.constEnd(); KEntryMapConstIterator it = entryMap.findEntry(theGroup); if (it != theEnd) { ++it; // advance past the special group entry marker QSet tmp; for (; it != theEnd && it.key().mGroup == theGroup; ++it) { const KEntryKey &key = it.key(); if (!key.mKey.isNull() && !it->bDeleted) { tmp << QString::fromUtf8(key.mKey); } } keys = tmp.toList(); } return keys; } QStringList KConfig::keyList(const QString &aGroup) const { Q_D(const KConfig); const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); return d->keyListImpl(theGroup); } QMap KConfig::entryMap(const QString &aGroup) const { Q_D(const KConfig); QMap theMap; const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); const KEntryMapConstIterator theEnd = d->entryMap.constEnd(); KEntryMapConstIterator it = d->entryMap.findEntry(theGroup, nullptr, nullptr); if (it != theEnd) { ++it; // advance past the special group entry marker for (; it != theEnd && it.key().mGroup == theGroup; ++it) { // leave the default values and deleted entries out if (!it->bDeleted && !it.key().bDefault) { const QString key = QString::fromUtf8(it.key().mKey.constData()); // the localized entry should come first, so don't overwrite it // with the non-localized entry if (!theMap.contains(key)) { if (it->bExpand) { theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData()))); } else { theMap.insert(key, QString::fromUtf8(it->mValue.constData())); } } } } } return theMap; } bool KConfig::sync() { Q_D(KConfig); if (isImmutable() || name().isEmpty()) { // can't write to an immutable or anonymous file. return false; } QHash notifyGroupsLocal; QHash notifyGroupsGlobal; if (d->bDirty && d->mBackend) { const QByteArray utf8Locale(locale().toUtf8()); // Create the containing dir, maybe it wasn't there d->mBackend->createEnclosing(); // lock the local file if (d->configState == ReadWrite && !d->lockLocal()) { qWarning() << "couldn't lock local file"; return false; } // Rewrite global/local config only if there is a dirty entry in it. bool writeGlobals = false; bool writeLocals = false; for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) { auto e = it.value(); if (e.bDirty) { if (e.bGlobal) { writeGlobals = true; if (e.bNotify) { notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; } } else { writeLocals = true; if (e.bNotify) { notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; } } } } d->bDirty = false; // will revert to true if a config write fails if (d->wantGlobals() && writeGlobals) { QExplicitlySharedDataPointer tmp = KConfigBackend::create(*sGlobalFileName); if (d->configState == ReadWrite && !tmp->lock()) { qWarning() << "couldn't lock global file"; //unlock the local config if we're returning early if (d->mBackend->isLocked()) { d->mBackend->unlock(); } d->bDirty = true; return false; } if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) { d->bDirty = true; } if (tmp->isLocked()) { tmp->unlock(); } } if (writeLocals) { if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) { d->bDirty = true; } } if (d->mBackend->isLocked()) { d->mBackend->unlock(); } } if (!notifyGroupsLocal.isEmpty()) { d->notifyClients(notifyGroupsLocal, QStringLiteral("/") + name()); } if (!notifyGroupsGlobal.isEmpty()) { d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals")); } return !d->bDirty; } void KConfigPrivate::notifyClients(const QHash &changes, const QString &path) { #if KCONFIG_USE_DBUS qDBusRegisterMetaType(); qDBusRegisterMetaType>(); QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged")); message.setArguments({QVariant::fromValue(changes)}); QDBusConnection::sessionBus().send(message); #else Q_UNUSED(changes) Q_UNUSED(path) #endif } void KConfig::markAsClean() { Q_D(KConfig); d->bDirty = false; // clear any dirty flags that entries might have set const KEntryMapIterator theEnd = d->entryMap.end(); for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) { it->bDirty = false; it->bNotify = false; } } bool KConfig::isDirty() const { Q_D(const KConfig); return d->bDirty; } void KConfig::checkUpdate(const QString &id, const QString &updateFile) { const KConfigGroup cg(this, "$Version"); const QString cfg_id = updateFile + QLatin1Char(':') + id; const QStringList ids = cg.readEntry("update_info", QStringList()); if (!ids.contains(cfg_id)) { QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList() << QStringLiteral("--check") << updateFile); reparseConfiguration(); } } KConfig *KConfig::copyTo(const QString &file, KConfig *config) const { Q_D(const KConfig); if (!config) { config = new KConfig(QString(), SimpleConfig, d->resourceType); } config->d_func()->changeFileName(file); config->d_func()->entryMap = d->entryMap; config->d_func()->bFileImmutable = false; const KEntryMapIterator theEnd = config->d_func()->entryMap.end(); for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) { it->bDirty = true; } config->d_ptr->bDirty = true; return config; } QString KConfig::name() const { Q_D(const KConfig); return d->fileName; } KConfig::OpenFlags KConfig::openFlags() const { Q_D(const KConfig); return d->openFlags; } struct KConfigStaticData { QString globalMainConfigName; // Keep a copy so we can use it in global dtors, after qApp is gone QStringList appArgs; }; Q_GLOBAL_STATIC(KConfigStaticData, globalData) void KConfig::setMainConfigName(const QString &str) { globalData()->globalMainConfigName = str; } QString KConfig::mainConfigName() { KConfigStaticData* data = globalData(); if (data->appArgs.isEmpty()) data->appArgs = QCoreApplication::arguments(); // --config on the command line overrides everything else const QStringList args = data->appArgs; for (int i = 1; i < args.count(); ++i) { if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) { return args.at(i + 1); } } const QString globalName = data->globalMainConfigName; if (!globalName.isEmpty()) { return globalName; } QString appName = QCoreApplication::applicationName(); return appName + QLatin1String("rc"); } void KConfigPrivate::changeFileName(const QString &name) { fileName = name; QString file; if (name.isEmpty()) { if (wantDefaults()) { // accessing default app-specific config "appnamerc" fileName = KConfig::mainConfigName(); file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere? resourceType = QStandardPaths::GenericConfigLocation; fileName = QStringLiteral("kdeglobals"); file = *sGlobalFileName; } else { // anonymous config openFlags = KConfig::SimpleConfig; return; } } else if (QDir::isAbsolutePath(fileName)) { fileName = QFileInfo(fileName).canonicalFilePath(); if (fileName.isEmpty()) { // file doesn't exist (yet) fileName = name; } file = fileName; } else { file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } Q_ASSERT(!file.isEmpty()); bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0); if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend mBackend = KConfigBackend::create(file); } else { mBackend->setFilePath(file); } configState = mBackend->accessMode(); } void KConfig::reparseConfiguration() { Q_D(KConfig); if (d->fileName.isEmpty()) { return; } // Don't lose pending changes if (!d->isReadOnly() && d->bDirty) { sync(); } d->entryMap.clear(); d->bFileImmutable = false; { QMutexLocker locker(&s_globalFilesMutex); s_globalFiles()->clear(); } // Parse all desired files from the least to the most specific. if (d->wantGlobals()) { d->parseGlobalFiles(); } d->parseConfigFiles(); } QStringList KConfigPrivate::getGlobalFiles() const { QMutexLocker locker(&s_globalFilesMutex); if (s_globalFiles()->isEmpty()) { const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals")); const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals")); const bool useEtcKderc = !etc_kderc.isEmpty(); s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0)); for (const QString &dir1 : paths1) { s_globalFiles()->push_front(dir1); } for (const QString &dir2 : paths2) { s_globalFiles()->push_front(dir2); } if (useEtcKderc) { s_globalFiles()->push_front(etc_kderc); } } return *s_globalFiles(); } void KConfigPrivate::parseGlobalFiles() { const QStringList globalFiles = getGlobalFiles(); // qDebug() << "parsing global files" << globalFiles; // TODO: can we cache the values in etc_kderc / other global files // on a per-application basis? const QByteArray utf8Locale = locale.toUtf8(); for (const QString &file : globalFiles) { KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions; if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) parseOpts |= KConfigBackend::ParseDefaults; QExplicitlySharedDataPointer backend = KConfigBackend::create(file); if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) { break; } } } void KConfigPrivate::parseConfigFiles() { // can only read the file if there is a backend and a file name if (mBackend && !fileName.isEmpty()) { bFileImmutable = false; QList files; if (wantDefaults()) { if (bSuppressGlobal) { files = getGlobalFiles(); } else { if (QDir::isAbsolutePath(fileName)) { const QString canonicalFile = QFileInfo(fileName).canonicalFilePath(); if (!canonicalFile.isEmpty()) { // empty if it doesn't exist files << canonicalFile; } } else { const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName); for (const QString &f : localFilesPath) { files.prepend(QFileInfo(f).canonicalFilePath()); } // allow fallback to config files bundled in resources const QString resourceFile(QStringLiteral(":/kconfig/") + fileName); if (QFile::exists(resourceFile)) { files.prepend(resourceFile); } } } } else { files << mBackend->filePath(); } if (!isSimple()) { files = extraFiles.toList() + files; } // qDebug() << "parsing local files" << files; const QByteArray utf8Locale = locale.toUtf8(); for (const QString &file : qAsConst(files)) { if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) { switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) { case KConfigBackend::ParseOk: break; case KConfigBackend::ParseImmutable: bFileImmutable = true; break; case KConfigBackend::ParseOpenError: configState = KConfigBase::NoAccess; break; } } else { QExplicitlySharedDataPointer backend = KConfigBackend::create(file); bFileImmutable = (backend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions) == KConfigBackend::ParseImmutable); } if (bFileImmutable) { break; } } } } KConfig::AccessMode KConfig::accessMode() const { Q_D(const KConfig); return d->configState; } void KConfig::addConfigSources(const QStringList &files) { Q_D(KConfig); for (const QString &file : files) { d->extraFiles.push(file); } if (!files.isEmpty()) { reparseConfiguration(); } } QStringList KConfig::additionalConfigSources() const { Q_D(const KConfig); return d->extraFiles.toList(); } QString KConfig::locale() const { Q_D(const KConfig); return d->locale; } bool KConfigPrivate::setLocale(const QString &aLocale) { if (aLocale != locale) { locale = aLocale; return true; } return false; } bool KConfig::setLocale(const QString &locale) { Q_D(KConfig); if (d->setLocale(locale)) { reparseConfiguration(); return true; } return false; } void KConfig::setReadDefaults(bool b) { Q_D(KConfig); d->bReadDefaults = b; } bool KConfig::readDefaults() const { Q_D(const KConfig); return d->bReadDefaults; } bool KConfig::isImmutable() const { Q_D(const KConfig); return d->bFileImmutable; } bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const { Q_D(const KConfig); return isImmutable() || d->entryMap.getEntryOption(aGroup, nullptr, nullptr, KEntryMap::EntryImmutable); } #ifndef KDE_NO_DEPRECATED void KConfig::setForceGlobal(bool b) { Q_D(KConfig); d->bForceGlobal = b; } #endif #ifndef KDE_NO_DEPRECATED bool KConfig::forceGlobal() const { Q_D(const KConfig); return d->bForceGlobal; } #endif KConfigGroup KConfig::groupImpl(const QByteArray &group) { return KConfigGroup(this, group.constData()); } const KConfigGroup KConfig::groupImpl(const QByteArray &group) const { return KConfigGroup(this, group.constData()); } KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags) { KEntryMap::EntryOptions options = nullptr; if (flags & KConfig::Persistent) { options |= KEntryMap::EntryDirty; } if (flags & KConfig::Global) { options |= KEntryMap::EntryGlobal; } if (flags & KConfig::Localized) { options |= KEntryMap::EntryLocalized; } if (flags.testFlag(KConfig::Notify)) { options |= KEntryMap::EntryNotify; } return options; } void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags) { Q_D(KConfig); KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted; const QSet groups = d->allSubGroups(aGroup); for (const QByteArray &group : groups) { const QStringList keys = d->keyListImpl(group); for (const QString &_key : keys) { const QByteArray &key = _key.toUtf8(); if (d->canWriteEntry(group, key.constData())) { d->entryMap.setEntry(group, key, QByteArray(), options); d->bDirty = true; } } } } bool KConfig::isConfigWritable(bool warnUser) { Q_D(KConfig); bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false); if (warnUser && !allWritable) { QString errorMsg; if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately errorMsg = d->mBackend->nonWritableErrorMessage(); } // Note: We don't ask the user if we should not ask this question again because we can't save the answer. errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator."); QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog")); if (!cmdToExec.isEmpty()) { QProcess::execute(cmdToExec, QStringList() << QStringLiteral("--title") << QCoreApplication::applicationName() << QStringLiteral("--msgbox") << errorMsg); } } d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status return allWritable; } bool KConfig::hasGroupImpl(const QByteArray &aGroup) const { Q_D(const KConfig); // No need to look for the actual group entry anymore, or for subgroups: // a group exists if it contains any non-deleted entry. return d->hasNonDeletedEntries(aGroup); } bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const { if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) { return isDefault; } return true; } void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand) { KEntryMap::EntryOptions options = convertToOptions(flags); if (bForceGlobal) { options |= KEntryMap::EntryGlobal; } if (expand) { options |= KEntryMap::EntryExpansion; } if (value.isNull()) { // deleting entry options |= KEntryMap::EntryDeleted; } bool dirtied = entryMap.setEntry(group, key, value, options); if (dirtied && (flags & KConfigBase::Persistent)) { bDirty = true; } } -void KConfigPrivate::revertEntry(const QByteArray &group, const char *key) +void KConfigPrivate::revertEntry(const QByteArray &group, const char *key, KConfigBase::WriteConfigFlags flags) { - bool dirtied = entryMap.revertEntry(group, key); + KEntryMap::EntryOptions options = convertToOptions(flags); + + bool dirtied = entryMap.revertEntry(group, key, options); if (dirtied) { bDirty = true; } } QByteArray KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } const KEntryMapConstIterator it = entryMap.findEntry(group, key, flags); if (it == entryMap.constEnd()) { return QByteArray(); } return it->mValue; } QString KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } return entryMap.getEntry(group, key, QString(), flags, expand); } QStandardPaths::StandardLocation KConfig::locationType() const { Q_D(const KConfig); return d->resourceType; } void KConfig::virtual_hook(int /*id*/, void * /*data*/) { /* nothing */ } diff --git a/src/core/kconfig_p.h b/src/core/kconfig_p.h index 7433ab2..ac4cd63 100644 --- a/src/core/kconfig_p.h +++ b/src/core/kconfig_p.h @@ -1,121 +1,122 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 2001 Waldo Bastian Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCONFIG_P_H #define KCONFIG_P_H #include "kconfigdata.h" #include "kconfigbackend_p.h" #include "kconfiggroup.h" #include #include #include #include class KConfigPrivate { friend class KConfig; public: KConfig::OpenFlags openFlags; QStandardPaths::StandardLocation resourceType; void changeFileName(const QString &fileName); // functions for KConfigGroup bool canWriteEntry(const QByteArray &group, const char *key, bool isDefault = false) const; QString lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const; QByteArray lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const; void putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand = false); - void revertEntry(const QByteArray &group, const char *key); + void revertEntry(const QByteArray &group, const char *key, + KConfigBase::WriteConfigFlags flags); QStringList groupList(const QByteArray &group) const; // copies the entries from @p source to @p otherGroup changing all occurrences // of @p source with @p destination void copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const; QStringList keyListImpl(const QByteArray &theGroup) const; QSet allSubGroups(const QByteArray &parentGroup) const; bool hasNonDeletedEntries(const QByteArray &group) const; void notifyClients(const QHash &changes, const QString &path); static QString expandString(const QString &value); protected: QExplicitlySharedDataPointer mBackend; KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation type); virtual ~KConfigPrivate() { } bool bDynamicBackend: 1; // do we own the backend? private: bool bDirty: 1; bool bReadDefaults: 1; bool bFileImmutable: 1; bool bForceGlobal: 1; bool bSuppressGlobal: 1; static bool mappingsRegistered; KEntryMap entryMap; QString backendType; QStack extraFiles; QString locale; QString fileName; QString etc_kderc; KConfigBase::AccessMode configState; bool wantGlobals() const { return openFlags & KConfig::IncludeGlobals && !bSuppressGlobal; } bool wantDefaults() const { return openFlags & KConfig::CascadeConfig; } bool isSimple() const { return openFlags == KConfig::SimpleConfig; } bool isReadOnly() const { return configState == KConfig::ReadOnly; } bool setLocale(const QString &aLocale); QStringList getGlobalFiles() const; void parseGlobalFiles(); void parseConfigFiles(); void initCustomized(KConfig *); bool lockLocal(); }; #endif // KCONFIG_P_H diff --git a/src/core/kconfigdata.cpp b/src/core/kconfigdata.cpp index d80b7d0..e1f4ee7 100644 --- a/src/core/kconfigdata.cpp +++ b/src/core/kconfigdata.cpp @@ -1,340 +1,341 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999-2000 Preston Brown Copyright (C) 1996-2000 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include QDebug operator<<(QDebug dbg, const KEntryKey &key) { dbg.nospace() << "[" << key.mGroup << ", " << key.mKey << (key.bLocal ? " localized" : "") << (key.bDefault ? " default" : "") << (key.bRaw ? " raw" : "") << "]"; return dbg.space(); } QDebug operator<<(QDebug dbg, const KEntry &entry) { dbg.nospace() << "[" << entry.mValue << (entry.bDirty ? " dirty" : "") << (entry.bGlobal ? " global" : "") << (entry.bImmutable ? " immutable" : "") << (entry.bDeleted ? " deleted" : "") << (entry.bReverted ? " reverted" : "") << (entry.bExpand ? " expand" : "") << "]"; return dbg.space(); } QMap< KEntryKey, KEntry >::Iterator KEntryMap::findExactEntry(const QByteArray &group, const QByteArray &key, KEntryMap::SearchFlags flags) { KEntryKey theKey(group, key, bool(flags & SearchLocalized), bool(flags & SearchDefaults)); return find(theKey); } QMap< KEntryKey, KEntry >::Iterator KEntryMap::findEntry(const QByteArray &group, const QByteArray &key, KEntryMap::SearchFlags flags) { KEntryKey theKey(group, key, false, bool(flags & SearchDefaults)); // try the localized key first if (flags & SearchLocalized) { theKey.bLocal = true; Iterator it = find(theKey); if (it != end()) { return it; } theKey.bLocal = false; } return find(theKey); } QMap< KEntryKey, KEntry >::ConstIterator KEntryMap::findEntry(const QByteArray &group, const QByteArray &key, KEntryMap::SearchFlags flags) const { KEntryKey theKey(group, key, false, bool(flags & SearchDefaults)); // try the localized key first if (flags & SearchLocalized) { theKey.bLocal = true; ConstIterator it = find(theKey); if (it != constEnd()) { return it; } theKey.bLocal = false; } return find(theKey); } bool KEntryMap::setEntry(const QByteArray &group, const QByteArray &key, const QByteArray &value, KEntryMap::EntryOptions options) { KEntryKey k; KEntry e; bool newKey = false; const Iterator it = findExactEntry(group, key, SearchFlags(options >> 16)); if (key.isEmpty()) { // inserting a group marker k.mGroup = group; e.bImmutable = (options & EntryImmutable); if (options & EntryDeleted) { qWarning("Internal KConfig error: cannot mark groups as deleted"); } if (it == end()) { insert(k, e); return true; } else if (it.value() == e) { return false; } it.value() = e; return true; } if (it != end()) { if (it->bImmutable) { return false; // we cannot change this entry. Inherits group immutability. } k = it.key(); e = *it; //qDebug() << "found existing entry for key" << k; } else { // make sure the group marker is in the map KEntryMap const *that = this; ConstIterator cit = that->findEntry(group); if (cit == constEnd()) { insert(KEntryKey(group), KEntry()); } else if (cit->bImmutable) { return false; // this group is immutable, so we cannot change this entry. } k = KEntryKey(group, key); newKey = true; } // set these here, since we may be changing the type of key from the one we found k.bLocal = (options & EntryLocalized); k.bDefault = (options & EntryDefault); k.bRaw = (options & EntryRawKey); e.mValue = value; e.bDirty = e.bDirty || (options & EntryDirty); e.bNotify = e.bNotify || (options & EntryNotify); e.bGlobal = (options & EntryGlobal); //we can't use || here, because changes to entries in //kdeglobals would be written to kdeglobals instead //of the local config file, regardless of the globals flag e.bImmutable = e.bImmutable || (options & EntryImmutable); if (value.isNull()) { e.bDeleted = e.bDeleted || (options & EntryDeleted); } else { e.bDeleted = false; // setting a value to a previously deleted entry } e.bExpand = (options & EntryExpansion); e.bReverted = false; if (options & EntryLocalized) { e.bLocalizedCountry = (options & EntryLocalizedCountry); } else { e.bLocalizedCountry = false; } if (newKey) { //qDebug() << "inserting" << k << "=" << value; insert(k, e); if (k.bDefault) { k.bDefault = false; //qDebug() << "also inserting" << k << "=" << value; insert(k, e); } // TODO check for presence of unlocalized key return true; } else { // KEntry e2 = it.value(); if (options & EntryLocalized) { // fast exit checks for cases where the existing entry is more specific const KEntry &e2 = it.value(); if (e2.bLocalizedCountry && !e.bLocalizedCountry) { // lang_COUNTRY > lang return false; } } if (it.value() != e) { //qDebug() << "changing" << k << "from" << e.mValue << "to" << value; it.value() = e; if (k.bDefault) { KEntryKey nonDefaultKey(k); nonDefaultKey.bDefault = false; insert(nonDefaultKey, e); } if (!(options & EntryLocalized)) { KEntryKey theKey(group, key, true, false); //qDebug() << "non-localized entry, remove localized one:" << theKey; remove(theKey); if (k.bDefault) { theKey.bDefault = true; remove(theKey); } } return true; } else { //qDebug() << k << "was already set to" << e.mValue; if (!(options & EntryLocalized)) { //qDebug() << "unchanged non-localized entry, remove localized one."; KEntryKey theKey(group, key, true, false); bool ret = false; Iterator cit = find(theKey); if (cit != end()) { erase(cit); ret = true; } if (k.bDefault) { theKey.bDefault = true; Iterator cit = find(theKey); if (cit != end()) { erase(cit); return true; } } return ret; } //qDebug() << "localized entry, unchanged, return false"; // When we are writing a default, we know that the non- // default is the same as the default, so we can simply // use the same branch. return false; } } } QString KEntryMap::getEntry(const QByteArray &group, const QByteArray &key, const QString &defaultValue, KEntryMap::SearchFlags flags, bool *expand) const { const ConstIterator it = findEntry(group, key, flags); QString theValue = defaultValue; if (it != constEnd() && !it->bDeleted) { if (!it->mValue.isNull()) { const QByteArray data = it->mValue; theValue = QString::fromUtf8(data.constData(), data.length()); if (expand) { *expand = it->bExpand; } } } return theValue; } bool KEntryMap::hasEntry(const QByteArray &group, const QByteArray &key, KEntryMap::SearchFlags flags) const { const ConstIterator it = findEntry(group, key, flags); if (it == constEnd()) { return false; } if (it->bDeleted) { return false; } if (key.isNull()) { // looking for group marker return it->mValue.isNull(); } // if it->bReverted, we'll just return true; the real answer depends on lookup up with SearchDefaults, though. return true; } bool KEntryMap::getEntryOption(const QMap< KEntryKey, KEntry >::ConstIterator &it, KEntryMap::EntryOption option) const { if (it != constEnd()) { switch (option) { case EntryDirty: return it->bDirty; case EntryLocalized: return it.key().bLocal; case EntryGlobal: return it->bGlobal; case EntryImmutable: return it->bImmutable; case EntryDeleted: return it->bDeleted; case EntryExpansion: return it->bExpand; case EntryNotify: return it->bNotify; default: break; // fall through } } return false; } void KEntryMap::setEntryOption(QMap< KEntryKey, KEntry >::Iterator it, KEntryMap::EntryOption option, bool bf) { if (it != end()) { switch (option) { case EntryDirty: it->bDirty = bf; break; case EntryGlobal: it->bGlobal = bf; break; case EntryImmutable: it->bImmutable = bf; break; case EntryDeleted: it->bDeleted = bf; break; case EntryExpansion: it->bExpand = bf; break; case EntryNotify: it->bNotify = bf; break; default: break; // fall through } } } -bool KEntryMap::revertEntry(const QByteArray &group, const QByteArray &key, KEntryMap::SearchFlags flags) +bool KEntryMap::revertEntry(const QByteArray &group, const QByteArray &key, KEntryMap::EntryOptions options, KEntryMap::SearchFlags flags) { Q_ASSERT((flags & KEntryMap::SearchDefaults) == 0); Iterator entry = findEntry(group, key, flags); if (entry != end()) { //qDebug() << "reverting" << entry.key() << " = " << entry->mValue; if (entry->bReverted) { // already done before return false; } KEntryKey defaultKey(entry.key()); defaultKey.bDefault = true; //qDebug() << "looking up default entry with key=" << defaultKey; const ConstIterator defaultEntry = constFind(defaultKey); if (defaultEntry != constEnd()) { Q_ASSERT(defaultEntry.key().bDefault); //qDebug() << "found, update entry"; *entry = *defaultEntry; // copy default value, for subsequent lookups } else { entry->mValue = QByteArray(); } + entry->bNotify = entry->bNotify || (options & EntryNotify); entry->bDirty = true; entry->bReverted = true; // skip it when writing out to disk //qDebug() << "Here's what we have now:" << *this; return true; } return false; } diff --git a/src/core/kconfigdata.h b/src/core/kconfigdata.h index 2a5c643..2d09503 100644 --- a/src/core/kconfigdata.h +++ b/src/core/kconfigdata.h @@ -1,247 +1,247 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999-2000 Preston Brown Copyright (C) 1996-2000 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCONFIGDATA_H #define KCONFIGDATA_H #include #include #include #include /** * map/dict/list config node entry. * @internal */ struct KEntry { /** Constructor. @internal */ KEntry() : mValue(), bDirty(false), bGlobal(false), bImmutable(false), bDeleted(false), bExpand(false), bReverted(false), bLocalizedCountry(false), bNotify(false) {} /** @internal */ QByteArray mValue; /** * Must the entry be written back to disk? */ bool bDirty : 1; /** * Entry should be written to the global config file */ bool bGlobal: 1; /** * Entry can not be modified. */ bool bImmutable: 1; /** * Entry has been deleted. */ bool bDeleted: 1; /** * Whether to apply dollar expansion or not. */ bool bExpand: 1; /** * Entry has been reverted to its default value (from a more global file). */ bool bReverted: 1; /** * Entry is for a localized key. If @c false the value references just language e.g. "de", * if @c true the value references language and country, e.g. "de_DE". **/ bool bLocalizedCountry: 1; bool bNotify: 1; }; // These operators are used to check whether an entry which is about // to be written equals the previous value. As such, this intentionally // omits the dirty/notify flag from the comparison. inline bool operator ==(const KEntry &k1, const KEntry &k2) { return k1.bGlobal == k2.bGlobal && k1.bImmutable == k2.bImmutable && k1.bDeleted == k2.bDeleted && k1.bExpand == k2.bExpand && k1.mValue == k2.mValue; } inline bool operator !=(const KEntry &k1, const KEntry &k2) { return !(k1 == k2); } /** * key structure holding both the actual key and the group * to which it belongs. * @internal */ struct KEntryKey { /** Constructor. @internal */ KEntryKey(const QByteArray &_group = QByteArray(), const QByteArray &_key = QByteArray(), bool isLocalized = false, bool isDefault = false) : mGroup(_group), mKey(_key), bLocal(isLocalized), bDefault(isDefault), bRaw(false) { ; } /** * The "group" to which this EntryKey belongs */ QByteArray mGroup; /** * The _actual_ key of the entry in question */ QByteArray mKey; /** * Entry is localised or not */ bool bLocal : 1; /** * Entry indicates if this is a default value. */ bool bDefault: 1; /** @internal * Key is a raw unprocessed key. * @warning this should only be set during merging, never for normal use. */ bool bRaw: 1; }; /** * Compares two KEntryKeys (needed for QMap). The order is localized, localized-default, * non-localized, non-localized-default * @internal */ inline bool operator <(const KEntryKey &k1, const KEntryKey &k2) { int result = qstrcmp(k1.mGroup, k2.mGroup); if (result != 0) { return result < 0; } result = qstrcmp(k1.mKey, k2.mKey); if (result != 0) { return result < 0; } if (k1.bLocal != k2.bLocal) { return k1.bLocal; } return (!k1.bDefault && k2.bDefault); } QDebug operator<<(QDebug dbg, const KEntryKey &key); QDebug operator<<(QDebug dbg, const KEntry &entry); /** * \relates KEntry * type specifying a map of entries (key,value pairs). * The keys are actually a key in a particular config file group together * with the group name. * @internal */ class KEntryMap : public QMap { public: enum SearchFlag { SearchDefaults = 1, SearchLocalized = 2 }; Q_DECLARE_FLAGS(SearchFlags, SearchFlag) enum EntryOption { EntryDirty = 1, EntryGlobal = 2, EntryImmutable = 4, EntryDeleted = 8, EntryExpansion = 16, EntryRawKey = 32, EntryLocalizedCountry = 64, EntryNotify = 128, EntryDefault = (SearchDefaults << 16), EntryLocalized = (SearchLocalized << 16) }; Q_DECLARE_FLAGS(EntryOptions, EntryOption) Iterator findExactEntry(const QByteArray &group, const QByteArray &key = QByteArray(), SearchFlags flags = SearchFlags()); Iterator findEntry(const QByteArray &group, const QByteArray &key = QByteArray(), SearchFlags flags = SearchFlags()); ConstIterator findEntry(const QByteArray &group, const QByteArray &key = QByteArray(), SearchFlags flags = SearchFlags()) const; /** * Returns true if the entry gets dirtied or false in other case */ bool setEntry(const QByteArray &group, const QByteArray &key, const QByteArray &value, EntryOptions options); void setEntry(const QByteArray &group, const QByteArray &key, const QString &value, EntryOptions options) { setEntry(group, key, value.toUtf8(), options); } QString getEntry(const QByteArray &group, const QByteArray &key, const QString &defaultValue = QString(), SearchFlags flags = SearchFlags(), bool *expand = nullptr) const; bool hasEntry(const QByteArray &group, const QByteArray &key = QByteArray(), SearchFlags flags = SearchFlags()) const; bool getEntryOption(const ConstIterator &it, EntryOption option) const; bool getEntryOption(const QByteArray &group, const QByteArray &key, SearchFlags flags, EntryOption option) const { return getEntryOption(findEntry(group, key, flags), option); } void setEntryOption(Iterator it, EntryOption option, bool bf); void setEntryOption(const QByteArray &group, const QByteArray &key, SearchFlags flags, EntryOption option, bool bf) { setEntryOption(findEntry(group, key, flags), option, bf); } - bool revertEntry(const QByteArray &group, const QByteArray &key, SearchFlags flags = SearchFlags()); + bool revertEntry(const QByteArray &group, const QByteArray &key, EntryOptions options, SearchFlags flags = SearchFlags()); }; Q_DECLARE_OPERATORS_FOR_FLAGS(KEntryMap::SearchFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(KEntryMap::EntryOptions) /** * \relates KEntry * type for iterating over keys in a KEntryMap in sorted order. * @internal */ typedef QMap::Iterator KEntryMapIterator; /** * \relates KEntry * type for iterating over keys in a KEntryMap in sorted order. * It is const, thus you cannot change the entries in the iterator, * only examine them. * @internal */ typedef QMap::ConstIterator KEntryMapConstIterator; #endif diff --git a/src/core/kconfiggroup.cpp b/src/core/kconfiggroup.cpp index cb40b60..92c58f6 100644 --- a/src/core/kconfiggroup.cpp +++ b/src/core/kconfiggroup.cpp @@ -1,1297 +1,1307 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfiggroup.h" #include "kconfiggroup_p.h" #include "kconfig.h" #include "kconfig_p.h" #include "ksharedconfig.h" #include "kconfigdata.h" #include #include #include #include #include #include #include #include #include #include class KConfigGroupPrivate : public QSharedData { public: KConfigGroupPrivate(KConfig *owner, bool isImmutable, bool isConst, const QByteArray &name) : mOwner(owner), mName(name), bImmutable(isImmutable), bConst(isConst) { } KConfigGroupPrivate(const KSharedConfigPtr &owner, const QByteArray &name) : sOwner(owner), mOwner(sOwner.data()), mName(name), bImmutable(name.isEmpty() ? owner->isImmutable() : owner->isGroupImmutable(name)), bConst(false) { } KConfigGroupPrivate(KConfigGroup *parent, bool isImmutable, bool isConst, const QByteArray &name) : sOwner(parent->d->sOwner), mOwner(parent->d->mOwner), mName(name), bImmutable(isImmutable), bConst(isConst) { if (!parent->d->mName.isEmpty()) { mParent = parent->d; } } KConfigGroupPrivate(const KConfigGroupPrivate *other, bool isImmutable, const QByteArray &name) : sOwner(other->sOwner), mOwner(other->mOwner), mName(name), bImmutable(isImmutable), bConst(other->bConst) { if (!other->mName.isEmpty()) { mParent = const_cast(other); } } KSharedConfig::Ptr sOwner; KConfig *mOwner; QExplicitlySharedDataPointer mParent; QByteArray mName; /* bitfield */ const bool bImmutable: 1; // is this group immutable? const bool bConst: 1; // is this group read-only? QByteArray fullName() const { if (!mParent) { return name(); } return mParent->fullName(mName); } QByteArray name() const { if (mName.isEmpty()) { return QByteArrayLiteral(""); } return mName; } QByteArray fullName(const QByteArray &aGroup) const { if (mName.isEmpty()) { return aGroup; } return fullName() + '\x1d' + aGroup; } static QExplicitlySharedDataPointer create(KConfigBase *master, const QByteArray &name, bool isImmutable, bool isConst) { QExplicitlySharedDataPointer data; if (dynamic_cast(master)) { data = new KConfigGroupPrivate(static_cast(master), isImmutable, isConst, name); } else { data = new KConfigGroupPrivate(dynamic_cast(master), isImmutable, isConst, name); } return data; } static QByteArray serializeList(const QList &list); static QStringList deserializeList(const QString &data); }; QByteArray KConfigGroupPrivate::serializeList(const QList &list) { QByteArray value; if (!list.isEmpty()) { QList::ConstIterator it = list.constBegin(); const QList::ConstIterator end = list.constEnd(); value = QByteArray(*it).replace('\\', QByteArrayLiteral("\\\\")).replace(',', QByteArrayLiteral("\\,")); while (++it != end) { // In the loop, so it is not done when there is only one element. // Doing it repeatedly is a pretty cheap operation. value.reserve(4096); value += ','; value += QByteArray(*it).replace('\\', QByteArrayLiteral("\\\\")).replace(',', QByteArrayLiteral("\\,")); } // To be able to distinguish an empty list from a list with one empty element. if (value.isEmpty()) { value = QByteArrayLiteral("\\0"); } } return value; } QStringList KConfigGroupPrivate::deserializeList(const QString &data) { if (data.isEmpty()) { return QStringList(); } if (data == QLatin1String("\\0")) { return QStringList(QString()); } QStringList value; QString val; val.reserve(data.size()); bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p].unicode() == '\\') { quoted = true; } else if (data[p].unicode() == ',') { val.squeeze(); // release any unused memory value.append(val); val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } value.append(val); return value; } static QVector asIntList(const QByteArray &string) { const auto &splitString = string.split(','); QVector list; list.reserve(splitString.count()); for (const QByteArray &s : splitString) { list << s.toInt(); } return list; } static QVector asRealList(const QByteArray &string) { const auto &splitString = string.split(','); QVector list; list.reserve(splitString.count()); for (const QByteArray &s : splitString) { list << s.toDouble(); } return list; } static QString errString(const char *pKey, const QByteArray &value, const QVariant &aDefault) { return QStringLiteral("\"%1\" - conversion of \"%3\" to %2 failed") .arg( QString::fromLatin1(pKey), QString::fromLatin1(QVariant::typeToName(aDefault.type())), QString::fromLatin1(value) ); } static QString formatError(int expected, int got) { return QStringLiteral(" (wrong format: expected %1 items, got %2)").arg(expected).arg(got); } QVariant KConfigGroup::convertToQVariant(const char *pKey, const QByteArray &value, const QVariant &aDefault) { // if a type handler is added here you must add a QVConversions definition // to conversioncheck.h, or ConversionCheck::to_QVariant will not allow // readEntry to convert to QVariant. switch (static_cast(aDefault.type())) { case QMetaType::UnknownType: return QVariant(); case QMetaType::QString: // this should return the raw string not the dollar expanded string. // imho if processed string is wanted should call // readEntry(key, QString) not readEntry(key, QVariant) return QString::fromUtf8(value); case QMetaType::QVariantList: case QMetaType::QStringList: return KConfigGroupPrivate::deserializeList(QString::fromUtf8(value)); case QMetaType::QByteArray: return value; case QMetaType::Bool: { const QByteArray lower(value.toLower()); if (lower == "false" || lower == "no" || lower == "off" || lower == "0") { return false; } return true; } case QMetaType::Double: case QMetaType::Float: case QMetaType::Int: case QMetaType::UInt: case QMetaType::LongLong: case QMetaType::ULongLong: { QVariant tmp = value; if (!tmp.convert(aDefault.type())) { tmp = aDefault; } return tmp; } case QMetaType::QPoint: { const auto list = asIntList(value); if (list.count() != 2) { qWarning() << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } return QPoint(list.at(0), list.at(1)); } case QMetaType::QPointF: { const auto list = asRealList(value); if (list.count() != 2) { qWarning() << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } return QPointF(list.at(0), list.at(1)); } case QMetaType::QRect: { const auto list = asIntList(value); if (list.count() != 4) { qWarning() << errString(pKey, value, aDefault) << formatError(4, list.count()); return aDefault; } const QRect rect(list.at(0), list.at(1), list.at(2), list.at(3)); if (!rect.isValid()) { qWarning() << errString(pKey, value, aDefault); return aDefault; } return rect; } case QMetaType::QRectF: { const auto list = asRealList(value); if (list.count() != 4) { qWarning() << errString(pKey, value, aDefault) << formatError(4, list.count()); return aDefault; } const QRectF rect(list.at(0), list.at(1), list.at(2), list.at(3)); if (!rect.isValid()) { qWarning() << errString(pKey, value, aDefault); return aDefault; } return rect; } case QMetaType::QSize: { const auto list = asIntList(value); if (list.count() != 2) { qWarning() << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } const QSize size(list.at(0), list.at(1)); if (!size.isValid()) { qWarning() << errString(pKey, value, aDefault); return aDefault; } return size; } case QMetaType::QSizeF: { const auto list = asRealList(value); if (list.count() != 2) { qWarning() << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } const QSizeF size(list.at(0), list.at(1)); if (!size.isValid()) { qWarning() << errString(pKey, value, aDefault); return aDefault; } return size; } case QMetaType::QDateTime: { const auto list = asIntList(value); if (list.count() != 6) { qWarning() << errString(pKey, value, aDefault) << formatError(6, list.count()); return aDefault; } const QDate date(list.at(0), list.at(1), list.at(2)); const QTime time(list.at(3), list.at(4), list.at(5)); const QDateTime dt(date, time); if (!dt.isValid()) { qWarning() << errString(pKey, value, aDefault); return aDefault; } return dt; } case QMetaType::QDate: { auto list = asIntList(value); if (list.count() == 6) { list = list.mid(0, 3); // don't break config files that stored QDate as QDateTime } if (list.count() != 3) { qWarning() << errString(pKey, value, aDefault) << formatError(3, list.count()); return aDefault; } const QDate date(list.at(0), list.at(1), list.at(2)); if (!date.isValid()) { qWarning() << errString(pKey, value, aDefault); return aDefault; } return date; } case QMetaType::QColor: case QMetaType::QFont: qWarning() << "KConfigGroup::readEntry was passed GUI type '" << aDefault.typeName() << "' but KConfigGui isn't linked! If it is linked to your program, " "this is a platform bug. Please inform the KDE developers"; break; case QMetaType::QUrl: return QUrl(QString::fromUtf8(value)); default: break; } qWarning() << "unhandled type " << aDefault.typeName(); return QVariant(); } #ifdef Q_OS_WIN # include #endif static bool cleanHomeDirPath(QString &path, const QString &homeDir) { #ifdef Q_OS_WIN //safer if (!QDir::toNativeSeparators(path).startsWith(QDir::toNativeSeparators(homeDir))) { return false; } #else if (!path.startsWith(homeDir)) { return false; } #endif int len = homeDir.length(); // replace by "$HOME" if possible if (len && (path.length() == len || path[len] == QLatin1Char('/'))) { path.replace(0, len, QStringLiteral("$HOME")); return true; } return false; } static QString translatePath(QString path) // krazy:exclude=passbyvalue { if (path.isEmpty()) { return path; } // only "our" $HOME should be interpreted path.replace(QLatin1Char('$'), QLatin1String("$$")); const bool startsWithFile = path.startsWith(QLatin1String("file:"), Qt::CaseInsensitive); path = startsWithFile ? QUrl(path).toLocalFile() : path; if (QDir::isRelativePath(path)) { return path; } // we can not use KGlobal::dirs()->relativeLocation("home", path) here, // since it would not recognize paths without a trailing '/'. // All of the 3 following functions to return the user's home directory // can return different paths. We have to test all them. const QString homeDir0 = QFile::decodeName(qgetenv("HOME")); const QString homeDir1 = QDir::homePath(); const QString homeDir2 = QDir(homeDir1).canonicalPath(); if (cleanHomeDirPath(path, homeDir0) || cleanHomeDirPath(path, homeDir1) || cleanHomeDirPath(path, homeDir2)) { // qDebug() << "Path was replaced\n"; } if (startsWithFile) { path = QUrl::fromLocalFile(path).toString(); } return path; } KConfigGroup::KConfigGroup() : d() { } bool KConfigGroup::isValid() const { return bool(d); } KConfigGroupGui _kde_internal_KConfigGroupGui; static inline bool readEntryGui(const QByteArray &data, const char *key, const QVariant &input, QVariant &output) { if (_kde_internal_KConfigGroupGui.readEntryGui) { return _kde_internal_KConfigGroupGui.readEntryGui(data, key, input, output); } return false; } static inline bool writeEntryGui(KConfigGroup *cg, const char *key, const QVariant &input, KConfigGroup::WriteConfigFlags flags) { if (_kde_internal_KConfigGroupGui.writeEntryGui) { return _kde_internal_KConfigGroupGui.writeEntryGui(cg, key, input, flags); } return false; } KConfigGroup::KConfigGroup(KConfigBase *master, const QString &_group) : d(KConfigGroupPrivate::create(master, _group.toUtf8(), master->isGroupImmutable(_group), false)) { } KConfigGroup::KConfigGroup(KConfigBase *master, const char *_group) : d(KConfigGroupPrivate::create(master, _group, master->isGroupImmutable(_group), false)) { } KConfigGroup::KConfigGroup(const KConfigBase *master, const QString &_group) : d(KConfigGroupPrivate::create(const_cast(master), _group.toUtf8(), master->isGroupImmutable(_group), true)) { } KConfigGroup::KConfigGroup(const KConfigBase *master, const char *_group) : d(KConfigGroupPrivate::create(const_cast(master), _group, master->isGroupImmutable(_group), true)) { } KConfigGroup::KConfigGroup(const KSharedConfigPtr &master, const QString &_group) : d(new KConfigGroupPrivate(master, _group.toUtf8())) { } KConfigGroup::KConfigGroup(const KSharedConfigPtr &master, const char *_group) : d(new KConfigGroupPrivate(master, _group)) { } KConfigGroup &KConfigGroup::operator=(const KConfigGroup &rhs) { d = rhs.d; return *this; } KConfigGroup::KConfigGroup(const KConfigGroup &rhs) : d(rhs.d) { } KConfigGroup::~KConfigGroup() { d.reset(); } KConfigGroup KConfigGroup::groupImpl(const QByteArray &aGroup) { Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group"); Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group"); KConfigGroup newGroup; newGroup.d = new KConfigGroupPrivate(this, isGroupImmutableImpl(aGroup), d->bConst, aGroup); return newGroup; } const KConfigGroup KConfigGroup::groupImpl(const QByteArray &aGroup) const { Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group"); Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group"); KConfigGroup newGroup; newGroup.d = new KConfigGroupPrivate(const_cast(this), isGroupImmutableImpl(aGroup), true, aGroup); return newGroup; } KConfigGroup KConfigGroup::parent() const { Q_ASSERT_X(isValid(), "KConfigGroup::parent", "accessing an invalid group"); KConfigGroup parentGroup; if (d->mParent) { parentGroup.d = d->mParent; } else { parentGroup.d = new KConfigGroupPrivate(d->mOwner, d->mOwner->isImmutable(), d->bConst, ""); // make sure we keep the refcount up on the KConfig object parentGroup.d->sOwner = d->sOwner; } return parentGroup; } void KConfigGroup::deleteGroup(WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroup", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroup", "deleting a read-only group"); config()->deleteGroup(d->fullName(), flags); } #ifndef KDE_NO_DEPRECATED void KConfigGroup::changeGroup(const QString &group) { Q_ASSERT_X(isValid(), "KConfigGroup::changeGroup", "accessing an invalid group"); d.detach(); d->mName = group.toUtf8(); } #endif #ifndef KDE_NO_DEPRECATED void KConfigGroup::changeGroup(const char *group) { Q_ASSERT_X(isValid(), "KConfigGroup::changeGroup", "accessing an invalid group"); d.detach(); d->mName = group; } #endif QString KConfigGroup::name() const { Q_ASSERT_X(isValid(), "KConfigGroup::name", "accessing an invalid group"); return QString::fromUtf8(d->name()); } bool KConfigGroup::exists() const { Q_ASSERT_X(isValid(), "KConfigGroup::exists", "accessing an invalid group"); return config()->hasGroup(d->fullName()); } bool KConfigGroup::sync() { Q_ASSERT_X(isValid(), "KConfigGroup::sync", "accessing an invalid group"); if (!d->bConst) { return config()->sync(); } return false; } QMap KConfigGroup::entryMap() const { Q_ASSERT_X(isValid(), "KConfigGroup::entryMap", "accessing an invalid group"); return config()->entryMap(QString::fromUtf8(d->fullName())); } KConfig *KConfigGroup::config() { Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group"); return d->mOwner; } const KConfig *KConfigGroup::config() const { Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group"); return d->mOwner; } bool KConfigGroup::isEntryImmutable(const char *key) const { Q_ASSERT_X(isValid(), "KConfigGroup::isEntryImmutable", "accessing an invalid group"); return (isImmutable() || !config()->d_func()->canWriteEntry(d->fullName(), key, config()->readDefaults())); } bool KConfigGroup::isEntryImmutable(const QString &key) const { return isEntryImmutable(key.toUtf8().constData()); } QString KConfigGroup::readEntryUntranslated(const QString &pKey, const QString &aDefault) const { return readEntryUntranslated(pKey.toUtf8().constData(), aDefault); } QString KConfigGroup::readEntryUntranslated(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntryUntranslated", "accessing an invalid group"); QString result = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchFlags(), nullptr); if (result.isNull()) { return aDefault; } return result; } QString KConfigGroup::readEntry(const char *key, const char *aDefault) const { return readEntry(key, QString::fromUtf8(aDefault)); } QString KConfigGroup::readEntry(const QString &key, const char *aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QString KConfigGroup::readEntry(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); bool expand = false; // read value from the entry map QString aValue = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized, &expand); if (aValue.isNull()) { aValue = aDefault; } if (expand) { return KConfigPrivate::expandString(aValue); } return aValue; } QString KConfigGroup::readEntry(const QString &key, const QString &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readEntry(const char *key, const QStringList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); const QString data = readEntry(key, QString()); if (data.isNull()) { return aDefault; } return KConfigGroupPrivate::deserializeList(data); } QStringList KConfigGroup::readEntry(const QString &key, const QStringList &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QVariant KConfigGroup::readEntry(const char *key, const QVariant &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); const QByteArray data = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized); if (data.isNull()) { return aDefault; } QVariant value; if (!readEntryGui(data, key, aDefault, value)) { return convertToQVariant(key, data, aDefault); } return value; } QVariant KConfigGroup::readEntry(const QString &key, const QVariant &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QVariantList KConfigGroup::readEntry(const char *key, const QVariantList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); const QString data = readEntry(key, QString()); if (data.isNull()) { return aDefault; } const auto &list = KConfigGroupPrivate::deserializeList(data); QVariantList value; value.reserve(list.count()); for (const QString &v : list) { value << v; } return value; } QVariantList KConfigGroup::readEntry(const QString &key, const QVariantList &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readXdgListEntry(const QString &key, const QStringList &aDefault) const { return readXdgListEntry(key.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readXdgListEntry(const char *key, const QStringList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readXdgListEntry", "accessing an invalid group"); const QString data = readEntry(key, QString()); if (data.isNull()) { return aDefault; } QStringList value; QString val; val.reserve(data.size()); // XXX List serialization being a separate layer from low-level parsing is // probably a bug. No affected entries are defined, though. bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p] == QLatin1Char('\\')) { quoted = true; } else if (data[p] == QLatin1Char(';')) { value.append(val); val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } if (!val.isEmpty()) { value.append(val); } return value; } QString KConfigGroup::readPathEntry(const QString &pKey, const QString &aDefault) const { return readPathEntry(pKey.toUtf8().constData(), aDefault); } QString KConfigGroup::readPathEntry(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group"); bool expand = false; QString aValue = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized, &expand); if (aValue.isNull()) { aValue = aDefault; } return KConfigPrivate::expandString(aValue); } QStringList KConfigGroup::readPathEntry(const QString &pKey, const QStringList &aDefault) const { return readPathEntry(pKey.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readPathEntry(const char *key, const QStringList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group"); const QString data = readPathEntry(key, QString()); if (data.isNull()) { return aDefault; } return KConfigGroupPrivate::deserializeList(data); } void KConfigGroup::writeEntry(const char *key, const QString &value, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); writeEntry(key, value.toUtf8(), flags); } void KConfigGroup::writeEntry(const QString &key, const QString &value, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), value, flags); } void KConfigGroup::writeEntry(const QString &key, const char *value, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); writeEntry(key.toUtf8().constData(), QVariant(QString::fromLatin1(value)), pFlags); } void KConfigGroup::writeEntry(const char *key, const char *value, WriteConfigFlags pFlags) { writeEntry(key, QVariant(QString::fromLatin1(value)), pFlags); } void KConfigGroup::writeEntry(const char *key, const QByteArray &value, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); config()->d_func()->putData(d->fullName(), key, value.isNull() ? QByteArray("") : value, flags); } void KConfigGroup::writeEntry(const QString &key, const QByteArray &value, WriteConfigFlags pFlags) { writeEntry(key.toUtf8().constData(), value, pFlags); } void KConfigGroup::writeEntry(const char *key, const QStringList &list, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); QList balist; balist.reserve(list.count()); for (const QString &entry : list) { balist.append(entry.toUtf8()); } writeEntry(key, KConfigGroupPrivate::serializeList(balist), flags); } void KConfigGroup::writeEntry(const QString &key, const QStringList &list, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), list, flags); } void KConfigGroup::writeEntry(const char *key, const QVariantList &list, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); QList data; data.reserve(list.count()); for (const QVariant &v : list) { if (v.type() == QVariant::ByteArray) { data << v.toByteArray(); } else { data << v.toString().toUtf8(); } } writeEntry(key, KConfigGroupPrivate::serializeList(data), flags); } void KConfigGroup::writeEntry(const char *key, const QVariant &value, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); if (writeEntryGui(this, key, value, flags)) { return; // GUI type that was handled } QByteArray data; // if a type handler is added here you must add a QVConversions definition // to conversioncheck.h, or ConversionCheck::to_QVariant will not allow // writeEntry to convert to QVariant. switch (static_cast(value.type())) { case QMetaType::UnknownType: data = ""; break; case QMetaType::QByteArray: data = value.toByteArray(); break; case QMetaType::QString: case QMetaType::Int: case QMetaType::UInt: case QMetaType::Double: case QMetaType::Float: case QMetaType::Bool: case QMetaType::LongLong: case QMetaType::ULongLong: data = value.toString().toUtf8(); break; case QMetaType::QVariantList: if (!value.canConvert(QMetaType::QStringList)) qWarning() << "not all types in \"" << key << "\" can convert to QString," " information will be lost"; Q_FALLTHROUGH(); case QMetaType::QStringList: writeEntry(key, value.toList(), flags); return; case QMetaType::QPoint: { const QPoint rPoint = value.toPoint(); const QVariantList list{ rPoint.x(), rPoint.y() }; writeEntry(key, list, flags); return; } case QMetaType::QPointF: { const QPointF point = value.toPointF(); const QVariantList list{ point.x(), point.y() }; writeEntry(key, list, flags); return; } case QMetaType::QRect: { const QRect rRect = value.toRect(); const QVariantList list{ rRect.left(), rRect.top(), rRect.width(), rRect.height() }; writeEntry(key, list, flags); return; } case QMetaType::QRectF: { const QRectF rRectF = value.toRectF(); const QVariantList list{ rRectF.left(), rRectF.top(), rRectF.width(), rRectF.height() }; writeEntry(key, list, flags); return; } case QMetaType::QSize: { const QSize rSize = value.toSize(); const QVariantList list{ rSize.width(), rSize.height() }; writeEntry(key, list, flags); return; } case QMetaType::QSizeF: { const QSizeF rSizeF = value.toSizeF(); const QVariantList list{ rSizeF.width(), rSizeF.height() }; writeEntry(key, list, flags); return; } case QMetaType::QDate: { const QDate date = value.toDate(); const QVariantList list{ date.year(), date.month(), date.day() }; writeEntry(key, list, flags); return; } case QMetaType::QDateTime: { const QDateTime rDateTime = value.toDateTime(); const QTime time = rDateTime.time(); const QDate date = rDateTime.date(); const QVariantList list{ date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), }; writeEntry(key, list, flags); return; } case QMetaType::QColor: case QMetaType::QFont: qWarning() << "KConfigGroup::writeEntry was passed GUI type '" << value.typeName() << "' but KConfigGui isn't linked! If it is linked to your program, this is a platform bug. " "Please inform the KDE developers"; break; case QMetaType::QUrl: data = QUrl(value.toUrl()).toString().toUtf8(); break; default: qWarning() << "KConfigGroup::writeEntry - unhandled type" << value.typeName() << "in group" << name(); } writeEntry(key, data, flags); } void KConfigGroup::writeEntry(const QString &key, const QVariant &value, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), value, flags); } void KConfigGroup::writeEntry(const QString &key, const QVariantList &list, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), list, flags); } void KConfigGroup::writeXdgListEntry(const QString &key, const QStringList &value, WriteConfigFlags pFlags) { writeXdgListEntry(key.toUtf8().constData(), value, pFlags); } void KConfigGroup::writeXdgListEntry(const char *key, const QStringList &list, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeXdgListEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeXdgListEntry", "writing to a read-only group"); QString value; value.reserve(4096); // XXX List serialization being a separate layer from low-level escaping is // probably a bug. No affected entries are defined, though. QStringList::ConstIterator it = list.constBegin(); const QStringList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { QString val(*it); val.replace(QLatin1Char('\\'), QStringLiteral("\\\\")).replace(QLatin1Char(';'), QStringLiteral("\\;")); value += val; value += QLatin1Char(';'); } writeEntry(key, value, flags); } void KConfigGroup::writePathEntry(const QString &pKey, const QString &path, WriteConfigFlags pFlags) { writePathEntry(pKey.toUtf8().constData(), path, pFlags); } void KConfigGroup::writePathEntry(const char *pKey, const QString &path, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group"); config()->d_func()->putData(d->fullName(), pKey, translatePath(path).toUtf8(), pFlags, true); } void KConfigGroup::writePathEntry(const QString &pKey, const QStringList &value, WriteConfigFlags pFlags) { writePathEntry(pKey.toUtf8().constData(), value, pFlags); } void KConfigGroup::writePathEntry(const char *pKey, const QStringList &value, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group"); QList list; list.reserve(value.length()); for (const QString &path : value) { list << translatePath(path).toUtf8(); } config()->d_func()->putData(d->fullName(), pKey, KConfigGroupPrivate::serializeList(list), pFlags, true); } void KConfigGroup::deleteEntry(const char *key, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::deleteEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteEntry", "deleting from a read-only group"); config()->d_func()->putData(d->fullName(), key, QByteArray(), flags); } void KConfigGroup::deleteEntry(const QString &key, WriteConfigFlags flags) { deleteEntry(key.toUtf8().constData(), flags); } void KConfigGroup::revertToDefault(const char *key) +{ + revertToDefault(key, WriteConfigFlags()); +} + +void KConfigGroup::revertToDefault(const char *key, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::revertToDefault", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::revertToDefault", "writing to a read-only group"); - config()->d_func()->revertEntry(d->fullName(), key); + config()->d_func()->revertEntry(d->fullName(), key, flags); } void KConfigGroup::revertToDefault(const QString &key) { - revertToDefault(key.toUtf8().constData()); + revertToDefault(key, WriteConfigFlags()); +} + +void KConfigGroup::revertToDefault(const QString &key, WriteConfigFlags flags) +{ + revertToDefault(key.toUtf8().constData(), flags); } bool KConfigGroup::hasDefault(const char *key) const { Q_ASSERT_X(isValid(), "KConfigGroup::hasDefault", "accessing an invalid group"); KEntryMap::SearchFlags flags = KEntryMap::SearchDefaults | KEntryMap::SearchLocalized; return !config()->d_func()->lookupData(d->fullName(), key, flags).isNull(); } bool KConfigGroup::hasDefault(const QString &key) const { return hasDefault(key.toUtf8().constData()); } bool KConfigGroup::hasKey(const char *key) const { Q_ASSERT_X(isValid(), "KConfigGroup::hasKey", "accessing an invalid group"); KEntryMap::SearchFlags flags = KEntryMap::SearchLocalized; if (config()->readDefaults()) { flags |= KEntryMap::SearchDefaults; } return !config()->d_func()->lookupData(d->fullName(), key, flags).isNull(); } bool KConfigGroup::hasKey(const QString &key) const { return hasKey(key.toUtf8().constData()); } bool KConfigGroup::isImmutable() const { Q_ASSERT_X(isValid(), "KConfigGroup::isImmutable", "accessing an invalid group"); return d->bImmutable; } QStringList KConfigGroup::groupList() const { Q_ASSERT_X(isValid(), "KConfigGroup::groupList", "accessing an invalid group"); return config()->d_func()->groupList(d->fullName()); } QStringList KConfigGroup::keyList() const { Q_ASSERT_X(isValid(), "KConfigGroup::keyList", "accessing an invalid group"); return entryMap().keys(); } void KConfigGroup::markAsClean() { Q_ASSERT_X(isValid(), "KConfigGroup::markAsClean", "accessing an invalid group"); config()->markAsClean(); } KConfigGroup::AccessMode KConfigGroup::accessMode() const { Q_ASSERT_X(isValid(), "KConfigGroup::accessMode", "accessing an invalid group"); return config()->accessMode(); } bool KConfigGroup::hasGroupImpl(const QByteArray &b) const { Q_ASSERT_X(isValid(), "KConfigGroup::hasGroupImpl", "accessing an invalid group"); return config()->hasGroup(d->fullName(b)); } void KConfigGroup::deleteGroupImpl(const QByteArray &b, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroupImpl", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroupImpl", "deleting from a read-only group"); config()->deleteGroup(d->fullName(b), flags); } bool KConfigGroup::isGroupImmutableImpl(const QByteArray &b) const { Q_ASSERT_X(isValid(), "KConfigGroup::isGroupImmutableImpl", "accessing an invalid group"); if (!hasGroupImpl(b)) { // group doesn't exist yet return d->bImmutable; // child groups are immutable if the parent is immutable. } return config()->isGroupImmutable(d->fullName(b)); } void KConfigGroup::copyTo(KConfigBase *other, WriteConfigFlags pFlags) const { Q_ASSERT_X(isValid(), "KConfigGroup::copyTo", "accessing an invalid group"); Q_ASSERT(other != nullptr); if (KConfigGroup *otherGroup = dynamic_cast(other)) { config()->d_func()->copyGroup(d->fullName(), otherGroup->d->fullName(), otherGroup, pFlags); } else if (KConfig *otherConfig = dynamic_cast(other)) { KConfigGroup newGroup = otherConfig->group(d->fullName()); otherConfig->d_func()->copyGroup(d->fullName(), d->fullName(), &newGroup, pFlags); } else { Q_ASSERT_X(false, "KConfigGroup::copyTo", "unknown type of KConfigBase"); } } void KConfigGroup::reparent(KConfigBase *parent, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::reparent", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::reparent", "reparenting a read-only group"); Q_ASSERT_X(!d->bImmutable, "KConfigGroup::reparent", "reparenting an immutable group"); Q_ASSERT(parent != nullptr); KConfigGroup oldGroup(*this); d = KConfigGroupPrivate::create(parent, d->mName, false, false); oldGroup.copyTo(this, pFlags); oldGroup.deleteGroup(); // so that the entries with the old group name are deleted on sync } diff --git a/src/core/kconfiggroup.h b/src/core/kconfiggroup.h index 0cedeaa..8b0174b 100644 --- a/src/core/kconfiggroup.h +++ b/src/core/kconfiggroup.h @@ -1,768 +1,773 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer Copyright (c) 2001 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCONFIGGROUP_H #define KCONFIGGROUP_H #include "kconfigbase.h" #include #include #include #include class KConfig; class KConfigGroupPrivate; class KSharedConfig; /** * \class KConfigGroup kconfiggroup.h * * A class for one specific group in a KConfig object. * * If you want to access the top-level entries of a KConfig * object, which are not associated with any group, use an * empty group name. * * A KConfigGroup will be read-only if it is constructed from a * const config object or from another read-only group. */ class KCONFIGCORE_EXPORT KConfigGroup : public KConfigBase { public: /** * Constructs an invalid group. * * \see isValid */ KConfigGroup(); /** * Construct a config group corresponding to @p group in @p master. * * This allows the creation of subgroups by passing another * group as @p master. * * @p group is the group name encoded in UTF-8. */ KConfigGroup(KConfigBase *master, const QString &group); /** Overload for KConfigGroup(KConfigBase*,const QString&) */ KConfigGroup(KConfigBase *master, const char *group); /** * Construct a read-only config group. * * A read-only group will silently ignore any attempts to write to it. * * This allows the creation of subgroups by passing an existing group * as @p master. */ KConfigGroup(const KConfigBase *master, const QString &group); /** Overload for KConfigGroup(const KConfigBase*,const QString&) */ KConfigGroup(const KConfigBase *master, const char *group); /** Overload for KConfigGroup(const KConfigBase*,const QString&) */ KConfigGroup(const QExplicitlySharedDataPointer &master, const QString &group); /** Overload for KConfigGroup(const KConfigBase*,const QString&) */ KConfigGroup(const QExplicitlySharedDataPointer &master, const char *group); /** * Creates a copy of a group. */ KConfigGroup(const KConfigGroup &); KConfigGroup &operator=(const KConfigGroup &); ~KConfigGroup(); /** * Whether the group is valid. * * A group is invalid if it was constructed without arguments. * * You should not call any functions on an invalid group. * * @return @c true if the group is valid, @c false if it is invalid. */ bool isValid() const; /** * The name of this group. * * The root group is named "". */ QString name() const; /** * Check whether the containing KConfig object acutally contains a * group with this name. */ bool exists() const; /** * @reimp * * Syncs the parent config. */ bool sync() override; /// @reimp void markAsClean() override; /// @reimp AccessMode accessMode() const override; /** * Return the config object that this group belongs to */ KConfig *config(); /** * Return the config object that this group belongs to */ const KConfig *config() const; /** * Changes the group of the object * * @deprecated * Create another KConfigGroup from the parent of this group instead. */ #ifndef KDE_NO_DEPRECATED KCONFIGCORE_DEPRECATED void changeGroup(const QString &group); #endif /** * Overload for changeGroup(const QString&) * * @deprecated * Create another KConfigGroup from the parent of this group instead. */ #ifndef KDE_NO_DEPRECATED KCONFIGCORE_DEPRECATED void changeGroup(const char *group); #endif /** * Copies the entries in this group to another configuration object * * @note @p other can be either another group or a different file. * * @param other the configuration object to copy this group's entries to * @param pFlags the flags to use when writing the entries to the * other configuration object * * @since 4.1 */ void copyTo(KConfigBase *other, WriteConfigFlags pFlags = Normal) const; /** * Changes the configuration object that this group belongs to * * @note @p other can be another group, the top-level KConfig object or * a different KConfig object entirely. * * If @p parent is already the parent of this group, this method will have * no effect. * * @param parent the config object to place this group under * @param pFlags the flags to use in determining which storage source to * write the data to * * @since 4.1 */ void reparent(KConfigBase *parent, WriteConfigFlags pFlags = Normal); /** * Returns the group that this group belongs to * * @return the parent group, or an invalid group if this is a top-level * group * * @since 4.1 */ KConfigGroup parent() const; /** * @reimp */ QStringList groupList() const override; /** * Returns a list of keys this group contains */ QStringList keyList() const; /** * Delete all entries in the entire group * * @param pFlags flags passed to KConfig::deleteGroup * * @see deleteEntry() */ void deleteGroup(WriteConfigFlags pFlags = Normal); using KConfigBase::deleteGroup; /** * Reads the value of an entry specified by @p pKey in the current group * * This template method makes it possible to write * QString foo = readEntry("...", QString("default")); * and the same with all other types supported by QVariant. * * The return type of the method is simply the same as the type of the default value. * * @note readEntry("...", Qt::white) will not compile because Qt::white is an enum. * You must turn it into readEntry("...", QColor(Qt::white)). * * @note Only the following QVariant types are allowed : String, * StringList, List, Font, Point, Rect, Size, Color, Int, UInt, Bool, * Double, LongLong, ULongLong, DateTime and Date. * * @param key The key to search for * @param aDefault A default value returned if the key was not found * @return The value for this key, or @p aDefault. * * @see writeEntry(), deleteEntry(), hasKey() */ template T readEntry(const QString &key, const T &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } /** Overload for readEntry(const QString&, const T&) const */ template T readEntry(const char *key, const T &aDefault) const; /** * Reads the value of an entry specified by @p key in the current group * * @param key the key to search for * @param aDefault a default value returned if the key was not found * @return the value for this key, or @p aDefault if the key was not found * * @see writeEntry(), deleteEntry(), hasKey() */ QVariant readEntry(const QString &key, const QVariant &aDefault) const; /** Overload for readEntry(const QString&, const QVariant&) */ QVariant readEntry(const char *key, const QVariant &aDefault) const; /** * Reads the string value of an entry specified by @p key in the current group * * If you want to read a path, please use readPathEntry(). * * @param key the key to search for * @param aDefault a default value returned if the key was not found * @return the value for this key, or @p aDefault if the key was not found * * @see readPathEntry(), writeEntry(), deleteEntry(), hasKey() */ QString readEntry(const QString &key, const QString &aDefault) const; /** Overload for readEntry(const QString&, const QString&) */ QString readEntry(const char *key, const QString &aDefault) const; /** Overload for readEntry(const QString&, const QString&) */ QString readEntry(const QString &key, const char *aDefault = nullptr) const; /** Overload for readEntry(const QString&, const QString&) */ QString readEntry(const char *key, const char *aDefault = nullptr) const; /** * @copydoc readEntry(const char*, const QStringList&) const * * @warning This function doesn't convert the items returned * to any type. It's actually a list of QVariant::String's. If you * want the items converted to a specific type use * readEntry(const char*, const QList&) const */ QVariantList readEntry(const QString &key, const QVariantList &aDefault) const; /** Overload for readEntry(const QString&, const QVariantList&) */ QVariantList readEntry(const char *key, const QVariantList &aDefault) const; /** * Reads a list of strings from the config object * * @param key The key to search for * @param aDefault The default value to use if the key does not exist * @return The list, or @p aDefault if @p key does not exist * * @see readXdgListEntry(), writeEntry(), deleteEntry(), hasKey() */ QStringList readEntry(const QString &key, const QStringList &aDefault) const; /** Overload for readEntry(const QString&, const QStringList&) */ QStringList readEntry(const char *key, const QStringList &aDefault) const; /** * Reads a list of values from the config object * * @param key the key to search for * @param aDefault the default value to use if the key does not exist * @return the list, or @p aDefault if @p key does not exist * * @see readXdgListEntry(), writeEntry(), deleteEntry(), hasKey() */ template QList readEntry(const QString &key, const QList &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } /** Overload for readEntry(const QString&, const QList&) */ template QList readEntry(const char *key, const QList &aDefault) const; /** * Reads a list of strings from the config object, following XDG * desktop entry spec separator semantics * * @param pKey the key to search for * @param aDefault the default value to use if the key does not exist * @return the list, or @p aDefault if @p pKey does not exist * * @see readEntry(const QString&, const QStringList&) const */ QStringList readXdgListEntry(const QString &pKey, const QStringList &aDefault = QStringList()) const; /** Overload for readXdgListEntry(const QString&, const QStringList&) */ QStringList readXdgListEntry(const char *pKey, const QStringList &aDefault = QStringList()) const; /** * Reads a path * * Read the value of an entry specified by @p pKey in the current group * and interpret it as a path. This means, dollar expansion is activated * for this value, so that e.g. $HOME gets expanded. * * @param pKey The key to search for. * @param aDefault A default value returned if the key was not found. * @return The value for this key. Can be QString() if @p aDefault is null. */ QString readPathEntry(const QString &pKey, const QString &aDefault) const; /** Overload for readPathEntry(const QString&, const QString&) */ QString readPathEntry(const char *key, const QString &aDefault) const; /** * Reads a list of paths * * Read the value of an entry specified by @p pKey in the current group * and interpret it as a list of paths. This means, dollar expansion is activated * for this value, so that e.g. $HOME gets expanded. * * @param pKey the key to search for * @param aDefault a default value returned if the key was not found * @return the list, or @p aDefault if the key does not exist */ QStringList readPathEntry(const QString &pKey, const QStringList &aDefault) const; /** Overload for readPathEntry(const QString&, const QStringList&) */ QStringList readPathEntry(const char *key, const QStringList &aDefault) const; /** * Reads an untranslated string entry * * You should not normally need to use this. * * @param pKey the key to search for * @param aDefault a default value returned if the key was not found * @return the value for this key, or @p aDefault if the key does not exist */ QString readEntryUntranslated(const QString &pKey, const QString &aDefault = QString()) const; /** Overload for readEntryUntranslated(const QString&, const QString&) */ QString readEntryUntranslated(const char *key, const QString &aDefault = QString()) const; /** * Writes a value to the configuration object. * * @param key the key to write to * @param value the value to write * @param pFlags the flags to use when writing this entry * * @see readEntry(), writeXdgListEntry(), deleteEntry() */ void writeEntry(const QString &key, const QVariant &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const char *key, const QVariant &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const QString &key, const QString &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const char *key, const QString &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const QString &key, const QByteArray &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const char *key, const QByteArray &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const QString &key, const char *value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ template void writeEntry(const char *key, const T &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ template void writeEntry(const QString &key, const T &value, WriteConfigFlags pFlags = Normal) { writeEntry(key.toUtf8().constData(), value, pFlags); } /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const QString &key, const QStringList &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const char *key, const QStringList &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const QString &key, const QVariantList &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ void writeEntry(const char *key, const QVariantList &value, WriteConfigFlags pFlags = Normal); /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ template void writeEntry(const QString &key, const QList &value, WriteConfigFlags pFlags = Normal) { writeEntry(key.toUtf8().constData(), value, pFlags); } /** Overload for writeEntry(const QString&, const QVariant&, WriteConfigFlags) */ template void writeEntry(const char *key, const QList &value, WriteConfigFlags pFlags = Normal); /** * Writes a list of strings to the config object, following XDG * desktop entry spec separator semantics * * @param pKey the key to write to * @param value the list to write * @param pFlags the flags to use when writing this entry * * @see writeEntry(), readXdgListEntry() */ void writeXdgListEntry(const QString &pKey, const QStringList &value, WriteConfigFlags pFlags = Normal); /** Overload for writeXdgListEntry(const QString&, const QStringList&, WriteConfigFlags) */ void writeXdgListEntry(const char *pKey, const QStringList &value, WriteConfigFlags pFlags = Normal); /** * Writes a file path to the configuration * * If the path is located under $HOME, the user's home directory * is replaced with $HOME in the persistent storage. * The path should therefore be read back with readPathEntry() * * @param pKey the key to write to * @param path the path to write * @param pFlags the flags to use when writing this entry * * @see writeEntry(), readPathEntry() */ void writePathEntry(const QString &pKey, const QString &path, WriteConfigFlags pFlags = Normal); /** Overload for writePathEntry(const QString&, const QString&, WriteConfigFlags) */ void writePathEntry(const char *pKey, const QString &path, WriteConfigFlags pFlags = Normal); /** * Writes a list of paths to the configuration * * If any of the paths are located under $HOME, the user's home directory * is replaced with $HOME in the persistent storage. * The paths should therefore be read back with readPathEntry() * * @param pKey the key to write to * @param value the list to write * @param pFlags the flags to use when writing this entry * * @see writeEntry(), readPathEntry() */ void writePathEntry(const QString &pKey, const QStringList &value, WriteConfigFlags pFlags = Normal); /** Overload for writePathEntry(const QString&, const QStringList&, WriteConfigFlags) */ void writePathEntry(const char *pKey, const QStringList &value, WriteConfigFlags pFlags = Normal); /** * Deletes the entry specified by @p pKey in the current group * * This also hides system wide defaults. * * @param pKey the key to delete * @param pFlags the flags to use when deleting this entry * * @see deleteGroup(), readEntry(), writeEntry() */ void deleteEntry(const QString &pKey, WriteConfigFlags pFlags = Normal); /** Overload for deleteEntry(const QString&, WriteConfigFlags) */ void deleteEntry(const char *pKey, WriteConfigFlags pFlags = Normal); /** * Checks whether the key has an entry in this group * * Use this to determine if a key is not specified for the current * group (hasKey() returns false). * * If this returns @c false for a key, readEntry() (and its variants) * will return the default value passed to them. * * @param key the key to search for * @return @c true if the key is defined in this group by any of the * configuration sources, @c false otherwise * * @see readEntry() */ bool hasKey(const QString &key) const; /** Overload for hasKey(const QString&) const */ bool hasKey(const char *key) const; /** * Whether this group may be changed * * @return @c false if the group may be changed, @c true otherwise */ bool isImmutable() const override; /** * Checks if it is possible to change the given entry * * If isImmutable() returns @c true, then this method will return * @c true for all inputs. * * @param key the key to check * @return @c false if the key may be changed using this configuration * group object, @c true otherwise */ bool isEntryImmutable(const QString &key) const; /** Overload for isEntryImmutable(const QString&) const */ bool isEntryImmutable(const char *key) const; /** * Reverts an entry to the default settings. * * Reverts the entry with key @p key in the current group in the * application specific config file to either the system wide (default) * value or the value specified in the global KDE config file. * * To revert entries in the global KDE config file, the global KDE config * file should be opened explicitly in a separate config object. * * @note This is @em not the same as deleting the key, as instead the * global setting will be copied to the configuration file that this * object manipulates. * * @param key The key of the entry to revert. */ + // TODO KF6 merge with the other one void revertToDefault(const QString &key); + void revertToDefault(const QString &key, WriteConfigFlags pFlag); + /** Overload for revertToDefault(const QString&) */ + // TODO KF6 merge with the other one void revertToDefault(const char *key); + void revertToDefault(const char *key, WriteConfigFlags pFlag); /** * Whether a default is specified for an entry in either the * system wide configuration file or the global KDE config file * * If an application computes a default value at runtime for * a certain entry, e.g. like: * \code * QColor computedDefault = qApp->palette().color(QPalette::Active, QPalette::Text); * QColor color = group.readEntry(key, computedDefault); * \endcode * then it may wish to make the following check before * writing back changes: * \code * if ( (value == computedDefault) && !group.hasDefault(key) ) * group.revertToDefault(key); * else * group.writeEntry(key, value); * \endcode * * This ensures that as long as the entry is not modified to differ from * the computed default, the application will keep using the computed default * and will follow changes the computed default makes over time. * * @param key the key of the entry to check * @return @c true if the global or system settings files specify a default * for @p key in this group, @c false otherwise */ bool hasDefault(const QString &key) const; /** Overload for hasDefault(const QString&) const */ bool hasDefault(const char *key) const; /** * Returns a map (tree) of entries for all entries in this group * * Only the actual entry string is returned, none of the * other internal data should be included. * * @return a map of entries in this group, indexed by key */ QMap entryMap() const; protected: bool hasGroupImpl(const QByteArray &group) const override; KConfigGroup groupImpl(const QByteArray &b) override; const KConfigGroup groupImpl(const QByteArray &b) const override; void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags) override; bool isGroupImmutableImpl(const QByteArray &aGroup) const override; private: QExplicitlySharedDataPointer d; friend class KConfigGroupPrivate; /** * Return the data in @p value converted to a QVariant * * @param pKey the name of the entry being converted, this is only used for error * reporting * @param value the UTF-8 data to be converted * @param aDefault the default value if @p pKey is not found * @return @p value converted to QVariant, or @p aDefault if @p value is invalid or cannot be converted. */ static QVariant convertToQVariant(const char *pKey, const QByteArray &value, const QVariant &aDefault); friend class KServicePrivate; // XXX yeah, ugly^5 }; #define KCONFIGGROUP_ENUMERATOR_ERROR(ENUM) \ "The Qt MetaObject system does not seem to know about \"" ENUM \ "\" please use Q_ENUM or Q_FLAG to register it." /** * To add support for your own enums in KConfig, you can declare them with Q_ENUM() * in a QObject subclass (which will make moc generate the code to turn the * enum into a string and vice-versa), and then (in the cpp code) * use the macro * KCONFIGGROUP_DECLARE_ENUM_QOBJECT(MyClass, MyEnum) * */ #define KCONFIGGROUP_DECLARE_ENUM_QOBJECT(Class, Enum) \ template<> \ Class::Enum KConfigGroup::readEntry(const char* key, const Class::Enum& def) const\ { \ const QMetaObject* M_obj = &Class::staticMetaObject; \ const int M_index = M_obj->indexOfEnumerator(#Enum); \ if(M_index == -1) qFatal(KCONFIGGROUP_ENUMERATOR_ERROR(#Enum)); \ const QMetaEnum M_enum = M_obj->enumerator(M_index); \ const QByteArray M_data = readEntry(key, QByteArray(M_enum.valueToKey(def)));\ return static_cast(M_enum.keyToValue(M_data.constData())); \ } \ inline Class::Enum Q_DECL_DEPRECATED readEntry(const KConfigGroup& group, const char* key, const Class::Enum& def)\ { return group.readEntry(key, def); } \ template<> \ void KConfigGroup::writeEntry(const char* key, const Class::Enum& value, KConfigBase::WriteConfigFlags flags)\ { \ const QMetaObject* M_obj = &Class::staticMetaObject; \ const int M_index = M_obj->indexOfEnumerator(#Enum); \ if(M_index == -1) qFatal(KCONFIGGROUP_ENUMERATOR_ERROR(#Enum)); \ const QMetaEnum M_enum = M_obj->enumerator(M_index); \ writeEntry(key, QByteArray(M_enum.valueToKey(value)), flags); \ } \ inline void Q_DECL_DEPRECATED writeEntry(KConfigGroup& group, const char* key, const Class::Enum& value, KConfigBase::WriteConfigFlags flags = KConfigBase::Normal)\ { group.writeEntry(key, value, flags); } /** * Similar to KCONFIGGROUP_DECLARE_ENUM_QOBJECT but for flags declared with Q_FLAG() * (where multiple values can be set at the same time) */ #define KCONFIGGROUP_DECLARE_FLAGS_QOBJECT(Class, Flags) \ template<> \ Class::Flags KConfigGroup::readEntry(const char* key, const Class::Flags& def) const\ { \ const QMetaObject* M_obj = &Class::staticMetaObject; \ const int M_index = M_obj->indexOfEnumerator(#Flags); \ if(M_index == -1) qFatal(KCONFIGGROUP_ENUMERATOR_ERROR(#Flags)); \ const QMetaEnum M_enum = M_obj->enumerator(M_index); \ const QByteArray M_data = readEntry(key, QByteArray(M_enum.valueToKeys(def)));\ return static_cast(M_enum.keysToValue(M_data.constData())); \ } \ inline Class::Flags Q_DECL_DEPRECATED readEntry(const KConfigGroup& group, const char* key, const Class::Flags& def)\ { return group.readEntry(key, def);} \ template<> \ void KConfigGroup::writeEntry(const char* key, const Class::Flags& value, KConfigBase::WriteConfigFlags flags)\ { \ const QMetaObject* M_obj = &Class::staticMetaObject; \ const int M_index = M_obj->indexOfEnumerator(#Flags); \ if(M_index == -1) qFatal(KCONFIGGROUP_ENUMERATOR_ERROR(#Flags)); \ const QMetaEnum M_enum = M_obj->enumerator(M_index); \ writeEntry(key, QByteArray(M_enum.valueToKeys(value)), flags); \ } \ inline void Q_DECL_DEPRECATED writeEntry(KConfigGroup& group, const char* key, const Class::Flags& value, KConfigBase::WriteConfigFlags flags = KConfigBase::Normal)\ { group.writeEntry(key, value, flags); } #include "conversioncheck.h" template T KConfigGroup::readEntry(const char *key, const T &defaultValue) const { ConversionCheck::to_QVariant(); return qvariant_cast(readEntry(key, qVariantFromValue(defaultValue))); } template QList KConfigGroup::readEntry(const char *key, const QList &defaultValue) const { ConversionCheck::to_QVariant(); ConversionCheck::to_QString(); QVariantList data; for (const T &value : defaultValue) { data.append(qVariantFromValue(value)); } QList list; const auto variantList = readEntry(key, data); for (const QVariant &value : variantList) { Q_ASSERT(value.canConvert()); list.append(qvariant_cast(value)); } return list; } template void KConfigGroup::writeEntry(const char *key, const T &value, WriteConfigFlags pFlags) { ConversionCheck::to_QVariant(); writeEntry(key, qVariantFromValue(value), pFlags); } template void KConfigGroup::writeEntry(const char *key, const QList &list, WriteConfigFlags pFlags) { ConversionCheck::to_QVariant(); ConversionCheck::to_QString(); QVariantList data; for (const T &value : list) { data.append(qVariantFromValue(value)); } writeEntry(key, data, pFlags); } #endif // KCONFIGGROUP_H