From b12b0a26c2172e79fbe6ce16a19b97e11c1b1f06 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Mon, 13 Sep 2010 13:19:09 -0400 Subject: [PATCH] list stuff --- gio/gio.symbols | 5 + gio/gmemorysettingsbackend.c | 117 ++++++++++++- gio/gsettings.c | 309 +++++++++++++++++++++++---------- gio/gsettings.h | 27 ++- gio/gsettingsbackend.c | 45 +++++ gio/gsettingsbackend.h | 21 ++- gio/gsettingsbackendinternal.h | 34 ++++ 7 files changed, 454 insertions(+), 104 deletions(-) diff --git a/gio/gio.symbols b/gio/gio.symbols index fa5d62340..ad700e985 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -1558,6 +1558,11 @@ g_settings_sync 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 #endif #endif diff --git a/gio/gmemorysettingsbackend.c b/gio/gmemorysettingsbackend.c index 6b60058e8..50c2f4128 100644 --- a/gio/gmemorysettingsbackend.c +++ b/gio/gmemorysettingsbackend.c @@ -32,11 +32,42 @@ G_TYPE_MEMORY_SETTINGS_BACKEND, \ GMemorySettingsBackend)) +typedef struct +{ + GHashTable/**/ *values; + GHashTable/**/ *lists; +} TablePair; + +static void +table_pair_free (gpointer data) +{ + TablePair *pair = data; + + g_hash_table_unref (pair->values); + g_hash_table_unref (pair->list); +} + +static TablePair * +table_pair_new (void) +{ + TablePair *pair; + + pair = g_slice_new (TablePair); + pair->values = g_hash_table_new (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_variant_unref); + pair->lists = g_hash_table_new (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_hash_table_unref); + + return pair; +} + + + typedef GSettingsBackendClass GMemorySettingsBackendClass; typedef struct { GSettingsBackend parent_instance; - GHashTable *table; + TablePair *root; } GMemorySettingsBackend; G_DEFINE_TYPE_WITH_CODE (GMemorySettingsBackend, @@ -45,6 +76,90 @@ G_DEFINE_TYPE_WITH_CODE (GMemorySettingsBackend, 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, 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, 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 (*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, diff --git a/gio/gsettings.c b/gio/gsettings.c index eb4370cbe..365ddd157 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -195,6 +195,7 @@ struct _GSettingsPrivate gchar *path; GDelayedSettingsBackend *delayed; + gboolean destroyed; }; enum @@ -465,6 +466,10 @@ g_settings_constructed (GObject *object) 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 @@ -1975,7 +1980,7 @@ g_settings_get_has_unapplied (GSettings *settings) G_DELAYED_SETTINGS_BACKEND (settings->priv->backend)); } -/* Extra API (reset, sync, get_child, is_writable, list_*) {{{1 */ +/* Extra API (reset, sync, get_child, is_writable) {{{1 */ /** * g_settings_reset: * @settings: a #GSettings object @@ -2089,95 +2094,6 @@ g_settings_get_child (GSettings *settings, return child; } -/** - * g_settings_list_keys: - * @settings: a #GSettings object - * @returns: a list of the keys on @settings - * - * Introspects the list of keys on @settings. - * - * You should probably not be calling this function from "normal" code - * (since you should already know what keys are in your schema). This - * function is intended for introspection reasons. - * - * You should free the return value with g_strfreev() when you are done - * with it. - */ -gchar ** -g_settings_list_keys (GSettings *settings) -{ - const GQuark *keys; - gchar **strv; - gint n_keys; - gint i, j; - - keys = g_settings_schema_list (settings->priv->schema, &n_keys); - strv = g_new (gchar *, n_keys + 1); - for (i = j = 0; i < n_keys; i++) - { - const gchar *key = g_quark_to_string (keys[i]); - - if (!g_str_has_suffix (key, "/")) - strv[j++] = g_strdup (key); - } - strv[j] = NULL; - - return strv; -} - -/** - * g_settings_list_children: - * @settings: a #GSettings object - * @returns: a list of the children on @settings - * - * Gets the list of children on @settings. - * - * The list is exactly the list of strings for which it is not an error - * to call g_settings_get_child(). - * - * For GSettings objects that are lists, this value can change at any - * time and you should connect to the "children-changed" signal to watch - * for those changes. Note that there is a race condition here: you may - * request a child after listing it only for it to have been destroyed - * in the meantime. For this reason, g_settings_get_chuld() may return - * %NULL even for a child that was listed by this function. - * - * For GSettings objects that are not lists, you should probably not be - * calling this function from "normal" code (since you should already - * know what children are in your schema). This function may still be - * useful there for introspection reasons, however. - * - * You should free the return value with g_strfreev() when you are done - * with it. - */ -gchar ** -g_settings_list_children (GSettings *settings) -{ - const GQuark *keys; - gchar **strv; - gint n_keys; - gint i, j; - - keys = g_settings_schema_list (settings->priv->schema, &n_keys); - strv = g_new (gchar *, n_keys + 1); - for (i = j = 0; i < n_keys; i++) - { - const gchar *key = g_quark_to_string (keys[i]); - - if (g_str_has_suffix (key, "/")) - { - gint length = strlen (key); - - strv[j] = g_memdup (key, length); - strv[j][length - 1] = '\0'; - j++; - } - } - strv[j] = NULL; - - return strv; -} - /* Binding {{{1 */ typedef struct { @@ -2736,6 +2652,219 @@ g_settings_unbind (gpointer object, 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; +} + +/** + * g_settings_list_keys: + * @settings: a #GSettings object + * @returns: a list of the keys on @settings + * + * Introspects the list of keys on @settings. + * + * You should probably not be calling this function from "normal" code + * (since you should already know what keys are in your schema). This + * function is intended for introspection reasons. + * + * You should free the return value with g_strfreev() when you are done + * with it. + */ +gchar ** +g_settings_list_keys (GSettings *settings) +{ + const GQuark *keys; + gchar **strv; + gint n_keys; + gint i, j; + + keys = g_settings_schema_list (settings->priv->schema, &n_keys); + strv = g_new (gchar *, n_keys + 1); + for (i = j = 0; i < n_keys; i++) + { + const gchar *key = g_quark_to_string (keys[i]); + + if (!g_str_has_suffix (key, "/")) + strv[j++] = g_strdup (key); + } + strv[j] = NULL; + + return strv; +} + +/** + * g_settings_list_children: + * @settings: a #GSettings object + * @returns: a list of the children on @settings + * + * Gets the list of children on @settings. + * + * The list is exactly the list of strings for which it is not an error + * to call g_settings_get_child(). + * + * For GSettings objects that are lists, this value can change at any + * time and you should connect to the "children-changed" signal to watch + * for those changes. Note that there is a race condition here: you may + * request a child after listing it only for it to have been destroyed + * in the meantime. For this reason, g_settings_get_chuld() may return + * %NULL even for a child that was listed by this function. + * + * For GSettings objects that are not lists, you should probably not be + * calling this function from "normal" code (since you should already + * know what children are in your schema). This function may still be + * useful there for introspection reasons, however. + * + * You should free the return value with g_strfreev() when you are done + * with it. + */ +gchar ** +g_settings_list_children (GSettings *settings) +{ + const GQuark *keys; + gchar **strv; + gint n_keys; + gint i, j; + + keys = g_settings_schema_list (settings->priv->schema, &n_keys); + strv = g_new (gchar *, n_keys + 1); + for (i = j = 0; i < n_keys; i++) + { + const gchar *key = g_quark_to_string (keys[i]); + + if (g_str_has_suffix (key, "/")) + { + gint length = strlen (key); + + strv[j] = g_memdup (key, length); + strv[j][length - 1] = '\0'; + j++; + } + } + strv[j] = NULL; + + return strv; +} + /* Epilogue {{{1 */ /* vim:set foldmethod=marker: */ diff --git a/gio/gsettings.h b/gio/gsettings.h index 294d885d1..c051ac236 100644 --- a/gio/gsettings.h +++ b/gio/gsettings.h @@ -58,7 +58,10 @@ struct _GSettingsClass 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 @@ -79,11 +82,6 @@ GSettings * g_settings_new_with_backend (const g GSettings * g_settings_new_with_backend_and_path (const gchar *schema, GSettingsBackend *backend, const gchar *path); -gchar ** g_settings_list_children (GSettings *settings); -gchar ** g_settings_list_keys (GSettings *settings); - -gboolean g_settings_get_destroyed (GSettings *settings); -GPermission * g_settings_get_permission (GSettings *settings); gboolean g_settings_set_value (GSettings *settings, const gchar *key, @@ -256,6 +254,23 @@ gpointer g_settings_get_mapped (GSettin GSettingsGetMapping mapping, gpointer user_data); +gchar ** g_settings_list_children (GSettings *settings); +gchar ** g_settings_list_keys (GSettings *settings); + +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 8485cd329..3414d736e 100644 --- a/gio/gsettingsbackend.c +++ b/gio/gsettingsbackend.c @@ -1050,3 +1050,48 @@ g_settings_backend_sync_default (void) if (class->sync) class->sync (backend); } + +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 781874eff..2f4aeaa0f 100644 --- a/gio/gsettingsbackend.h +++ b/gio/gsettingsbackend.h @@ -96,16 +96,23 @@ struct _GSettingsBackendClass gchar ** (*list) (GSettingsBackend *backend, const gchar *path, const gchar * const *schema_items); - gchar * (*add) (GSettingsBackend *backend, - const gchar *path, - const gchar *hint); - gboolean (*remove) (GSettingsBackend *backend, - const gchar *path, - const gchar *name); 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[20]; + gpointer padding[18]; }; struct _GSettingsBackend diff --git a/gio/gsettingsbackendinternal.h b/gio/gsettingsbackendinternal.h index 68a61a8f3..ea284e978 100644 --- a/gio/gsettingsbackendinternal.h +++ b/gio/gsettingsbackendinternal.h @@ -41,12 +41,24 @@ typedef struct 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 @@ void g_settings_backend_subscribe (GSettin 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 GMainContext * g_settings_backend_get_active_context (void); G_GNUC_INTERNAL