[gnome-builder] libide/tweaks: add binding abstraction



commit 2601379d32746342939f6acaba48af5a7b8e1d80
Author: Christian Hergert <chergert redhat com>
Date:   Wed Aug 24 01:58:25 2022 -0700

    libide/tweaks: add binding abstraction
    
    Rather than all the settings/object binding madness we have now, I want
    to see IdeTweaksWidget get a "binding" property that can have these as
    an object defined within it.
    
    That will allow for us to do a lot more interesting things without having
    to shoehorn the widgets multiple property bindings and/or actions.
    
    I don't like how many GSettings are created here, but we can optimize that
    later if it becomes a problem. GSettings already has backends which make
    a lot of that "fast enough".

 src/libide/tweaks/ide-tweaks-binding.c  | 242 ++++++++++++++++++++++
 src/libide/tweaks/ide-tweaks-binding.h  |  62 ++++++
 src/libide/tweaks/ide-tweaks-init.c     |   3 +
 src/libide/tweaks/ide-tweaks-property.c | 294 ++++++++++++++++++++++++++
 src/libide/tweaks/ide-tweaks-property.h |  49 +++++
 src/libide/tweaks/ide-tweaks-setting.c  | 355 ++++++++++++++++++++++++++++++++
 src/libide/tweaks/ide-tweaks-setting.h  |  54 +++++
 src/libide/tweaks/ide-tweaks-widget.c   |   3 +-
 src/libide/tweaks/libide-tweaks.h       |   3 +
 src/libide/tweaks/meson.build           |   6 +
 10 files changed, 1070 insertions(+), 1 deletion(-)
