Merge branch 'master' into wip/gsettings-list
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 2783df7..fc4b590 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1606,6 +1606,11 @@
 g_settings_list_keys
 g_settings_list_children
 g_settings_get_mapped
+g_settings_add_child
+g_settings_can_add_child
+g_settings_can_remove_child
+g_settings_remove_child
+g_settings_get_destroyed
 g_settings_get_range
 g_settings_range_check
 #endif
diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c
index f61bb8b..ad904e4 100644
--- a/gio/gkeyfilesettingsbackend.c
+++ b/gio/gkeyfilesettingsbackend.c
@@ -19,6 +19,7 @@
  *
  * Authors: Vincent Untz <vuntz@gnome.org>
  *          Ryan Lortie <desrt@desrt.ca>
+ *          Rodrigo Moya <rodrigo@gnome.org>
  */
 
 #include "config.h"
@@ -369,6 +370,14 @@
   return g_object_ref (kfsb->permission);
 }
 
+static gchar **
+g_keyfile_settings_backend_list (GSettingsBackend    *backend,
+				 const gchar         *path,
+				 const gchar * const *schema_items)
+{
+  GKeyfileSettingsBackend *kfsb = G_KEYFILE_SETTINGS_BACKEND (backend);
+}
+
 static void
 keyfile_to_tree (GKeyfileSettingsBackend *kfsb,
                  GTree                   *tree,
@@ -536,6 +545,7 @@
   class->reset = g_keyfile_settings_backend_reset;
   class->get_writable = g_keyfile_settings_backend_get_writable;
   class->get_permission = g_keyfile_settings_backend_get_permission;
+  class->list = g_keyfile_settings_backend_list;
   /* No need to implement subscribed/unsubscribe: the only point would be to
    * stop monitoring the file when there's no GSettings anymore, which is no
    * big win. */
diff --git a/gio/gmemorysettingsbackend.c b/gio/gmemorysettingsbackend.c
index 5d747b5..eceebc1 100644
--- a/gio/gmemorysettingsbackend.c
+++ b/gio/gmemorysettingsbackend.c
@@ -16,7 +16,8 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  *
- * Author: Ryan Lortie <desrt@desrt.ca>
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ *          Rodrigo Moya <rodrigo@gnome.org>
  */
 
 #include "config.h"
@@ -44,6 +45,90 @@
                          g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
                                                          g_define_type_id, "memory", 10))
 
