#pragma once #include #include #include #include "listnode.hpp" #include "schema.hpp" namespace ZShell::settings { inline QVariantMap vmap( std::initializer_list> entries) { QVariantMap map; for (const auto& [key, value] : entries) map.insert(key, value); return map; } template inline QList unionTypes() { static_assert(sizeof...(Ts) >= 2, "A union needs at least two types"); static_assert(sizeof...(Ts) <= 4, "A union cannot have more than 4 types"); static_assert( (!std::is_same_v && ...), "A union cannot contain QVariant"); return {QMetaType::fromType()...}; } namespace detail { template inline bool compare(const T& a, const T& b) { return a == b; } template bool compare(const T& a, const T& b) { return qFuzzyCompare(a + T(1), b + T(1)); } template bool compare(const QList& a, const QList& b) { if (a.size() != b.size()) return false; for (qsizetype i = 0; i < a.size(); ++i) if (!compare(a.at(i), b.at(i))) return false; return true; } } // namespace detail } // namespace ZShell::settings #define DEFAULT_ARG(...) __VA_ARGS__ #define SETTINGS_NODE_NO_CTOR(Class, Base) \ Q_OBJECT \ \ public: \ [[nodiscard]] const ZShell::settings::Schema& schema() const override { \ static const auto schema = ZShell::settings::Schema::build( \ &staticMetaObject, Base::staticMetaObject.propertyCount()); \ return schema; \ } \ \ private: \ using Self = Class; // For use in the below macros #define SETTINGS_NODE(Class, Base) \ SETTINGS_NODE_NO_CTOR(Class, Base) \ QML_ANONYMOUS \ \ public: \ explicit Class( \ Class* fallback = nullptr, \ QObject* parent = nullptr, \ bool globalOnly = false) \ : Base(fallback, parent, globalOnly) {} \ \ private: #define SETTINGS_PROPERTY_IMPL(Type, name, global, defaultVal, ...) \ Q_PROPERTY(Type name READ name WRITE set_##name NOTIFY name##Changed) \ \ public: \ [[nodiscard]] Type name() const { \ if (global || m_globalOnly) warnGlobalRead(QStringLiteral(#name)); \ return m_##name; \ } \ \ void set_##name(const Type& value) { \ if (!true /* TODO: validation */) return; \ \ if (rejectInvalidWrite(QStringLiteral(#name), value)) \ return; /* Skip writes of the wrong type */ \ \ if (rejectGlobalWrite(QStringLiteral(#name))) \ return; /* Skip writes to global only keys */ \ \ const auto needsNotify = \ !ZShell::settings::detail::compare(value, m_##name); \ m_##name = value; \ if (recordWrite(QStringLiteral(#name), needsNotify)) \ Q_EMIT name##Changed(); \ } \ \ Q_SIGNAL void name##Changed(); \ \ private: \ Type m_##name = fallbackValue(&Self::m_##name, defaultVal); \ inline static const bool s_register_##name = \ (ZShell::settings::Schema::annotate( \ &staticMetaObject, \ QStringLiteral(#name), \ {.defaultValue = QVariant::fromValue(defaultVal), \ .globalOnly = global, \ __VA_ARGS__}), \ true); #define SETTINGS_PROPERTY(Type, name, defaultVal, ...) \ SETTINGS_PROPERTY_IMPL( \ Type, name, false, DEFAULT_ARG(defaultVal), __VA_ARGS__) // Defines a global property on a node. Shorthand for .globalOnly = true. #define SETTINGS_GLOBAL_PROPERTY(Type, name, defaultVal, ...) \ SETTINGS_PROPERTY_IMPL( \ Type, name, true, DEFAULT_ARG(defaultVal), __VA_ARGS__) // Defines a subobject property on a node. Subobject properties are CONSTANT. #define SETTINGS_SUBOBJECT_IMPL(Type, name, global) \ Q_PROPERTY(Type* name READ name CONSTANT) \ \ public: \ [[nodiscard]] Type* name() const { \ if (global || m_globalOnly) warnGlobalRead(QStringLiteral(#name)); \ return m_##name; \ } \ \ private: \ Type* m_##name = \ new Type(fallbackValue(&Self::m_##name, nullptr), this, global); \ inline static const bool s_register_##name = \ (ZShell::settings::Schema::annotate( \ &staticMetaObject, \ QStringLiteral(#name), \ {.defaultValue = {}, .globalOnly = global}), \ true); #define SETTINGS_SUBOBJECT(Type, name) \ SETTINGS_SUBOBJECT_IMPL(Type, name, false) #define SETTINGS_GLOBAL_SUBOBJECT(Type, name) \ SETTINGS_SUBOBJECT_IMPL(Type, name, true) #define SETTINGS_LIST_TYPE(Element, Name) \ class Name : public ZShell::settings::ListNode { \ Q_OBJECT \ QML_ANONYMOUS \ \ public: \ explicit Name( \ Name* fallback = nullptr, \ QObject* parent = nullptr, \ bool globalOnly = false) \ : ZShell::settings::ListNode(fallback, parent, globalOnly) {} \ \ [[nodiscard]] Q_INVOKABLE Element* at( \ qsizetype index) const { /* Format ugh */ \ return static_cast(elementAt(index)); \ } \ [[nodiscard]] Q_INVOKABLE Element* insert( \ const QVariantMap& props, qsizetype index = -1) { \ return static_cast(insertElement(props, index)); \ } \ \ protected: \ [[nodiscard]] ZShell::settings::Node* createElement( \ ZShell::settings::Node* fallback) override { \ return new Element(static_cast(fallback), this); \ } \ }; #define SETTINGS_LIST_IMPL(Type, name, global, defaultVal, ...) \ Q_PROPERTY(Type* name READ name CONSTANT) \ \ public: \ [[nodiscard]] Type* name() const { \ if (global || m_globalOnly) warnGlobalRead(QStringLiteral(#name)); \ return m_##name; \ } \ \ private: \ Type* m_##name = \ new Type(fallbackValue(&Self::m_##name, nullptr), this, global); \ inline static const bool s_register_##name = \ (ZShell::settings::Schema::annotate( \ &staticMetaObject, \ QStringLiteral(#name), \ {.defaultValue = \ QVariant::fromValue>(defaultVal), \ .globalOnly = global, \ __VA_ARGS__}), \ true); #define SETTINGS_LIST(Type, name, defaultVal, ...) \ SETTINGS_LIST_IMPL(Type, name, false, DEFAULT_ARG(defaultVal), __VA_ARGS__) #define SETTINGS_GLOBAL_LIST(Type, name, defaultVal, ...) \ SETTINGS_LIST_IMPL(Type, name, true, DEFAULT_ARG(defaultVal), __VA_ARGS__)