---
diff --git a/src/libide/tweaks/ide-tweaks-binding.c b/src/libide/tweaks/ide-tweaks-binding.c
new file mode 100644
index 000000000..0693cecbf
--- /dev/null
+++ b/src/libide/tweaks/ide-tweaks-binding.c
@@ -0,0 +1,242 @@
+/* ide-tweaks-binding.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-tweaks-binding"
+
+#include "config.h"
+
+#include "ide-tweaks-binding.h"
+
+typedef struct
+{
+  GWeakRef    instance;
+  GParamSpec *pspec;
+  int         inhibit;
+} IdeTweaksBindingPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeTweaksBinding, ide_tweaks_binding, IDE_TYPE_TWEAKS_ITEM)
+
+enum {
+  CHANGED,
+  N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+ide_tweaks_binding_inhibit (IdeTweaksBinding *self)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+
+  g_assert (IDE_IS_TWEAKS_BINDING (self));
+  g_assert (priv->inhibit >= 0);
+
+  priv->inhibit++;
+}
+
+static void
+ide_tweaks_binding_uninhibit (IdeTweaksBinding *self)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+
+  g_assert (IDE_IS_TWEAKS_BINDING (self));
+  g_assert (priv->inhibit > 0);
+
+  priv->inhibit--;
+}
+
+static void
+ide_tweaks_binding_real_changed (IdeTweaksBinding *self)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+  g_autoptr(GObject) instance = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (IDE_IS_TWEAKS_BINDING (self));
+
+  if (!(instance = g_weak_ref_get (&priv->instance)))
+    return;
+
+  g_assert (G_IS_OBJECT (instance));
+  g_assert (priv->pspec != NULL);
+  g_assert (priv->inhibit > 0);
+
+  g_value_init (&value, priv->pspec->value_type);
+  if (ide_tweaks_binding_get_value (self, &value))
+    g_object_set_property (instance, priv->pspec->name, &value);
+}
+
+static void
+ide_tweaks_binding_dispose (GObject *object)
+{
+  IdeTweaksBinding *self = (IdeTweaksBinding *)object;
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+
+  g_weak_ref_set (&priv->instance, NULL);
+  priv->pspec = NULL;
+
+  G_OBJECT_CLASS (ide_tweaks_binding_parent_class)->dispose (object);
+}
+
+static void
+ide_tweaks_binding_finalize (GObject *object)
+{
+  IdeTweaksBinding *self = (IdeTweaksBinding *)object;
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+
+  g_weak_ref_clear (&priv->instance);
+
+  G_OBJECT_CLASS (ide_tweaks_binding_parent_class)->finalize (object);
+}
+
+static void
+ide_tweaks_binding_class_init (IdeTweaksBindingClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_tweaks_binding_dispose;
+  object_class->finalize = ide_tweaks_binding_finalize;
+
+  klass->changed = ide_tweaks_binding_real_changed;
+
+  signals [CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeTweaksBindingClass, changed),
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+ide_tweaks_binding_init (IdeTweaksBinding *self)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+
+  g_weak_ref_init (&priv->instance, NULL);
+}
+
+void
+ide_tweaks_binding_changed (IdeTweaksBinding *self)
+{
+  g_return_if_fail (IDE_IS_TWEAKS_BINDING (self));
+
+  ide_tweaks_binding_inhibit (self);
+  g_signal_emit (self, signals [CHANGED], 0);
+  ide_tweaks_binding_uninhibit (self);
+}
+
+gboolean
+ide_tweaks_binding_get_value (IdeTweaksBinding *self,
+                              GValue           *value)
+{
+  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);
+}
+
+void
+ide_tweaks_binding_set_value (IdeTweaksBinding *self,
+                              const GValue     *value)
+{
+  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);
+}
+
+static void
+ide_tweaks_binding_instance_notify_cb (IdeTweaksBinding *self,
+                                       GParamSpec       *pspec,
+                                       GObject          *instance)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (IDE_IS_TWEAKS_BINDING (self));
+  g_assert (pspec != NULL);
+  g_assert (G_IS_OBJECT (instance));
+
+  if (priv->inhibit > 0)
+    return;
+
+  g_value_init (&value, pspec->value_type);
+  g_object_get_property (instance, pspec->name, &value);
+  ide_tweaks_binding_set_value (self, &value);
+}
+
+void
+ide_tweaks_binding_unbind (IdeTweaksBinding *self)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+  g_autoptr(GObject) instance = NULL;
+
+  g_return_if_fail (IDE_IS_TWEAKS_BINDING (self));
+
+  if ((instance = g_weak_ref_get (&priv->instance)))
+    {
+      g_weak_ref_set (&priv->instance, NULL);
+      priv->pspec = NULL;
+
+      g_signal_handlers_disconnect_by_func (instance,
+                                            G_CALLBACK (ide_tweaks_binding_instance_notify_cb),
+                                            self);
+    }
+}
+
+void
+ide_tweaks_binding_bind (IdeTweaksBinding *self,
+                         gpointer          instance,
+                         const char       *property_name)
+{
+  IdeTweaksBindingPrivate *priv = ide_tweaks_binding_get_instance_private (self);
+  g_autofree char *signal_name = NULL;
+
+  g_return_if_fail (IDE_IS_TWEAKS_BINDING (self));
+  g_return_if_fail (G_IS_OBJECT (instance));
+  g_return_if_fail (property_name != NULL);
+  g_return_if_fail (priv->inhibit == 0);
+
+  ide_tweaks_binding_unbind (self);
+
+  if (!(priv->pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (instance), property_name)))
+    {
+      g_critical ("Object of type %s does not have a property named %s",
+                  G_OBJECT_TYPE_NAME (instance), property_name);
+      return;
+    }
+
+  g_weak_ref_set (&priv->instance, instance);
+
+  /* Get notifications on property changes */
+  signal_name = g_strdup_printf ("notify::%s", property_name);
+  g_signal_connect_object (instance,
+                           signal_name,
+                           G_CALLBACK (ide_tweaks_binding_instance_notify_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  /* Copy state to the widget */
+  ide_tweaks_binding_changed (self);
+}
diff --git a/src/libide/tweaks/ide-tweaks-binding.h b/src/libide/tweaks/ide-tweaks-binding.h
new file mode 100644
index 000000000..c299ba7ab
--- /dev/null
+++ b/src/libide/tweaks/ide-tweaks-binding.h
@@ -0,0 +1,62 @@
+/* ide-tweaks-binding.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_TWEAKS_INSIDE) && !defined (IDE_TWEAKS_COMPILATION)
+# error "Only <libide-tweaks.h> can be included directly."
+#endif
+
+#include "ide-tweaks-item.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TWEAKS_BINDING (ide_tweaks_binding_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (IdeTweaksBinding, ide_tweaks_binding, IDE, TWEAKS_BINDING, IdeTweaksItem)
+
+struct _IdeTweaksBindingClass
+{
+  IdeTweaksItemClass parent_class;
+
+  void     (*changed)   (IdeTweaksBinding *self);
+  gboolean (*get_value) (IdeTweaksBinding *self,
+                         GValue           *value);
+  void     (*set_value) (IdeTweaksBinding *self,
+                         const GValue     *value);
+};
+
+IDE_AVAILABLE_IN_ALL
+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
+void     ide_tweaks_binding_bind      (IdeTweaksBinding *self,
+                                       gpointer          instance,
+                                       const char       *property_name);
+IDE_AVAILABLE_IN_ALL
+void     ide_tweaks_binding_unbind    (IdeTweaksBinding *self);
+
+G_END_DECLS
diff --git a/src/libide/tweaks/ide-tweaks-init.c b/src/libide/tweaks/ide-tweaks-init.c
index fa0308603..63173d7d6 100644
--- a/src/libide/tweaks/ide-tweaks-init.c
+++ b/src/libide/tweaks/ide-tweaks-init.c
@@ -34,6 +34,7 @@ _ide_tweaks_init (void)
 
   g_type_ensure (IDE_TYPE_TWEAKS);
   g_type_ensure (IDE_TYPE_TWEAKS_ADDIN);
+  g_type_ensure (IDE_TYPE_TWEAKS_BINDING);
   g_type_ensure (IDE_TYPE_TWEAKS_CAPTION);
   g_type_ensure (IDE_TYPE_TWEAKS_CHOICE);
   g_type_ensure (IDE_TYPE_TWEAKS_COMBO);
@@ -45,8 +46,10 @@ _ide_tweaks_init (void)
   g_type_ensure (IDE_TYPE_TWEAKS_INFO);
   g_type_ensure (IDE_TYPE_TWEAKS_ITEM);
   g_type_ensure (IDE_TYPE_TWEAKS_PAGE);
+  g_type_ensure (IDE_TYPE_TWEAKS_PROPERTY);
   g_type_ensure (IDE_TYPE_TWEAKS_RADIO);
   g_type_ensure (IDE_TYPE_TWEAKS_SECTION);
+  g_type_ensure (IDE_TYPE_TWEAKS_SETTING);
   g_type_ensure (IDE_TYPE_TWEAKS_SETTINGS);
   g_type_ensure (IDE_TYPE_TWEAKS_SPIN);
   g_type_ensure (IDE_TYPE_TWEAKS_SWITCH);
diff --git a/src/libide/tweaks/ide-tweaks-property.c b/src/libide/tweaks/ide-tweaks-property.c
new file mode 100644
index 000000000..d14b964d3
--- /dev/null
+++ b/src/libide/tweaks/ide-tweaks-property.c
@@ -0,0 +1,294 @@
+/* ide-tweaks-property.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-tweaks-property"
+
+#include "config.h"
+
+#include "ide-tweaks-property.h"
+
+struct _IdeTweaksProperty
+{
+  IdeTweaksBinding parent_instance;
+  GWeakRef instance;
+  const char *name;
+  gulong notify_handler;
+};
+
+enum {
+  PROP_0,
+  PROP_OBJECT,
+  PROP_NAME,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (IdeTweaksProperty, ide_tweaks_property, IDE_TYPE_TWEAKS_BINDING)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_tweaks_property_object_notify_cb (IdeTweaksProperty *self,
+                                      GParamSpec        *pspec,
+                                      GObject           *instance)
+{
+  g_assert (IDE_IS_TWEAKS_PROPERTY (self));
+  g_assert (G_IS_OBJECT (instance));
+
+  ide_tweaks_binding_changed (IDE_TWEAKS_BINDING (self));
+}
+
+static GObject *
+ide_tweaks_property_acquire (IdeTweaksProperty *self)
+{
+  g_autoptr(GObject) instance = NULL;
+
+  g_assert (IDE_IS_TWEAKS_PROPERTY (self));
+
+  if (self->name == NULL)
+    return NULL;
+
+  if ((instance = g_weak_ref_get (&self->instance)))
+    {
+      if (self->notify_handler == 0)
+        {
+          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);
+        }
+    }
+
+  return g_steal_pointer (&instance);
+}
+
+static gboolean
+ide_tweaks_property_get_value (IdeTweaksBinding *binding,
+                               GValue           *value)
+{
+  IdeTweaksProperty *self = (IdeTweaksProperty *)binding;
+  g_autoptr(GObject) instance = NULL;
+
+  g_return_val_if_fail (IDE_IS_TWEAKS_PROPERTY (self), FALSE);
+  g_return_val_if_fail (G_IS_VALUE (value), FALSE);
+  g_return_val_if_fail (self->name != NULL, FALSE);
+
+  if ((instance = ide_tweaks_property_acquire (self)))
+    g_object_get_property (instance, self->name, value);
+
+  return instance != NULL;
+}
+
+static void
+ide_tweaks_property_set_value (IdeTweaksBinding *binding,
+                               const GValue     *value)
+{
+  IdeTweaksProperty *self = (IdeTweaksProperty *)binding;
+  g_autoptr(GObject) instance = NULL;
+
+  g_return_if_fail (IDE_IS_TWEAKS_PROPERTY (self));
+  g_return_if_fail (G_IS_VALUE (value));
+  g_return_if_fail (self->name != NULL);
+
+  if ((instance = ide_tweaks_property_acquire (self)))
+    g_object_set_property (instance, self->name, value);
+}
+
+static gboolean
+ide_tweaks_property_set_object_internal (IdeTweaksProperty *self,
+                                         GObject           *object)
+{
+  g_autoptr(GObject) previous = NULL;
+
+  g_assert (IDE_IS_TWEAKS_PROPERTY (self));
+  g_assert (!object || G_IS_OBJECT (object));
+
+  previous = g_weak_ref_get (&self->instance);
+  if (previous == object)
+    return FALSE;
+
+  g_clear_signal_handler (&self->notify_handler, previous);
+  g_weak_ref_set (&self->instance, object);
+
+  return TRUE;
+}
+
+static void
+ide_tweaks_property_dispose (GObject *object)
+{
+  IdeTweaksProperty *self = (IdeTweaksProperty *)object;
+
+  ide_tweaks_property_set_object_internal (self, NULL);
+  self->name = NULL;
+
+  g_assert (self->name == NULL);
+  g_assert (self->notify_handler == 0);
+  g_assert (g_weak_ref_get (&self->instance) == NULL);
+
+  G_OBJECT_CLASS (ide_tweaks_property_parent_class)->finalize (object);
+}
+
+static void
+ide_tweaks_property_finalize (GObject *object)
+{
+  IdeTweaksProperty *self = (IdeTweaksProperty *)object;
+
+  g_weak_ref_clear (&self->instance);
+
+  G_OBJECT_CLASS (ide_tweaks_property_parent_class)->finalize (object);
+}
+
+static void
+ide_tweaks_property_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeTweaksProperty *self = IDE_TWEAKS_PROPERTY (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, ide_tweaks_property_get_name (self));
+      break;
+
+    case PROP_OBJECT:
+      g_value_take_object (value, ide_tweaks_property_dup_object (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_tweaks_property_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeTweaksProperty *self = IDE_TWEAKS_PROPERTY (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      ide_tweaks_property_set_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_OBJECT:
+      ide_tweaks_property_set_object (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_tweaks_property_class_init (IdeTweaksPropertyClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeTweaksBindingClass *tweaks_binding_class = IDE_TWEAKS_BINDING_CLASS (klass);
+
+  object_class->dispose = ide_tweaks_property_dispose;
+  object_class->finalize = ide_tweaks_property_finalize;
+  object_class->get_property = ide_tweaks_property_get_property;
+  object_class->set_property = ide_tweaks_property_set_property;
+
+  tweaks_binding_class->get_value = ide_tweaks_property_get_value;
+  tweaks_binding_class->set_value = ide_tweaks_property_set_value;
+
+  properties[PROP_NAME] =
+    g_param_spec_string ("name", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_OBJECT] =
+    g_param_spec_object ("object", NULL, NULL,
+                         G_TYPE_OBJECT,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_tweaks_property_init (IdeTweaksProperty *self)
+{
+  g_weak_ref_init (&self->instance, NULL);
+}
+
+IdeTweaksProperty *
+ide_tweaks_property_new (void)
+{
+  return g_object_new (IDE_TYPE_TWEAKS_PROPERTY, NULL);
+}
+
+const char *
+ide_tweaks_property_get_name (IdeTweaksProperty *self)
+{
+  g_return_val_if_fail (IDE_IS_TWEAKS_PROPERTY (self), NULL);
+
+  return self->name;
+}
+
+void
+ide_tweaks_property_set_name (IdeTweaksProperty *self,
+                              const char        *name)
+{
+  g_return_if_fail (IDE_IS_TWEAKS_PROPERTY (self));
+
+  name = g_intern_string (name);
+
+  if (name != self->name)
+    {
+      self->name = name;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
+
+/**
+ * ide_tweaks_property_dup_object:
+ * @self: a #IdeTweaksProperty
+ *
+ * Gets the object to tweak the property of.
+ *
+ * Returns: (transfer full) (nullable): a #GObject or %NULL
+ */
+GObject *
+ide_tweaks_property_dup_object (IdeTweaksProperty *self)
+{
+  g_return_val_if_fail (IDE_IS_TWEAKS_PROPERTY (self), NULL);
+
+  return g_weak_ref_get (&self->instance);
+}
+
+void
+ide_tweaks_property_set_object (IdeTweaksProperty *self,
+                                GObject           *object)
+{
+  g_return_if_fail (IDE_IS_TWEAKS_PROPERTY (self));
+  g_return_if_fail (!object || G_IS_OBJECT (object));
+
+  if (ide_tweaks_property_set_object_internal (self, object))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_OBJECT]);
+}
diff --git a/src/libide/tweaks/ide-tweaks-property.h b/src/libide/tweaks/ide-tweaks-property.h
new file mode 100644
index 000000000..84d65f4b8
--- /dev/null
+++ b/src/libide/tweaks/ide-tweaks-property.h
@@ -0,0 +1,49 @@
+/* ide-tweaks-property.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_TWEAKS_INSIDE) && !defined (IDE_TWEAKS_COMPILATION)
+# error "Only <libide-tweaks.h> can be included directly."
+#endif
+
+#include "ide-tweaks-binding.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TWEAKS_PROPERTY (ide_tweaks_property_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeTweaksProperty, ide_tweaks_property, IDE, TWEAKS_PROPERTY, IdeTweaksBinding)
+
+IDE_AVAILABLE_IN_ALL
+IdeTweaksProperty *ide_tweaks_property_new        (void);
+IDE_AVAILABLE_IN_ALL
+GObject           *ide_tweaks_property_dup_object (IdeTweaksProperty *self);
+IDE_AVAILABLE_IN_ALL
+void               ide_tweaks_property_set_object (IdeTweaksProperty *self,
+                                                   GObject           *object);
+IDE_AVAILABLE_IN_ALL
+const char        *ide_tweaks_property_get_name   (IdeTweaksProperty *self);
+IDE_AVAILABLE_IN_ALL
+void               ide_tweaks_property_set_name   (IdeTweaksProperty *self,
+                                                   const char        *name);
+
+G_END_DECLS
diff --git a/src/libide/tweaks/ide-tweaks-setting.c b/src/libide/tweaks/ide-tweaks-setting.c
new file mode 100644
index 000000000..5c27cf083
--- /dev/null
+++ b/src/libide/tweaks/ide-tweaks-setting.c
@@ -0,0 +1,355 @@
+/* ide-tweaks-setting.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-tweaks-setting"
+
+#include "config.h"
+
+#include "ide-tweaks.h"
+#include "ide-tweaks-setting.h"
+
+#include "gsettings-mapping.h"
+
+struct _IdeTweaksSetting
+{
+  IdeTweaksBinding parent_instance;
+  const char *schema_id;
+  const char *schema_key;
+  char *path_suffix;
+  GSettings *settings;
+  const GVariantType *expected_type;
+  gulong changed_handler;
+};
+
+enum {
+  PROP_0,
+  PROP_SCHEMA_ID,
+  PROP_SCHEMA_KEY,
+  PROP_PATH_SUFFIX,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (IdeTweaksSetting, ide_tweaks_setting, IDE_TYPE_TWEAKS_BINDING)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_tweaks_setting_settings_changed_cb (IdeTweaksSetting *self,
+                                        const char       *key,
+                                        GSettings        *settings)
+{
+  g_assert (IDE_IS_TWEAKS_SETTING (self));
+  g_assert (key != NULL);
+  g_assert (G_IS_SETTINGS (settings));
+
+  if (settings != self->settings)
+    return;
+
+  ide_tweaks_binding_changed (IDE_TWEAKS_BINDING (self));
+}
+
+static GSettings *
+ide_tweaks_setting_acquire (IdeTweaksSetting    *self,
+                            const char         **key,
+                            const GVariantType **expected_type)
+{
+  g_assert (IDE_IS_TWEAKS_SETTING (self));
+
+  if (key != NULL)
+    *key = self->schema_key;
+
+  if (expected_type != NULL)
+    *expected_type = NULL;
+
+  if (self->schema_id == NULL || self->schema_key == NULL)
+    return NULL;
+
+  if (self->settings == NULL)
+    {
+      g_autoptr(GSettingsSchema) schema = NULL;
+      g_autoptr(GSettingsSchemaKey) schema_key = NULL;
+      g_autofree char *path = NULL;
+      g_autofree char *signal_name = NULL;
+      g_autoptr(GVariant) value = NULL;
+      IdeTweaksItem *root;
+      const char *project_id = NULL;
+
+      if ((root = ide_tweaks_item_get_root (IDE_TWEAKS_ITEM (self))) && IDE_IS_TWEAKS (root))
+        project_id = ide_tweaks_get_project_id (IDE_TWEAKS (root));
+
+      if (!(path = ide_settings_resolve_schema_path (self->schema_id, project_id, self->path_suffix)))
+        return NULL;
+
+      if (!(self->settings = g_settings_new_with_path (self->schema_id, path)))
+        return NULL;
+
+      g_object_get (self->settings,
+                    "settings-schema", &schema,
+                    NULL);
+      schema_key = g_settings_schema_get_key (schema, self->schema_key);
+      self->expected_type = g_settings_schema_key_get_value_type (schema_key);
+
+      value = g_settings_get_value (self->settings, self->schema_key);
+      signal_name = g_strdup_printf ("changed::%s", self->schema_key);
+
+      self->changed_handler =
+        g_signal_connect_object (self->settings,
+                                 signal_name,
+                                 G_CALLBACK (ide_tweaks_setting_settings_changed_cb),
+                                 self,
+                                 G_CONNECT_SWAPPED);
+    }
+
+  if (expected_type != NULL)
+    *expected_type = self->expected_type;
+
+  return g_object_ref (self->settings);
+}
+
+static void
+ide_tweaks_setting_release (IdeTweaksSetting *self)
+{
+  g_assert (IDE_IS_TWEAKS_SETTING (self));
+
+  g_clear_signal_handler (&self->changed_handler, self->settings);
+  g_clear_object (&self->settings);
+  self->expected_type = NULL;
+}
+
+static gboolean
+ide_tweaks_setting_get_value (IdeTweaksBinding *binding,
+                              GValue           *value)
+{
+  IdeTweaksSetting *self = (IdeTweaksSetting *)binding;
+  g_autoptr(GSettings) settings = NULL;
+  const char *key = NULL;
+
+  g_assert (IDE_IS_TWEAKS_SETTING (self));
+  g_assert (G_IS_VALUE (value));
+
+  if ((settings = ide_tweaks_setting_acquire (self, &key, NULL)))
+    {
+      g_autoptr(GVariant) variant = g_settings_get_value (settings, key);
+
+      if (variant != NULL)
+        return g_settings_get_mapping (value, variant, NULL);
+    }
+
+  return FALSE;
+}
+
+static void
+ide_tweaks_setting_set_value (IdeTweaksBinding *binding,
+                              const GValue     *value)
+{
+  IdeTweaksSetting *self = (IdeTweaksSetting *)binding;
+  g_autoptr(GSettings) settings = NULL;
+  const GVariantType *expected_type = NULL;
+  const char *key = NULL;
+
+  g_assert (IDE_IS_TWEAKS_SETTING (self));
+  g_assert (G_IS_VALUE (value));
+
+  if ((settings = ide_tweaks_setting_acquire (self, &key, &expected_type)))
+    {
+      g_autoptr(GVariant) variant = g_settings_set_mapping (value, expected_type, NULL);
+
+      if (variant != NULL)
+        g_settings_set_value (settings, key, variant);
+    }
+}
+
+static void
+ide_tweaks_setting_dispose (GObject *object)
+{
+  IdeTweaksSetting *self = (IdeTweaksSetting *)object;
+
+  ide_tweaks_setting_release (self);
+  g_clear_pointer (&self->path_suffix, g_free);
+
+  G_OBJECT_CLASS (ide_tweaks_setting_parent_class)->finalize (object);
+}
+
+static void
+ide_tweaks_setting_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  IdeTweaksSetting *self = IDE_TWEAKS_SETTING (object);
+
+  switch (prop_id)
+    {
+    case PROP_SCHEMA_ID:
+      g_value_set_static_string (value, ide_tweaks_setting_get_schema_id (self));
+      break;
+
+    case PROP_SCHEMA_KEY:
+      g_value_set_static_string (value, ide_tweaks_setting_get_schema_key (self));
+      break;
+
+    case PROP_PATH_SUFFIX:
+      g_value_set_string (value, ide_tweaks_setting_get_path_suffix (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_tweaks_setting_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  IdeTweaksSetting *self = IDE_TWEAKS_SETTING (object);
+
+  switch (prop_id)
+    {
+    case PROP_SCHEMA_ID:
+      ide_tweaks_setting_set_schema_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_SCHEMA_KEY:
+      ide_tweaks_setting_set_schema_key (self, g_value_get_string (value));
+      break;
+
+    case PROP_PATH_SUFFIX:
+      ide_tweaks_setting_set_path_suffix (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_tweaks_setting_class_init (IdeTweaksSettingClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeTweaksBindingClass *tweaks_binding_class = IDE_TWEAKS_BINDING_CLASS (klass);
+
+  object_class->dispose = ide_tweaks_setting_dispose;
+  object_class->get_property = ide_tweaks_setting_get_property;
+  object_class->set_property = ide_tweaks_setting_set_property;
+
+  tweaks_binding_class->get_value = ide_tweaks_setting_get_value;
+  tweaks_binding_class->set_value = ide_tweaks_setting_set_value;
+
+  properties[PROP_SCHEMA_ID] =
+    g_param_spec_string ("schema-id", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_SCHEMA_KEY] =
+    g_param_spec_string ("schema-key", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties[PROP_PATH_SUFFIX] =
+    g_param_spec_string ("path-suffix", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_tweaks_setting_init (IdeTweaksSetting *self)
+{
+}
+
+IdeTweaksSetting *
+ide_tweaks_setting_new (void)
+{
+  return g_object_new (IDE_TYPE_TWEAKS_SETTING, NULL);
+}
+
+const char *
+ide_tweaks_setting_get_schema_id (IdeTweaksSetting *self)
+{
+  g_return_val_if_fail (IDE_IS_TWEAKS_SETTING (self), NULL);
+
+  return self->schema_id;
+}
+
+void
+ide_tweaks_setting_set_schema_id (IdeTweaksSetting *self,
+                                  const char       *schema_id)
+{
+  g_return_if_fail (IDE_IS_TWEAKS_SETTING (self));
+
+  schema_id = g_intern_string (schema_id);
+
+  if (schema_id != self->schema_id)
+    {
+      ide_tweaks_setting_release (self);
+      self->schema_id = schema_id;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SCHEMA_ID]);
+    }
+}
+
+const char *
+ide_tweaks_setting_get_schema_key (IdeTweaksSetting *self)
+{
+  g_return_val_if_fail (IDE_IS_TWEAKS_SETTING (self), NULL);
+
+  return self->schema_key;
+}
+
+void
+ide_tweaks_setting_set_schema_key (IdeTweaksSetting *self,
+                                   const char       *schema_key)
+{
+  g_return_if_fail (IDE_IS_TWEAKS_SETTING (self));
+
+  schema_key = g_intern_string (schema_key);
+
+  if (schema_key != self->schema_key)
+    {
+      ide_tweaks_setting_release (self);
+      self->schema_key = schema_key;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SCHEMA_KEY]);
+    }
+}
+
+const char *
+ide_tweaks_setting_get_path_suffix (IdeTweaksSetting *self)
+{
+  g_return_val_if_fail (IDE_IS_TWEAKS_SETTING (self), NULL);
+
+  return self->path_suffix;
+}
+
+void
+ide_tweaks_setting_set_path_suffix (IdeTweaksSetting *self,
+                                    const char       *path_suffix)
+{
+  g_return_if_fail (IDE_IS_TWEAKS_SETTING (self));
+
+  path_suffix = g_intern_string (path_suffix);
+
+  if (ide_set_string (&self->path_suffix, path_suffix))
+    {
+      ide_tweaks_setting_release (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PATH_SUFFIX]);
+    }
+}
diff --git a/src/libide/tweaks/ide-tweaks-setting.h b/src/libide/tweaks/ide-tweaks-setting.h
new file mode 100644
index 000000000..30059dae8
--- /dev/null
+++ b/src/libide/tweaks/ide-tweaks-setting.h
@@ -0,0 +1,54 @@
+/* ide-tweaks-setting.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_TWEAKS_INSIDE) && !defined (IDE_TWEAKS_COMPILATION)
+# error "Only <libide-tweaks.h> can be included directly."
+#endif
+
+#include "ide-tweaks-binding.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TWEAKS_SETTING (ide_tweaks_setting_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeTweaksSetting, ide_tweaks_setting, IDE, TWEAKS_SETTING, IdeTweaksBinding)
+
+IDE_AVAILABLE_IN_ALL
+IdeTweaksSetting *ide_tweaks_setting_new             (void);
+IDE_AVAILABLE_IN_ALL
+const char       *ide_tweaks_setting_get_schema_id   (IdeTweaksSetting *self);
+IDE_AVAILABLE_IN_ALL
+void              ide_tweaks_setting_set_schema_id   (IdeTweaksSetting *self,
+                                                      const char       *schema_id);
+IDE_AVAILABLE_IN_ALL
+const char       *ide_tweaks_setting_get_schema_key  (IdeTweaksSetting *self);
+IDE_AVAILABLE_IN_ALL
+void              ide_tweaks_setting_set_schema_key  (IdeTweaksSetting *self,
+                                                      const char       *schema_key);
+IDE_AVAILABLE_IN_ALL
+const char       *ide_tweaks_setting_get_path_suffix (IdeTweaksSetting *self);
+IDE_AVAILABLE_IN_ALL
+void              ide_tweaks_setting_set_path_suffix (IdeTweaksSetting *self,
+                                                      const char       *path_suffix);
+
+G_END_DECLS
diff --git a/src/libide/tweaks/ide-tweaks-widget.c b/src/libide/tweaks/ide-tweaks-widget.c
index c9f2c6d87..7c5db0279 100644
--- a/src/libide/tweaks/ide-tweaks-widget.c
+++ b/src/libide/tweaks/ide-tweaks-widget.c
@@ -85,7 +85,8 @@ ide_tweaks_widget_copy (IdeTweaksItem *item)
           (pspec->flags & G_PARAM_CONSTRUCT_ONLY) != 0)
         continue;
 
-      if (g_type_is_a (pspec->value_type, IDE_TYPE_TWEAKS_SETTINGS))
+      if (g_type_is_a (pspec->value_type, IDE_TYPE_TWEAKS_SETTINGS) ||
+          g_type_is_a (pspec->value_type, IDE_TYPE_TWEAKS_BINDING))
         clone_item_property (item, copy, pspec->name);
     }
 
diff --git a/src/libide/tweaks/libide-tweaks.h b/src/libide/tweaks/libide-tweaks.h
index d354275c7..380d557d4 100644
--- a/src/libide/tweaks/libide-tweaks.h
+++ b/src/libide/tweaks/libide-tweaks.h
@@ -23,6 +23,7 @@
 #define IDE_TWEAKS_INSIDE
 # include "ide-tweaks.h"
 # include "ide-tweaks-addin.h"
+# include "ide-tweaks-binding.h"
 # include "ide-tweaks-caption.h"
 # include "ide-tweaks-choice.h"
 # include "ide-tweaks-combo.h"
@@ -34,8 +35,10 @@
 # include "ide-tweaks-info.h"
 # include "ide-tweaks-item.h"
 # include "ide-tweaks-page.h"
+# include "ide-tweaks-property.h"
 # include "ide-tweaks-radio.h"
 # include "ide-tweaks-section.h"
+# include "ide-tweaks-setting.h"
 # include "ide-tweaks-settings.h"
 # include "ide-tweaks-spin.h"
 # include "ide-tweaks-switch.h"
diff --git a/src/libide/tweaks/meson.build b/src/libide/tweaks/meson.build
index a283e522a..3403991e8 100644
--- a/src/libide/tweaks/meson.build
+++ b/src/libide/tweaks/meson.build
@@ -10,6 +10,7 @@ libide_tweaks_public_headers = [
   'libide-tweaks.h',
   'ide-tweaks.h',
   'ide-tweaks-addin.h',
+  'ide-tweaks-binding.h',
   'ide-tweaks-caption.h',
   'ide-tweaks-choice.h',
   'ide-tweaks-combo.h',
@@ -21,8 +22,10 @@ libide_tweaks_public_headers = [
   'ide-tweaks-info.h',
   'ide-tweaks-item.h',
   'ide-tweaks-page.h',
+  'ide-tweaks-property.h',
   'ide-tweaks-radio.h',
   'ide-tweaks-section.h',
+  'ide-tweaks-setting.h',
   'ide-tweaks-settings.h',
   'ide-tweaks-spin.h',
   'ide-tweaks-switch.h',
@@ -40,6 +43,7 @@ install_headers(libide_tweaks_public_headers, subdir: libide_tweaks_header_subdi
 libide_tweaks_public_sources = [
   'ide-tweaks.c',
   'ide-tweaks-addin.c',
+  'ide-tweaks-binding.c',
   'ide-tweaks-caption.c',
   'ide-tweaks-choice.c',
   'ide-tweaks-combo.c',
@@ -51,8 +55,10 @@ libide_tweaks_public_sources = [
   'ide-tweaks-info.c',
   'ide-tweaks-item.c',
   'ide-tweaks-page.c',
+  'ide-tweaks-property.c',
   'ide-tweaks-radio.c',
   'ide-tweaks-section.c',
+  'ide-tweaks-setting.c',
   'ide-tweaks-settings.c',
   'ide-tweaks-spin.c',
   'ide-tweaks-switch.c',


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