diff --git a/src/qmlcontrols/kcmcontrols/CMakeLists.txt b/src/qmlcontrols/kcmcontrols/CMakeLists.txt --- a/src/qmlcontrols/kcmcontrols/CMakeLists.txt +++ b/src/qmlcontrols/kcmcontrols/CMakeLists.txt @@ -2,6 +2,8 @@ set(kcmcontrols_SRCS kcmcontrolsplugin.cpp + settingstateproxy.cpp + settingstatebindingprivate.cpp ) add_library(kcmcontrolsplugin SHARED ${kcmcontrols_SRCS}) @@ -12,6 +14,7 @@ Qt5::Gui KF5::CoreAddons KF5::QuickAddons + KF5::ConfigCore ) install(TARGETS kcmcontrolsplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kcm) diff --git a/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp b/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp --- a/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp +++ b/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp @@ -24,12 +24,15 @@ #include - +#include "settingstateproxy.h" +#include "settingstatebindingprivate.h" void KCMControlsPlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QByteArray("org.kde.kcm")); - qmlRegisterUncreatableType("org.kde.kcm", 1, 0, "ConfigModule", + qmlRegisterUncreatableType(uri, 1, 0, "ConfigModule", QLatin1String("Do not create objects of type ConfigModule")); + qmlRegisterType(uri, 1, 3, "SettingStateProxy"); + qmlRegisterType("org.kde.kcm.private", 1, 3, "SettingStateBindingPrivate"); } diff --git a/src/qmlcontrols/kcmcontrols/qml/SettingStateBinding.qml b/src/qmlcontrols/kcmcontrols/qml/SettingStateBinding.qml new file mode 100644 --- /dev/null +++ b/src/qmlcontrols/kcmcontrols/qml/SettingStateBinding.qml @@ -0,0 +1,123 @@ +/* + Copyright (c) 2020 Kevin Ottens + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + 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. +*/ + +import QtQuick 2.8 +import QtQuick.Layouts 1.3 +import org.kde.kcm 1.3 as KCM +import org.kde.kcm.private 1.3 + +/** + * SettingStateBinding automatically impacts the representation + * of an item based on the state of a setting. It will disable + * the item if the setting is immutable and use a visual indicator + * for the state of the setting. + * + * This is a higher level convenience wrapper for KCM.SettingStateProxy + * and KCM.SettingStateIndicator. + * + * @since 5.70 + */ +Item { + id: root + + /** + * target: Item + * The graphical element whose state we want to manage based on a setting + */ + property Item target + + /** + * configObject: KCoreConfigSkeleton + * The config object which will be monitored for setting state changes + */ + property alias configObject: settingState.configObject + + /** + * itemName: string + * The name of the item representing the setting in the config object + */ + property alias itemName: settingState.itemName + + /** + * extraEnabledPredicate: bool + * SettingStateBinding will manage the enabled property of the target + * based on the immutability state of the setting it represents. But, + * sometimes that enabled state needs to bind to other properties as + * well to be computed properly. This extra predicate will thus be + * combined with the immutability state of the setting to determine + * the effective enabled state of the target. + */ + property bool extraEnabledPredicate: true + + /** + * indicatorAsOverlay: bool + * If true the state indicator will be displayed inside the boundaries + * of the target item which is convenient for items with lots of empty + * space like list or grid views (default is false) + */ + property bool indicatorAsOverlay: false + + + QtObject { + id: impl + property KCM.SettingStateIndicator indicator: null + } + + onTargetChanged: { + if (impl.indicator) { + impl.indicator.destroy() + impl.indicator = null + } + + if (target) { + impl.indicator = indicatorComponent.createObject(target, { + settingState: settingState + }) + } + } + + // We use it via a Component because we potentially need + // to escape the parent/siblings only constraint for anchoring + // (since we anchor to root.target which could be anywhere) + Component { + id: indicatorComponent + KCM.SettingStateIndicator { + readonly property real directionFactor: (Qt.application.layoutDirection === Qt.LeftToRight ? 1 : -1) + x: helper.leftCoord - (root.indicatorAsOverlay ? width : 0) * directionFactor + y: root.indicatorAsOverlay ? 0 : Math.round((parent.height - height) / 2) + settingState: impl.settingState + } + } + + Binding { + target: root.target + property: "enabled" + value: extraEnabledPredicate && !settingState.immutable + } + + KCM.SettingStateProxy { + id: settingState + } + + SettingStateBindingPrivate { + id: helper + target: root.target + indicator: impl.indicator + indicatorAsOverlay: root.indicatorAsOverlay + } +} diff --git a/src/qmlcontrols/kcmcontrols/qml/SettingStateIndicator.qml b/src/qmlcontrols/kcmcontrols/qml/SettingStateIndicator.qml new file mode 100644 --- /dev/null +++ b/src/qmlcontrols/kcmcontrols/qml/SettingStateIndicator.qml @@ -0,0 +1,50 @@ +/* + Copyright (c) 2020 Kevin Ottens + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + 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. +*/ + +import QtQuick 2.8 +import QtQuick.Controls 2.12 +import org.kde.kcm 1.3 as KCM +import org.kde.kirigami 2.2 as Kirigami + +/** + * A simple icon which automatically picks an icon, displays or hides itself + * depending on a setting state. + * + * @since 5.70 + */ +ToolButton { + id: root + + /** + * settingState: KCM.SettingStateProxy + * The state used by the indicator to decides if and how it is displayed + */ + property KCM.SettingStateProxy settingState + + visible: settingState && !settingState.defaulted + focusPolicy: Qt.NoFocus + + implicitWidth: icon.width + Kirigami.Units.largeSpacing + implicitHeight: icon.height + Kirigami.Units.largeSpacing + + icon.name: "edit-reset" + icon.width: Kirigami.Units.iconSizes.small + icon.height: Kirigami.Units.iconSizes.small + + onClicked: settingState.resetToDefault() +} diff --git a/src/qmlcontrols/kcmcontrols/qml/qmldir b/src/qmlcontrols/kcmcontrols/qml/qmldir --- a/src/qmlcontrols/kcmcontrols/qml/qmldir +++ b/src/qmlcontrols/kcmcontrols/qml/qmldir @@ -7,3 +7,5 @@ ScrollView 1.2 ScrollView.qml ScrollViewKCM 1.2 ScrollViewKCM.qml SimpleKCM 1.1 SimpleKCM.qml +SettingStateIndicator 1.3 SettingStateIndicator.qml +SettingStateBinding 1.3 SettingStateBinding.qml diff --git a/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.h b/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.h new file mode 100644 --- /dev/null +++ b/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.h @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Kevin Ottens + * + * 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 SETTINGSTATEBINDINGPRIVATE_H +#define SETTINGSTATEBINDINGPRIVATE_H + +#include +#include + +class SettingStateBindingPrivate : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(QQuickItem *indicator READ indicator WRITE setIndicator NOTIFY indicatorChanged) + Q_PROPERTY(bool indicatorAsOverlay READ isIndicatorAsOverlay WRITE setIndicatorAsOverlay NOTIFY indicatorAsOverlayChanged) + Q_PROPERTY(qreal leftCoord READ leftCoord NOTIFY leftCoordChanged) +public: + using QObject::QObject; + + QQuickItem *target() const; + void setTarget(QQuickItem *target); + + QQuickItem *indicator() const; + void setIndicator(QQuickItem *indicator); + + bool isIndicatorAsOverlay() const; + void setIndicatorAsOverlay(bool asOverlay); + + qreal leftCoord() const; + +Q_SIGNALS: + void targetChanged(); + void indicatorChanged(); + void indicatorAsOverlayChanged(); + void leftCoordChanged(); + +private: + qreal formLeftCoord() const; + qreal overlayLeftCoord() const; + + void triggerCoordChanges(); + + QPointer m_target; + QPointer m_indicator; + bool m_indicatorAsOverlay = false; +}; + +#endif diff --git a/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.cpp b/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.cpp new file mode 100644 --- /dev/null +++ b/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.cpp @@ -0,0 +1,279 @@ +/* + * Copyright 2020 Kevin Ottens + * + * 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 "settingstatebindingprivate.h" + +#include +#include + +namespace { +constexpr const char *bindingProperty = "__SettingStateBindingPrivate_binding"; +constexpr const char *hasBindingProperty = "__SettingStateBindingPrivate_hasBinding"; + +QByteArray itemClassName(QQuickItem *item) +{ + // Split since some exported types will be of the form: Foo_QMLTYPE_XX + const auto className = QByteArray(item->metaObject()->className()).split('_').first(); + return className; +} + +bool isLayout(QQuickItem *item) +{ + const auto className = itemClassName(item); + // We pretend StackLayout to not be a layout so that + // later on the positioning of one tab isn't polluted + // by the elemends from another tab. + if (className.endsWith("StackLayout")) { + return false; + } else { + return className.endsWith("Layout") + || className.endsWith("Column") + || className.endsWith("Row") + || className.endsWith("Grid"); + } +} + +bool isView(QQuickItem *item) +{ + const auto className = itemClassName(item); + return className.contains("GridView") + || className.endsWith("ListView"); +} + +bool hasNonOverlayIndicator(QQuickItem *item) +{ + if (!item->property(hasBindingProperty).toBool()) { + return false; + } + + const auto binding = item->property(bindingProperty).value(); + return !binding->isIndicatorAsOverlay(); +} + +QList findDescendantItems(QQuickItem *item) +{ + const auto children = item->childItems(); + auto result = children; + for (auto child : children) { + result += findDescendantItems(child); + } + return result; +} + +QQuickItem *findOuterLayout(QQuickItem *item) +{ + if (!item->parentItem()) { + return item; + } else if (isLayout(item) && !isLayout(item->parentItem())) { + return item; + } else { + return findOuterLayout(item->parentItem()); + } +} + +QQuickItem *findOuterView(QQuickItem *item) +{ + // This is mostly useful for situations where + // the target is a GridViewInternal for some + // reason but really what we need is finding the GridView + if (!isView(item)) { + return nullptr; + } else { + const auto parentView = findOuterView(item->parentItem()); + if (!parentView) { + return item; + } else { + return parentView; + } + } +} + +} + +QQuickItem *SettingStateBindingPrivate::target() const +{ + return m_target; +} + +void SettingStateBindingPrivate::setTarget(QQuickItem *target) +{ + if (m_target == target) { + return; + } + + if (m_target) { + m_target->setProperty(hasBindingProperty, QVariant()); + m_target->setProperty(bindingProperty, QVariant()); + disconnect(m_target); + + const auto outerView = findOuterView(m_target); + if (outerView) { + disconnect(outerView); + } + } + + m_target = target; + if (m_target) { + m_target->setProperty(hasBindingProperty, true); + m_target->setProperty(bindingProperty, QVariant::fromValue(this)); + connect(m_target, &QQuickItem::parentChanged, this, &SettingStateBindingPrivate::triggerCoordChanges); + connect(m_target, &QQuickItem::widthChanged, this, &SettingStateBindingPrivate::triggerCoordChanges); + connect(m_target, &QQuickItem::heightChanged, this, &SettingStateBindingPrivate::triggerCoordChanges); + connect(m_target, &QQuickItem::xChanged, this, &SettingStateBindingPrivate::triggerCoordChanges); + connect(m_target, &QQuickItem::yChanged, this, &SettingStateBindingPrivate::triggerCoordChanges); + + // For indicators which are in overlay mode, they need to change position + // when scrollbars are shown/hidden + const auto outerView = findOuterView(m_target); + const auto view = outerView? outerView : m_target.data(); + connect(view, &QQuickItem::visibleChildrenChanged, this, &SettingStateBindingPrivate::triggerCoordChanges); + } + + emit targetChanged(); + triggerCoordChanges(); +} + +QQuickItem *SettingStateBindingPrivate::indicator() const +{ + return m_indicator; +} + +void SettingStateBindingPrivate::setIndicator(QQuickItem *indicator) +{ + if (m_indicator == indicator) { + return; + } + + m_indicator = indicator; + + emit indicatorChanged(); + triggerCoordChanges(); +} + +bool SettingStateBindingPrivate::isIndicatorAsOverlay() const +{ + return m_indicatorAsOverlay; +} + +void SettingStateBindingPrivate::setIndicatorAsOverlay(bool asOverlay) +{ + if (m_indicatorAsOverlay == asOverlay) { + return; + } + + m_indicatorAsOverlay = asOverlay; + + emit indicatorAsOverlayChanged(); + triggerCoordChanges(); +} + +qreal SettingStateBindingPrivate::leftCoord() const +{ + if (!m_target || !m_indicator) { + return 0.0; + } + + if (m_indicatorAsOverlay) { + return overlayLeftCoord(); + } else { + return formLeftCoord(); + } +} + +qreal SettingStateBindingPrivate::formLeftCoord() const +{ + const auto parent = m_indicator->parentItem(); + const auto outerLayout = findOuterLayout(m_target); + + const auto siblings = [=] { + auto result = findDescendantItems(outerLayout); + result.erase(std::remove_if(result.begin(), result.end(), + [=](QQuickItem *item) { + return !hasNonOverlayIndicator(item); + }), + result.end()); + return result; + }(); + + const auto leftToRight = QGuiApplication::layoutDirection() == Qt::LeftToRight; + const auto xValues = [=] { + auto result = QVector(); + result.reserve(siblings.size()); + std::transform(siblings.cbegin(), siblings.cend(), + std::back_inserter(result), + [=](QQuickItem *item) { + const auto localX = leftToRight ? item->width() : -m_indicator->width(); + return parent->mapFromItem(item, {localX, 0.0}).x(); + }); + return result; + }(); + if (leftToRight) { + return *std::max_element(xValues.cbegin(), xValues.cend()); + } else { + return *std::min_element(xValues.cbegin(), xValues.cend()); + } +} + +qreal SettingStateBindingPrivate::overlayLeftCoord() const +{ + const auto parent = m_indicator->parentItem(); + const auto leftToRight = QGuiApplication::layoutDirection() == Qt::LeftToRight; + + const auto localX = leftToRight ? m_target->width() : -m_indicator->width(); + const auto parentX = parent->mapFromItem(m_target, {localX, 0.0}).x(); + + const auto outerView = findOuterView(m_target); + + // Heuristic to find the vertical scroll bars which are visible and which + // might overlap with the indicator if left on the board of a view + const auto verticalScrollbars = [=] { + auto result = findDescendantItems(outerView? outerView : m_target.data()); + result.erase(std::remove_if(result.begin(), result.end(), + [=](QQuickItem *item) { + if (!item->isVisible() + || itemClassName(item) != "ScrollBar" + || item->width() > item->height()) { + return true; + } + + const auto localSbX = leftToRight ? item->width() : -m_indicator->width(); + const auto parentSbX = parent->mapFromItem(item, {localSbX, 0.0}).x(); + return qAbs(parentSbX - parentX) > m_indicator->width(); + }), + result.end()); + return result; + }(); + Q_ASSERT(verticalScrollbars.size() <= 1); + + const auto scrollbarWidth = verticalScrollbars.isEmpty() ? 0.0 : verticalScrollbars.first()->width(); + return parentX + (leftToRight? -1.0 : 1.0) * scrollbarWidth; +} + +void SettingStateBindingPrivate::triggerCoordChanges() +{ + const auto outerLayout = findOuterLayout(m_target); + const auto items = findDescendantItems(outerLayout); + + for (auto item : items) { + const auto binding = item->property(bindingProperty).value(); + if (binding) { + emit binding->leftCoordChanged(); + } + } +} diff --git a/src/qmlcontrols/kcmcontrols/settingstateproxy.h b/src/qmlcontrols/kcmcontrols/settingstateproxy.h new file mode 100644 --- /dev/null +++ b/src/qmlcontrols/kcmcontrols/settingstateproxy.h @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Kevin Ottens + * + * 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 SETTINGSTATEPROXY_H +#define SETTINGSTATEPROXY_H + +#include +#include + +#include + +/** + * This element allows to represent in a declarative way the + * state of a particular setting in a config object. + * + * @since 5.70 + */ +class SettingStateProxy : public QObject +{ + Q_OBJECT + + /** + * The config object which will be monitored for setting state changes + */ + Q_PROPERTY(KCoreConfigSkeleton *configObject READ configObject WRITE setConfigObject NOTIFY configObjectChanged) + + /** + * The name of the item representing the setting in the config object + */ + Q_PROPERTY(QString itemName READ itemName WRITE setItemName NOTIFY itemNameChanged) + + + /** + * Indicates if the setting is marked as immutable + */ + Q_PROPERTY(bool immutable READ isImmutable NOTIFY immutableChanged) + + + /** + * Indicates if the setting differs from its default value + */ + Q_PROPERTY(bool defaulted READ isDefaulted NOTIFY defaultedChanged) +public: + using QObject::QObject; + + KCoreConfigSkeleton *configObject() const; + void setConfigObject(KCoreConfigSkeleton *configObject); + + QString itemName() const; + void setItemName(const QString &itemName); + + bool isImmutable() const; + bool isDefaulted() const; + + Q_INVOKABLE void resetToDefault(); + +Q_SIGNALS: + void configObjectChanged(); + void itemNameChanged(); + + void immutableChanged(); + void defaultedChanged(); + +private Q_SLOTS: + void updateState(); + +private: + void connectSetting(); + + QPointer m_configObject; + QString m_itemName; + bool m_immutable = false; + bool m_defaulted = true; +}; + +#endif diff --git a/src/qmlcontrols/kcmcontrols/settingstateproxy.cpp b/src/qmlcontrols/kcmcontrols/settingstateproxy.cpp new file mode 100644 --- /dev/null +++ b/src/qmlcontrols/kcmcontrols/settingstateproxy.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2020 Kevin Ottens + * + * 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 "settingstateproxy.h" + +#include +#include + +KCoreConfigSkeleton *SettingStateProxy::configObject() const +{ + return m_configObject; +} + +void SettingStateProxy::setConfigObject(KCoreConfigSkeleton *configObject) +{ + if (m_configObject == configObject) { + return; + } + + if (m_configObject) { + m_configObject->disconnect(this); + } + + m_configObject = configObject; + emit configObjectChanged(); + updateState(); + connectSetting(); +} + +QString SettingStateProxy::itemName() const +{ + return m_itemName; +} + +void SettingStateProxy::setItemName(const QString &itemName) +{ + if (m_itemName == itemName) { + return; + } + + if (m_configObject) { + m_configObject->disconnect(this); + } + + m_itemName = itemName; + emit itemNameChanged(); + updateState(); + connectSetting(); +} + +bool SettingStateProxy::isImmutable() const +{ + return m_immutable; +} + +bool SettingStateProxy::isDefaulted() const +{ + return m_defaulted; +} + +void SettingStateProxy::resetToDefault() +{ + const auto item = m_configObject ? m_configObject->findItem(m_itemName) : nullptr; + if (item) { + item->setDefault(); + } +} + +void SettingStateProxy::updateState() +{ + const auto item = m_configObject ? m_configObject->findItem(m_itemName) : nullptr; + const auto immutable = item ? item->isImmutable() : false; + const auto defaulted = item ? item->isDefault() : true; + + if (m_immutable != immutable) { + m_immutable = immutable; + emit immutableChanged(); + } + + if (m_defaulted != defaulted) { + m_defaulted = defaulted; + emit defaultedChanged(); + } +} + +void SettingStateProxy::connectSetting() +{ + const auto item = m_configObject ? m_configObject->findItem(m_itemName) : nullptr; + if (!item) { + return; + } + + const auto updateStateSlotIndex = metaObject()->indexOfMethod("updateState()"); + Q_ASSERT(updateStateSlotIndex >= 0); + const auto updateStateSlot = metaObject()->method(updateStateSlotIndex); + Q_ASSERT(updateStateSlot.isValid()); + + const auto itemHasSignals = dynamic_cast(item) || dynamic_cast(item); + if (!itemHasSignals) { + qWarning() << "Attempting to use SettingStateProxy with a non signalling item:" << m_itemName; + return; + } + + const auto propertyName = [this] { + auto name = m_itemName; + if (name.at(0).isUpper()) { + name[0] = name[0].toLower(); + } + return name.toUtf8(); + }(); + + const auto metaObject = m_configObject->metaObject(); + const auto propertyIndex = metaObject->indexOfProperty(propertyName.constData()); + Q_ASSERT(propertyIndex >= 0); + const auto property = metaObject->property(propertyIndex); + Q_ASSERT(property.isValid()); + if (!property.hasNotifySignal()) { + qWarning() << "Attempting to use SettingStateProxy with a non notifying property:" << propertyName; + return; + } + + const auto changedSignal = property.notifySignal(); + Q_ASSERT(changedSignal.isValid()); + connect(m_configObject, changedSignal, this, updateStateSlot); + connect(m_configObject, &KCoreConfigSkeleton::configChanged, + this, &SettingStateProxy::updateState); +}