[gnome-builder] libide/tweaks: implement bindings with transforms



commit 58c48f27e293742980ae055231d904d2c55e53c1
Author: Christian Hergert <chergert redhat com>
Date:   Wed Aug 24 12:55:50 2022 -0700

    libide/tweaks: implement bindings with transforms

 src/libide/tweaks/ide-tweaks-binding.c  | 170 +++++++++++++++++++++++++++++++-
 src/libide/tweaks/ide-tweaks-binding.h  |  43 +++++---
 src/libide/tweaks/ide-tweaks-property.c |  56 +++++++++--
 src/libide/tweaks/ide-tweaks-setting.c  |  48 ++++++++-
 4 files changed, 286 insertions(+), 31 deletions(-)
---
diff --git a/src/libide/tweaks/ide-tweaks-binding.c b/src/libide/tweaks/ide-tweaks-binding.c
index 0693cecbf..8c5331c3c 100644
--- a/src/libide/tweaks/ide-tweaks-binding.c
+++ b/src/libide/tweaks/ide-tweaks-binding.c
@@ -24,11 +24,20 @@
 
 #include "ide-tweaks-binding.h"
 
+typedef struct
+{
+  IdeTweaksBindingTransform get_transform;
+  IdeTweaksBindingTransform set_transform;
+  gpointer user_data;
+  GDestroyNotify notify;
+} Binding;
+
 typedef struct
 {
   GWeakRef    instance;
   GParamSpec *pspec;
   int         inhibit;
+  Binding    *binding;
 } IdeTweaksBindingPrivate;
 
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeTweaksBinding, ide_tweaks_binding, IDE_TYPE_TWEAKS_ITEM)
@@ -40,6 +49,80 @@ enum {
 
 static guint signals [N_SIGNALS];
 
+static void
+binding_finalize (gpointer data)
+{
+  Binding *binding = data;
+
+  if (binding->notify)
+    binding->notify (binding->user_data);
+
+  binding->get_transform = NULL;
+  binding->set_transform = NULL;
+  binding->user_data = NULL;
+  binding->notify = NULL;
+}
+
+static void
+binding_unref (Binding *binding)
+{
+  g_atomic_rc_box_release_full (binding, binding_finalize);
+}
+
+static Binding *
+binding_new (IdeTweaksBindingTransform get_transform,
+             IdeTweaksBindingTransform set_transform,
+             gpointer                  user_data,
+             GDestroyNotify            notify)
+{
+  Binding *binding;
+
+  binding = g_atomic_rc_box_new0 (Binding);
+  binding->get_transform = get_transform;
+  binding->set_transform = set_transform;
+  binding->user_data = user_data;
+  binding->notify = notify;
+
+  return binding;
+}
+
+static gboolean
+binding_get (Binding      *binding,
+             const GValue *from_value,
+             GValue       *to_value)
+{
+  if (binding->get_transform)
+    return binding->get_transform (from_value, to_value, binding->user_data);
+  g_value_copy (from_value, to_value);
+  return TRUE;
+}
+
+static gboolean
+binding_set (Binding      *binding,
+             const GValue *from_value,
+             GValue       *to_value)
+{
+  if (binding->set_transform)
+    return binding->set_transform (from_value, to_value, binding->user_data);
+  g_value_copy (from_value, to_value);
+  return TRUE;
+}
+
+static gboolean
+ide_tweaks_binding_get_expected_type (IdeTweaksBinding *self,
+                                      GType            *type)
+{
+  g_assert (IDE_IS_TWEAKS_BINDING (self));
+  g_assert (type != NULL);
+
+  if (IDE_TWEAKS_BINDING_GET_CLASS (self)->get_expected_type)
+    *type = IDE_TWEAKS_BINDING_GET_CLASS (self)->get_expected_type (self);
+  else
+    *type = G_TYPE_INVALID;
+
+  return *type != G_TYPE_INVALID;
+}
+
 static void
 ide_tweaks_binding_inhibit (IdeTweaksBinding *self)
 {
@@ -89,6 +172,7 @@ ide_tweaks_binding_dispose (GObject *object)
   IdeTweaksBinding *self = (IdeTweaksBinding *)object;
   IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
 
+  g_clear_pointer (&priv->binding, binding_unref);
   g_weak_ref_set (&priv->instance, NULL);
   priv->pspec = NULL;
 
@@ -148,22 +232,46 @@ gboolean
 ide_tweaks_binding_get_value (IdeTweaksBinding *self,
                               GValue           *value)
 {
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+  g_auto(GValue) from_value = G_VALUE_INIT;
+
   g_return_val_if_fail (IDE_IS_TWEAKS_BINDING (self), FALSE);
   g_return_val_if_fail (value != NULL, FALSE);
   g_return_val_if_fail (G_VALUE_TYPE (value) != G_TYPE_INVALID, FALSE);
 
-  return IDE_TWEAKS_BINDING_GET_CLASS (self)->get_value (self, value);
+  if (priv->binding == NULL || priv->pspec == NULL)
+    return IDE_TWEAKS_BINDING_GET_CLASS (self)->get_value (self, value);
+
+  /* TODO: You could optimize an extra GValue copy out here */
+  g_value_init (&from_value, priv->pspec->value_type);
+  if (IDE_TWEAKS_BINDING_GET_CLASS (self)->get_value (self, &from_value))
+    return binding_get (priv->binding, &from_value, value);
+
+  return FALSE;
 }
 
 void
 ide_tweaks_binding_set_value (IdeTweaksBinding *self,
                               const GValue     *value)
 {
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+  g_auto(GValue) to_value = G_VALUE_INIT;
+  GType type;
+
   g_return_if_fail (IDE_IS_TWEAKS_BINDING (self));
   g_return_if_fail (value != NULL);
   g_return_if_fail (G_IS_VALUE (value));
 
-  IDE_TWEAKS_BINDING_GET_CLASS (self)->set_value (self, value);
+  if (priv->binding == NULL || !ide_tweaks_binding_get_expected_type (self, &type))
+    {
+      IDE_TWEAKS_BINDING_GET_CLASS (self)->set_value (self, value);
+      return;
+    }
+
+  /* TODO: You could optimize an extra GValue copy out here */
+  g_value_init (&to_value, type);
+  if (binding_set (priv->binding, value, &to_value))
+    IDE_TWEAKS_BINDING_GET_CLASS (self)->set_value (self, &to_value);
 }
 
 static void
@@ -194,6 +302,8 @@ ide_tweaks_binding_unbind (IdeTweaksBinding *self)
 
   g_return_if_fail (IDE_IS_TWEAKS_BINDING (self));
 
+  g_clear_pointer (&priv->binding, binding_unref);
+
   if ((instance = g_weak_ref_get (&priv->instance)))
     {
       g_weak_ref_set (&priv->instance, NULL);
@@ -205,10 +315,28 @@ ide_tweaks_binding_unbind (IdeTweaksBinding *self)
     }
 }
 
+static gboolean dummy_cb (gpointer data) { return FALSE; };
+
+/**
+ * ide_tweaks_binding_bind_with_transform:
+ * @self: a #IdeTweaksBinding
+ * @instance: a #GObject
+ * @property_name: a property of @instance
+ * @get_transform: (nullable) (scope async): an #IdeTweaksBindingTransform or %NULL
+ * @set_transform: (nullable) (scope async): an #IdeTweaksBindingTransform or %NULL
+ * @user_data: closure data for @get_transform and @set_transform
+ * @notify: closure notify for @user_data
+ *
+ * Binds the value with an optional transform.
+ */
 void
-ide_tweaks_binding_bind (IdeTweaksBinding *self,
-                         gpointer          instance,
-                         const char       *property_name)
+ide_tweaks_binding_bind_with_transform (IdeTweaksBinding          *self,
+                                        gpointer                   instance,
+                                        const char                *property_name,
+                                        IdeTweaksBindingTransform  get_transform,
+                                        IdeTweaksBindingTransform  set_transform,
+                                        gpointer                   user_data,
+                                        GDestroyNotify             notify)
 {
   IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
   g_autofree char *signal_name = NULL;
@@ -224,10 +352,12 @@ ide_tweaks_binding_bind (IdeTweaksBinding *self,
     {
       g_critical ("Object of type %s does not have a property named %s",
                   G_OBJECT_TYPE_NAME (instance), property_name);
+      g_idle_add_full (G_PRIORITY_LOW, dummy_cb, user_data, notify);
       return;
     }
 
   g_weak_ref_set (&priv->instance, instance);
+  priv->binding = binding_new (get_transform, set_transform, user_data, notify);
 
   /* Get notifications on property changes */
   signal_name = g_strdup_printf ("notify::%s", property_name);
@@ -240,3 +370,33 @@ ide_tweaks_binding_bind (IdeTweaksBinding *self,
   /* Copy state to the widget */
   ide_tweaks_binding_changed (self);
 }
+
+void
+ide_tweaks_binding_bind (IdeTweaksBinding *self,
+                         gpointer          instance,
+                         const char       *property_name)
+{
+  ide_tweaks_binding_bind_with_transform (self, instance, property_name, NULL, NULL, NULL, NULL);
+}
+
+/**
+ * ide_tweaks_binding_dup_string:
+ * @self: a #IdeTweaksBinding
+ *
+ * Gets the current value as a newly allocated string.
+ *
+ * Returns: (transfer full) (nullable): a string or %NULL
+ */
+char *
+ide_tweaks_binding_dup_string (IdeTweaksBinding *self)
+{
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_return_val_if_fail (IDE_IS_TWEAKS_BINDING (self), NULL);
+
+  g_value_init (&value, G_TYPE_STRING);
+  if (ide_tweaks_binding_get_value (self, &value))
+    return g_value_dup_string (&value);
+
+  return NULL;
+}
diff --git a/src/libide/tweaks/ide-tweaks-binding.h b/src/libide/tweaks/ide-tweaks-binding.h
index c299ba7ab..b4b3e7843 100644
--- a/src/libide/tweaks/ide-tweaks-binding.h
+++ b/src/libide/tweaks/ide-tweaks-binding.h
@@ -30,6 +30,10 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TWEAKS_BINDING (ide_tweaks_binding_get_type())
 
+typedef gboolean (*IdeTweaksBindingTransform) (const GValue *from_value,
+                                               GValue       *to_value,
+                                               gpointer      user_data);
+
 IDE_AVAILABLE_IN_ALL
 G_DECLARE_DERIVABLE_TYPE (IdeTweaksBinding, ide_tweaks_binding, IDE, TWEAKS_BINDING, IdeTweaksItem)
 
@@ -37,26 +41,37 @@ struct _IdeTweaksBindingClass
 {
   IdeTweaksItemClass parent_class;
 
-  void     (*changed)   (IdeTweaksBinding *self);
-  gboolean (*get_value) (IdeTweaksBinding *self,
-                         GValue           *value);
-  void     (*set_value) (IdeTweaksBinding *self,
-                         const GValue     *value);
+  void     (*changed)           (IdeTweaksBinding *self);
+  gboolean (*get_value)         (IdeTweaksBinding *self,
+                                 GValue           *value);
+  void     (*set_value)         (IdeTweaksBinding *self,
+                                 const GValue     *value);
+  GType    (*get_expected_type) (IdeTweaksBinding *self);
 };
 
 IDE_AVAILABLE_IN_ALL
-void     ide_tweaks_binding_changed   (IdeTweaksBinding *self);
+void     ide_tweaks_binding_changed             (IdeTweaksBinding *self);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_tweaks_binding_get_value           (IdeTweaksBinding *self,
+                                                 GValue           *value);
+IDE_AVAILABLE_IN_ALL
+void     ide_tweaks_binding_set_value           (IdeTweaksBinding *self,
+                                                 const GValue     *value);
 IDE_AVAILABLE_IN_ALL
-gboolean ide_tweaks_binding_get_value (IdeTweaksBinding *self,
-                                       GValue           *value);
+char    *ide_tweaks_binding_dup_string          (IdeTweaksBinding *self);
 IDE_AVAILABLE_IN_ALL
-void     ide_tweaks_binding_set_value (IdeTweaksBinding *self,
-                                       const GValue     *value);
+void     ide_tweaks_binding_bind                (IdeTweaksBinding *self,
+                                                 gpointer          instance,
+                                                 const char       *property_name);
 IDE_AVAILABLE_IN_ALL
-void     ide_tweaks_binding_bind      (IdeTweaksBinding *self,
-                                       gpointer          instance,
-                                       const char       *property_name);
+void     ide_tweaks_binding_bind_with_transform (IdeTweaksBinding          *self,
+                                                 gpointer                   instance,
+                                                 const char                *property_name,
+                                                 IdeTweaksBindingTransform  get_transform,
+                                                 IdeTweaksBindingTransform  set_transform,
+                                                 gpointer                   user_data,
+                                                 GDestroyNotify             notify);
 IDE_AVAILABLE_IN_ALL
-void     ide_tweaks_binding_unbind    (IdeTweaksBinding *self);
+void     ide_tweaks_binding_unbind              (IdeTweaksBinding *self);
 
 G_END_DECLS
diff --git a/src/libide/tweaks/ide-tweaks-property.c b/src/libide/tweaks/ide-tweaks-property.c
index d14b964d3..bbcb66a37 100644
--- a/src/libide/tweaks/ide-tweaks-property.c
+++ b/src/libide/tweaks/ide-tweaks-property.c
@@ -28,6 +28,7 @@ struct _IdeTweaksProperty
 {
   IdeTweaksBinding parent_instance;
   GWeakRef instance;
+  GParamSpec *pspec;
   const char *name;
   gulong notify_handler;
 };
@@ -70,18 +71,39 @@ ide_tweaks_property_acquire (IdeTweaksProperty *self)
         {
           g_autofree char *signal_name = g_strdup_printf ("notify::%s", self->name);
 
-          self->notify_handler =
-            g_signal_connect_object (instance,
-                                     signal_name,
-                                     G_CALLBACK (ide_tweaks_property_object_notify_cb),
-                                     self,
-                                     G_CONNECT_SWAPPED);
+          self->pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (instance), self->name);
+
+          if (self->pspec == NULL)
+            g_critical ("Object %s has no property named %s",
+                        G_OBJECT_TYPE_NAME (instance), self->name);
+          else
+            self->notify_handler =
+              g_signal_connect_object (instance,
+                                       signal_name,
+                                       G_CALLBACK (ide_tweaks_property_object_notify_cb),
+                                       self,
+                                       G_CONNECT_SWAPPED);
         }
     }
 
   return g_steal_pointer (&instance);
 }
 