+static GHashTable *
+g_memory_settings_backend_get_table (GHashTable   *table,
+                                     const gchar **key_ptr)
+{
+  const gchar *key = *key_ptr;
+  gchar *prefix;
+  gchar *name;
+  gint i, j;
+
+  for (i = 2; key[i - 2] != ':' || key[i - 1] != '/'; i++)
+    if (key[i - 2] == '\0')
+      /* no :/ in the string -- pass through */
+      return table;
+
+  for (j = i + 1; key[j - 1] != '/'; j++)
+    if (key[j - 1] == '\0')
+      /* string of form /some/path:/child -- definitely invalid */
+      return NULL;
+
+  /* We now have this situation:
+   *
+   *   /some/path:/child/item
+   *   ^0          ^i    ^j
+   *
+   * So we can split the string into 3 parts:
+   *
+   *   prefix   = /some/path:/
+   *   child    = child/
+   *   *key_ptr = item
+   */
+  prefix = g_strndup (key, i);
+  name = g_strndup (key + i, j - i);
+
+  /* name is either like 'foo' or ':foo'
+   *
+   * If it doesn't start with a colon, it's in the schema.
+   */
+  if (name[0] == ':')
+    {
+      table = g_hash_table_lookup (table, prefix);
+      if (table)
+        table = g_hash_table_lookup (table, name);
+    }
+  else
+    {
+      gpointer value;
+
+      if (!g_hash_table_lookup_extended (table, prefix, NULL, &value))
+        g_hash_table_insert (table, prefix,
+                             value = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                            g_free, (GDestroyNotify) g_hash_table_unref));
+
+      table = value;
+
+      if (!g_hash_table_lookup_extended (table, prefix, NULL, &value))
+        g_hash_table_insert (table, prefix,
+                             value = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                            g_free, (GDestroyNotify) g_hash_table_unref));
+    }
+
+    if (key[i] == ':' && key[i + 1] == '/')
+      {
+        gchar *prefix;
+        gchar *name;
+        gint j;
+
+        i += 2;
+
+        for (j = i; key[j] != '/'; j++)
+          /* found a path of the form /a:/b
+           * which is never a valid spot for a key.
+           */
+          if (key[j] == '\0')
+            return NULL;
+
+        name = g_strndup ((const gchar *) key + i, j - i);
+        prefix = g_strndup (key, i);
+        table = g_hash_table_lookup (table, prefix);
+        g_free (prefix);
+      }
+
+  return table;
+}
+
 static GVariant *
 g_memory_settings_backend_read (GSettingsBackend   *backend,
                                 const gchar        *key,
@@ -52,11 +137,16 @@
 {
   GMemorySettingsBackend *memory = G_MEMORY_SETTINGS_BACKEND (backend);
   GVariant *value;
+  GHashTable *table;
 
   if (default_value)
     return NULL;
 
-  value = g_hash_table_lookup (memory->table, key);
+  table = g_memory_settings_backend_get_table (memory->table, &key);
+  if (!table)
+    return NULL;
+
+  value = g_hash_table_lookup (table, key);
 
   if (value != NULL)
     g_variant_ref (value);
@@ -72,8 +162,13 @@
 {
   GMemorySettingsBackend *memory = G_MEMORY_SETTINGS_BACKEND (backend);
   GVariant *old_value;
+  GHashTable *table;
 
-  old_value = g_hash_table_lookup (memory->table, key);
+  table = g_memory_settings_backend_get_table (memory->table, &key);
+  if (!table)
+    return FALSE;
+
+  old_value = g_hash_table_lookup (table, key);
   g_variant_ref_sink (value);
 
   if (old_value == NULL || !g_variant_equal (value, old_value))
@@ -93,11 +188,16 @@
                                      gpointer data)
 {
   GMemorySettingsBackend *memory = data;
+  GHashTable *table;
+
+  table = g_memory_settings_backend_get_table (memory->table, &key);
+  if (!table)
+    return FALSE;
 
   if (value != NULL)
-    g_hash_table_insert (memory->table, g_strdup (key), g_variant_ref (value));
+    g_hash_table_insert (table, g_strdup (key), g_variant_ref (value));
   else
-    g_hash_table_remove (memory->table, key);
+    g_hash_table_remove (table, key);
 
   return FALSE;
 }
@@ -119,10 +219,15 @@
                                  gpointer          origin_tag)
 {
   GMemorySettingsBackend *memory = G_MEMORY_SETTINGS_BACKEND (backend);
+  GHashTable *table;
 
-  if (g_hash_table_lookup (memory->table, key))
+  table = g_memory_settings_backend_get_table (memory->table, &key);
+  if (!table)
+    return;
+
+  if (g_hash_table_lookup (table, key))
     {
-      g_hash_table_remove (memory->table, key);
+      g_hash_table_remove (table, key);
       g_settings_backend_changed (backend, key, origin_tag);
     }
 }
@@ -141,6 +246,92 @@
   return g_simple_permission_new (TRUE);
 }
 
