From 6a7f6d36715ec91114b27b7568ef482837e8c651 Mon Sep 17 00:00:00 2001 From: Kjell Ahlstedt Date: Mon, 16 Jan 2023 14:28:13 +0100 Subject: Gio::Settings: Add bind() overloads and unbind() Add the bind() overloads with mapping functions. --- gio/src/settings.ccg | 91 ++++++++++++++++++ gio/src/settings.hg | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 353 insertions(+), 4 deletions(-) diff --git a/gio/src/settings.ccg b/gio/src/settings.ccg index 05ff5e21..7343bb64 100644 --- a/gio/src/settings.ccg +++ b/gio/src/settings.ccg @@ -19,9 +19,94 @@ #include #include +namespace +{ +struct SettingsMapSlots +{ + SettingsMapSlots(const Gio::Settings::SlotGetMapping& get_mapping, + const Gio::Settings::SlotSetMapping& set_mapping) + : from_setting_to_property(get_mapping), from_property_to_setting(set_mapping) + { + } + + Gio::Settings::SlotGetMapping from_setting_to_property; + Gio::Settings::SlotSetMapping from_property_to_setting; +}; + +gboolean +Settings_get_mapping_callback( + GValue* to_value, GVariant* from_variant, gpointer user_data) +{ + Gio::Settings::SlotGetMapping& the_slot = + static_cast(user_data)->from_setting_to_property; + + bool result = false; + try + { + result = the_slot(to_value, from_variant); + } + catch (...) + { + Glib::exception_handlers_invoke(); + } + return result; +} + +GVariant* +Settings_set_mapping_callback( + const GValue* from_value, const GVariantType* expected_type, gpointer user_data) +{ + Gio::Settings::SlotSetMapping& the_slot = + static_cast(user_data)->from_property_to_setting; + + GVariant* result = nullptr; + try + { + result = the_slot(from_value, expected_type); + } + catch (...) + { + Glib::exception_handlers_invoke(); + } + return result; +} + +void +Settings_map_callback_destroy(gpointer user_data) +{ + delete static_cast(user_data); +} + +} // anonymous namespace + namespace Gio { +void Settings::bind_value(const Glib::ustring& key, + Glib::ObjectBase* object, const Glib::ustring& property, BindFlags flags, + const SlotGetMapping& get_mapping, const SlotSetMapping& set_mapping) +{ + if (get_mapping.empty() && set_mapping.empty()) + { + // No user-supplied mappings. + g_settings_bind(gobj(), key.c_str(), object->gobj(), + property.c_str(), (GSettingsBindFlags)flags); + } + else + { + // Create copies of the slots. A pointer to this will be passed + // through the callback's data parameter. It will be deleted + // when Settings_map_callback_destroy() is called. + SettingsMapSlots* slots_copy = new SettingsMapSlots(get_mapping, set_mapping); + + g_settings_bind_with_mapping(gobj(), key.c_str(), + object->gobj(), property.c_str(), (GSettingsBindFlags)flags, + get_mapping.empty() ? nullptr : &Settings_get_mapping_callback, + set_mapping.empty() ? nullptr : &Settings_set_mapping_callback, slots_copy, + &Settings_map_callback_destroy); + } +} + void Settings::get_value(const Glib::ustring& key, Glib::VariantBase& value) const { @@ -66,4 +151,10 @@ Settings::bind_writable( bind_writable(key, property_proxy.get_object(), property_proxy.get_name(), inverted); } +//static +void Settings::unbind(const Glib::PropertyProxy_Base& property_proxy) +{ + unbind(property_proxy.get_object(), property_proxy.get_name()); +} + } diff --git a/gio/src/settings.hg b/gio/src/settings.hg index d08260d2..66c8b4d5 100644 --- a/gio/src/settings.hg +++ b/gio/src/settings.hg @@ -176,17 +176,190 @@ public: _WRAP_METHOD(std::vector list_children() const, g_settings_list_children) _IGNORE(g_settings_list_keys) - _IGNORE(g_settings_get_range, g_settings_list_relocatable_schemas) // deprecated + _IGNORE(g_settings_get_range, g_settings_list_relocatable_schemas) dnl// deprecated _IGNORE(g_settings_range_check) #m4 _CONVERSION(`Glib::ObjectBase*',`gpointer',(gpointer)$3->gobj()) _WRAP_METHOD(void bind(const Glib::ustring& key, Glib::ObjectBase* object, const Glib::ustring& property, BindFlags flags = BindFlags::DEFAULT), g_settings_bind) + + /** Create a binding between the @a key in the @a settings object + * and the @a property_proxy. + * + * The binding uses the default GIO mapping functions to map + * between the settings and property values. These functions + * handle booleans, numeric types and string types in a + * straightforward way. Use g_settings_bind_with_mapping() if + * you need a custom mapping, or map between types that are not + * supported by the default mapping functions. + * + * Unless the @a flags include Gio::Settings::BindFlags::NO_SENSITIVITY, this + * function also establishes a binding between the writability of + * @a key and the "sensitive" property of @a object (if @a object has + * a boolean property by that name). See bind_writable() + * for more details about writable bindings. + * + * Note that the lifecycle of the binding is tied to @a object, + * and that you can have only one binding per object property. + * If you bind the same property twice on the same object, the second + * binding overrides the first one. + * + * @param key The key to bind. + * @param property_proxy The property to bind. + * @param flags Flags for the binding. + */ void bind(const Glib::ustring& key, const Glib::PropertyProxy_Base& property_proxy, BindFlags flags = BindFlags::DEFAULT); - // TODO: implement bind_with_mapping + + /** A slot to be called to map values in a binding. + * + * For instance: + * @code + * std::optional map_ustring_to_int(const Glib::ustring& from_string); + * @endcode + * + * @return A value of type T_to if the mapping was successful, + * and an empty optional with no value (i.e. std::nullopt) otherwise. + */ + template + using SlotTypedMapping = sigc::slot(const T_from&)>; + + // Gio::Settings::bind() is similar to Glib::Binding::bind_property(). + /** Create a binding between the @a key in the @a settings object + * and the property @a property of @a object. + * + * The binding uses the provided mapping functions to map between + * settings and property values. + * + * Note that the lifecycle of the binding is tied to @a object, + * and that you can have only one binding per object property. + * If you bind the same property twice on the same object, the second + * binding overrides the first one. + * + * The template parameters T_setting and T_property must be + * explicitly specified in each call. The compiler can't deduce them. + * For instance: + * @code + * m_settings->bind("transition", + * m_transition, "selected", Gio::Settings::BindFlags::DEFAULT, + * sigc::mem_fun(*this, &ExampleAppPrefs::map_from_ustring_to_int), + * sigc::mem_fun(*this, &ExampleAppPrefs::map_from_int_to_ustring)); + * @endcode + * + * @newin{2,76} + * + * @param key The key to bind. + * @param object A Glib::ObjectBase. + * @param property The name of the property to bind. + * @param flags Flags for the binding. + * @param slot_get_mapping A function that gets called to convert values from + * setting to parameter, or an empty slot to use the default GIO mapping. + * @param slot_set_mapping A function that gets called to convert values from + * parameter to setting, or an empty slot to use the default GIO mapping. + * + * @tparam T_setting Type of the setting. Must be a type that can be + * stored in a Glib::Variant object. + * @tparam T_property Type of the property. Must be a type that can be + * stored in a Glib::Value object. + */ + template + void bind(const Glib::ustring& key, + Glib::ObjectBase* object, const Glib::ustring& property, BindFlags flags, + const SlotTypedMapping& slot_get_mapping, + const SlotTypedMapping& slot_set_mapping) + { + return bind_value(key, object, property, flags, + slot_get_mapping.empty() ? SlotGetMapping() : GetMappingProp(slot_get_mapping), + slot_set_mapping.empty() ? SlotSetMapping() : SetMappingProp(slot_set_mapping)); + } + + /** Create a binding between the @a key in the @a settings object + * and the @a property_proxy. + * + * The binding uses the provided mapping functions to map between + * settings and property values. + * + * Note that the lifecycle of the binding is tied to @a property_proxy's + * object, and that you can have only one binding per object property. + * If you bind the same property twice on the same object, the second + * binding overrides the first one. + * + * The template parameters T_setting and T_property must be + * explicitly specified in each call. The compiler can't deduce them. + * For instance: + * @code + * m_settings->bind("transition", + * m_transition->property_selected(), Gio::Settings::BindFlags::DEFAULT, + * sigc::mem_fun(*this, &ExampleAppPrefs::map_from_ustring_to_int), + * sigc::mem_fun(*this, &ExampleAppPrefs::map_from_int_to_ustring)); + * @endcode + * + * @newin{2,76} + * + * @param key The key to bind. + * @param property_proxy The property to bind. + * @param flags Flags for the binding. + * @param slot_get_mapping A function that gets called to convert values from + * setting to parameter, or an empty slot to use the default GIO mapping. + * @param slot_set_mapping A function that gets called to convert values from + * parameter to setting, or an empty slot to use the default GIO mapping. + * + * @tparam T_setting Type of the setting. Must be a type that can be + * stored in a Glib::Variant object. + * @tparam T_property Type of the property. Must be a type that can be + * stored in a Glib::Value object. + */ + template + void bind(const Glib::ustring& key, + const Glib::PropertyProxy_Base& property_proxy, BindFlags flags, + const SlotTypedMapping& slot_get_mapping, + const SlotTypedMapping& slot_set_mapping) + { + bind(key, property_proxy.get_object(), + property_proxy.get_name(), flags, slot_get_mapping, slot_set_mapping); + } + _IGNORE(g_settings_bind_with_mapping) + _WRAP_METHOD(void bind_writable(const Glib::ustring& key, Glib::ObjectBase* object, const Glib::ustring& property, bool inverted=false), g_settings_bind_writable) + + /** Create a binding between the writability of @a key in the + * @a settings object and the @a property_proxy. + * + * The property must be boolean; "sensitive" or "visible" + * properties of widgets are the most likely candidates. + * + * Writable bindings are always uni-directional; changes of the + * writability of the setting will be propagated to the object + * property, not the other way. + * + * When the @a inverted argument is true, the binding inverts the + * value as it passes from the setting to the object, i.e. the property + * will be set to true if the key is not writable. + * + * Note that the lifecycle of the binding is tied to @a object, + * and that you can have only one binding per object property. + * If you bind the same property twice on the same object, the second + * binding overrides the first one. + * + * @param key The key to bind. + * @param property_proxy The boolean property to bind. + * @param inverted Whether to 'invert' the value. + */ void bind_writable(const Glib::ustring& key, const Glib::PropertyProxy_Base& property_proxy, bool inverted=false); - // TODO: unbind is not actually a class method + + _WRAP_METHOD(static void unbind(Glib::ObjectBase* object, const Glib::ustring& property), + g_settings_unbind, newin "2,76") + + /** Removes an existing binding for @a property_proxy. + * + * Note that bindings are automatically removed when the + * object is finalized, so it is rarely necessary to call this + * function. + * + * @newin{2,76} + * + * @param property_proxy The property whose binding is removed. + */ + static void unbind(const Glib::PropertyProxy_Base& property_proxy); _WRAP_METHOD(Glib::RefPtr create_action(const Glib::ustring& key), g_settings_create_action) @@ -207,6 +380,91 @@ public: _WRAP_SIGNAL(bool writable_change_event(GQuark key), "writable-change-event") _WRAP_SIGNAL(void writable_changed(const Glib::ustring& key), writable_changed, detail_name key) -}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // SlotGetMapping and SlotSetMapping must be public. They are used in + // the anonymous namespace in settings.ccg. + /* A slot to be called to map values in a binding created by bind_value(). + * + * For instance: + * @code + * bool map_from_setting_to_property(GValue* to_value, GVariant* from_variant); + * @endcode + * + * @return true if the mapping was successful, and false otherwise. + */ + using SlotGetMapping = sigc::slot; + + /* A slot to be called to map values in a binding created by bind_value(). + * + * For instance: + * @code + * GVariant* map_from_property_to_setting( + * const GValue* from_value, const GVariantType* expected_type); + * @endcode + * + * @return A new GVariant if the mapping was successful, + * and nullptr otherwise. + */ + using SlotSetMapping = sigc::slot; +#endif // DOXYGEN_SHOULD_SKIP_THIS + +private: + void bind_value(const Glib::ustring& key, + Glib::ObjectBase* object, const Glib::ustring& property, BindFlags flags, + const SlotGetMapping& get_mapping, const SlotSetMapping& set_mapping); + + // The functor GetMappingProp can be implicitly converted to a SlotGetMapping + // and used in a call to bind_value(). + template + class GetMappingProp + { + public: + explicit GetMappingProp(const SlotTypedMapping& slot) : typed_mapping(slot) {} + + bool operator()(GValue* to_value, GVariant* from_variant) + { + Glib::Variant from_glib_variant(from_variant, true); + const auto to = typed_mapping(from_glib_variant.get()); + + if (!to.has_value()) + return false; + + Glib::Value to_glib_value; + to_glib_value.init(to_value); + to_glib_value.set(*to); + g_value_copy(to_glib_value.gobj(), to_value); + return true; + } + + private: + SlotTypedMapping typed_mapping; + }; // Settings::GetMappingProp + + // The functor SetMappingProp can be implicitly converted to a SlotGetMapping + // and used in a call to bind_value(). + template + class SetMappingProp + { + public: + explicit SetMappingProp(const SlotTypedMapping& slot) : typed_mapping(slot) {} + + GVariant* operator()(const GValue* from_value, const GVariantType* /* expected_type */) + { + Glib::Value from_glib_value; + from_glib_value.init(from_value); + const auto to = typed_mapping(from_glib_value.get()); + + if (!to.has_value()) + return nullptr; + + auto to_glib_variant = Glib::Variant::create(*to); + return to_glib_variant.gobj_copy(); + } + + private: + SlotTypedMapping typed_mapping; + }; // Settings::SetMappingProp +}; // Settings } // namespace Gio -- cgit v1.2.1