diff --git a/src/qmlcontrols/kcmcontrols/CMakeLists.txt b/src/qmlcontrols/kcmcontrols/CMakeLists.txt index 94a4873..c177656 100644 --- a/src/qmlcontrols/kcmcontrols/CMakeLists.txt +++ b/src/qmlcontrols/kcmcontrols/CMakeLists.txt @@ -1,19 +1,22 @@ project(kcmcontrols) set(kcmcontrols_SRCS kcmcontrolsplugin.cpp + settingstateproxy.cpp + settingstatebindingprivate.cpp ) add_library(kcmcontrolsplugin SHARED ${kcmcontrols_SRCS}) target_link_libraries(kcmcontrolsplugin Qt5::Core Qt5::Quick Qt5::Qml Qt5::Gui KF5::CoreAddons KF5::QuickAddons + KF5::ConfigCore ) install(TARGETS kcmcontrolsplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kcm) install(DIRECTORY qml/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kcm) diff --git a/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp b/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp index 03ee47e..63e4a88 100644 --- a/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp +++ b/src/qmlcontrols/kcmcontrols/kcmcontrolsplugin.cpp @@ -1,35 +1,38 @@ /* Copyright 2017 by Marco Martin 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 OR COPYRIGHT HOLDERS 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 "kcmcontrolsplugin.h" #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 index 0000000..4fd3501 --- /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 index 0000000..5db2b76 --- /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 index a553909..6f3b147 100644 --- a/src/qmlcontrols/kcmcontrols/qml/qmldir +++ b/src/qmlcontrols/kcmcontrols/qml/qmldir @@ -1,9 +1,11 @@ module org.kde.kcm plugin kcmcontrolsplugin GridDelegate 1.1 GridDelegate.qml GridViewKCM 1.1 GridViewKCM.qml GridView 1.1 GridView.qml 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.cpp b/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.cpp new file mode 100644 index 0000000..bde47ec --- /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/settingstatebindingprivate.h b/src/qmlcontrols/kcmcontrols/settingstatebindingprivate.h new file mode 100644 index 0000000..9008d59 --- /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/settingstateproxy.cpp b/src/qmlcontrols/kcmcontrols/settingstateproxy.cpp new file mode 100644 index 0000000..1e6d3c2 --- /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); +} diff --git a/src/qmlcontrols/kcmcontrols/settingstateproxy.h b/src/qmlcontrols/kcmcontrols/settingstateproxy.h new file mode 100644 index 0000000..eadc950 --- /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