+static gchar **
+g_memory_settings_backend_list (GSettingsBackend    *backend,
+				const gchar         *path,
+				const gchar * const *schema_items)
+{
+  gchar **result = NULL;
+  GHashTable *table;
+  GMemorySettingsBackend *memory = G_MEMORY_SETTINGS_BACKEND (backend);
+
+  table = g_memory_settings_backend_get_table (memory->table, &path);
+  if (table != NULL)
+    {
+      GHashTableIter iter;
+      gchar *key_name;
+      guint i;
+      GSList *list = NULL;
+
+      while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL))
+        {
+	  gboolean in_schema_items = FALSE;
+
+	  for (i = 0; i < g_strv_length ((gchar **) schema_items); i++)
+	    {
+	      if (g_str_equal (key_name, schema_items[i]))
+	        {
+		  /* It's in both lists, so return it */
+		  list = g_slist_append (list, key_name);
+		  in_schema_items = TRUE;
+		}
+	    }
+	  if (!in_schema_items)
+	    {
+	      /* Has been added by the user */
+	      list = g_slist_append (list, key_name);
+	    }
+	}
+
+      result = g_new0 (gchar *, g_slist_length (list) + 1);
+      i = 0;
+      while (list != NULL)
+        {
+	  result[i] = g_strdup ((const gchar *) list->data);
+	  i++;
+
+	  list = g_slist_remove (list, list->data);
+	}
+
+      result[i] = NULL;
+    }
+
+  return result;
+}
+
+static gboolean
+g_memory_settings_backend_can_insert (GSettingsBackend *backend,
+				      const gchar      *path)
+{
+  return FALSE;
+}
+
+static gboolean
+g_memory_settings_backend_can_remove (GSettingsBackend *backend,
+				      const gchar      *path,
+				      const gchar      *id)
+{
+  return FALSE;
+}
+
+static gboolean
+g_memory_settings_backend_insert (GSettingsBackend *backend,
+				  const gchar      *path,
+				  gint              index_,
+				  const gchar      *prefix,
+				  gchar           **name)
+{
+  return FALSE;
+}
+
+static gboolean
+g_memory_settings_backend_remove (GSettingsBackend *backend,
+				  const gchar      *path,
+				  const gchar      *id)
+{
+  return FALSE;
+}
+
 static void
 g_memory_settings_backend_finalize (GObject *object)
 {
@@ -171,6 +362,11 @@
   backend_class->reset = g_memory_settings_backend_reset;
   backend_class->get_writable = g_memory_settings_backend_get_writable;
   backend_class->get_permission = g_memory_settings_backend_get_permission;
+  backend_class->list = g_memory_settings_backend_list;
+  backend_class->can_insert = g_memory_settings_backend_can_insert;
+  backend_class->can_remove = g_memory_settings_backend_can_remove;
+  backend_class->insert = g_memory_settings_backend_insert;
+  backend_class->remove = g_memory_settings_backend_remove;
   object_class->finalize = g_memory_settings_backend_finalize;
 }
 
diff --git a/gio/gsettings.c b/gio/gsettings.c
index ec48006..c9a23b7 100644
--- a/gio/gsettings.c
+++ b/gio/gsettings.c
@@ -213,6 +213,7 @@
   gchar *path;
 
   GDelayedSettingsBackend *delayed;
+  gboolean destroyed;
 };
 
 enum
@@ -496,6 +497,10 @@
                             settings->priv->main_context);
   g_settings_backend_subscribe (settings->priv->backend,
                                 settings->priv->path);
+
+  settings->priv->destroyed =
+    !g_settings_backend_check (settings->priv->backend,
+                               settings->priv->path);
 }
 
 static void
@@ -2936,6 +2941,130 @@
   g_object_set_qdata (object, binding_quark, NULL);
 }
 