+static void
+ide_tweaks_property_release (IdeTweaksProperty *self)
+{
+  g_autoptr(GObject) instance = NULL;
+
+  g_assert (IDE_IS_TWEAKS_PROPERTY (self));
+
+  if ((instance = g_weak_ref_get (&self->instance)))
+    g_clear_signal_handler (&self->notify_handler, instance);
+
+  self->pspec = NULL;
+  self->notify_handler = 0;
+  g_weak_ref_set (&self->instance, NULL);
+}
+
 static gboolean
 ide_tweaks_property_get_value (IdeTweaksBinding *binding,
                                GValue           *value)
@@ -127,21 +149,38 @@ ide_tweaks_property_set_object_internal (IdeTweaksProperty *self,
   if (previous == object)
     return FALSE;
 
-  g_clear_signal_handler (&self->notify_handler, previous);
+  ide_tweaks_property_release (self);
+
   g_weak_ref_set (&self->instance, object);
 
   return TRUE;
 }
 
+static GType
+ide_tweaks_property_get_expected_type (IdeTweaksBinding *binding)
+{
+  IdeTweaksProperty *self = (IdeTweaksProperty *)binding;
+  g_autoptr(GObject) instance = NULL;
+
+  g_assert (IDE_IS_TWEAKS_PROPERTY (self));
+
+  if ((instance = ide_tweaks_property_acquire (self)))
+    return self->pspec->value_type;
+
+  return G_TYPE_INVALID;
+}
+
 static void
 ide_tweaks_property_dispose (GObject *object)
 {
   IdeTweaksProperty *self = (IdeTweaksProperty *)object;
 
-  ide_tweaks_property_set_object_internal (self, NULL);
+  ide_tweaks_property_release (self);
+
   self->name = NULL;
 
   g_assert (self->name == NULL);
+  g_assert (self->pspec == NULL);
   g_assert (self->notify_handler == 0);
   g_assert (g_weak_ref_get (&self->instance) == NULL);
 
@@ -217,6 +256,7 @@ ide_tweaks_property_class_init (IdeTweaksPropertyClass *klass)
 
   tweaks_binding_class->get_value = ide_tweaks_property_get_value;
   tweaks_binding_class->set_value = ide_tweaks_property_set_value;
+  tweaks_binding_class->get_expected_type = ide_tweaks_property_get_expected_type;
 
   properties[PROP_NAME] =
     g_param_spec_string ("name", NULL, NULL,
diff --git a/src/libide/tweaks/ide-tweaks-setting.c b/src/libide/tweaks/ide-tweaks-setting.c
index 5c27cf083..9e786014f 100644
--- a/src/libide/tweaks/ide-tweaks-setting.c
+++ b/src/libide/tweaks/ide-tweaks-setting.c
@@ -149,7 +149,10 @@ ide_tweaks_setting_get_value (IdeTweaksBinding *binding,
       g_autoptr(GVariant) variant = g_settings_get_value (settings, key);
 
       if (variant != NULL)
-        return g_settings_get_mapping (value, variant, NULL);
+        {
+          g_variant_ref_sink (variant);
+          return g_settings_get_mapping (value, variant, NULL);
+        }
     }
 
   return FALSE;
@@ -169,13 +172,49 @@ ide_tweaks_setting_set_value (IdeTweaksBinding *binding,
 
   if ((settings = ide_tweaks_setting_acquire (self, &key, &expected_type)))
     {
-      g_autoptr(GVariant) variant = g_settings_set_mapping (value, expected_type, NULL);
+      g_autoptr(GVariant) new_value = g_settings_set_mapping (value, expected_type, NULL);
+      g_autoptr(GVariant) old_value = g_settings_get_value (settings, key);
 
-      if (variant != NULL)
-        g_settings_set_value (settings, key, variant);
+      if (new_value)
+        g_variant_take_ref (new_value);
+
+      if (new_value && old_value && !g_variant_equal (new_value, old_value))
+        g_settings_set_value (settings, key, new_value);
     }
 }
 
+static GType
+ide_tweaks_setting_get_expected_type (IdeTweaksBinding *binding)
+{
+  IdeTweaksSetting *self = IDE_TWEAKS_SETTING (binding);
+
+  if (self->expected_type == NULL)
+    return G_TYPE_INVALID;
+
+#define MAP_VARIANT_TYPE_TO_GTYPE(variant_type, gtype)            \
+  G_STMT_START {                                                  \
+    if (g_variant_type_equal (self->expected_type, variant_type)) \
+      return gtype;                                               \
+  } G_STMT_END
+
+  /* Just the basics really for GSettings */
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_BYTE, G_TYPE_UCHAR);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_BYTESTRING, G_TYPE_STRING);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_DOUBLE, G_TYPE_DOUBLE);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_INT32, G_TYPE_INT);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_INT64, G_TYPE_INT64);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_STRING, G_TYPE_STRING);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_STRING_ARRAY, G_TYPE_STRV);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_UINT32, G_TYPE_UINT);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_UINT64, G_TYPE_UINT64);
+  MAP_VARIANT_TYPE_TO_GTYPE (G_VARIANT_TYPE_VARIANT, G_TYPE_VARIANT);
+
+#undef MAP_VARIANT_TYPE_TO_GTYPE
+
+  return G_TYPE_INVALID;
+}
+
 static void
 ide_tweaks_setting_dispose (GObject *object)
 {
@@ -253,6 +292,7 @@ ide_tweaks_setting_class_init (IdeTweaksSettingClass *klass)
 
   tweaks_binding_class->get_value = ide_tweaks_setting_get_value;
   tweaks_binding_class->set_value = ide_tweaks_setting_set_value;
+  tweaks_binding_class->get_expected_type = ide_tweaks_setting_get_expected_type;
 
   properties[PROP_SCHEMA_ID] =
     g_param_spec_string ("schema-id", NULL, NULL,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]