diff options
| -rw-r--r-- | autotests/kconfig_compiler/CMakeLists.txt | 25 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/kconfigcompiler_test_signals.cpp | 214 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/signals_test.kcfg | 17 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/signals_test_no_singleton.kcfgc | 9 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/signals_test_no_singleton_dpointer.kcfgc | 9 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/signals_test_singleton.kcfgc | 9 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/signals_test_singleton_dpointer.kcfgc | 9 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/test_signal.cpp.ref | 22 | ||||
| -rw-r--r-- | autotests/kconfig_compiler/test_signal.h.ref | 3 | ||||
| -rw-r--r-- | src/core/kcoreconfigskeleton.cpp | 74 | ||||
| -rw-r--r-- | src/core/kcoreconfigskeleton.h | 51 | ||||
| -rw-r--r-- | src/kconfig_compiler/kconfig_compiler.cpp | 79 | 
12 files changed, 489 insertions, 32 deletions
| diff --git a/autotests/kconfig_compiler/CMakeLists.txt b/autotests/kconfig_compiler/CMakeLists.txt index a2ebb945..289e9583 100644 --- a/autotests/kconfig_compiler/CMakeLists.txt +++ b/autotests/kconfig_compiler/CMakeLists.txt @@ -3,14 +3,19 @@  #	../kconfig_compiler_kf5 $(srcdir)/test5.kcfg $(srcdir)/test5.kcfgc  macro(GEN_KCFG_TEST_SOURCE _testName _srcs) +   cmake_parse_arguments(ARG "" "KCFG" "" ${ARGN} ) +   set(_kcfgFile ${ARG_KCFG}) +   if (NOT _kcfgFile) +      set(_kcfgFile "${_testName}.kcfg") +   endif()     add_custom_command(        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_testName}.cpp ${CMAKE_CURRENT_BINARY_DIR}/${_testName}.h -      COMMAND ${KConfig_KCFGC_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${_testName}.kcfg ${CMAKE_CURRENT_SOURCE_DIR}/${_testName}.kcfgc -      DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${_testName}.kcfg ${CMAKE_CURRENT_SOURCE_DIR}/${_testName}.kcfgc kconfig_compiler_kf5) +      COMMAND ${KConfig_KCFGC_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${_kcfgFile} ${CMAKE_CURRENT_SOURCE_DIR}/${_testName}.kcfgc +      DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${_kcfgFile} ${CMAKE_CURRENT_SOURCE_DIR}/${_testName}.kcfgc kconfig_compiler_kf5) -#   set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${_testName}.h PROPERTIES GENERATED TRUE) +   # set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${_testName}.h PROPERTIES GENERATED TRUE)     qt5_generate_moc(${CMAKE_CURRENT_BINARY_DIR}/${_testName}.h ${CMAKE_CURRENT_BINARY_DIR}/${_testName}.moc ) -# do not run automoc on the generated file +   # do not run automoc on the generated file     set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${_testName}.cpp PROPERTIES SKIP_AUTOMOC TRUE)     set( ${_srcs} ${${_srcs}} ${CMAKE_CURRENT_BINARY_DIR}/${_testName}.cpp ${CMAKE_CURRENT_BINARY_DIR}/${_testName}.h ) @@ -197,6 +202,18 @@ target_link_libraries(test_signal KF5::ConfigGui)  ########### next target ############### +set(kconfigcompiler_test_signals_SRCS kconfigcompiler_test_signals.cpp) +gen_kcfg_test_source(signals_test_singleton kconfigcompiler_test_signals_SRCS KCFG signals_test.kcfg) +gen_kcfg_test_source(signals_test_no_singleton kconfigcompiler_test_signals_SRCS KCFG signals_test.kcfg) +gen_kcfg_test_source(signals_test_singleton_dpointer kconfigcompiler_test_signals_SRCS KCFG signals_test.kcfg) +gen_kcfg_test_source(signals_test_no_singleton_dpointer kconfigcompiler_test_signals_SRCS KCFG signals_test.kcfg) +add_executable(kconfigcompiler_test_signals ${kconfigcompiler_test_signals_SRCS}) +ecm_mark_as_test(kconfigcompiler_test_signals) +target_link_libraries(kconfigcompiler_test_signals Qt5::Test KF5::ConfigGui) +add_test(kconfig-kconfigcompiler-signals kconfigcompiler_test_signals) + +########### next target ############### +  set(kconfigcompiler_test_SRCS kconfigcompiler_test.cpp )  add_executable(kconfigcompiler_test ${kconfigcompiler_test_SRCS})  ecm_mark_as_test(kconfigcompiler_test) diff --git a/autotests/kconfig_compiler/kconfigcompiler_test_signals.cpp b/autotests/kconfig_compiler/kconfigcompiler_test_signals.cpp new file mode 100644 index 00000000..3017fe2f --- /dev/null +++ b/autotests/kconfig_compiler/kconfigcompiler_test_signals.cpp @@ -0,0 +1,214 @@ +/* +Copyright (c) 2014 Alexander Richardson <alex.richardson@gmx.de> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "signals_test_singleton.h" +#include "signals_test_no_singleton.h" +#include "signals_test_singleton_dpointer.h" +#include "signals_test_no_singleton_dpointer.h" +#include <QtTest/QtTestGui> +#include <QtTest/QSignalSpy> +#include <QtCore/QSharedPointer> +#include <QtCore/QtGlobal> +#include <QtCore/QDebug> +#include <QTemporaryFile> +#include <QFileInfo> +#include <functional> + +class KConfigCompiler_Test_Signals : public QObject +{ +    Q_OBJECT +private Q_SLOTS: +    void testSetters(); +    void testSetters_data(); +    void testSetProperty(); +    void testSetProperty_data(); +    void initTestCase(); +    void cleanupTestCase(); +}; + +static SignalsTestNoSingleton* noSingleton; +static SignalsTestNoSingletonDpointer* noSingletonDpointer; + +void KConfigCompiler_Test_Signals::initTestCase() +{ +    // These tests do a lot quite a few I/O operations, speed that up by using a QTemporaryFile. +    // At least on Linux this is often a tmpfs which means only RAM operations +    QTemporaryFile* tempFile1 = new QTemporaryFile(this); +    QTemporaryFile* tempFile2 = new QTemporaryFile(this); +    QTemporaryFile* tempFile3 = new QTemporaryFile(this); +    QTemporaryFile* tempFile4 = new QTemporaryFile(this); +    QVERIFY(tempFile1->open()); +    QVERIFY(tempFile2->open()); +    QVERIFY(tempFile3->open()); +    QVERIFY(tempFile4->open()); + +    SignalsTestSingleton::instance(QFileInfo(*tempFile1).absoluteFilePath()); +    SignalsTestSingletonDpointer::instance(QFileInfo(*tempFile2).absoluteFilePath()); +    noSingleton = new SignalsTestNoSingleton( +                KSharedConfig::openConfig(QFileInfo(*tempFile3).absoluteFilePath(), KConfig::SimpleConfig)); +    noSingletonDpointer = new SignalsTestNoSingletonDpointer( +                KSharedConfig::openConfig(QFileInfo(*tempFile4).absoluteFilePath(), KConfig::SimpleConfig)); +} + +void KConfigCompiler_Test_Signals::cleanupTestCase() +{ +    //ensure these instances are deleted before the temporary files are closed +    delete noSingleton; +    delete noSingletonDpointer; +    delete SignalsTestSingleton::self(); +    delete SignalsTestSingletonDpointer::self(); +} + +struct TestSettersArg { +    // default constructor required for Q_DECLARE_METATYPE +    TestSettersArg() : obj(nullptr) {} +    template<typename T> +    TestSettersArg(T* object) : obj(object) { +        // we can also call static methods using object->foo() so this works for all four cases +        getter = [object]() { return object->foo(); }; +        defaultGetter = [object]() { return object->defaultFooValue(); }; +        setter = [object](const QString& s) { object->setFoo(s); }; +    } +    KCoreConfigSkeleton* obj; +    std::function<QString()> getter; +    std::function<QString()> defaultGetter; +    std::function<void(const QString&)> setter; +}; + +Q_DECLARE_METATYPE(TestSettersArg); // so that QFETCH works + +void KConfigCompiler_Test_Signals::testSetters_data() +{ +    QTest::addColumn<TestSettersArg>("params"); +    QTest::newRow("singleton") << TestSettersArg(SignalsTestSingleton::self()); +    QTest::newRow("singleton dpointer") << TestSettersArg(SignalsTestSingletonDpointer::self()); +    QTest::newRow("non-singleton") << TestSettersArg(noSingleton); +    QTest::newRow("non-singleton dpointer") << TestSettersArg(noSingleton); +} + +/** Ensure that a signal is emitted whenever the data is changed by using the generated setters */ +void KConfigCompiler_Test_Signals::testSetters() +{ +    QFETCH(TestSettersArg, params); +    const char* signal = SIGNAL(fooChanged(QString)); +    const QString defaultValue = QStringLiteral("default"); +    const QString changedValue = QStringLiteral("changed"); + +    // make sure we are in the default state +    params.obj->setDefaults(); +    params.obj->writeConfig(); + +    QList<QVariant> args; +    QSignalSpy spy(params.obj, signal); +    QVERIFY2(spy.isValid(), signal); + +    //change value via setter, should get signal +    QCOMPARE(params.getter(), defaultValue); +    QCOMPARE(defaultValue, params.defaultGetter()); +    QCOMPARE(params.getter(), params.defaultGetter()); +    QVERIFY(changedValue != params.getter()); +    params.setter(changedValue); +    QCOMPARE(params.getter(), changedValue); +    QCOMPARE(spy.count(), 0); //should have no change yet, only after writeConfig() +    params.obj->writeConfig(); +    QCOMPARE(spy.count(), 1); +    args = spy.takeFirst(); +    QCOMPARE(args.size(), 1); +    QCOMPARE(args[0].value<QString>(), changedValue); + +    //reset to default values via setDefaults() +    QVERIFY(params.getter() != params.defaultGetter()); +    QVERIFY(params.getter() != defaultValue); +    QCOMPARE(params.getter(), changedValue); +    params.obj->setDefaults(); +    QCOMPARE(params.getter(), params.defaultGetter()); +    QCOMPARE(params.getter(), defaultValue); + +    QCOMPARE(spy.count(), 0); //should have no change yet, only after writeConfig() +    params.obj->writeConfig(); +    //TODO: This currently fails since setDefaults() does not yet cause emitting a signal +    QCOMPARE(spy.count(), 1); +    args = spy.takeFirst(); +    QCOMPARE(args.size(), 1); +    QCOMPARE(args[0].value<QString>(), defaultValue); +} + +Q_DECLARE_METATYPE(KCoreConfigSkeleton*); + +void KConfigCompiler_Test_Signals::testSetProperty_data() +{ +    QTest::addColumn<KCoreConfigSkeleton*>("obj"); +    QTest::newRow("singleton") << static_cast<KCoreConfigSkeleton*>(SignalsTestSingleton::self()); +    QTest::newRow("singleton dpointer") << static_cast<KCoreConfigSkeleton*>(SignalsTestSingletonDpointer::self()); +    QTest::newRow("non-singleton") << static_cast<KCoreConfigSkeleton*>(noSingleton); +    QTest::newRow("non-singleton dpointer") << static_cast<KCoreConfigSkeleton*>(noSingletonDpointer); +} + +/** Test that the signal is emitted when modifying the values using the underlying KConfigSkeletonItem (bypassing the setters) */ +void KConfigCompiler_Test_Signals::testSetProperty() +{ +    QFETCH(KCoreConfigSkeleton*, obj); +    const char* signal = SIGNAL(fooChanged(QString)); +    const QString propertyName = QStringLiteral("foo"); +    const QString defaultValue = QStringLiteral("default"); +    const QString newValue = QStringLiteral("changed"); +    obj->setDefaults(); +    obj->writeConfig(); + +    KConfigSkeletonItem* item = obj->findItem(propertyName); +    QVERIFY2(item, "Item must exist"); +    QVERIFY2(!item->isImmutable(), "Item must not be immutable"); +    QVERIFY2(!obj->isImmutable(propertyName), "Item must not be immutable"); + +    //listen for all expected signals +    QSignalSpy spy(obj, signal); +    QVERIFY2(spy.isValid(), signal); + +    QVERIFY(item->isEqual(defaultValue)); +    QVERIFY(!item->isEqual(newValue)); + +    item->setProperty(newValue); //change value now +    //should have no change yet, only after writeConfig() +    QCOMPARE(spy.count(), 0); +    obj->writeConfig(); +    //now check for the signal emissions +    QCOMPARE(spy.count(), 1); +    QList<QVariant> args = spy.takeFirst(); +    QCOMPARE(args.size(), 1); +    QVERIFY(item->isEqual(args[0])); + +    //now reset to default +    QVERIFY(!item->isEqual(defaultValue)); +    item->setDefault(); +    QVERIFY(item->isEqual(defaultValue)); +    //should have no change yet, only after writeConfig() +    QCOMPARE(spy.count(), 0); +    obj->writeConfig(); +    //now check for the signal emissions +    QCOMPARE(spy.count(), 1); +    args = spy.takeFirst(); +    QCOMPARE(args.size(), 1); +    QVERIFY(item->isEqual(args[0])); +} + +QTEST_MAIN(KConfigCompiler_Test_Signals) + +#include "kconfigcompiler_test_signals.moc" diff --git a/autotests/kconfig_compiler/signals_test.kcfg b/autotests/kconfig_compiler/signals_test.kcfg new file mode 100644 index 00000000..766c56eb --- /dev/null +++ b/autotests/kconfig_compiler/signals_test.kcfg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Author: Alex Richardson --> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" +      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 +      http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > +  <kcfgfile arg="true"/> +  <signal name="fooChanged"> +    <argument type="String">foo</argument> +  </signal> +  <group name="Something"> +    <entry key="foo" type="String"> +      <default>default</default> +      <emit signal="fooChanged" /> +    </entry> +  </group> +</kcfg> diff --git a/autotests/kconfig_compiler/signals_test_no_singleton.kcfgc b/autotests/kconfig_compiler/signals_test_no_singleton.kcfgc new file mode 100644 index 00000000..4d096e08 --- /dev/null +++ b/autotests/kconfig_compiler/signals_test_no_singleton.kcfgc @@ -0,0 +1,9 @@ +File=signals_test.kcfg +ClassName=SignalsTestNoSingleton +Singleton=false +Mutators=true +MemberVariables=private +GlobalEnums=false +UseEnumTypes=false +ItemAccessors=false +DefaultValueGetters=true diff --git a/autotests/kconfig_compiler/signals_test_no_singleton_dpointer.kcfgc b/autotests/kconfig_compiler/signals_test_no_singleton_dpointer.kcfgc new file mode 100644 index 00000000..11b7c435 --- /dev/null +++ b/autotests/kconfig_compiler/signals_test_no_singleton_dpointer.kcfgc @@ -0,0 +1,9 @@ +File=signals_test.kcfg +ClassName=SignalsTestNoSingletonDpointer +Singleton=false +Mutators=true +MemberVariables=dpointer +GlobalEnums=false +UseEnumTypes=false +ItemAccessors=false +DefaultValueGetters=true diff --git a/autotests/kconfig_compiler/signals_test_singleton.kcfgc b/autotests/kconfig_compiler/signals_test_singleton.kcfgc new file mode 100644 index 00000000..59e04452 --- /dev/null +++ b/autotests/kconfig_compiler/signals_test_singleton.kcfgc @@ -0,0 +1,9 @@ +File=signals_test.kcfg +ClassName=SignalsTestSingleton +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=false +UseEnumTypes=false +ItemAccessors=false +DefaultValueGetters=true diff --git a/autotests/kconfig_compiler/signals_test_singleton_dpointer.kcfgc b/autotests/kconfig_compiler/signals_test_singleton_dpointer.kcfgc new file mode 100644 index 00000000..1f13493a --- /dev/null +++ b/autotests/kconfig_compiler/signals_test_singleton_dpointer.kcfgc @@ -0,0 +1,9 @@ +File=signals_test.kcfg +ClassName=SignalsTestSingletonDpointer +Singleton=true +Mutators=true +MemberVariables=dpointer +GlobalEnums=false +UseEnumTypes=false +ItemAccessors=false +DefaultValueGetters=true diff --git a/autotests/kconfig_compiler/test_signal.cpp.ref b/autotests/kconfig_compiler/test_signal.cpp.ref index fd2d4bc9..35b5cba2 100644 --- a/autotests/kconfig_compiler/test_signal.cpp.ref +++ b/autotests/kconfig_compiler/test_signal.cpp.ref @@ -30,19 +30,21 @@ TestSignal::TestSignal(  )  {    Q_ASSERT(!s_globalTestSignal()->q);    s_globalTestSignal()->q = this; +  KConfigCompilerSignallingItem::NotifyFunction notifyFunction = static_cast<KConfigCompilerSignallingItem::NotifyFunction>(&TestSignal::itemChanged); +    setCurrentGroup( QLatin1String( "Appearance" ) ); -  KConfigSkeleton::ItemString  *itemEmoticonTheme; -  itemEmoticonTheme = new KConfigSkeleton::ItemString( currentGroup(), QLatin1String( "emoticonTheme" ), mEmoticonTheme, QLatin1String( "Default" ) ); +  KConfigCompilerSignallingItem  *itemEmoticonTheme; +  itemEmoticonTheme = new KConfigCompilerSignallingItem(new KConfigSkeleton::ItemString( currentGroup(), QLatin1String( "emoticonTheme" ), mEmoticonTheme, QLatin1String( "Default" ) ), this, notifyFunction, signalEmoticonSettingsChanged);    addItem( itemEmoticonTheme, QLatin1String( "emoticonTheme" ) ); -  KConfigSkeleton::ItemBool  *itemUseEmoticon; -  itemUseEmoticon = new KConfigSkeleton::ItemBool( currentGroup(), QLatin1String( "useEmoticon" ), mUseEmoticon, true ); +  KConfigCompilerSignallingItem  *itemUseEmoticon; +  itemUseEmoticon = new KConfigCompilerSignallingItem(new KConfigSkeleton::ItemBool( currentGroup(), QLatin1String( "useEmoticon" ), mUseEmoticon, true ), this, notifyFunction, signalEmoticonSettingsChanged);    addItem( itemUseEmoticon, QLatin1String( "useEmoticon" ) ); -  KConfigSkeleton::ItemBool  *itemEmoticonRequireSpace; -  itemEmoticonRequireSpace = new KConfigSkeleton::ItemBool( currentGroup(), QLatin1String( "emoticonRequireSpace" ), mEmoticonRequireSpace, true ); +  KConfigCompilerSignallingItem  *itemEmoticonRequireSpace; +  itemEmoticonRequireSpace = new KConfigCompilerSignallingItem(new KConfigSkeleton::ItemBool( currentGroup(), QLatin1String( "emoticonRequireSpace" ), mEmoticonRequireSpace, true ), this, notifyFunction, signalEmoticonSettingsChanged);    addItem( itemEmoticonRequireSpace, QLatin1String( "emoticonRequireSpace" ) ); -  KConfigSkeleton::ItemString  *itemStylePath; -  itemStylePath = new KConfigSkeleton::ItemString( currentGroup(), QLatin1String( "stylePath" ), mStylePath ); +  KConfigCompilerSignallingItem  *itemStylePath; +  itemStylePath = new KConfigCompilerSignallingItem(new KConfigSkeleton::ItemString( currentGroup(), QLatin1String( "stylePath" ), mStylePath ), this, notifyFunction, signalStyleChanged);    addItem( itemStylePath, QLatin1String( "stylePath" ) );    KConfigSkeleton::ItemString  *itemStyleCSSVariant;    itemStyleCSSVariant = new KConfigSkeleton::ItemString( currentGroup(), QLatin1String( "styleVariant" ), mStyleCSSVariant ); @@ -69,5 +71,9 @@ bool TestSignal::usrWriteConfig()    return true;  } +void TestSignal::itemChanged(quint64 flags) { +  mSettingsChanged |= flags; +} +  #include "test_signal.moc" diff --git a/autotests/kconfig_compiler/test_signal.h.ref b/autotests/kconfig_compiler/test_signal.h.ref index 737718d0..19b8b400 100644 --- a/autotests/kconfig_compiler/test_signal.h.ref +++ b/autotests/kconfig_compiler/test_signal.h.ref @@ -132,6 +132,9 @@ class TestSignal : public KConfigSkeleton      */      void styleChanged(const QString & stylePath, const QString & StyleCSSVariant); +  private: +    void itemChanged(quint64 flags); +    protected:      TestSignal();      friend class TestSignalHelper; diff --git a/src/core/kcoreconfigskeleton.cpp b/src/core/kcoreconfigskeleton.cpp index d9b95b4b..98d9cdcc 100644 --- a/src/core/kcoreconfigskeleton.cpp +++ b/src/core/kcoreconfigskeleton.cpp @@ -1339,3 +1339,77 @@ KConfigSkeletonItem *KCoreConfigSkeleton::findItem(const QString &name) const      return d->mItemDict.value(name);  } +KConfigCompilerSignallingItem::KConfigCompilerSignallingItem(KConfigSkeletonItem* item, QObject* object, +        KConfigCompilerSignallingItem::NotifyFunction targetFunction, quint64 userData) +    : KConfigSkeletonItem(item->group(), item->key()), mItem(item), mTargetFunction(targetFunction), +    mObject(object), mUserData(userData) +{ +    Q_ASSERT(mTargetFunction); +    Q_ASSERT(mItem); +    Q_ASSERT(mObject); +} + +KConfigCompilerSignallingItem::~KConfigCompilerSignallingItem() +{ +} + +bool KConfigCompilerSignallingItem::isEqual(const QVariant& p) const +{ +    return mItem->isEqual(p); +} + +QVariant KConfigCompilerSignallingItem::property() const +{ +    return mItem->property(); +} + +void KConfigCompilerSignallingItem::readConfig(KConfig* c) +{ +    QVariant oldValue = mItem->property(); +    mItem->readConfig(c); +    //readConfig() changes mIsImmutable, update it here as well +    KConfigGroup cg(c, mGroup ); +    readImmutability(cg); +    if (!mItem->isEqual(oldValue)) { +        invokeNotifyFunction(); +    } +} + +void KConfigCompilerSignallingItem::readDefault(KConfig* c) +{ +    mItem->readDefault(c); +    //readDefault() changes mIsImmutable, update it here as well +    KConfigGroup cg(c, mGroup ); +    readImmutability(cg); +} + +void KConfigCompilerSignallingItem::writeConfig(KConfig* c) +{ +    mItem->writeConfig(c); +} + +void KConfigCompilerSignallingItem::setDefault() +{ +    QVariant oldValue = mItem->property(); +    mItem->setDefault(); +    if (!mItem->isEqual(oldValue)) { +        invokeNotifyFunction(); +    } +} + +void KConfigCompilerSignallingItem::setProperty(const QVariant& p) +{ +    if (!mItem->isEqual(p)) { +        mItem->setProperty(p); +        invokeNotifyFunction(); +    } +} + +void KConfigCompilerSignallingItem::swapDefault() +{ +    QVariant oldValue = mItem->property(); +    mItem->swapDefault(); +    if (!mItem->isEqual(oldValue)) { +        invokeNotifyFunction(); +    } +} diff --git a/src/core/kcoreconfigskeleton.h b/src/core/kcoreconfigskeleton.h index c1a15877..9cd07994 100644 --- a/src/core/kcoreconfigskeleton.h +++ b/src/core/kcoreconfigskeleton.h @@ -304,6 +304,57 @@ protected:  };  /** + * \class KConfigSkeletonChangeNotifyingItem kcoreconfigskeleton.h <KConfigSkeletonChangeNotifyingItem> + * + * @author Alex Richardson + * @see KConfigSkeletonItem + * + * + * This class wraps a @ref KConfigSkeletonItem and invokes a function whenever the value changes. + * That function must take one quint64 parameter. Whenever the property value of the wrapped KConfigSkeletonItem + * changes this function will be invoked with the stored user data passed in the constructor. + * It does not call a function with the new value since this class is designed solely for the kconfig_compiler generated + * code and is therefore probably not suited for any other usecases. + */ +class KCONFIGCORE_EXPORT KConfigCompilerSignallingItem : public KConfigSkeletonItem +{ +public: +    typedef void (QObject::* NotifyFunction)(quint64 arg); +    /** +    * Constructor. +    * +    * @param item the KConfigSkeletonItem to wrap +    * @param targetFunction the method to invoke whenever the value of @p item changes +    * @param object The object on which the method is invoked. +    * @param userData This data will be passed to @p targetFunction on every property change +    */ +    KConfigCompilerSignallingItem(KConfigSkeletonItem *item, QObject* object, +            NotifyFunction targetFunction, quint64 userData); +    virtual ~KConfigCompilerSignallingItem(); + +    virtual void readConfig(KConfig *) Q_DECL_OVERRIDE; +    virtual void writeConfig(KConfig *) Q_DECL_OVERRIDE; +    virtual void readDefault(KConfig *) Q_DECL_OVERRIDE; +    virtual void setProperty(const QVariant &p) Q_DECL_OVERRIDE; +    virtual bool isEqual(const QVariant &p) const Q_DECL_OVERRIDE; +    virtual QVariant property() const Q_DECL_OVERRIDE; +    virtual void setDefault() Q_DECL_OVERRIDE; +    virtual void swapDefault() Q_DECL_OVERRIDE; +private: +    inline void invokeNotifyFunction() +    { +        // call the pointer to member function using the strange ->* operator +        (mObject->*mTargetFunction)(mUserData); +    } +private: +    QScopedPointer<KConfigSkeletonItem> mItem; +    NotifyFunction mTargetFunction; +    QObject* mObject; +    quint64 mUserData; +}; + + +/**   * \class KCoreConfigSkeleton kcoreconfigskeleton.h <KCoreConfigSkeleton>   *   * @short Class for handling preferences settings for an application. diff --git a/src/kconfig_compiler/kconfig_compiler.cpp b/src/kconfig_compiler/kconfig_compiler.cpp index 0c4254a2..7d84cfbc 100644 --- a/src/kconfig_compiler/kconfig_compiler.cpp +++ b/src/kconfig_compiler/kconfig_compiler.cpp @@ -1152,12 +1152,17 @@ static QString itemDeclaration(const CfgEntry *e, const CfgConfig &cfg)          return QString();      } +    QString type; +    if (!e->signalList().isEmpty()) { +        type = "KConfigCompilerSignallingItem"; +    } else { +        type = cfg.inherits + "::Item" + itemType(e->type()); +    } +      QString fCap = e->name();      fCap[0] = fCap[0].toUpper(); -    return "  " + cfg.inherits + "::Item" + itemType(e->type()) + -           "  *item" + fCap + -           ((!e->param().isEmpty()) ? (QString("[%1]").arg(e->paramMax() + 1)) : QString()) + -           ";\n"; +    return "  " + type + "  *item" + fCap + +            ( (!e->param().isEmpty())?(QString("[%1]").arg(e->paramMax()+1)) : QString()) + ";\n";  }  // returns the name of an item variable @@ -1192,24 +1197,41 @@ static QString itemPath(const CfgEntry *e, const CfgConfig &cfg)      return result;  } -QString newItem(const QString &type, const QString &name, const QString &key, -                const QString &defaultValue, const CfgConfig &cfg, const QString ¶m = QString()) -{ -    QString t = "new " + cfg.inherits + "::Item" + itemType(type) + -                "( currentGroup(), " + key + ", " + varPath(name, cfg) + param; -    if (type == "Enum") { -        t += ", values" + name; +QString newItem(const CfgEntry* entry, const QString &key, const QString& defaultValue, +                const CfgConfig &cfg, const QString ¶m = QString()) { + +    QList<Signal> sigs = entry->signalList(); +    QString t; +    if (!sigs.isEmpty()) { +        t += "new KConfigCompilerSignallingItem("; +    } +    t += "new "+ cfg.inherits + "::Item" + itemType(entry->type()) + "( currentGroup(), " +            + key + ", " + varPath( entry->name(), cfg ) + param; + +    if (entry->type() == "Enum") { +        t += ", values" + entry->name();      }      if (!defaultValue.isEmpty()) {          t += ", "; -        if (type == "String") { +        if (entry->type() == "String") {              t += defaultValue;          } else {              t += defaultValue;          }      } -    t += " );"; +    t += " )"; +    if (!sigs.isEmpty()) { +        t += ", this, notifyFunction, "; +        //append the signal flags +        for (int i = 0; i < sigs.size(); ++i) { +            if (i != 0) +                t += " | "; +            t += signalEnumName(sigs[i].name); +        } +        t += ")"; +    } +    t += ";";      return t;  } @@ -2019,6 +2041,10 @@ int main(int argc, char **argv)              h << ");" << endl;          }          h << endl; + +        h << "  private:" << endl; +        h << "    void itemChanged(quint64 flags);" << endl; +        h << endl;      }      h << "  protected:" << endl; @@ -2185,7 +2211,10 @@ int main(int argc, char **argv)          }          cpp << endl << "    // items" << endl;          for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { -            cpp << "    " + cfg.inherits + "::Item" << itemType((*itEntry)->type()) << " *" << itemVar(*itEntry, cfg); +            const QString declType = (*itEntry)->signalList().isEmpty() +                    ? QString(cfg.inherits + "::Item" + itemType((*itEntry)->type())) +                    : "KConfigCompilerSignallingItem"; +            cpp << "    " << declType << " *" << itemVar( *itEntry, cfg );              if (!(*itEntry)->param().isEmpty()) {                  cpp << QString("[%1]").arg((*itEntry)->paramMax() + 1);              } @@ -2302,6 +2331,14 @@ int main(int argc, char **argv)      group.clear(); +    if (hasSignals) { +        // this cast to base-class pointer-to-member is valid C++ +        // http://stackoverflow.com/questions/4272909/is-it-safe-to-upcast-a-method-pointer-and-use-it-with-base-class-pointer/ +        cpp << "  KConfigCompilerSignallingItem::NotifyFunction notifyFunction =" +            << " static_cast<KConfigCompilerSignallingItem::NotifyFunction>(&" +            << cfg.className << "::itemChanged);" << endl << endl; +    } +      for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) {          if ((*itEntry)->group() != group) {              if (!group.isEmpty()) { @@ -2353,7 +2390,7 @@ int main(int argc, char **argv)          if ((*itEntry)->param().isEmpty()) {              // Normal case              cpp << "  " << itemPath(*itEntry, cfg) << " = " -                << newItem((*itEntry)->type(), (*itEntry)->name(), key, (*itEntry)->defaultValue(), cfg) << endl; +                << newItem((*itEntry), key, (*itEntry)->defaultValue(), cfg) << endl;              if (!(*itEntry)->minValue().isEmpty()) {                  cpp << "  " << itemPath(*itEntry, cfg) << "->setMinValue(" << (*itEntry)->minValue() << ");" << endl; @@ -2388,8 +2425,7 @@ int main(int argc, char **argv)                  }                  cpp << "  " << itemVarStr << " = " -                    << newItem((*itEntry)->type(), (*itEntry)->name(), paramString(key, *itEntry, i), defaultStr, cfg,  QString("[%1]").arg(i)) -                    << endl; +                    << newItem((*itEntry), paramString(key, *itEntry, i), defaultStr, cfg, QString("[%1]").arg(i)) << endl;                  if (cfg.setUserTexts) {                      cpp << userTextsFunctions(*itEntry, cfg, itemVarStr, (*itEntry)->paramName()); @@ -2537,10 +2573,13 @@ int main(int argc, char **argv)          cpp << "  " << varPath("settingsChanged", cfg) << " = 0;" << endl;          cpp << "  return true;" << endl;          cpp << "}" << endl; -    } -    // Add includemoc if they are signals defined. -    if (hasSignals) { +        cpp << endl; +        cpp << "void " << cfg.className << "::" << "itemChanged(quint64 flags) {" << endl; +        cpp << "  " << varPath("settingsChanged", cfg) << " |= flags;" << endl; +        cpp << "}" << endl; + +        // Add includemoc if they are signals defined.          cpp << endl;          cpp << "#include \"" << mocFileName << "\"" << endl;          cpp << endl; | 