+/* Children (add, remove, can_{add,remove}, list, get_destroyed) {{{1 */
+
+/**
+ * g_settings_add_child:
+ * @settings: a list #GSettings
+ * @prefix: the prefix of the new child name
+ * @name: return location for the new child name
+ * @returns: %TRUE if the child was added
+ *
+ * Adds a child to a list.
+ *
+ * The child will be created with a new unique name that starts with
+ * @prefix.  For example, if @prefix is "item" then the child's name may
+ * end up looking like "item0", "item1", or so on.  No particular format
+ * is specified -- only that the resulting name will have the given
+ * prefix.
+ *
+ * If the creation was successful then @name (if non-%NULL) is set to
+ * the name of the new child and %TRUE is returned.
+ *
+ * If the creation fails then %FALSE is returned and @name is untouched.
+ **/
+gboolean
+g_settings_add_child (GSettings    *settings,
+                      const gchar  *prefix,
+                      gchar       **name)
+{
+  g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE);
+  g_return_val_if_fail (prefix != NULL, FALSE);
+
+  return g_settings_backend_insert (settings->priv->backend,
+                                    settings->priv->path,
+                                    -1, prefix, name);
+}
+
+/**
+ * g_settings_remove_child:
+ * @settings: a list #GSettings
+ * @id: the id of the child to remove
+ * @returns: %TRUE if the child was successfully removed
+ *
+ * Removes a child from the list.
+ *
+ * In the case that the removal was successful then %TRUE is returned.
+ * In the case that it failed, %FALSE is returned.
+ *
+ * The return value of this function is not particularly useful, since
+ * it is not specified if the non-operation resulting from the request
+ * to remove a non-existent child is considered to be a success (and
+ * this situation can easily arise as a race condition).  Most usually
+ * you will actually probably want to ignore the return value here and
+ * just monitor the "children-changed" signal and make changes to your
+ * user interface accordingly.
+ **/
+gboolean
+g_settings_remove_child (GSettings   *settings,
+                         const gchar *id)
+{
+  g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE);
+  g_return_val_if_fail (id != NULL, FALSE);
+
+  return g_settings_backend_remove (settings->priv->backend,
+                                    settings->priv->path,
+                                    id);
+}
+
+/**
+ * g_settings_can_add_child:
+ * @settings: a #GSettings object
+ * @returns: %TRUE if a call to g_settings_add_child() would succeed
+ *
+ * Checks if it is valid to add children to @settings.
+ **/
+gboolean
+g_settings_can_add_child (GSettings *settings)
+{
+  g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE);
+
+  return g_settings_backend_can_insert (settings->priv->backend,
+                                        settings->priv->path);
+}
+
+/**
+ * g_settings_can_remove_child:
+ * @settings: a #GSettings object
+ * @id: the identifier of an existing child
+ * @returns: %TRUE if a call to g_settings_remove_child() would succeed
+ *
+ * Checks if it is valid to remove @id from @settings.
+ *
+ * The return value of this function is unspecified for the case that
+ * @id does not exist.
+ **/
+gboolean
+g_settings_can_remove_child (GSettings   *settings,
+                             const gchar *id)
+{
+  g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE);
+
+  return g_settings_backend_can_remove (settings->priv->backend,
+                                        settings->priv->path, id);
+}
+
+/**
+ * g_settings_get_destroyed:
+ * @settings: a #GSettings
+ * @returns: %TRUE if @settings has been destroyed
+ *
+ * Checks if @settings has been destroyed.
+ *
+ * If @settings is a member of a list (or a child thereof) and has since
+ * been removed from the list then it will be considered as having been
+ * destroyed.  The "notify" signal will be emitted on the "destroyed"
+ * property and this function will return %TRUE thereafter.
+ *
+ * A destroyed #GSettings object is never revived and nearly all
+ * operations performed on it will be meaningless.
+ **/
+gboolean
+g_settings_get_destroyed (GSettings *settings)
+{
+  return settings->priv->destroyed;
+}
+
 /* Epilogue {{{1 */
 
 /* vim:set foldmethod=marker: */
diff --git a/gio/gsettings.h b/gio/gsettings.h
index 324ad36..f30c44a 100644
--- a/gio/gsettings.h
+++ b/gio/gsettings.h
@@ -58,7 +58,10 @@
                                         const GQuark *keys,
                                         gint          n_keys);
 
-  gpointer padding[20];
+  void        (*can_remove_changed)    (GSettings    *settings);
+  void        (*children_changed)      (GSettings    *settings);
+
+  gpointer padding[18];
 };
 
 struct _GSettings
@@ -260,6 +263,20 @@
                                                                          GSettingsGetMapping      mapping,
                                                                          gpointer                 user_data);
 
+gboolean                g_settings_can_add_child                        (GSettings               *settings);
+
+gboolean                g_settings_can_remove_child                     (GSettings               *settings,
+                                                                         const gchar             *id);
+
+gboolean                g_settings_add_child                            (GSettings               *settings,
+                                                                         const gchar             *prefix,
+                                                                         gchar                  **name);
+
+gboolean                g_settings_remove_child                         (GSettings               *settings,
+                                                                         const gchar             *id);
+
+gboolean                g_settings_get_destroyed                        (GSettings               *settings);
+
 G_END_DECLS
 
 #endif  /* __G_SETTINGS_H__ */
diff --git a/gio/gsettingsbackend.c b/gio/gsettingsbackend.c
index 24a6f9a..ee0a93f 100644
--- a/gio/gsettingsbackend.c
+++ b/gio/gsettingsbackend.c
@@ -1030,3 +1030,57 @@
   if (class->sync)
     class->sync (backend);
 }
+
+gchar **
+g_settings_backend_list (GSettingsBackend    *backend,
+			 const gchar         *path,
+			 const gchar * const *schema_items)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->list (backend, path, schema_items);
+}
+
+gboolean
+g_settings_backend_can_insert (GSettingsBackend *backend,
+                               const gchar      *path)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->can_insert (backend, path);
+}
+
+gboolean
+g_settings_backend_can_remove (GSettingsBackend *backend,
+                               const gchar      *path,
+                               const gchar      *id)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->can_remove (backend, path, id);
+}
+
+gboolean
+g_settings_backend_insert (GSettingsBackend  *backend,
+                           const gchar       *path,
+                           gint               index_,
+                           const gchar       *prefix,
+                           gchar            **name)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->insert (backend, path, index_, prefix, name);
+}
+
+gboolean
+g_settings_backend_remove (GSettingsBackend *backend,
+                           const gchar      *path,
+                           const gchar      *id)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->remove (backend, path, id);
+}
+
+gboolean
+g_settings_backend_check (GSettingsBackend *backend,
+                          const gchar      *path)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->check (backend, path);
+}
diff --git a/gio/gsettingsbackend.h b/gio/gsettingsbackend.h
index e674187..2854529 100644
--- a/gio/gsettingsbackend.h
+++ b/gio/gsettingsbackend.h
@@ -93,7 +93,26 @@
   GPermission * (*get_permission)   (GSettingsBackend    *backend,
                                      const gchar         *path);
 
-  gpointer padding[24];
+  gchar **      (*list)             (GSettingsBackend    *backend,
+                                     const gchar         *path,
+                                     const gchar * const *schema_items);
+  gboolean      (*check)            (GSettingsBackend    *backend,
+                                     const gchar         *path);
+  gboolean      (*can_insert)       (GSettingsBackend    *backend,
+                                     const gchar         *path);
+  gboolean      (*can_remove)       (GSettingsBackend    *backend,
+                                     const gchar         *path,
+                                     const gchar         *id);
+  gboolean      (*insert)           (GSettingsBackend    *backend,
+                                     const gchar         *path,
+                                     gint                 index_,
+                                     const gchar         *prefix,
+                                     gchar              **name);
+  gboolean      (*remove)           (GSettingsBackend    *backend,
+                                     const gchar         *path,
+                                     const gchar         *id);
+
+  gpointer padding[18];
 };
 
 struct _GSettingsBackend
diff --git a/gio/gsettingsbackendinternal.h b/gio/gsettingsbackendinternal.h
index e009b31..8a423e0 100644
--- a/gio/gsettingsbackendinternal.h
+++ b/gio/gsettingsbackendinternal.h
@@ -41,12 +41,24 @@
                                   const gchar         *prefix,
                                   const gchar * const *names,
                                   gpointer             origin_tag);
+
   void (* writable_changed)      (GObject             *target,
                                   GSettingsBackend    *backend,
                                   const gchar         *key);
   void (* path_writable_changed) (GObject             *target,
                                   GSettingsBackend    *backend,
                                   const gchar         *path);
+
+  void (* children_changed)      (GObject             *target,
+                                  GSettingsBackend    *backend,
+                                  const gchar         *path,
+                                  gpointer             origin_tag);
+  void (* can_add_changed)       (GObject             *target,
+                                  GSettingsBackend    *backend,
+                                  const gchar         *path);
+  void (* can_remove_changed)    (GObject             *target,
+                                  GSettingsBackend    *backend,
+                                  const gchar         *path);
 } GSettingsListenerVTable;
 
 G_GNUC_INTERNAL
@@ -91,6 +103,28 @@
 G_GNUC_INTERNAL
 GPermission *           g_settings_backend_get_permission               (GSettingsBackend               *backend,
                                                                          const gchar                    *path);
+
+G_GNUC_INTERNAL
+gboolean                g_settings_backend_check                        (GSettingsBackend               *backend,
+                                                                         const gchar                    *path);
+G_GNUC_INTERNAL
+gboolean                g_settings_backend_insert                       (GSettingsBackend               *backend,
+                                                                         const gchar                    *path,
+                                                                         gint                            index,
+                                                                         const gchar                    *prefix,
+                                                                         gchar                         **name);
+G_GNUC_INTERNAL
+gboolean                g_settings_backend_remove                       (GSettingsBackend               *backend,
+                                                                         const gchar                    *path,
+                                                                         const gchar                    *id);
+G_GNUC_INTERNAL
+gboolean                g_settings_backend_can_insert                   (GSettingsBackend               *backend,
+                                                                         const gchar                    *path);
+G_GNUC_INTERNAL
+gboolean                g_settings_backend_can_remove                   (GSettingsBackend               *backend,
+                                                                         const gchar                    *path,
+                                                                         const gchar                    *id);
+
 G_GNUC_INTERNAL
 void                    g_settings_backend_sync_default                 (void);