From 597290d5c81bf889a694e286ea2434655b82a404 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Mon, 14 Jun 2010 17:29:41 -0400 Subject: [PATCH] GSettings: major refactor. Add enums, range. --- gio/Makefile.am | 2 +- gio/gio.symbols | 2 + gio/gschema-compile.c | 1326 +++++++++++++++------ gio/gsettings-mapping.c | 24 +- gio/gsettings.c | 2287 +++++++++++++++++++++--------------- gio/gsettings.h | 6 +- gio/gsettingsbackend.c | 27 +- gio/gsettingsschema.c | 45 +- gio/gsettingsschema.h | 5 +- gio/strinfo.c | 308 +++++ gio/tests/enums.xml.template | 18 + gio/tests/gsettings.c | 148 ++- gio/tests/org.gtk.test.gschema.xml | 28 + gio/tests/testenum.h | 7 + 14 files changed, 2893 insertions(+), 1340 deletions(-) create mode 100644 gio/strinfo.c create mode 100644 gio/tests/enums.xml.template create mode 100644 gio/tests/testenum.h diff --git a/gio/Makefile.am b/gio/Makefile.am index 9841d5c..b338c5f 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -374,7 +374,7 @@ libgio_2_0_la_SOURCES = \ $(marshal_sources) \ $(NULL) -EXTRA_DIST += gnullapplication.c gdbusapplication.c +EXTRA_DIST += gnullapplication.c gdbusapplication.c strinfo.c $(libgio_2_0_la_OBJECTS): $(marshal_sources) diff --git a/gio/gio.symbols b/gio/gio.symbols index 707e0cc..4ced155 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -1468,6 +1468,8 @@ g_settings_get_double g_settings_set_double g_settings_get_boolean g_settings_set_boolean +g_settings_get_enum +g_settings_set_enum g_settings_sync #endif #endif diff --git a/gio/gschema-compile.c b/gio/gschema-compile.c index f462a7b..87fd3fb 100644 --- a/gio/gschema-compile.c +++ b/gio/gschema-compile.c @@ -19,6 +19,8 @@ * Author: Ryan Lortie */ +/* Prologue {{{1 */ +#define _GNU_SOURCE #include "config.h" #include @@ -30,30 +32,578 @@ #include #include "gvdb/gvdb-builder.h" +#include "strinfo.c" -typedef struct +/* Handling of {{{1 */ +typedef GString EnumState; + +static void +enum_state_free (gpointer data) +{ + EnumState *state = data; + + g_string_free (state, TRUE); +} + +static void +enum_state_add_value (EnumState *state, + const gchar *nick, + const gchar *valuestr, + GError **error) +{ + gint64 value; + gchar *end; + + if (nick[0] == '\0' || nick[1] == '\0') + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "enum nick must be a minimum of 2 characters"); + return; + } + + value = g_ascii_strtoll (valuestr, &end, 0); + if (*end || value > G_MAXINT32 || value < G_MININT32) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "invalid numeric value"); + return; + } + + if (strinfo_builder_contains (state, nick)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified", nick); + return; + } + + strinfo_builder_append_item (state, nick, value); +} + +static void +enum_state_end (EnumState **state_ptr, + GError **error) { - gboolean byteswap; + EnumState *state; - GVariantBuilder key_options; - GHashTable *schemas; - gchar *schemalist_domain; + state = *state_ptr; + *state_ptr = NULL; - GHashTable *schema; - GvdbItem *schema_root; - gchar *schema_domain; + if (state->len == 0) + g_set_error_literal (error, + G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + " must contain at least one "); +} + +/* Handling of {{{1 */ +typedef struct +{ + /* for , @child_schema will be set. + * for , everything else will be set. + */ + gchar *child_schema; - GString *string; - GvdbItem *key; - GVariant *value; - GVariant *min, *max; - GString *choices; - gchar l10n; - gchar *context; GVariantType *type; -} ParseState; + gboolean have_gettext_domain; + + gchar l10n; + gchar *l10n_context; + GString *unparsed_default_value; + GVariant *default_value; + + GString *strinfo; + gboolean is_enum; + + GVariant *minimum; + GVariant *maximum; + + gboolean has_choices; + gboolean has_aliases; + + gboolean checked; + GVariant *serialised; +} KeyState; + +static KeyState * +key_state_new (const gchar *type_string, + const gchar *gettext_domain, + EnumState *enum_data) +{ + KeyState *state; + + state = g_slice_new0 (KeyState); + state->type = g_variant_type_new (type_string); + state->have_gettext_domain = gettext_domain != NULL; + + if ((state->is_enum = (enum_data != NULL))) + state->strinfo = g_string_new_len (enum_data->str, enum_data->len); + else + state->strinfo = g_string_new (NULL); + + return state; +} + +static KeyState * +key_state_new_child (const gchar *child_schema) +{ + KeyState *state; + + state = g_slice_new0 (KeyState); + state->child_schema = g_strdup (child_schema); + + return state; +} + +static gboolean +is_valid_choices (GVariant *variant, + GString *strinfo) +{ + switch (g_variant_classify (variant)) + { + case G_VARIANT_CLASS_MAYBE: + case G_VARIANT_CLASS_ARRAY: + { + gboolean valid = TRUE; + GVariantIter iter; + + g_variant_iter_init (&iter, variant); + + while (valid && (variant = g_variant_iter_next_value (&iter))) + { + valid = is_valid_choices (variant, strinfo); + g_variant_unref (variant); + } + + return valid; + } + + case G_VARIANT_CLASS_STRING: + return strinfo_is_string_valid ((const guint32 *) strinfo->str, + strinfo->len / 4, + g_variant_get_string (variant, NULL)); + default: + g_assert_not_reached (); + } +} + + +/* Gets called at or to check for + * validity of the default value so that any inconsistency is + * reported as soon as it is encountered. + */ +static void +key_state_check_range (KeyState *state, + GError **error) +{ + if (state->default_value) + { + if (state->minimum) + { + if (g_variant_compare (state->default_value, state->minimum) < 0 || + g_variant_compare (state->default_value, state->maximum) > 0) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " is not contained in " + "the specified range"); + } + } + + else if (state->strinfo->len) + { + if (!is_valid_choices (state->default_value, state->strinfo)) + { + if (state->is_enum) + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " is not a valid member of " + "the specified enumerated type"); + + else + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " contains string not in " + ""); + } + } + } +} + +static void +key_state_set_range (KeyState *state, + const gchar *min_str, + const gchar *max_str, + GError **error) +{ + if (state->minimum) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified for this key"); + return; + } + + if (strchr ("ynqiuxtd", *(char *) state->type) == NULL) + { + gchar *type = g_variant_type_dup_string (state->type); + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " not allowed for keys of type \"%s\"\n", type); + g_free (type); + return; + } + + state->minimum = g_variant_parse (state->type, min_str, NULL, NULL, error); + if (state->minimum == NULL) + return; + + state->maximum = g_variant_parse (state->type, max_str, NULL, NULL, error); + if (state->maximum == NULL) + return; + + if (g_variant_compare (state->minimum, state->maximum) > 0) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " specified minimum is greater than maxmimum"); + return; + } + + key_state_check_range (state, error); +} + +static GString * +key_state_start_default (KeyState *state, + const gchar *l10n, + const gchar *context, + GError **error) +{ + if (l10n != NULL) + { + if (strcmp (l10n, "messages") == 0) + state->l10n = 'm'; + + else if (strcmp (l10n, "time") == 0) + state->l10n = 't'; + + else + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "unsupported l10n category: %s", l10n); + return NULL; + } + + if (!state->have_gettext_domain) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "l10n requested, but no " + "gettext domain given"); + return NULL; + } + + state->l10n_context = g_strdup (context); + } + + else if (context != NULL) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "translation context given for " + " value without l10n enabled"); + return NULL; + } + + return g_string_new (NULL); +} + +static void +key_state_end_default (KeyState *state, + GString **string, + GError **error) +{ + state->unparsed_default_value = *string; + *string = NULL; + + state->default_value = g_variant_parse (state->type, + state->unparsed_default_value->str, + NULL, NULL, error); + key_state_check_range (state, error); +} + +static void +key_state_start_choices (KeyState *state, + GError **error) +{ + const GVariantType *type = state->type; + + if (state->is_enum) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " can not be specified for keys " + "tagged as having an enumerated type"); + return; + } + + if (state->has_choices) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified for this key"); + return; + } + + while (g_variant_type_is_maybe (type) || g_variant_type_is_array (type)) + type = g_variant_type_element (type); + + if (!g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + { + gchar *type_string = g_variant_type_dup_string (state->type); + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " not allowed for keys of type '%s'", + type_string); + g_free (type_string); + return; + } +} + +static void +key_state_add_choice (KeyState *state, + const gchar *choice, + GError **error) +{ + if (strinfo_builder_contains (state->strinfo, choice)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already given", choice); + return; + } + + strinfo_builder_append_item (state->strinfo, choice, 0); + state->has_choices = TRUE; +} + +static void +key_state_end_choices (KeyState *state, + GError **error) +{ + if (!state->has_choices) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + " must contain at least one "); + return; + } + + key_state_check_range (state, error); +} + +static void +key_state_start_aliases (KeyState *state, + GError **error) +{ + if (state->has_aliases) + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified for this key"); + + if (!state->is_enum && !state->has_choices) + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " can only be specified for keys with " + "enumerated types or after "); +} + +static void +key_state_add_alias (KeyState *state, + const gchar *alias, + const gchar *target, + GError **error) +{ + if (strinfo_builder_contains (state->strinfo, alias)) + { + if (strinfo_is_string_valid ((guint32 *) state->strinfo->str, + state->strinfo->len / 4, + alias)) + { + if (state->is_enum) + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " given when '%s' is already " + "a member of the enumerated type", alias, alias); + + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " given when " + " was already given", + alias, alias); + } + + else + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified", alias); + + return; + } + + if (!strinfo_builder_append_alias (state->strinfo, alias, target)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "alias target '%s' is not in %s", target, + state->is_enum ? "enumerated type" : ""); + return; + } + + state->has_aliases = TRUE; +} + +static void +key_state_end_aliases (KeyState *state, + GError **error) +{ + if (!state->has_aliases) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + " must contain at least one "); + return; + } +} + +static gboolean +key_state_check (KeyState *state, + GError **error) +{ + if (state->checked) + return TRUE; + + return state->checked = TRUE; +} + +static GVariant * +key_state_serialise (KeyState *state) +{ + if (state->serialised == NULL) + { + if (state->child_schema) + { + state->serialised = g_variant_new_string (state->child_schema); + } + + else + { + GVariantBuilder builder; + + g_assert (key_state_check (state, NULL)); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); + + /* default value */ + g_variant_builder_add_value (&builder, state->default_value); + + /* translation */ + if (state->l10n) + { + if (state->l10n_context) + { + gint len; + + /* Contextified messages are supported by prepending + * the context, followed by '\004' to the start of the + * message string. We do that here to save GSettings + * the work later on. + */ + len = strlen (state->l10n_context); + state->l10n_context[len] = '\004'; + g_string_prepend_len (state->unparsed_default_value, + state->l10n_context, len + 1); + g_free (state->l10n_context); + state->l10n_context = NULL; + } + + g_variant_builder_add (&builder, "(y(y&s))", 'l', state->l10n, + state->unparsed_default_value); + g_string_free (state->unparsed_default_value, TRUE); + state->unparsed_default_value = NULL; + } + + /* choice, aliases, enums */ + if (state->strinfo->len) + { + GVariant *array; + gpointer data; + gsize size; + + data = state->strinfo->str; + size = state->strinfo->len; + + array = g_variant_new_from_data (G_VARIANT_TYPE ("au"), + data, size, TRUE, + g_free, data); + + g_string_free (state->strinfo, FALSE); + state->strinfo = NULL; + + g_variant_builder_add (&builder, "(y@au)", + state->is_enum ? 'e' : 'c', + array); + } + + /* range */ + if (state->minimum || state->maximum) + g_variant_builder_add (&builder, "(y(**))", 'r', + state->minimum, state->maximum); + + state->serialised = g_variant_builder_end (&builder); + } + + g_variant_ref_sink (state->serialised); + } + + return g_variant_ref (state->serialised); +} + +static void +key_state_free (gpointer data) +{ + KeyState *state = data; + + if (state->type) + g_variant_type_free (state->type); + + g_free (state->l10n_context); + + if (state->unparsed_default_value) + g_string_free (state->unparsed_default_value, TRUE); + + if (state->default_value) + g_variant_unref (state->default_value); + + if (state->strinfo) + g_string_free (state->strinfo, TRUE); + + if (state->minimum) + g_variant_unref (state->minimum); + + if (state->maximum) + g_variant_unref (state->maximum); + + if (state->serialised) + g_variant_unref (state->serialised); + + g_slice_free (KeyState, state); +} + +/* Key name validity {{{1 */ static gboolean allow_any_name = FALSE; static gboolean @@ -120,68 +670,195 @@ is_valid_keyname (const gchar *key, return TRUE; } -static gboolean -type_allows_choices (const GVariantType *type) +/* Handling of {{{1 */ +typedef struct { - if (g_variant_type_is_array (type) || - g_variant_type_is_maybe (type)) - return type_allows_choices (g_variant_type_element (type)); + gchar *path; + gchar *gettext_domain; + + GHashTable *keys; +} SchemaState; + +static SchemaState * +schema_state_new (const gchar *path, + const gchar *gettext_domain) +{ + SchemaState *state; + + state = g_slice_new (SchemaState); + state->path = g_strdup (path); + state->gettext_domain = g_strdup (gettext_domain); + state->keys = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, key_state_free); - return g_variant_type_equal (type, G_VARIANT_TYPE_STRING); + return state; } -static gboolean -type_allows_range (const GVariantType *type) +static void +schema_state_free (gpointer data) { - static const char range_types[] = "ynqiuxtd"; + SchemaState *state = data; - return strchr (range_types, *(const char*) type) != NULL; + g_free (state->path); + g_free (state->gettext_domain); + g_hash_table_unref (state->keys); } -static gboolean -is_valid_choices (GVariant *variant, - const gchar *choices) +static void +schema_state_add_child (SchemaState *state, + const gchar *name, + const gchar *schema, + GError **error) { - switch (g_variant_classify (variant)) + gchar *childname; + + if (!is_valid_keyname (name, error)) + return; + + childname = g_strconcat (name, "/", NULL); + + if (g_hash_table_lookup (state->keys, childname)) { - case G_VARIANT_CLASS_MAYBE: - case G_VARIANT_CLASS_ARRAY: - { - gsize i, n; - GVariant *child; - gboolean is_valid; + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified", name); + return; + } - n = g_variant_n_children (variant); - for (i = 0; i < n; ++i) - { - child = g_variant_get_child_value (variant, i); - is_valid = is_valid_choices (child, choices); - g_variant_unref (child); + g_hash_table_insert (state->keys, childname, + key_state_new_child (schema)); +} - if (!is_valid) - return FALSE; - } +static KeyState * +schema_state_add_key (SchemaState *state, + GHashTable *enum_table, + const gchar *name, + const gchar *type_string, + const gchar *enum_type, + GError **error) +{ + GString *enum_data; + KeyState *key; + + if (!is_valid_keyname (name, error)) + return NULL; + + if (g_hash_table_lookup (state->keys, name)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified", name); + return NULL; + } + + if ((type_string == NULL) == (enum_type == NULL)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_MISSING_ATTRIBUTE, + "exactly one of 'type' or 'enum' must " + "be specified as an attribute to "); + return NULL; + } - return TRUE; + if (enum_type != NULL) + { + enum_data = g_hash_table_lookup (enum_table, enum_type); + + if (enum_data == NULL) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " not (yet) defined.", enum_type); + return NULL; } - case G_VARIANT_CLASS_STRING: + g_assert (type_string == NULL); + type_string = "s"; + } + else + { + if (!g_variant_type_string_is_valid (type_string)) { - const gchar *string; + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "invalid GVariant type string '%s'", type_string); + return NULL; + } - g_variant_get (variant, "&s", &string); + enum_data = NULL; + } - while ((choices = strstr (choices, string)) && choices[-1] != 0xff); + key = key_state_new (type_string, state->gettext_domain, enum_data); + g_hash_table_insert (state->keys, g_strdup (name), key); - return choices != NULL; - } + return key; +} - default: - g_assert_not_reached (); +/* Handling of toplevel state {{{1 */ +typedef struct +{ + GHashTable *schema_table; /* string -> SchemaState */ + GHashTable *enum_table; /* string -> GString */ + + gchar *schemalist_domain; /* the gettext domain */ + + SchemaState *schema_state; /* non-NULL when inside */ + KeyState *key_state; /* non-NULL when inside */ + GString *enum_state; /* non-NULL when inside */ + + GString *string; /* non-NULL when accepting text */ +} ParseState; + +static void +parse_state_start_schema (ParseState *state, + const gchar *id, + const gchar *path, + const gchar *gettext_domain, + GError **error) +{ + if (g_hash_table_lookup (state->schema_table, id)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified", id); + return; + } + + if (path && !(g_str_has_prefix (path, "/") && g_str_has_suffix (path, "/"))) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "a path, if given, must begin and " + "end with a slash"); + return; } + + state->schema_state = schema_state_new (path, gettext_domain); + g_hash_table_insert (state->schema_table, g_strdup (id), + state->schema_state); } static void +parse_state_start_enum (ParseState *state, + const gchar *id, + GError **error) +{ + if (g_hash_table_lookup (state->enum_table, id)) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " already specified", id); + return; + } + + state->enum_state = g_string_new (NULL); + g_hash_table_insert (state->enum_table, g_strdup (id), state->enum_state); +} + +/* GMarkup Parser Functions {{{1 */ + +/* Start element {{{2 */ +static void start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, @@ -205,6 +882,7 @@ start_element (GMarkupParseContext *context, #define STRING G_MARKUP_COLLECT_STRING #define NO_ATTRS() COLLECT (G_MARKUP_COLLECT_INVALID, NULL) + /* Toplevel items {{{3 */ if (container == NULL) { if (strcmp (element_name, "schemalist") == 0) @@ -215,84 +893,46 @@ start_element (GMarkupParseContext *context, return; } } + + + /* children of {{{3 */ else if (strcmp (container, "schemalist") == 0) { if (strcmp (element_name, "schema") == 0) { - const gchar *id, *path; - + const gchar *id, *path, *gettext_domain; if (COLLECT (STRING, "id", &id, OPTIONAL | STRING, "path", &path, - OPTIONAL | STRDUP, "gettext-domain", - &state->schema_domain)) - { - if (!g_hash_table_lookup (state->schemas, id)) - { - state->schema = gvdb_hash_table_new (state->schemas, id); - state->schema_root = gvdb_hash_table_insert (state->schema, ""); - - if (path != NULL) - { - if (!g_str_has_prefix (path, "/") || - !g_str_has_suffix (path, "/")) - { - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "a path, if given, must begin and " - "end with a slash"); - return; - } - - gvdb_hash_table_insert_string (state->schema, - ".path", path); - } - } - else - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - " already specified", id); - } + OPTIONAL | STRDUP, "gettext-domain", &gettext_domain)) + parse_state_start_schema (state, id, path, gettext_domain, error); + return; + } + + else if (strcmp (element_name, "enum") == 0) + { + const gchar *id; + if (COLLECT (STRING, "id", &id)) + parse_state_start_enum (state, id, error); return; } } + + + /* children of {{{3 */ else if (strcmp (container, "schema") == 0) { if (strcmp (element_name, "key") == 0) { - const gchar *name, *type; - - if (COLLECT (STRING, "name", &name, STRING, "type", &type)) - { - if (!is_valid_keyname (name, error)) - return; - - if (!g_hash_table_lookup (state->schema, name)) - { - state->key = gvdb_hash_table_insert (state->schema, name); - gvdb_item_set_parent (state->key, state->schema_root); - } - else - { - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - " already specified", name); - return; - } + const gchar *name, *type_string, *enum_type; - if (g_variant_type_string_is_valid (type)) - state->type = g_variant_type_new (type); - else - { - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "invalid GVariant type string '%s'", type); - return; - } - - g_variant_builder_init (&state->key_options, - G_VARIANT_TYPE ("a{sv}")); - } + if (COLLECT (STRING, "name", &name, + OPTIONAL | STRING, "type", &type_string, + OPTIONAL | STRING, "enum", &enum_type)) + state->key_state = schema_state_add_key (state->schema_state, + state->enum_table, + name, type_string, + enum_type, error); return; } else if (strcmp (element_name, "child") == 0) @@ -300,167 +940,98 @@ start_element (GMarkupParseContext *context, const gchar *name, *schema; if (COLLECT (STRING, "name", &name, STRING, "schema", &schema)) - { - gchar *childname; - - if (!is_valid_keyname (name, error)) - return; - - childname = g_strconcat (name, "/", NULL); - - if (!g_hash_table_lookup (state->schema, childname)) - gvdb_hash_table_insert_string (state->schema, childname, schema); - else - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - " already specified", name); - - g_free (childname); - return; - } + schema_state_add_child (state->schema_state, + name, schema, error); + return; } } + + + /* children of {{{3 */ else if (strcmp (container, "key") == 0) { if (strcmp (element_name, "default") == 0) { - const gchar *l10n; - - if (COLLECT (STRING | OPTIONAL, "l10n", &l10n, - STRDUP | OPTIONAL, "context", &state->context)) - { - if (l10n != NULL) - { - if (!g_hash_table_lookup (state->schema, ".gettext-domain")) - { - const gchar *domain = state->schema_domain ? - state->schema_domain : - state->schemalist_domain; - - if (domain == NULL) - { - g_set_error_literal (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "l10n requested, but no " - "gettext domain given"); - return; - } - - gvdb_hash_table_insert_string (state->schema, - ".gettext-domain", - domain); - - if (strcmp (l10n, "messages") == 0) - state->l10n = 'm'; - else if (strcmp (l10n, "time") == 0) - state->l10n = 't'; - else - { - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "unsupported l10n category: %s", l10n); - return; - } - } - } - else - { - state->l10n = '\0'; - - if (state->context != NULL) - { - g_set_error_literal (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "translation context given for " - " value without l10n enabled"); - return; - } - } - - state->string = g_string_new (NULL); - } - + const gchar *l10n, *context; + if (COLLECT (STRING | OPTIONAL, "l10n", &l10n, + STRING | OPTIONAL, "context", &context)) + state->string = key_state_start_default (state->key_state, + l10n, context, error); return; } + else if (strcmp (element_name, "summary") == 0 || strcmp (element_name, "description") == 0) { - state->string = g_string_new (NULL); - NO_ATTRS (); + if (NO_ATTRS ()) + state->string = g_string_new (NULL); return; } + else if (strcmp (element_name, "range") == 0) { - const gchar *min_str, *max_str; - - if (!type_allows_range (state->type)) - { - gchar *type = g_variant_type_dup_string (state->type); - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "Element <%s> not allowed for keys of type \"%s\"\n", - element_name, type); - g_free (type); - return; - } - - if (!COLLECT (STRING, "min", &min_str, - STRING, "max", &max_str)) - return; - - state->min = g_variant_parse (state->type, min_str, NULL, NULL, error); - if (state->min == NULL) - return; - - state->max = g_variant_parse (state->type, max_str, NULL, NULL, error); - if (state->max == NULL) - return; - - if (g_variant_compare (state->min, state->max) > 0) - { - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "Element <%s> specified minimum is greater than maxmimum", - element_name); - return; - } - - g_variant_builder_add (&state->key_options, "{sv}", "range", - g_variant_new ("(@?@?)", state->min, state->max)); + const gchar *min, *max; + if (COLLECT (STRING, "min", &min, STRING, "max", &max)) + key_state_set_range (state->key_state, min, max, error); return; } + else if (strcmp (element_name, "choices") == 0) { - if (!type_allows_choices (state->type)) - { - gchar *type = g_variant_type_dup_string (state->type); - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "Element <%s> not allowed for keys of type \"%s\"\n", - element_name, type); - g_free (type); - return; - } - - state->choices = g_string_new ("\xff"); + if (NO_ATTRS ()) + key_state_start_choices (state->key_state, error); + return; + } - NO_ATTRS (); + else if (strcmp (element_name, "aliases") == 0) + { + if (NO_ATTRS ()) + key_state_start_aliases (state->key_state, error); return; } } + + + /* children of {{{3 */ else if (strcmp (container, "choices") == 0) { if (strcmp (element_name, "choice") == 0) { const gchar *value; - if (COLLECT (STRING, "value", &value)) - g_string_append_printf (state->choices, "%s\xff", value); + key_state_add_choice (state->key_state, value, error); + return; + } + } + + /* children of {{{3 */ + else if (strcmp (container, "aliases") == 0) + { + if (strcmp (element_name, "alias") == 0) + { + const gchar *value, *target; + if (COLLECT (STRING, "value", &value, STRING, "target", &target)) + key_state_add_alias (state->key_state, value, target, error); return; } } + + /* children of {{{3 */ + else if (strcmp (container, "enum") == 0) + { + if (strcmp (element_name, "value") == 0) + { + const gchar *nick, *valuestr; + if (COLLECT (STRING, "nick", &nick, + STRING, "value", &valuestr)) + enum_state_add_value (state->enum_state, nick, valuestr, error); + return; + } + } + /* 3}}} */ + if (container) g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "Element <%s> not allowed inside <%s>\n", @@ -469,6 +1040,36 @@ start_element (GMarkupParseContext *context, g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "Element <%s> not allowed at toplevel\n", element_name); } +/* 2}}} */ +/* End element {{{2 */ + +static void +key_state_end (KeyState **state_ptr, + GError **error) +{ + KeyState *state; + + state = *state_ptr; + *state_ptr = NULL; + + if (state->default_value == NULL) + { + g_set_error_literal (error, + G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "element is required in \n"); + return; + } +} + +static void +schema_state_end (SchemaState **state_ptr, + GError **error) +{ + SchemaState *state; + + state = *state_ptr; + *state_ptr = NULL; +} static void end_element (GMarkupParseContext *context, @@ -478,103 +1079,37 @@ end_element (GMarkupParseContext *context, { ParseState *state = user_data; - if (strcmp (element_name, "default") == 0) + if (strcmp (element_name, "schemalist") == 0) { - state->value = g_variant_parse (state->type, state->string->str, - NULL, NULL, error); - if (state->value == NULL) - return; - - if (state->l10n) - { - if (state->context) - { - gint len; - - /* Contextified messages are supported by prepending the - * context, followed by '\004' to the start of the message - * string. We do that here to save GSettings the work - * later on. - * - * Note: we are about to g_free() the context anyway... - */ - len = strlen (state->context); - state->context[len] = '\004'; - g_string_prepend_len (state->string, state->context, len + 1); - } + g_free (state->schemalist_domain); + state->schemalist_domain = NULL; + } - g_variant_builder_add (&state->key_options, "{sv}", "l10n", - g_variant_new ("(ys)", - state->l10n, - state->string->str)); - } + else if (strcmp (element_name, "enum") == 0) + enum_state_end (&state->enum_state, error); - g_string_free (state->string, TRUE); - state->string = NULL; - g_free (state->context); - state->context = NULL; - } + else if (strcmp (element_name, "schema") == 0) + schema_state_end (&state->schema_state, error); else if (strcmp (element_name, "key") == 0) - { - if (state->value == NULL) - { - g_set_error_literal (error, - G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, - "element is required in \n"); - return; - } + key_state_end (&state->key_state, error); - if (state->min != NULL) - { - if (g_variant_compare (state->value, state->min) < 0 || - g_variant_compare (state->value, state->max) > 0) - { - g_set_error (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - " is not contained in the specified range"); - return; - } + else if (strcmp (element_name, "default") == 0) + key_state_end_default (state->key_state, &state->string, error); - state->min = state->max = NULL; - } - else if (state->choices != NULL) - { - if (!is_valid_choices (state->value, state->choices->str)) - { - g_set_error_literal (error, G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - " contains string not in "); - return; - } - - state->choices = NULL; - } + else if (strcmp (element_name, "choices") == 0) + key_state_end_choices (state->key_state, error); - gvdb_item_set_value (state->key, - g_variant_new ("(*a{sv})", state->value, - &state->key_options)); - state->value = NULL; - } + else if (strcmp (element_name, "aliases") == 0) + key_state_end_aliases (state->key_state, error); - else if (strcmp (element_name, "summary") == 0 || - strcmp (element_name, "description") == 0) + if (state->string) { g_string_free (state->string, TRUE); state->string = NULL; } - - else if (strcmp (element_name, "choices") == 0) - { - gchar *choices; - - choices = g_string_free (state->choices, FALSE); - g_variant_builder_add (&state->key_options, "{sv}", "choices", - g_variant_new_byte_array (choices, -1)); - g_free (choices); - } } - +/* Text {{{2 */ static void text (GMarkupParseContext *context, const gchar *text, @@ -600,20 +1135,120 @@ text (GMarkupParseContext *context, } } +/* Write to GVDB {{{1 */ +typedef struct +{ + GHashTable *table; + GvdbItem *root; +} GvdbPair; + +static void +gvdb_pair_init (GvdbPair *pair) +{ + pair->table = gvdb_hash_table_new (NULL, NULL); + pair->root = gvdb_hash_table_insert (pair->table, ""); +} + +typedef struct +{ + GvdbPair pair; + gboolean l10n; +} OutputSchemaData; + +static void +output_key (gpointer key, + gpointer value, + gpointer user_data) +{ + OutputSchemaData *data; + const gchar *name; + KeyState *state; + GvdbItem *item; + + name = key; + state = value; + data = user_data; + + item = gvdb_hash_table_insert (data->pair.table, name); + gvdb_item_set_parent (item, data->pair.root); + gvdb_item_set_value (item, key_state_serialise (state)); + + if (state->l10n) + data->l10n = TRUE; +} + +static void +output_schema (gpointer key, + gpointer value, + gpointer user_data) +{ + OutputSchemaData data; + GvdbPair *root_pair; + SchemaState *state; + const gchar *id; + GvdbItem *item; + + id = key; + state = value; + root_pair = user_data; + + gvdb_pair_init (&data.pair); + data.l10n = FALSE; + + item = gvdb_hash_table_insert (root_pair->table, id); + gvdb_item_set_parent (item, root_pair->root); + gvdb_item_set_hash_table (item, data.pair.table); + + g_hash_table_foreach (state->keys, output_key, &data); + + if (state->path) + gvdb_hash_table_insert_string (data.pair.table, ".path", state->path); + + if (data.l10n) + gvdb_hash_table_insert_string (data.pair.table, + ".gettext-domain", + state->gettext_domain); +} + +static gboolean +write_to_file (GHashTable *schema_table, + const gchar *filename, + GError **error) +{ + gboolean success; + GvdbPair pair; + + gvdb_pair_init (&pair); + + g_hash_table_foreach (schema_table, output_schema, &pair); + + success = gvdb_table_write_contents (pair.table, filename, + G_BYTE_ORDER != G_LITTLE_ENDIAN, + error); + g_hash_table_unref (pair.table); + + return success; +} + +/* Parser driver {{{1 */ static GHashTable * -parse_gschema_files (gchar **files, - gboolean byteswap, - GError **error) +parse_gschema_files (gchar **files, + GError **error) { GMarkupParser parser = { start_element, end_element, text }; GMarkupParseContext *context; - ParseState state = { byteswap, }; + ParseState state = { 0, }; const gchar *filename; context = g_markup_parse_context_new (&parser, G_MARKUP_PREFIX_ERROR_POSITION, &state, NULL); - state.schemas = gvdb_hash_table_new (NULL, NULL); + + state.enum_table = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, enum_state_free); + + state.schema_table = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, schema_state_free); while ((filename = *files++) != NULL) { @@ -636,13 +1271,24 @@ parse_gschema_files (gchar **files, } } - return state.schemas; + g_hash_table_unref (state.enum_table); + + return state.schema_table; +} + +static gint +compare_strings (gconstpointer a, + gconstpointer b) +{ + gchar *one = *(gchar **) a; + gchar *two = *(gchar **) b; + + return strcmp (one, two); } int main (int argc, char **argv) { - gboolean byteswap = G_BYTE_ORDER != G_LITTLE_ENDIAN; GError *error; GHashTable *table; GDir *dir; @@ -712,7 +1358,8 @@ main (int argc, char **argv) while ((file = g_dir_read_name (dir)) != NULL) { - if (g_str_has_suffix (file, ".gschema.xml")) + if (g_str_has_suffix (file, ".gschema.xml") || + g_str_has_suffix (file, ".enums.xml")) g_ptr_array_add (files, g_build_filename (srcdir, file, NULL)); } @@ -729,14 +1376,15 @@ main (int argc, char **argv) return 1; } } + g_ptr_array_sort (files, compare_strings); g_ptr_array_add (files, NULL); schema_files = (char **) g_ptr_array_free (files, FALSE); } - if (!(table = parse_gschema_files (schema_files, byteswap, &error)) || - (!dry_run && !gvdb_table_write_contents (table, target, byteswap, &error))) + if (!(table = parse_gschema_files (schema_files, &error)) || + (!dry_run && !write_to_file (table, target, &error))) { fprintf (stderr, "%s\n", error->message); return 1; @@ -746,3 +1394,7 @@ main (int argc, char **argv) return 0; } + +/* Epilogue {{{1 */ + +/* vim:set foldmethod=marker: */ diff --git a/gio/gsettings-mapping.c b/gio/gsettings-mapping.c index 6b17bfb..cf1ac11 100644 --- a/gio/gsettings-mapping.c +++ b/gio/gsettings-mapping.c @@ -377,6 +377,21 @@ g_settings_set_mapping (const GValue *value, return g_variant_new_signature (g_value_get_string (value)); } + else if (G_VALUE_HOLDS_ENUM (value)) + { + GEnumValue *enumval; + GEnumClass *eclass; + + eclass = g_type_class_ref (G_VALUE_TYPE (value)); + enumval = g_enum_get_value (eclass, g_value_get_enum (value)); + g_type_class_unref (eclass); + + if (enumval) + return g_variant_new_string (enumval->value_nick); + else + return NULL; + } + type_string = g_variant_type_dup_string (expected_type); g_critical ("No GSettings bind handler for type \"%s\".", type_string); g_free (type_string); @@ -426,8 +441,11 @@ g_settings_get_mapping (GValue *value, g_variant_is_of_type (variant, G_VARIANT_TYPE_OBJECT_PATH) || g_variant_is_of_type (variant, G_VARIANT_TYPE_SIGNATURE)) { - g_value_set_string (value, g_variant_get_string (variant, NULL)); - return TRUE; + if (G_VALUE_HOLDS_STRING (value)) + { + g_value_set_string (value, g_variant_get_string (variant, NULL)); + return TRUE; + } } else if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) { @@ -470,6 +488,8 @@ g_settings_mapping_is_compatible (GType gvalue_type, g_variant_type_equal (variant_type, G_VARIANT_TYPE ("ay")) || g_variant_type_equal (variant_type, G_VARIANT_TYPE_OBJECT_PATH) || g_variant_type_equal (variant_type, G_VARIANT_TYPE_SIGNATURE)); + else if (G_TYPE_IS_ENUM (gvalue_type)) + ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE_STRING); return ok; } diff --git a/gio/gsettings.c b/gio/gsettings.c index 15de030..c2d991b 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -19,7 +19,10 @@ * Author: Ryan Lortie */ +/* Prelude {{{1 */ +#define _GNU_SOURCE #include "config.h" + #include #include #include @@ -36,6 +39,8 @@ #include "gioalias.h" +#include "strinfo.c" + /** * SECTION:gsettings * @short_description: a high-level API for application settings @@ -181,6 +186,7 @@ static guint g_settings_signals[N_SIGNALS]; G_DEFINE_TYPE (GSettings, g_settings, G_TYPE_OBJECT) +/* Signals {{{1 */ static gboolean g_settings_real_change_event (GSettings *settings, const GQuark *keys, @@ -341,135 +347,7 @@ settings_backend_path_writable_changed (GSettingsBackend *backend, 0, (GQuark) 0, &ignore_this); } -static void -g_settings_constructed (GObject *object) -{ - GSettings *settings = G_SETTINGS (object); - const gchar *schema_path; - - settings->priv->schema = g_settings_schema_new (settings->priv->schema_name); - schema_path = g_settings_schema_get_path (settings->priv->schema); - - if (settings->priv->path && schema_path && strcmp (settings->priv->path, schema_path) != 0) - g_error ("settings object created with schema '%s' and path '%s', but " - "path '%s' is specified by schema", - settings->priv->schema_name, settings->priv->path, schema_path); - - if (settings->priv->path == NULL) - { - if (schema_path == NULL) - g_error ("attempting to create schema '%s' without a path", - settings->priv->schema_name); - - settings->priv->path = g_strdup (schema_path); - } - - settings->priv->backend = g_settings_backend_get_with_context (settings->priv->context); - g_settings_backend_watch (settings->priv->backend, G_OBJECT (settings), - settings->priv->main_context, - settings_backend_changed, - settings_backend_path_changed, - settings_backend_keys_changed, - settings_backend_writable_changed, - settings_backend_path_writable_changed); - g_settings_backend_subscribe (settings->priv->backend, - settings->priv->path); -} - -static void -g_settings_init (GSettings *settings) -{ - settings->priv = G_TYPE_INSTANCE_GET_PRIVATE (settings, - G_TYPE_SETTINGS, - GSettingsPrivate); - - settings->priv->main_context = g_main_context_get_thread_default (); - - if (settings->priv->main_context == NULL) - settings->priv->main_context = g_main_context_default (); - - g_main_context_ref (settings->priv->main_context); -} - -/** - * g_settings_delay: - * @settings: a #GSettings object - * - * Changes the #GSettings object into 'delay-apply' mode. In this - * mode, changes to @settings are not immediately propagated to the - * backend, but kept locally until g_settings_apply() is called. - * - * Since: 2.26 - */ -void -g_settings_delay (GSettings *settings) -{ - g_return_if_fail (G_IS_SETTINGS (settings)); - - if (settings->priv->delayed) - return; - - settings->priv->delayed = - g_delayed_settings_backend_new (settings->priv->backend, - settings, - settings->priv->main_context); - g_settings_backend_unwatch (settings->priv->backend, G_OBJECT (settings)); - g_object_unref (settings->priv->backend); - - settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed); - g_settings_backend_watch (settings->priv->backend, G_OBJECT (settings), - settings->priv->main_context, - settings_backend_changed, - settings_backend_path_changed, - settings_backend_keys_changed, - settings_backend_writable_changed, - settings_backend_path_writable_changed); -} - -/** - * g_settings_apply: - * @settings: a #GSettings instance - * - * Applies any changes that have been made to the settings. This - * function does nothing unless @settings is in 'delay-apply' mode; - * see g_settings_set_delay_apply(). In the normal case settings are - * always applied immediately. - **/ -void -g_settings_apply (GSettings *settings) -{ - if (settings->priv->delayed) - { - GDelayedSettingsBackend *delayed; - - delayed = G_DELAYED_SETTINGS_BACKEND (settings->priv->backend); - g_delayed_settings_backend_apply (delayed); - } -} - -/** - * g_settings_revert: - * @settings: a #GSettings instance - * - * Reverts all non-applied changes to the settings. This function - * does nothing unless @settings is in 'delay-apply' mode; see - * g_settings_set_delay_apply(). In the normal case settings are - * always applied immediately. - * - * Change notifications will be emitted for affected keys. - **/ -void -g_settings_revert (GSettings *settings) -{ - if (settings->priv->delayed) - { - GDelayedSettingsBackend *delayed; - - delayed = G_DELAYED_SETTINGS_BACKEND (settings->priv->backend); - g_delayed_settings_backend_revert (delayed); - } -} - +/* Properties, Construction, Destruction {{{1 */ static void g_settings_set_property (GObject *object, guint prop_id, @@ -498,26 +376,6 @@ g_settings_set_property (GObject *object, } } -/** - * g_settings_get_has_unapplied: - * @settings: a #GSettings object - * @returns: %TRUE if @settings has unapplied changes - * - * Returns whether the #GSettings object has any unapplied - * changes. This can only be the case if it is in 'delayed-apply' mode. - * - * Since: 2.26 - */ -gboolean -g_settings_get_has_unapplied (GSettings *settings) -{ - g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); - - return settings->priv->delayed && - g_delayed_settings_backend_get_has_unapplied ( - G_DELAYED_SETTINGS_BACKEND (settings->priv->backend)); -} - static void g_settings_get_property (GObject *object, guint prop_id, @@ -542,6 +400,41 @@ g_settings_get_property (GObject *object, } static void +g_settings_constructed (GObject *object) +{ + GSettings *settings = G_SETTINGS (object); + const gchar *schema_path; + + settings->priv->schema = g_settings_schema_new (settings->priv->schema_name); + schema_path = g_settings_schema_get_path (settings->priv->schema); + + if (settings->priv->path && schema_path && strcmp (settings->priv->path, schema_path) != 0) + g_error ("settings object created with schema '%s' and path '%s', but " + "path '%s' is specified by schema", + settings->priv->schema_name, settings->priv->path, schema_path); + + if (settings->priv->path == NULL) + { + if (schema_path == NULL) + g_error ("attempting to create schema '%s' without a path", + settings->priv->schema_name); + + settings->priv->path = g_strdup (schema_path); + } + + settings->priv->backend = g_settings_backend_get_with_context (settings->priv->context); + g_settings_backend_watch (settings->priv->backend, G_OBJECT (settings), + settings->priv->main_context, + settings_backend_changed, + settings_backend_path_changed, + settings_backend_keys_changed, + settings_backend_writable_changed, + settings_backend_path_writable_changed); + g_settings_backend_subscribe (settings->priv->backend, + settings->priv->path); +} + +static void g_settings_finalize (GObject *object) { GSettings *settings = G_SETTINGS (object); @@ -557,6 +450,21 @@ g_settings_finalize (GObject *object) } static void +g_settings_init (GSettings *settings) +{ + settings->priv = G_TYPE_INSTANCE_GET_PRIVATE (settings, + G_TYPE_SETTINGS, + GSettingsPrivate); + + settings->priv->main_context = g_main_context_get_thread_default (); + + if (settings->priv->main_context == NULL) + settings->priv->main_context = g_main_context_default (); + + g_main_context_ref (settings->priv->main_context); +} + +static void g_settings_class_init (GSettingsClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); @@ -730,108 +638,570 @@ g_settings_class_init (GSettingsClass *class) } +/* Construction (new, new_with_path, etc.) {{{1 */ /** - * g_settings_get_value: - * @settings: a #GSettings object - * @key: the key to get the value for - * @returns: a new #GVariant + * g_settings_new: + * @schema: the name of the schema + * @returns: a new #GSettings object * - * Gets the value that is stored in @settings for @key. + * Creates a new #GSettings object with a given schema. * - * It is a programmer error to give a @key that isn't valid for - * @settings. + * Signals on the newly created #GSettings object will be dispatched + * via the thread-default #GMainContext in effect at the time of the + * call to g_settings_new(). The new #GSettings will hold a reference + * on the context. See g_main_context_push_thread_default(). * * Since: 2.26 */ -GVariant * -g_settings_get_value (GSettings *settings, - const gchar *key) +GSettings * +g_settings_new (const gchar *schema) { - const gchar *unparsed = NULL; - GVariant *value, *options; - const GVariantType *type; - gchar lc_char = '\0'; - GVariant *sval; - gchar *path; - - g_return_val_if_fail (G_IS_SETTINGS (settings), NULL); + g_return_val_if_fail (schema != NULL, NULL); - sval = g_settings_schema_get_value (settings->priv->schema, key, &options); + return g_object_new (G_TYPE_SETTINGS, + "schema", schema, + NULL); +} - if G_UNLIKELY (sval == NULL) - g_error ("schema '%s' does not contain a key named '%s'", - settings->priv->schema_name, key); +/** + * g_settings_new_with_path: + * @schema: the name of the schema + * @path: the path to use + * @returns: a new #GSettings object + * + * Creates a new #GSettings object with a given schema and path. + * + * You only need to do this if you want to directly create a settings + * object with a schema that doesn't have a specified path of its own. + * That's quite rare. + * + * It is a programmer error to call this function for a schema that + * has an explicitly specified path. + * + * Since: 2.26 + */ +GSettings * +g_settings_new_with_path (const gchar *schema, + const gchar *path) +{ + g_return_val_if_fail (schema != NULL, NULL); + g_return_val_if_fail (path != NULL, NULL); - path = g_strconcat (settings->priv->path, key, NULL); - type = g_variant_get_type (sval); - value = g_settings_backend_read (settings->priv->backend, path, type, FALSE); - g_free (path); + return g_object_new (G_TYPE_SETTINGS, + "schema", schema, + "path", path, + NULL); +} - if (options != NULL) +/** + * g_settings_new_with_context: + * @schema: the name of the schema + * @context: the context to use + * @returns: a new #GSettings object + * + * Creates a new #GSettings object with a given schema and context. + * + * Creating settings objects with a context allow accessing settings + * from a database other than the usual one. For example, it may make + * sense to specify "defaults" in order to get a settings object that + * modifies the system default settings instead of the settings for this + * user. + * + * It is a programmer error to call this function for an unsupported + * context. Use g_settings_supports_context() to determine if a context + * is supported if you are unsure. + * + * Since: 2.26 + */ +GSettings * +g_settings_new_with_context (const gchar *schema, + const gchar *context) +{ + g_return_val_if_fail (schema != NULL, NULL); + g_return_val_if_fail (context != NULL, NULL); + + return g_object_new (G_TYPE_SETTINGS, + "schema", schema, + "context", context, + NULL); +} + +/** + * g_settings_new_with_context_and_path: + * @schema: the name of the schema + * @path: the path to use + * @returns: a new #GSettings object + * + * Creates a new #GSettings object with a given schema, context and + * path. + * + * This is a mix of g_settings_new_with_context() and + * g_settings_new_with_path(). + * + * Since: 2.26 + */ +GSettings * +g_settings_new_with_context_and_path (const gchar *schema, + const gchar *context, + const gchar *path) +{ + g_return_val_if_fail (schema != NULL, NULL); + g_return_val_if_fail (context != NULL, NULL); + g_return_val_if_fail (path != NULL, NULL); + + return g_object_new (G_TYPE_SETTINGS, + "schema", schema, + "context", context, + "path", path, + NULL); +} + +/* Internal read/write utilities, enum conversion, validation {{{1 */ +typedef struct +{ + GSettings *settings; + const gchar *key; + + GSettingsSchema *schema; + + gboolean is_enum; + const guint32 *strinfo; + gsize strinfo_length; + + const gchar *unparsed; + gchar lc_char; + + const GVariantType *type; + GVariant *minimum, *maximum; + GVariant *default_value; +} GSettingsKeyInfo; + +static void +g_settings_get_key_info (GSettingsKeyInfo *info, + GSettings *settings, + const gchar *key) +{ + GVariantIter *iter; + GVariant *data; + guchar code; + + memset (info, 0, sizeof *info); + + iter = g_settings_schema_get_value (settings->priv->schema, key); + + info->default_value = g_variant_iter_next_value (iter); + info->type = g_variant_get_type (info->default_value); + info->schema = settings->priv->schema; + info->settings = settings; + info->key = key; + + while (g_variant_iter_next (iter, "(y*)", &code, &data)) + { + switch (code) + { + case 'l': + /* translation requeted */ + g_variant_get (data, "(y&s)", &info->lc_char, &info->unparsed); + break; + + case 'e': + /* enumerated types, ... */ + info->is_enum = TRUE; + case 'c': + /* ..., choices, aliases */ + info->strinfo = g_variant_get_fixed_array (data, + &info->strinfo_length, + sizeof (guint32)); + break; + + case 'r': + g_variant_get (data, "(**)", &info->minimum, &info->maximum); + break; + + default: + g_warning ("unknown schema extension '%c'", code); + break; + } + + g_variant_unref (data); + } + + g_variant_iter_free (iter); +} + +static void +g_settings_free_key_info (GSettingsKeyInfo *info) +{ + if (info->minimum) + g_variant_unref (info->minimum); + + if (info->maximum) + g_variant_unref (info->maximum); + + g_variant_unref (info->default_value); +} + +static gboolean +g_settings_write_to_backend (GSettingsKeyInfo *info, + GVariant *value) +{ + gboolean success; + gchar *path; + + path = g_strconcat (info->settings->priv->path, info->key, NULL); + success = g_settings_backend_write (info->settings->priv->backend, + path, value, NULL); + g_free (path); + + return success; +} + +static gboolean +g_settings_type_check (GSettingsKeyInfo *info, + GVariant *value) +{ + g_return_val_if_fail (value != NULL, FALSE); + + return g_variant_is_of_type (value, info->type); +} + +static gboolean +g_settings_range_check (GSettingsKeyInfo *info, + GVariant *value) +{ + if (info->minimum == NULL && info->strinfo == NULL) + return TRUE; + + if (g_variant_is_container (value)) { + gboolean ok = TRUE; GVariantIter iter; - const gchar *option; - GVariant *option_value; + GVariant *child; - g_variant_iter_init (&iter, options); - while (g_variant_iter_loop (&iter, "{&sv}", &option, &option_value)) + g_variant_iter_init (&iter, value); + while (ok && (child = g_variant_iter_next_value (&iter))) { - if (strcmp (option, "l10n") == 0) - g_variant_get (option_value, "(y&s)", &lc_char, &unparsed); - else - g_warning ("unknown schema extension '%s'", option); + ok = g_settings_range_check (info, value); + g_variant_unref (child); } + + return ok; } - if (value && !g_variant_is_of_type (value, type)) + if (info->minimum) { - g_variant_unref (value); - value = NULL; + return g_variant_compare (info->minimum, value) <= 0 && + g_variant_compare (value, info->maximum) <= 0; } - if (value == NULL && lc_char != '\0') - /* we will need to translate the schema default value */ - { - const gchar *translated; - GError *error = NULL; - const gchar *domain; + return strinfo_is_string_valid (info->strinfo, + info->strinfo_length, + g_variant_get_string (value, NULL)); +} + +static GVariant * +g_settings_range_fixup (GSettingsKeyInfo *info, + GVariant *value) +{ + const gchar *target; - domain = g_settings_schema_get_gettext_domain (settings->priv->schema); + if (g_settings_range_check (info, value)) + return g_variant_ref (value); - if (lc_char == 't') - translated = g_dcgettext (domain, unparsed, LC_TIME); - else - translated = g_dgettext (domain, unparsed); + if (info->strinfo == NULL) + return NULL; - if (translated != unparsed) - /* it was translated, so we need to re-parse it */ + if (g_variant_is_container (value)) + { + GVariantBuilder builder; + GVariantIter iter; + GVariant *child; + + g_variant_builder_init (&builder, g_variant_get_type (value)); + + while ((child = g_variant_iter_next_value (&iter))) { - value = g_variant_parse (g_variant_get_type (sval), - translated, NULL, NULL, &error); + GVariant *fixed; - if (value == NULL) + fixed = g_settings_range_fixup (info, child); + g_variant_unref (child); + + if (fixed == NULL) { - g_warning ("Failed to parse translated string `%s' for " - "key `%s' in schema `%s': %s", unparsed, key, - settings->priv->schema_name, error->message); - g_warning ("Using untranslated default instead."); - g_error_free (error); + g_variant_builder_clear (&builder); + return NULL; } + + g_variant_builder_add_value (&builder, fixed); + g_variant_unref (fixed); } + + return g_variant_ref_sink (g_variant_builder_end (&builder)); + } + + target = strinfo_string_from_alias (info->strinfo, info->strinfo_length, + g_variant_get_string (value, NULL)); + return target ? g_variant_ref_sink (g_variant_new_string (target)) : NULL; +} + +static GVariant * +g_settings_read_from_backend (GSettingsKeyInfo *info) +{ + GVariant *value; + GVariant *fixup; + gchar *path; + + path = g_strconcat (info->settings->priv->path, info->key, NULL); + value = g_settings_backend_read (info->settings->priv->backend, + path, info->type, FALSE); + g_free (path); + + if (value != NULL) + { + fixup = g_settings_range_fixup (info, value); + g_variant_unref (value); + } + else + fixup = NULL; + + return fixup; +} + +static GVariant * +g_settings_get_translated_default (GSettingsKeyInfo *info) +{ + const gchar *translated; + GError *error = NULL; + const gchar *domain; + GVariant *value; + + if (info->lc_char == '\0') + /* translation not requested for this key */ + return NULL; + + domain = g_settings_schema_get_gettext_domain (info->schema); + + if (info->lc_char == 't') + translated = g_dcgettext (domain, info->unparsed, LC_TIME); + else + translated = g_dgettext (domain, info->unparsed); + + if (translated == info->unparsed) + /* the default value was not translated */ + return NULL; + + /* try to parse the translation of the unparsed default */ + value = g_variant_parse (info->type, translated, NULL, NULL, &error); + + if (value == NULL) + { + g_warning ("Failed to parse translated string `%s' for " + "key `%s' in schema `%s': %s", info->unparsed, info->key, + info->settings->priv->schema_name, error->message); + g_warning ("Using untranslated default instead."); + g_error_free (error); + } + + else if (!g_settings_range_check (info, value)) + { + g_warning ("Translated default `%s' for key `%s' in schema `%s' " + "is outside of valid range", info->unparsed, info->key, + info->settings->priv->schema_name); + g_variant_unref (value); + value = NULL; } + return value; +} + +static gint +g_settings_to_enum (GSettingsKeyInfo *info, + GVariant *value) +{ + gboolean it_worked; + guint result; + + it_worked = strinfo_enum_from_string (info->strinfo, info->strinfo_length, + g_variant_get_string (value, NULL), + &result); + + /* 'value' can only come from the backend after being filtered for validity, + * from the translation after being filtered for validity, or from the schema + * itself (which the schema compiler checks for validity). If this assertion + * fails then it's really a bug in GSettings or the schema compiler... + */ + g_assert (it_worked); + + return result; +} + +static GVariant * +g_settings_from_enum (GSettingsKeyInfo *info, + gint value) +{ + const gchar *string; + + string = strinfo_string_from_enum (info->strinfo, + info->strinfo_length, + value); + + if (string == NULL) + return NULL; + + return g_variant_ref_sink (g_variant_new_string (string)); +} + +/* Public Get/Set API {{{1 (get, get_value, set, set_value) */ +/** + * g_settings_get_value: + * @settings: a #GSettings object + * @key: the key to get the value for + * @returns: a new #GVariant + * + * Gets the value that is stored in @settings for @key. + * + * It is a programmer error to give a @key that isn't valid for + * @settings. + * + * Since: 2.26 + */ +GVariant * +g_settings_get_value (GSettings *settings, + const gchar *key) +{ + GSettingsKeyInfo info; + GVariant *value; + + g_return_val_if_fail (G_IS_SETTINGS (settings), NULL); + g_return_val_if_fail (key != NULL, NULL); + + g_settings_get_key_info (&info, settings, key); + value = g_settings_read_from_backend (&info); + + if (value == NULL) + value = g_settings_get_translated_default (&info); + if (value == NULL) - /* either translation failed or there was none to do. - * use the pre-compiled default. - */ - value = g_variant_ref (sval); + value = g_variant_ref (info.default_value); - g_variant_unref (sval); + g_settings_free_key_info (&info); return value; } /** + * g_settings_get_enum: + * @settings: a #GSettings object + * @key: the key to get the value for + * @returns: the enum value + * + * Gets the value that is stored in @settings for @key and converts it + * to the enum value that it represents. + * + * In order to use this function the type of the value must be a string + * and it must be marked in the schema file as an enumerated type. + * + * It is a programmer error to give a @key that isn't valid for + * @settings, or is not marked as an enumerated type in the schema. + * + * If the value stored in the configuration database is not a valid + * value for the enumerated type then this function will return the + * default value. + * + * Since: 2.26 + **/ +gint +g_settings_get_enum (GSettings *settings, + const gchar *key) +{ + GSettingsKeyInfo info; + GVariant *value; + gint result; + + g_return_val_if_fail (G_IS_SETTINGS (settings), -1); + g_return_val_if_fail (key != NULL, -1); + + g_settings_get_key_info (&info, settings, key); + + if (!info.is_enum) + { + g_critical ("g_settings_get_enum() called on key `%s' which is not " + "associated with an enumerated type", info.key); + g_settings_free_key_info (&info); + return -1; + } + + value = g_settings_read_from_backend (&info); + + if (value == NULL) + value = g_settings_get_translated_default (&info); + + if (value == NULL) + value = g_variant_ref (info.default_value); + + result = g_settings_to_enum (&info, value); + g_settings_free_key_info (&info); + g_variant_unref (value); + + return result; +} + +/** + * g_settings_set_enum: + * @settings: a #GSettings object + * @key: a key, within @settings + * @value: an enumerated value + * Returns: %TRUE, if the set succeeds + * + * Looks up the enumerated type nick for @value and writes it to @key, + * within @settings. + * + * It is a programmer error for @key to not be listed in the schema or + * for it not to be tagged as an enumerated type, or for @value not to + * be a valid value for the named type. + * + * After performing the write, accessing @key directly + * g_settings_get_string() will return the 'nick' associated with + * @value. + **/ +gboolean +g_settings_set_enum (GSettings *settings, + const gchar *key, + gint value) +{ + GSettingsKeyInfo info; + GVariant *variant; + gboolean success; + + g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); + g_return_val_if_fail (key != NULL, FALSE); + + g_settings_get_key_info (&info, settings, key); + + if (!info.is_enum) + { + g_critical ("g_settings_set_enum() called on key `%s' which is not " + "associated with an enumerated type", info.key); + return FALSE; + } + + if (!(variant = g_settings_from_enum (&info, value))) + { + g_critical ("g_settings_set_enum(): invalid enum value %d for key `%s' " + "in schema `%s'. Doing nothing.", value, info.key, + info.settings->priv->schema_name); + g_settings_free_key_info (&info); + return FALSE; + } + + success = g_settings_write_to_backend (&info, variant); + g_settings_free_key_info (&info); + g_variant_unref (variant); + + return success; +} + +/** * g_settings_set_value: * @settings: a #GSettings object * @key: the name of the key to set @@ -854,25 +1224,17 @@ g_settings_set_value (GSettings *settings, const gchar *key, GVariant *value) { - gboolean correct_type; - gboolean result; - GVariant *sval; - gchar *path; + GSettingsKeyInfo info; g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); + g_return_val_if_fail (key != NULL, FALSE); - sval = g_settings_schema_get_value (settings->priv->schema, key, NULL); - correct_type = g_variant_is_of_type (value, g_variant_get_type (sval)); - g_variant_unref (sval); - - g_return_val_if_fail (correct_type, FALSE); - - path = g_strconcat (settings->priv->path, key, NULL); - result = g_settings_backend_write (settings->priv->backend, - path, value, NULL); - g_free (path); + g_settings_get_key_info (&info, settings, key); + g_return_val_if_fail (g_settings_type_check (&info, value), FALSE); + g_return_val_if_fail (g_settings_range_check (&info, value), FALSE); + g_settings_free_key_info (&info); - return result; + return g_settings_write_to_backend (&info, value); } /** @@ -947,564 +1309,630 @@ g_settings_set (GSettings *settings, return g_settings_set_value (settings, key, value); } +/* Convenience API (get, set_string, int, double, boolean, strv) {{{1 */ /** - * g_settings_is_writable: + * g_settings_get_string: * @settings: a #GSettings object - * @name: the name of a key - * @returns: %TRUE if the key @name is writable + * @key: the key to get the value for + * @returns: a newly-allocated string * - * Finds out if a key can be written or not + * Gets the value that is stored at @key in @settings. + * + * A convenience variant of g_settings_get() for strings. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type string. * * Since: 2.26 */ -gboolean -g_settings_is_writable (GSettings *settings, - const gchar *name) +gchar * +g_settings_get_string (GSettings *settings, + const gchar *key) { - gboolean writable; - gchar *path; - - g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); + GVariant *value; + gchar *result; - path = g_strconcat (settings->priv->path, name, NULL); - writable = g_settings_backend_get_writable (settings->priv->backend, path); - g_free (path); + value = g_settings_get_value (settings, key); + result = g_variant_dup_string (value, NULL); + g_variant_unref (value); - return writable; + return result; } /** - * g_settings_get_child: + * g_settings_set_string: * @settings: a #GSettings object - * @name: the name of the 'child' schema - * @returns: a 'child' settings object + * @key: the name of the key to set + * @value: the value to set it to + * @returns: %TRUE if setting the key succeeded, + * %FALSE if the key was not writable * - * Creates a 'child' settings object which has a base path of - * base-path/@name", where - * base-path is the base path of @settings. + * Sets @key in @settings to @value. * - * The schema for the child settings object must have been declared - * in the schema of @settings using a child element. + * A convenience variant of g_settings_set() for strings. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type string. * * Since: 2.26 */ -GSettings * -g_settings_get_child (GSettings *settings, - const gchar *name) +gboolean +g_settings_set_string (GSettings *settings, + const gchar *key, + const gchar *value) { - const gchar *child_schema; - gchar *child_path; - gchar *child_name; - GSettings *child; - - g_return_val_if_fail (G_IS_SETTINGS (settings), NULL); - - child_name = g_strconcat (name, "/", NULL); - child_schema = g_settings_schema_get_string (settings->priv->schema, - child_name); - if (child_schema == NULL) - g_error ("Schema '%s' has no child '%s'", - settings->priv->schema_name, name); - - child_path = g_strconcat (settings->priv->path, child_name, NULL); - child = g_object_new (G_TYPE_SETTINGS, - "schema", child_schema, - "path", child_path, - NULL); - g_free (child_path); - g_free (child_name); - - return child; + return g_settings_set_value (settings, key, g_variant_new_string (value)); } /** - * g_settings_new: - * @schema: the name of the schema - * @returns: a new #GSettings object + * g_settings_get_int: + * @settings: a #GSettings object + * @key: the key to get the value for + * @returns: an integer * - * Creates a new #GSettings object with a given schema. + * Gets the value that is stored at @key in @settings. * - * Signals on the newly created #GSettings object will be dispatched - * via the thread-default #GMainContext in effect at the time of the - * call to g_settings_new(). The new #GSettings will hold a reference - * on the context. See g_main_context_push_thread_default(). + * A convenience variant of g_settings_get() for 32-bit integers. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type int32. * * Since: 2.26 */ -GSettings * -g_settings_new (const gchar *schema) +gint +g_settings_get_int (GSettings *settings, + const gchar *key) { - return g_object_new (G_TYPE_SETTINGS, - "schema", schema, - NULL); + GVariant *value; + gint result; + + value = g_settings_get_value (settings, key); + result = g_variant_get_int32 (value); + g_variant_unref (value); + + return result; } /** - * g_settings_new_with_path: - * @schema: the name of the schema - * @path: the path to use - * @returns: a new #GSettings object + * g_settings_set_int: + * @settings: a #GSettings object + * @key: the name of the key to set + * @value: the value to set it to + * @returns: %TRUE if setting the key succeeded, + * %FALSE if the key was not writable * - * Creates a new #GSettings object with a given schema and path. + * Sets @key in @settings to @value. * - * You only need to do this if you want to directly create a settings - * object with a schema that doesn't have a specified path of its own. - * That's quite rare. + * A convenience variant of g_settings_set() for 32-bit integers. * - * It is a programmer error to call this function for a schema that - * has an explicitly specified path. + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type int32. * * Since: 2.26 */ -GSettings * -g_settings_new_with_path (const gchar *schema, - const gchar *path) +gboolean +g_settings_set_int (GSettings *settings, + const gchar *key, + gint value) { - return g_object_new (G_TYPE_SETTINGS, - "schema", schema, - "path", path, - NULL); + return g_settings_set_value (settings, key, g_variant_new_int32 (value)); } /** - * g_settings_new_with_context: - * @schema: the name of the schema - * @context: the context to use - * @returns: a new #GSettings object + * g_settings_get_double: + * @settings: a #GSettings object + * @key: the key to get the value for + * @returns: a double * - * Creates a new #GSettings object with a given schema and context. + * Gets the value that is stored at @key in @settings. * - * Creating settings objects with a context allow accessing settings - * from a database other than the usual one. For example, it may make - * sense to specify "defaults" in order to get a settings object that - * modifies the system default settings instead of the settings for this - * user. + * A convenience variant of g_settings_get() for doubles. * - * It is a programmer error to call this function for an unsupported - * context. Use g_settings_supports_context() to determine if a context - * is supported if you are unsure. + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type double. * * Since: 2.26 */ -GSettings * -g_settings_new_with_context (const gchar *schema, - const gchar *context) +gdouble +g_settings_get_double (GSettings *settings, + const gchar *key) { - return g_object_new (G_TYPE_SETTINGS, - "schema", schema, - "context", context, - NULL); + GVariant *value; + gdouble result; + + value = g_settings_get_value (settings, key); + result = g_variant_get_double (value); + g_variant_unref (value); + + return result; } /** - * g_settings_new_with_context_and_path: - * @schema: the name of the schema - * @path: the path to use - * @returns: a new #GSettings object + * g_settings_set_double: + * @settings: a #GSettings object + * @key: the name of the key to set + * @value: the value to set it to + * @returns: %TRUE if setting the key succeeded, + * %FALSE if the key was not writable * - * Creates a new #GSettings object with a given schema, context and - * path. + * Sets @key in @settings to @value. * - * This is a mix of g_settings_new_with_context() and - * g_settings_new_with_path(). + * A convenience variant of g_settings_set() for doubles. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type double. * * Since: 2.26 */ -GSettings * -g_settings_new_with_context_and_path (const gchar *schema, - const gchar *context, - const gchar *path) +gboolean +g_settings_set_double (GSettings *settings, + const gchar *key, + gdouble value) { - return g_object_new (G_TYPE_SETTINGS, - "schema", schema, - "context", context, - "path", path, - NULL); + return g_settings_set_value (settings, key, g_variant_new_double (value)); } -typedef struct +/** + * g_settings_get_boolean: + * @settings: a #GSettings object + * @key: the key to get the value for + * @returns: a boolean + * + * Gets the value that is stored at @key in @settings. + * + * A convenience variant of g_settings_get() for booleans. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type boolean. + * + * Since: 2.26 + */ +gboolean +g_settings_get_boolean (GSettings *settings, + const gchar *key) { - GSettings *settings; - GObject *object; + GVariant *value; + gboolean result; - GSettingsBindGetMapping get_mapping; - GSettingsBindSetMapping set_mapping; - gpointer user_data; - GDestroyNotify destroy; + value = g_settings_get_value (settings, key); + result = g_variant_get_boolean (value); + g_variant_unref (value); - guint writable_handler_id; - guint property_handler_id; - const GParamSpec *property; - guint key_handler_id; - GVariantType *type; - const gchar *key; + return result; +} - /* prevent recursion */ - gboolean running; -} GSettingsBinding; - -static void -g_settings_binding_free (gpointer data) +/** + * g_settings_set_boolean: + * @settings: a #GSettings object + * @key: the name of the key to set + * @value: the value to set it to + * @returns: %TRUE if setting the key succeeded, + * %FALSE if the key was not writable + * + * Sets @key in @settings to @value. + * + * A convenience variant of g_settings_set() for booleans. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type boolean. + * + * Since: 2.26 + */ +gboolean +g_settings_set_boolean (GSettings *settings, + const gchar *key, + gboolean value) { - GSettingsBinding *binding = data; - - g_assert (!binding->running); - - if (binding->writable_handler_id) - g_signal_handler_disconnect (binding->settings, - binding->writable_handler_id); - - if (binding->key_handler_id) - g_signal_handler_disconnect (binding->settings, - binding->key_handler_id); - - if (g_signal_handler_is_connected (binding->object, - binding->property_handler_id)) - g_signal_handler_disconnect (binding->object, - binding->property_handler_id); + return g_settings_set_value (settings, key, g_variant_new_boolean (value)); +} - g_variant_type_free (binding->type); - g_object_unref (binding->settings); +/** + * g_settings_get_strv: + * @settings: a #GSettings object + * @key: the key to get the value for + * @returns: a newly-allocated, %NULL-terminated array of strings + * + * Gets the value that is stored at @key in @settings. + * + * A convenience variant of g_settings_get() for string arrays. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type 'string array'. + * + * Since: 2.26 + */ +gchar ** +g_settings_get_strv (GSettings *settings, + const gchar *key) +{ + GVariant *value; + gchar **result; - if (binding->destroy) - binding->destroy (binding->user_data); + value = g_settings_get_value (settings, key); + result = g_variant_dup_strv (value, NULL); + g_variant_unref (value); - g_slice_free (GSettingsBinding, binding); + return result; } -static GQuark -g_settings_binding_quark (const char *property) +/** + * g_settings_set_strv: + * @settings: a #GSettings object + * @key: the name of the key to set + * @value: (allow-none): the value to set it to, or %NULL + * @returns: %TRUE if setting the key succeeded, + * %FALSE if the key was not writable + * + * Sets @key in @settings to @value. + * + * A convenience variant of g_settings_set() for string arrays. If + * @value is %NULL, then @key is set to be the empty array. + * + * It is a programmer error to pass a @key that isn't valid for + * @settings or is not of type 'string array'. + * + * Since: 2.26 + */ +gboolean +g_settings_set_strv (GSettings *settings, + const gchar *key, + const gchar * const *value) { - GQuark quark; - gchar *tmp; + GVariant *array; - tmp = g_strdup_printf ("gsettingsbinding-%s", property); - quark = g_quark_from_string (tmp); - g_free (tmp); + if (value != NULL) + array = g_variant_new_strv (value, -1); + else + array = g_variant_new_strv (NULL, 0); - return quark; + return g_settings_set_value (settings, key, array); } - -static void -g_settings_binding_key_changed (GSettings *settings, - const gchar *key, - gpointer user_data) +/* Delayed apply (delay, apply, revert, get_has_unapplied) {{{1 */ +/** + * g_settings_delay: + * @settings: a #GSettings object + * + * Changes the #GSettings object into 'delay-apply' mode. In this + * mode, changes to @settings are not immediately propagated to the + * backend, but kept locally until g_settings_apply() is called. + * + * Since: 2.26 + */ +void +g_settings_delay (GSettings *settings) { - GSettingsBinding *binding = user_data; - GValue value = { 0, }; - GVariant *variant; - - g_assert (settings == binding->settings); - g_assert (key == binding->key); + g_return_if_fail (G_IS_SETTINGS (settings)); - if (binding->running) + if (settings->priv->delayed) return; - binding->running = TRUE; - - g_value_init (&value, binding->property->value_type); - variant = g_settings_get_value (settings, key); - if (binding->get_mapping (&value, variant, binding->user_data)) - g_object_set_property (binding->object, - binding->property->name, - &value); - g_value_unset (&value); + settings->priv->delayed = + g_delayed_settings_backend_new (settings->priv->backend, + settings, + settings->priv->main_context); + g_settings_backend_unwatch (settings->priv->backend, G_OBJECT (settings)); + g_object_unref (settings->priv->backend); - binding->running = FALSE; + settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed); + g_settings_backend_watch (settings->priv->backend, G_OBJECT (settings), + settings->priv->main_context, + settings_backend_changed, + settings_backend_path_changed, + settings_backend_keys_changed, + settings_backend_writable_changed, + settings_backend_path_writable_changed); } -static void -g_settings_binding_property_changed (GObject *object, - const GParamSpec *pspec, - gpointer user_data) +/** + * g_settings_apply: + * @settings: a #GSettings instance + * + * Applies any changes that have been made to the settings. This + * function does nothing unless @settings is in 'delay-apply' mode; + * see g_settings_set_delay_apply(). In the normal case settings are + * always applied immediately. + **/ +void +g_settings_apply (GSettings *settings) { - GSettingsBinding *binding = user_data; - GValue value = { 0, }; - GVariant *variant; - - g_assert (object == binding->object); - g_assert (pspec == binding->property); - - if (binding->running) - return; - - binding->running = TRUE; - - g_value_init (&value, pspec->value_type); - g_object_get_property (object, pspec->name, &value); - if ((variant = binding->set_mapping (&value, binding->type, - binding->user_data))) + if (settings->priv->delayed) { - g_settings_set_value (binding->settings, - binding->key, - g_variant_ref_sink (variant)); - g_variant_unref (variant); - } - g_value_unset (&value); + GDelayedSettingsBackend *delayed; - binding->running = FALSE; + delayed = G_DELAYED_SETTINGS_BACKEND (settings->priv->backend); + g_delayed_settings_backend_apply (delayed); + } } /** - * g_settings_bind: - * @settings: a #GSettings object - * @key: the key to bind - * @object: a #GObject - * @property: the name of the property to bind - * @flags: flags for the binding - * - * Create a binding between the @key in the @settings object - * and the property @property of @object. - * - * The binding uses the default GIO mapping functions to map - * between the settings and property values. These functions - * handle booleans, numeric types and string types in a - * straightforward way. Use g_settings_bind_with_mapping() if - * you need a custom mapping, or map between types that are not - * supported by the default mapping functions. - * - * Unless the @flags include %G_SETTINGS_BIND_NO_SENSITIVITY, this - * function also establishes a binding between the writability of - * @key and the "sensitive" property of @object (if @object has - * a boolean property by that name). See g_settings_bind_writable() - * for more details about writable bindings. + * g_settings_revert: + * @settings: a #GSettings instance * - * Note that the lifecycle of the binding is tied to the object, - * and that you can have only one binding per object property. - * If you bind the same property twice on the same object, the second - * binding overrides the first one. + * Reverts all non-applied changes to the settings. This function + * does nothing unless @settings is in 'delay-apply' mode; see + * g_settings_set_delay_apply(). In the normal case settings are + * always applied immediately. * - * Since: 2.26 - */ + * Change notifications will be emitted for affected keys. + **/ void -g_settings_bind (GSettings *settings, - const gchar *key, - gpointer object, - const gchar *property, - GSettingsBindFlags flags) +g_settings_revert (GSettings *settings) { - g_settings_bind_with_mapping (settings, key, object, property, - flags, NULL, NULL, NULL, NULL); + if (settings->priv->delayed) + { + GDelayedSettingsBackend *delayed; + + delayed = G_DELAYED_SETTINGS_BACKEND (settings->priv->backend); + g_delayed_settings_backend_revert (delayed); + } } /** - * g_settings_bind_with_mapping: + * g_settings_get_has_unapplied: * @settings: a #GSettings object - * @key: the key to bind - * @object: a #GObject - * @property: the name of the property to bind - * @flags: flags for the binding - * @get_mapping: a function that gets called to convert values - * from @settings to @object, or %NULL to use the default GIO mapping - * @set_mapping: a function that gets called to convert values - * from @object to @settings, or %NULL to use the default GIO mapping - * @user_data: data that gets passed to @get_mapping and @set_mapping - * @destroy: #GDestroyNotify function for @user_data - * - * Create a binding between the @key in the @settings object - * and the property @property of @object. - * - * The binding uses the provided mapping functions to map between - * settings and property values. + * @returns: %TRUE if @settings has unapplied changes * - * Note that the lifecycle of the binding is tied to the object, - * and that you can have only one binding per object property. - * If you bind the same property twice on the same object, the second - * binding overrides the first one. + * Returns whether the #GSettings object has any unapplied + * changes. This can only be the case if it is in 'delayed-apply' mode. * * Since: 2.26 */ -void -g_settings_bind_with_mapping (GSettings *settings, - const gchar *key, - gpointer object, - const gchar *property, - GSettingsBindFlags flags, - GSettingsBindGetMapping get_mapping, - GSettingsBindSetMapping set_mapping, - gpointer user_data, - GDestroyNotify destroy) +gboolean +g_settings_get_has_unapplied (GSettings *settings) { - GSettingsBinding *binding; - GObjectClass *objectclass; - gchar *detailed_signal; - GQuark binding_quark; - - g_return_if_fail (G_IS_SETTINGS (settings)); - - objectclass = G_OBJECT_GET_CLASS (object); - - binding = g_slice_new0 (GSettingsBinding); - binding->settings = g_object_ref (settings); - binding->object = object; - binding->key = g_intern_string (key); - binding->property = g_object_class_find_property (objectclass, property); - binding->user_data = user_data; - binding->destroy = destroy; - binding->get_mapping = get_mapping ? get_mapping : g_settings_get_mapping; - binding->set_mapping = set_mapping ? set_mapping : g_settings_set_mapping; - - if (!(flags & (G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET))) - flags |= G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET; - - if (binding->property == NULL) - { - g_critical ("g_settings_bind: no property '%s' on class '%s'", - property, G_OBJECT_TYPE_NAME (object)); - return; - } + g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); - if ((flags & G_SETTINGS_BIND_GET) && (binding->property->flags & G_PARAM_WRITABLE) == 0) - { - g_critical ("g_settings_bind: property '%s' on class '%s' is not writable", - property, G_OBJECT_TYPE_NAME (object)); - return; - } - if ((flags & G_SETTINGS_BIND_SET) && (binding->property->flags & G_PARAM_READABLE) == 0) - { - g_critical ("g_settings_bind: property '%s' on class '%s' is not readable", - property, G_OBJECT_TYPE_NAME (object)); - return; - } + return settings->priv->delayed && + g_delayed_settings_backend_get_has_unapplied ( + G_DELAYED_SETTINGS_BACKEND (settings->priv->backend)); +} - { - GVariant *value; +/* Extra API (sync, get_child, is_writable) {{{1 */ +/** + * g_settings_sync: + * @context: the context to sync, or %NULL + * + * Ensures that all pending operations for the given context are + * complete. + * + * Writes made to a #GSettings are handled asynchronously. For this + * reason, it is very unlikely that the changes have it to disk by the + * time g_settings_set() returns. + * + * This call will block until all of the writes have made it to the + * backend. Since the mainloop is not running, no change notifications + * will be dispatched during this call (but some may be queued by the + * time the call is done). + **/ +void +g_settings_sync (const gchar *context) +{ + GSettingsBackend *backend; - value = g_settings_schema_get_value (settings->priv->schema, key, NULL); - binding->type = g_variant_type_copy (g_variant_get_type (value)); - if (value) - g_variant_unref (value); - } + if (context == NULL) + context = ""; - if (binding->type == NULL) - { - g_critical ("g_settings_bind: no key '%s' on schema '%s'", - key, settings->priv->schema_name); - return; - } + backend = g_settings_backend_get_with_context (context); + g_settings_backend_sync (backend); +} - if (((get_mapping == NULL && (flags & G_SETTINGS_BIND_GET)) || - (set_mapping == NULL && (flags & G_SETTINGS_BIND_SET))) && - !g_settings_mapping_is_compatible (binding->property->value_type, - binding->type)) - { - g_critical ("g_settings_bind: property '%s' on class '%s' has type" - "'%s' which is not compatible with type '%s' of key '%s'" - "on schema '%s'", property, G_OBJECT_TYPE_NAME (object), - g_type_name (binding->property->value_type), - g_variant_type_dup_string (binding->type), key, - settings->priv->schema_name); - return; - } +/** + * g_settings_is_writable: + * @settings: a #GSettings object + * @name: the name of a key + * @returns: %TRUE if the key @name is writable + * + * Finds out if a key can be written or not + * + * Since: 2.26 + */ +gboolean +g_settings_is_writable (GSettings *settings, + const gchar *name) +{ + gboolean writable; + gchar *path; - if ((flags & G_SETTINGS_BIND_SET) && - (~flags & G_SETTINGS_BIND_NO_SENSITIVITY)) - { - GParamSpec *sensitive; + g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE); - sensitive = g_object_class_find_property (objectclass, "sensitive"); + path = g_strconcat (settings->priv->path, name, NULL); + writable = g_settings_backend_get_writable (settings->priv->backend, path); + g_free (path); - if (sensitive && sensitive->value_type == G_TYPE_BOOLEAN && - (sensitive->flags & G_PARAM_WRITABLE)) - g_settings_bind_writable (settings, binding->key, - object, "sensitive", FALSE); - } + return writable; +} - if (flags & G_SETTINGS_BIND_SET) - { - detailed_signal = g_strdup_printf ("notify::%s", property); - binding->property_handler_id = - g_signal_connect (object, detailed_signal, - G_CALLBACK (g_settings_binding_property_changed), - binding); - g_free (detailed_signal); +/** + * g_settings_get_child: + * @settings: a #GSettings object + * @name: the name of the 'child' schema + * @returns: a 'child' settings object + * + * Creates a 'child' settings object which has a base path of + * base-path/@name", where + * base-path is the base path of @settings. + * + * The schema for the child settings object must have been declared + * in the schema of @settings using a child element. + * + * Since: 2.26 + */ +GSettings * +g_settings_get_child (GSettings *settings, + const gchar *name) +{ + const gchar *child_schema; + gchar *child_path; + gchar *child_name; + GSettings *child; - if (~flags & G_SETTINGS_BIND_GET) - g_settings_binding_property_changed (object, - binding->property, - binding); - } + g_return_val_if_fail (G_IS_SETTINGS (settings), NULL); - if (flags & G_SETTINGS_BIND_GET) - { - if (~flags & G_SETTINGS_BIND_GET_NO_CHANGES) - { - detailed_signal = g_strdup_printf ("changed::%s", key); - binding->key_handler_id = - g_signal_connect (settings, detailed_signal, - G_CALLBACK (g_settings_binding_key_changed), - binding); - g_free (detailed_signal); - } + child_name = g_strconcat (name, "/", NULL); + child_schema = g_settings_schema_get_string (settings->priv->schema, + child_name); + if (child_schema == NULL) + g_error ("Schema '%s' has no child '%s'", + settings->priv->schema_name, name); - g_settings_binding_key_changed (settings, binding->key, binding); - } + child_path = g_strconcat (settings->priv->path, child_name, NULL); + child = g_object_new (G_TYPE_SETTINGS, + "schema", child_schema, + "path", child_path, + NULL); + g_free (child_path); + g_free (child_name); - binding_quark = g_settings_binding_quark (property); - g_object_set_qdata_full (object, binding_quark, - binding, g_settings_binding_free); + return child; } +/* Binding {{{1 */ typedef struct { GSettings *settings; - gpointer object; + GObject *object; + + GSettingsBindGetMapping get_mapping; + GSettingsBindSetMapping set_mapping; + gpointer user_data; + GDestroyNotify destroy; + + guint writable_handler_id; + guint property_handler_id; + const GParamSpec *property; + guint key_handler_id; + GVariantType *type; const gchar *key; - const gchar *property; - gboolean inverted; - gulong handler_id; -} GSettingsWritableBinding; + + /* prevent recursion */ + gboolean running; +} GSettingsBinding; static void -g_settings_writable_binding_free (gpointer data) +g_settings_binding_free (gpointer data) { - GSettingsWritableBinding *binding = data; + GSettingsBinding *binding = data; - g_signal_handler_disconnect (binding->settings, binding->handler_id); - g_object_unref (binding->settings); - g_slice_free (GSettingsWritableBinding, binding); -} + g_assert (!binding->running); -static void -g_settings_binding_writable_changed (GSettings *settings, - const gchar *key, - gpointer user_data) + if (binding->writable_handler_id) + g_signal_handler_disconnect (binding->settings, + binding->writable_handler_id); + + if (binding->key_handler_id) + g_signal_handler_disconnect (binding->settings, + binding->key_handler_id); + + if (g_signal_handler_is_connected (binding->object, + binding->property_handler_id)) + g_signal_handler_disconnect (binding->object, + binding->property_handler_id); + + g_variant_type_free (binding->type); + g_object_unref (binding->settings); + + if (binding->destroy) + binding->destroy (binding->user_data); + + g_slice_free (GSettingsBinding, binding); +} + +static GQuark +g_settings_binding_quark (const char *property) { - GSettingsWritableBinding *binding = user_data; - gboolean writable; + GQuark quark; + gchar *tmp; + + tmp = g_strdup_printf ("gsettingsbinding-%s", property); + quark = g_quark_from_string (tmp); + g_free (tmp); + + return quark; +} + +static void +g_settings_binding_key_changed (GSettings *settings, + const gchar *key, + gpointer user_data) +{ + GSettingsBinding *binding = user_data; + GValue value = { 0, }; + GVariant *variant; g_assert (settings == binding->settings); g_assert (key == binding->key); - writable = g_settings_is_writable (settings, key); + if (binding->running) + return; - if (binding->inverted) - writable = !writable; + binding->running = TRUE; - g_object_set (binding->object, binding->property, writable, NULL); + g_value_init (&value, binding->property->value_type); + variant = g_settings_get_value (settings, key); + if (binding->get_mapping (&value, variant, binding->user_data)) + g_object_set_property (binding->object, + binding->property->name, + &value); + g_value_unset (&value); + + binding->running = FALSE; +} + +static void +g_settings_binding_property_changed (GObject *object, + const GParamSpec *pspec, + gpointer user_data) +{ + GSettingsBinding *binding = user_data; + GValue value = { 0, }; + GVariant *variant; + + g_assert (object == binding->object); + g_assert (pspec == binding->property); + + if (binding->running) + return; + + binding->running = TRUE; + + g_value_init (&value, pspec->value_type); + g_object_get_property (object, pspec->name, &value); + if ((variant = binding->set_mapping (&value, binding->type, + binding->user_data))) + { + g_settings_set_value (binding->settings, + binding->key, + g_variant_ref_sink (variant)); + g_variant_unref (variant); + } + g_value_unset (&value); + + binding->running = FALSE; } /** - * g_settings_bind_writable: + * g_settings_bind: * @settings: a #GSettings object * @key: the key to bind * @object: a #GObject - * @property: the name of a boolean property to bind - * @inverted: whether to 'invert' the value + * @property: the name of the property to bind + * @flags: flags for the binding * - * Create a binding between the writability of @key in the - * @settings object and the property @property of @object. - * The property must be boolean; "sensitive" or "visible" - * properties of widgets are the most likely candidates. + * Create a binding between the @key in the @settings object + * and the property @property of @object. * - * Writable bindings are always uni-directional; changes of the - * writability of the setting will be propagated to the object - * property, not the other way. + * The binding uses the default GIO mapping functions to map + * between the settings and property values. These functions + * handle booleans, numeric types and string types in a + * straightforward way. Use g_settings_bind_with_mapping() if + * you need a custom mapping, or map between types that are not + * supported by the default mapping functions. * - * When the @inverted argument is %TRUE, the binding inverts the - * value as it passes from the setting to the object, i.e. @property - * will be set to %TRUE if the key is not - * writable. + * Unless the @flags include %G_SETTINGS_BIND_NO_SENSITIVITY, this + * function also establishes a binding between the writability of + * @key and the "sensitive" property of @object (if @object has + * a boolean property by that name). See g_settings_bind_writable() + * for more details about writable bindings. * * Note that the lifecycle of the binding is tied to the object, * and that you can have only one binding per object property. @@ -1514,380 +1942,317 @@ g_settings_binding_writable_changed (GSettings *settings, * Since: 2.26 */ void -g_settings_bind_writable (GSettings *settings, - const gchar *key, - gpointer object, - const gchar *property, - gboolean inverted) +g_settings_bind (GSettings *settings, + const gchar *key, + gpointer object, + const gchar *property, + GSettingsBindFlags flags) { - GSettingsWritableBinding *binding; + g_settings_bind_with_mapping (settings, key, object, property, + flags, NULL, NULL, NULL, NULL); +} + +/** + * g_settings_bind_with_mapping: + * @settings: a #GSettings object + * @key: the key to bind + * @object: a #GObject + * @property: the name of the property to bind + * @flags: flags for the binding + * @get_mapping: a function that gets called to convert values + * from @settings to @object, or %NULL to use the default GIO mapping + * @set_mapping: a function that gets called to convert values + * from @object to @settings, or %NULL to use the default GIO mapping + * @user_data: data that gets passed to @get_mapping and @set_mapping + * @destroy: #GDestroyNotify function for @user_data + * + * Create a binding between the @key in the @settings object + * and the property @property of @object. + * + * The binding uses the provided mapping functions to map between + * settings and property values. + * + * Note that the lifecycle of the binding is tied to the object, + * and that you can have only one binding per object property. + * If you bind the same property twice on the same object, the second + * binding overrides the first one. + * + * Since: 2.26 + */ +void +g_settings_bind_with_mapping (GSettings *settings, + const gchar *key, + gpointer object, + const gchar *property, + GSettingsBindFlags flags, + GSettingsBindGetMapping get_mapping, + GSettingsBindSetMapping set_mapping, + gpointer user_data, + GDestroyNotify destroy) +{ + GSettingsBinding *binding; + GObjectClass *objectclass; gchar *detailed_signal; - GParamSpec *pspec; + GQuark binding_quark; g_return_if_fail (G_IS_SETTINGS (settings)); - pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property); - if (pspec == NULL) + objectclass = G_OBJECT_GET_CLASS (object); + + binding = g_slice_new0 (GSettingsBinding); + binding->settings = g_object_ref (settings); + binding->object = object; + binding->key = g_intern_string (key); + binding->property = g_object_class_find_property (objectclass, property); + binding->user_data = user_data; + binding->destroy = destroy; + binding->get_mapping = get_mapping ? get_mapping : g_settings_get_mapping; + binding->set_mapping = set_mapping ? set_mapping : g_settings_set_mapping; + + if (!(flags & (G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET))) + flags |= G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET; + + if (binding->property == NULL) { - g_critical ("g_settings_bind_writable: no property '%s' on class '%s'", + g_critical ("g_settings_bind: no property '%s' on class '%s'", property, G_OBJECT_TYPE_NAME (object)); return; } - if ((pspec->flags & G_PARAM_WRITABLE) == 0) + + if ((flags & G_SETTINGS_BIND_GET) && (binding->property->flags & G_PARAM_WRITABLE) == 0) { - g_critical ("g_settings_bind_writable: property '%s' on class '%s' is not writable", + g_critical ("g_settings_bind: property '%s' on class '%s' is not writable", + property, G_OBJECT_TYPE_NAME (object)); + return; + } + if ((flags & G_SETTINGS_BIND_SET) && (binding->property->flags & G_PARAM_READABLE) == 0) + { + g_critical ("g_settings_bind: property '%s' on class '%s' is not readable", property, G_OBJECT_TYPE_NAME (object)); return; } - binding = g_slice_new (GSettingsWritableBinding); - binding->settings = g_object_ref (settings); - binding->object = object; - binding->key = g_intern_string (key); - binding->property = g_intern_string (property); - binding->inverted = inverted; - - detailed_signal = g_strdup_printf ("writable-changed::%s", key); - binding->handler_id = - g_signal_connect (settings, detailed_signal, - G_CALLBACK (g_settings_binding_writable_changed), - binding); - g_free (detailed_signal); - - g_object_set_qdata_full (object, g_settings_binding_quark (property), - binding, g_settings_writable_binding_free); - - g_settings_binding_writable_changed (settings, binding->key, binding); -} - -/** - * g_settings_unbind: - * @object: the object - * @property: the property whose binding is removed - * - * Removes an existing binding for @property on @object. - * - * Note that bindings are automatically removed when the - * object is finalized, so it is rarely necessary to call this - * function. - * - * Since: 2.26 - */ -void -g_settings_unbind (gpointer object, - const gchar *property) -{ - GQuark binding_quark; - - binding_quark = g_settings_binding_quark (property); - g_object_set_qdata (object, binding_quark, NULL); -} + { + GVariantIter *iter; + GVariant *value; -/** - * g_settings_get_string: - * @settings: a #GSettings object - * @key: the key to get the value for - * @returns: a newly-allocated string - * - * Gets the value that is stored at @key in @settings. - * - * A convenience variant of g_settings_get() for strings. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type string. - * - * Since: 2.26 - */ -gchar * -g_settings_get_string (GSettings *settings, - const gchar *key) -{ - GVariant *value; - gchar *result; + iter = g_settings_schema_get_value (settings->priv->schema, key); + value = g_variant_iter_next_value (iter); + binding->type = g_variant_type_copy (g_variant_get_type (value)); + g_variant_iter_free (iter); + g_variant_unref (value); + } - value = g_settings_get_value (settings, key); - result = g_variant_dup_string (value, NULL); - g_variant_unref (value); + if (binding->type == NULL) + { + g_critical ("g_settings_bind: no key '%s' on schema '%s'", + key, settings->priv->schema_name); + return; + } - return result; -} + if (((get_mapping == NULL && (flags & G_SETTINGS_BIND_GET)) || + (set_mapping == NULL && (flags & G_SETTINGS_BIND_SET))) && + !g_settings_mapping_is_compatible (binding->property->value_type, + binding->type)) + { + g_critical ("g_settings_bind: property '%s' on class '%s' has type " + "'%s' which is not compatible with type '%s' of key '%s' " + "on schema '%s'", property, G_OBJECT_TYPE_NAME (object), + g_type_name (binding->property->value_type), + g_variant_type_dup_string (binding->type), key, + settings->priv->schema_name); + return; + } -/** - * g_settings_set_string: - * @settings: a #GSettings object - * @key: the name of the key to set - * @value: the value to set it to - * @returns: %TRUE if setting the key succeeded, - * %FALSE if the key was not writable - * - * Sets @key in @settings to @value. - * - * A convenience variant of g_settings_set() for strings. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type string. - * - * Since: 2.26 - */ -gboolean -g_settings_set_string (GSettings *settings, - const gchar *key, - const gchar *value) -{ - return g_settings_set_value (settings, key, g_variant_new_string (value)); -} + if ((flags & G_SETTINGS_BIND_SET) && + (~flags & G_SETTINGS_BIND_NO_SENSITIVITY)) + { + GParamSpec *sensitive; -/** - * g_settings_get_int: - * @settings: a #GSettings object - * @key: the key to get the value for - * @returns: an integer - * - * Gets the value that is stored at @key in @settings. - * - * A convenience variant of g_settings_get() for 32-bit integers. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type int32. - * - * Since: 2.26 - */ -gint -g_settings_get_int (GSettings *settings, - const gchar *key) -{ - GVariant *value; - gint result; + sensitive = g_object_class_find_property (objectclass, "sensitive"); - value = g_settings_get_value (settings, key); - result = g_variant_get_int32 (value); - g_variant_unref (value); + if (sensitive && sensitive->value_type == G_TYPE_BOOLEAN && + (sensitive->flags & G_PARAM_WRITABLE)) + g_settings_bind_writable (settings, binding->key, + object, "sensitive", FALSE); + } - return result; -} + if (flags & G_SETTINGS_BIND_SET) + { + detailed_signal = g_strdup_printf ("notify::%s", property); + binding->property_handler_id = + g_signal_connect (object, detailed_signal, + G_CALLBACK (g_settings_binding_property_changed), + binding); + g_free (detailed_signal); -/** - * g_settings_set_int: - * @settings: a #GSettings object - * @key: the name of the key to set - * @value: the value to set it to - * @returns: %TRUE if setting the key succeeded, - * %FALSE if the key was not writable - * - * Sets @key in @settings to @value. - * - * A convenience variant of g_settings_set() for 32-bit integers. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type int32. - * - * Since: 2.26 - */ -gboolean -g_settings_set_int (GSettings *settings, - const gchar *key, - gint value) -{ - return g_settings_set_value (settings, key, g_variant_new_int32 (value)); -} + if (~flags & G_SETTINGS_BIND_GET) + g_settings_binding_property_changed (object, + binding->property, + binding); + } -/** - * g_settings_get_double: - * @settings: a #GSettings object - * @key: the key to get the value for - * @returns: a double - * - * Gets the value that is stored at @key in @settings. - * - * A convenience variant of g_settings_get() for doubles. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type double. - * - * Since: 2.26 - */ -gdouble -g_settings_get_double (GSettings *settings, - const gchar *key) -{ - GVariant *value; - gdouble result; + if (flags & G_SETTINGS_BIND_GET) + { + if (~flags & G_SETTINGS_BIND_GET_NO_CHANGES) + { + detailed_signal = g_strdup_printf ("changed::%s", key); + binding->key_handler_id = + g_signal_connect (settings, detailed_signal, + G_CALLBACK (g_settings_binding_key_changed), + binding); + g_free (detailed_signal); + } - value = g_settings_get_value (settings, key); - result = g_variant_get_double (value); - g_variant_unref (value); + g_settings_binding_key_changed (settings, binding->key, binding); + } - return result; + binding_quark = g_settings_binding_quark (property); + g_object_set_qdata_full (object, binding_quark, + binding, g_settings_binding_free); } -/** - * g_settings_set_double: - * @settings: a #GSettings object - * @key: the name of the key to set - * @value: the value to set it to - * @returns: %TRUE if setting the key succeeded, - * %FALSE if the key was not writable - * - * Sets @key in @settings to @value. - * - * A convenience variant of g_settings_set() for doubles. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type double. - * - * Since: 2.26 - */ -gboolean -g_settings_set_double (GSettings *settings, - const gchar *key, - gdouble value) +/* Writability binding {{{1 */ +typedef struct { - return g_settings_set_value (settings, key, g_variant_new_double (value)); -} + GSettings *settings; + gpointer object; + const gchar *key; + const gchar *property; + gboolean inverted; + gulong handler_id; +} GSettingsWritableBinding; -/** - * g_settings_get_boolean: - * @settings: a #GSettings object - * @key: the key to get the value for - * @returns: a boolean - * - * Gets the value that is stored at @key in @settings. - * - * A convenience variant of g_settings_get() for booleans. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type boolean. - * - * Since: 2.26 - */ -gboolean -g_settings_get_boolean (GSettings *settings, - const gchar *key) +static void +g_settings_writable_binding_free (gpointer data) { - GVariant *value; - gboolean result; - - value = g_settings_get_value (settings, key); - result = g_variant_get_boolean (value); - g_variant_unref (value); + GSettingsWritableBinding *binding = data; - return result; + g_signal_handler_disconnect (binding->settings, binding->handler_id); + g_object_unref (binding->settings); + g_slice_free (GSettingsWritableBinding, binding); } -/** - * g_settings_set_boolean: - * @settings: a #GSettings object - * @key: the name of the key to set - * @value: the value to set it to - * @returns: %TRUE if setting the key succeeded, - * %FALSE if the key was not writable - * - * Sets @key in @settings to @value. - * - * A convenience variant of g_settings_set() for booleans. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type boolean. - * - * Since: 2.26 - */ -gboolean -g_settings_set_boolean (GSettings *settings, - const gchar *key, - gboolean value) +static void +g_settings_binding_writable_changed (GSettings *settings, + const gchar *key, + gpointer user_data) { - return g_settings_set_value (settings, key, g_variant_new_boolean (value)); -} + GSettingsWritableBinding *binding = user_data; + gboolean writable; -/** - * g_settings_get_strv: - * @settings: a #GSettings object - * @key: the key to get the value for - * @returns: a newly-allocated, %NULL-terminated array of strings - * - * Gets the value that is stored at @key in @settings. - * - * A convenience variant of g_settings_get() for string arrays. - * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type 'string array'. - * - * Since: 2.26 - */ -gchar ** -g_settings_get_strv (GSettings *settings, - const gchar *key) -{ - GVariant *value; - gchar **result; + g_assert (settings == binding->settings); + g_assert (key == binding->key); - value = g_settings_get_value (settings, key); - result = g_variant_dup_strv (value, NULL); - g_variant_unref (value); + writable = g_settings_is_writable (settings, key); - return result; + if (binding->inverted) + writable = !writable; + + g_object_set (binding->object, binding->property, writable, NULL); } /** - * g_settings_set_strv: + * g_settings_bind_writable: * @settings: a #GSettings object - * @key: the name of the key to set - * @value: (allow-none): the value to set it to, or %NULL - * @returns: %TRUE if setting the key succeeded, - * %FALSE if the key was not writable + * @key: the key to bind + * @object: a #GObject + * @property: the name of a boolean property to bind + * @inverted: whether to 'invert' the value * - * Sets @key in @settings to @value. + * Create a binding between the writability of @key in the + * @settings object and the property @property of @object. + * The property must be boolean; "sensitive" or "visible" + * properties of widgets are the most likely candidates. * - * A convenience variant of g_settings_set() for string arrays. If - * @value is %NULL, then @key is set to be the empty array. + * Writable bindings are always uni-directional; changes of the + * writability of the setting will be propagated to the object + * property, not the other way. * - * It is a programmer error to pass a @key that isn't valid for - * @settings or is not of type 'string array'. + * When the @inverted argument is %TRUE, the binding inverts the + * value as it passes from the setting to the object, i.e. @property + * will be set to %TRUE if the key is not + * writable. + * + * Note that the lifecycle of the binding is tied to the object, + * and that you can have only one binding per object property. + * If you bind the same property twice on the same object, the second + * binding overrides the first one. * * Since: 2.26 */ -gboolean -g_settings_set_strv (GSettings *settings, - const gchar *key, - const gchar * const *value) +void +g_settings_bind_writable (GSettings *settings, + const gchar *key, + gpointer object, + const gchar *property, + gboolean inverted) { - GVariant *array; + GSettingsWritableBinding *binding; + gchar *detailed_signal; + GParamSpec *pspec; - if (value) - array = g_variant_new_strv (value, -1); - else - array = g_variant_new_strv (NULL, 0); + g_return_if_fail (G_IS_SETTINGS (settings)); - return g_settings_set_value (settings, key, array); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property); + if (pspec == NULL) + { + g_critical ("g_settings_bind_writable: no property '%s' on class '%s'", + property, G_OBJECT_TYPE_NAME (object)); + return; + } + if ((pspec->flags & G_PARAM_WRITABLE) == 0) + { + g_critical ("g_settings_bind_writable: property '%s' on class '%s' is not writable", + property, G_OBJECT_TYPE_NAME (object)); + return; + } + + binding = g_slice_new (GSettingsWritableBinding); + binding->settings = g_object_ref (settings); + binding->object = object; + binding->key = g_intern_string (key); + binding->property = g_intern_string (property); + binding->inverted = inverted; + + detailed_signal = g_strdup_printf ("writable-changed::%s", key); + binding->handler_id = + g_signal_connect (settings, detailed_signal, + G_CALLBACK (g_settings_binding_writable_changed), + binding); + g_free (detailed_signal); + + g_object_set_qdata_full (object, g_settings_binding_quark (property), + binding, g_settings_writable_binding_free); + + g_settings_binding_writable_changed (settings, binding->key, binding); } /** - * g_settings_sync: - * @context: the context to sync, or %NULL + * g_settings_unbind: + * @object: the object + * @property: the property whose binding is removed * - * Ensures that all pending operations for the given context are - * complete. + * Removes an existing binding for @property on @object. * - * Writes made to a #GSettings are handled asynchronously. For this - * reason, it is very unlikely that the changes have it to disk by the - * time g_settings_set() returns. + * Note that bindings are automatically removed when the + * object is finalized, so it is rarely necessary to call this + * function. * - * This call will block until all of the writes have made it to the - * backend. Since the mainloop is not running, no change notifications - * will be dispatched during this call (but some may be queued by the - * time the call is done). - **/ + * Since: 2.26 + */ void -g_settings_sync (const gchar *context) +g_settings_unbind (gpointer object, + const gchar *property) { - GSettingsBackend *backend; - - if (context == NULL) - context = ""; + GQuark binding_quark; - backend = g_settings_backend_get_with_context (context); - g_settings_backend_sync (backend); + binding_quark = g_settings_binding_quark (property); + g_object_set_qdata (object, binding_quark, NULL); } +/* Epilogue {{{1 */ #define __G_SETTINGS_C__ #include "gioaliasdef.c" + +/* vim:set foldmethod=marker: */ diff --git a/gio/gsettings.h b/gio/gsettings.h index d766bd0..aad224e 100644 --- a/gio/gsettings.h +++ b/gio/gsettings.h @@ -121,7 +121,11 @@ gchar ** g_settings_get_strv (GSettin gboolean g_settings_set_strv (GSettings *settings, const gchar *key, const gchar *const *value); - +gint g_settings_get_enum (GSettings *settings, + const gchar *key); +gboolean g_settings_set_enum (GSettings *settings, + const gchar *key, + gint value); GSettings * g_settings_get_child (GSettings *settings, const gchar *name); diff --git a/gio/gsettingsbackend.c b/gio/gsettingsbackend.c index dd22593..044ef48 100644 --- a/gio/gsettingsbackend.c +++ b/gio/gsettingsbackend.c @@ -732,7 +732,8 @@ g_settings_backend_changed_tree (GSettingsBackend *backend, * g_settings_backend_read: * @backend: a #GSettingsBackend implementation * @key: the key to read - * @expected_type: a #GVariantType hint + * @expected_type: a #GVariantType + * @default_value: if the default value should be returned * @returns: the value that was read, or %NULL * * Reads a key. This call will never block. @@ -740,11 +741,13 @@ g_settings_backend_changed_tree (GSettingsBackend *backend, * If the key exists, the value associated with it will be returned. * If the key does not exist, %NULL will be returned. * - * If @expected_type is given, it serves as a type hint to the backend. - * If you expect a key of a certain type then you should give - * @expected_type to increase your chances of getting it. Some backends - * may ignore this argument and return values of a different type; it is - * mostly used by backends that don't store strong type information. + * The returned value will be of the type given in @expected_type. If + * the backend stored a value of a different type then %NULL will be + * returned. + * + * If @default_value is %TRUE then this gets the default value from the + * backend (ie: the one that the backend would contain if + * g_settings_reset() were called). */ GVariant * g_settings_backend_read (GSettingsBackend *backend, @@ -752,8 +755,18 @@ g_settings_backend_read (GSettingsBackend *backend, const GVariantType *expected_type, gboolean default_value) { - return G_SETTINGS_BACKEND_GET_CLASS (backend) + GVariant *value; + + value = G_SETTINGS_BACKEND_GET_CLASS (backend) ->read (backend, key, expected_type, default_value); + + if G_UNLIKELY (value && !g_variant_is_of_type (value, expected_type)) + { + g_variant_unref (value); + value = NULL; + } + + return value; } /*< private > diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index c0fbf17..275e427 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -162,37 +162,32 @@ g_settings_schema_new (const gchar *name) return schema; } -GVariant * -g_settings_schema_get_value (GSettingsSchema *schema, - const gchar *key, - GVariant **options) +GVariantIter * +g_settings_schema_get_value (GSettingsSchema *schema, + const gchar *key) { - GVariant *variant, *value; -#if G_BYTE_ORDER == G_BIG_ENDIAN - GVariant *tmp; + GVariantIter *iter; + GVariant *value; - tmp = gvdb_table_get_value (schema->priv->table, key); + value = gvdb_table_get_value (schema->priv->table, key); - if (tmp) - { - variant = g_variant_byteswap (tmp); - g_variant_unref (tmp); - } - else - variant = NULL; -#else - variant = gvdb_table_get_value (schema->priv->table, key); -#endif + if G_UNLIKELY (value == NULL) + g_error ("schema does not contain a key named '%s'", key); + +#if G_BYTE_ORDER == G_BIG_ENDIAN + { + GVariant *tmp; - if (variant == NULL) - return NULL; + tmp = g_variant_byteswap (value); + g_variant_unref (value); + value = tmp; + } +#endif - value = g_variant_get_child_value (variant, 0); - if (options != NULL) - *options = g_variant_get_child_value (variant, 1); - g_variant_unref (variant); + iter = g_variant_iter_new (value); + g_variant_unref (value); - return value; + return iter; } const gchar * diff --git a/gio/gsettingsschema.h b/gio/gsettingsschema.h index 9ac86a9..31ba1f4 100644 --- a/gio/gsettingsschema.h +++ b/gio/gsettingsschema.h @@ -61,9 +61,8 @@ const gchar * g_settings_schema_get_path (GSettin G_GNUC_INTERNAL const gchar * g_settings_schema_get_gettext_domain (GSettingsSchema *schema); G_GNUC_INTERNAL -GVariant * g_settings_schema_get_value (GSettingsSchema *schema, - const gchar *key, - GVariant **options); +GVariantIter * g_settings_schema_get_value (GSettingsSchema *schema, + const gchar *key); G_GNUC_INTERNAL gboolean g_settings_schema_has_key (GSettingsSchema *schema, const gchar *key); diff --git a/gio/strinfo.c b/gio/strinfo.c new file mode 100644 index 0000000..04c90c0 --- /dev/null +++ b/gio/strinfo.c @@ -0,0 +1,308 @@ +/* + * Copyright © 2010 Codethink Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#include +#include + +/** + * The string info map is an efficient data structure designed to be + * used with a small set of items. It is used by GSettings schemas for + * three purposes: + * + * 1) Implement with a list of valid strings + * + * 2) Implement by mapping one string to another + * + * 3) Implement enumerated types by mapping strings to integer values + * (and back). + * + * The map is made out of an array of uint32s. Each entry in the array + * is an integer value, followed by a specially formatted string value: + * + * The string starts with the byte 0xff or 0xfe, followed by the + * content of the string, followed by a nul byte, followed by + * additional nul bytes for padding, followed by a 0xff byte. + * + * Padding is added so that the entire formatted string takes up a + * multiple of 4 bytes, and not less than 8 bytes. The requirement + * for a string to take up 8 bytes is so that the scanner doesn't lose + * synch and mistake a string for an integer value. + * + * The first byte of the formatted string depends on if the integer is + * an enum value (0xff) or an alias (0xfe). If it is an alias then the + * number refers to the word offset within the info map at which the + * integer corresponding to the "target" value is stored. + * + * For example, consider the case of the string info map representing an + * enumerated type of 'foo' (value 1) and 'bar' (value 2) and 'baz' + * (alias for 'bar'). Note that string info maps are always little + * endian. + * + * x01 x00 x00 x00 xff 'f' 'o' 'o' x00 x00 x00 xff x02 x00 x00 x00 + * xff 'b' 'a' 'r' x00 x00 x00 xff x03 x00 x00 x00 xfe 'b' 'a' 'z' + * x00 x00 x00 xff + * + * + * The operations that someone may want to perform with the map: + * + * - lookup if a string is valid (and not an alias) + * - lookup the integer value for a enum 'nick' + * - lookup the integer value for the target of an alias + * - lookup an alias and convert it to its target string + * - lookup the enum nick for a given value + * + * In order to lookup if a string is valid, it is padded on either side + * (as described) and scanned for in the array. For example, you might + * look for "foo": + * + * xff 'f' 'o' 'o' x00 x00 x00 xff + * + * In order to lookup the integer value for a nick, the string is padded + * on either side and scanned for in the array, as above. Instead of + * merely succeeding, we look at the integer value to the left of the + * match. This is the enum value. + * + * In order to lookup an alias and convert it to its target enum value, + * the string is padded on either side (as described, with 0xfe) and + * scanned for. For example, you might look for "baz": + * + * xfe 'b' 'a' 'z' x00 x00 x00 xff + * + * The integer immediately preceeding the match then contains the offset + * of the integer value of the target. In our example, that's '3'. + * This index is dereferenced to find the enum value of '2'. + * + * To convert the alias to its target string, 5 bytes just need to be + * added past the start of the integer value to find the start of the + * string. + * + * To lookup the enum nick for a given value, the value is searched for + * in the array. To ensure that the value isn't matching the inside of a + * string, we must check that it is either the first item in the array or + * immediately preceeded by the byte 0xff. It must also be immediately + * followed by the byte 0xff. + * + * Because strings always take up a minimum of 2 words, because 0xff or + * 0xfe never appear inside of a utf-8 string and because no two integer + * values ever appear in sequence, the only way we can have the + * sequence: + * + * xff __ __ __ __ xff (or 0xfe) + * + * is in the event of an integer nested between two strings. + * + * For implementation simplicity/efficiency, strings may not be more + * than 65 characters in length (ie: 17 32bit words after padding). + * + * In the event that we are doing (ie: not an enum type) then + * the value of each choice is set to zero and ignored. + */ + +#define STRINFO_MAX_WORDS 17 +G_GNUC_UNUSED static guint +strinfo_string_to_words (const gchar *string, + guint32 *words, + gboolean alias) +{ + guint n_words; + gsize size; + + size = strlen (string); + + n_words = MAX (2, (size + 6) >> 2); + + if (n_words > STRINFO_MAX_WORDS) + return FALSE; + + words[0] = GUINT32_TO_LE (alias ? 0xfe : 0xff); + words[n_words - 1] = GUINT32_TO_BE (0xff); + memcpy (((gchar *) words) + 1, string, size + 1); + + return n_words; +} + +G_GNUC_UNUSED static gint +strinfo_scan (const guint32 *strinfo, + guint length, + const guint32 *words, + guint n_words) +{ + guint i = 0; + + while (i <= length - n_words) + { + guint j = 0; + + for (j = 0; j < n_words; j++) + if (strinfo[i + j] != words[j]) + break; + + if (j == n_words) + return i; /* match */ + + /* skip at least one word, continue */ + i += j ? j : 1; + } + + return -1; +} + +G_GNUC_UNUSED static gint +strinfo_find_string (const guint32 *strinfo, + guint length, + const gchar *string, + gboolean alias) +{ + guint32 words[STRINFO_MAX_WORDS]; + guint n_words; + + if (length == 0) + return -1; + + n_words = strinfo_string_to_words (string, words, alias); + + return strinfo_scan (strinfo + 1, length - 1, words, n_words); +} + +G_GNUC_UNUSED static gint +strinfo_find_integer (const guint32 *strinfo, + guint length, + guint32 value) +{ + guint i; + + for (i = 0; i < length; i++) + if (strinfo[i] == value) + { + const guchar *charinfo = (const guchar *) &strinfo[i]; + + /* make sure it has 0xff on either side */ + if ((i == 0 || charinfo[-1] == 0xff) && charinfo[4] == 0xff) + return i; + } + + return -1; +} + +G_GNUC_UNUSED static gboolean +strinfo_is_string_valid (const guint32 *strinfo, + guint length, + const gchar *string) +{ + return strinfo_find_string (strinfo, length, string, FALSE) != -1; +} + +G_GNUC_UNUSED static gboolean +strinfo_enum_from_string (const guint32 *strinfo, + guint length, + const gchar *string, + guint *result) +{ + gint index; + + index = strinfo_find_string (strinfo, length, string, FALSE); + + if (index < 0) + return FALSE; + + *result = strinfo[index]; + return TRUE; +} + +G_GNUC_UNUSED static const gchar * +strinfo_string_from_enum (const guint32 *strinfo, + guint length, + guint value) +{ + gint index; + + index = strinfo_find_integer (strinfo, length, value); + + if (index < 0) + return NULL; + + return 1 + (const gchar *) &strinfo[index + 1]; +} + +G_GNUC_UNUSED static const gchar * +strinfo_string_from_alias (const guint32 *strinfo, + guint length, + const gchar *alias) +{ + gint index; + + index = strinfo_find_string (strinfo, length, alias, TRUE); + + if (index < 0) + return NULL; + + return 1 + (const gchar *) &strinfo[GUINT32_TO_LE (strinfo[index]) + 1]; +} + +G_GNUC_UNUSED static void +strinfo_builder_append_item (GString *builder, + const gchar *string, + guint value) +{ + guint32 words[STRINFO_MAX_WORDS]; + guint n_words; + + value = GUINT32_TO_LE (value); + + n_words = strinfo_string_to_words (string, words, FALSE); + g_string_append_len (builder, (void *) &value, sizeof value); + g_string_append_len (builder, (void *) words, 4 * n_words); +} + +G_GNUC_UNUSED static gboolean +strinfo_builder_append_alias (GString *builder, + const gchar *alias, + const gchar *target) +{ + guint32 words[STRINFO_MAX_WORDS]; + guint n_words; + guint value; + gint index; + + index = strinfo_find_string ((const guint32 *) builder->str, + builder->len / 4, target, FALSE); + + if (index == -1) + return FALSE; + + value = GUINT32_TO_LE (index); + + n_words = strinfo_string_to_words (alias, words, TRUE); + g_string_append_len (builder, (void *) &value, sizeof value); + g_string_append_len (builder, (void *) words, 4 * n_words); + + return TRUE; +} + +G_GNUC_UNUSED static gboolean +strinfo_builder_contains (GString *builder, + const gchar *string) +{ + return strinfo_find_string ((const guint32 *) builder->str, + builder->len / 4, string, FALSE) != -1 || + strinfo_find_string ((const guint32 *) builder->str, + builder->len / 4, string, FALSE) != -1; +} diff --git a/gio/tests/enums.xml.template b/gio/tests/enums.xml.template new file mode 100644 index 0000000..67d07b5 --- /dev/null +++ b/gio/tests/enums.xml.template @@ -0,0 +1,18 @@ +/*** BEGIN comment ***/ + +/*** END comment ***/ +/*** BEGIN file-header ***/ + +/*** END file-header ***/ +/*** BEGIN value-header ***/ + <@type@ id='org.gtk.test.@EnumName@'> +/*** END value-header ***/ +/*** BEGIN value-production ***/ + +/*** END value-production ***/ +/*** BEGIN value-tail ***/ + +/*** END value-tail ***/ +/*** BEGIN file-tail ***/ + +/*** END file-tail ***/ diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index fa64b23..8f014c7 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -6,6 +6,8 @@ #define G_SETTINGS_ENABLE_BACKEND #include +#include "testenum.h" + static gboolean backend_set; /* These tests rely on the schemas in org.gtk.test.gschema.xml @@ -41,7 +43,7 @@ test_basic (void) abort (); } g_test_trap_assert_failed (); - g_test_trap_assert_stderr ("*correct_type*"); + g_test_trap_assert_stderr ("*g_settings_type_check*"); } g_settings_get (settings, "greeting", "s", &str); @@ -120,7 +122,7 @@ test_wrong_type (void) settings = g_settings_new ("org.gtk.test"); - g_settings_set (settings, "greetings", "o", "/a/path"); + g_settings_set (settings, "greeting", "o", "/a/path"); } g_test_trap_assert_failed (); g_test_trap_assert_stderr ("*CRITICAL*"); @@ -1186,9 +1188,137 @@ glib_translations_work (void) return str != orig; } +#include "../strinfo.c" + +static void +test_strinfo (void) +{ + /* "foo" has a value of 1 + * "bar" has a value of 2 + * "baz" is an alias for "bar" + */ + gchar array[] = + "\1\0\0\0" "\xff""foo" "\0\0\0\xff" "\2\0\0\0" + "\xff" "bar" "\0\0\0\xff" "\3\0\0\0" "\xfe""baz" + "\0\0\0\xff"; + const guint32 *strinfo = (guint32 *) array; + guint length = sizeof array / 4; + guint result; + + { + /* build it and compare */ + GString *builder; + + builder = g_string_new (NULL); + strinfo_builder_append_item (builder, "foo", 1); + strinfo_builder_append_item (builder, "bar", 2); + g_assert (strinfo_builder_append_alias (builder, "baz", "bar")); + g_assert_cmpint (builder->len % 4, ==, 0); + g_assert_cmpint (builder->len / 4, ==, length); + g_assert (memcmp (builder->str, strinfo, length * 4) == 0); + g_string_free (builder, TRUE); + } + + g_assert_cmpstr (strinfo_string_from_alias (strinfo, length, "foo"), + ==, NULL); + g_assert_cmpstr (strinfo_string_from_alias (strinfo, length, "bar"), + ==, NULL); + g_assert_cmpstr (strinfo_string_from_alias (strinfo, length, "baz"), + ==, "bar"); + g_assert_cmpstr (strinfo_string_from_alias (strinfo, length, "quux"), + ==, NULL); + + g_assert (strinfo_enum_from_string (strinfo, length, "foo", &result)); + g_assert_cmpint (result, ==, 1); + g_assert (strinfo_enum_from_string (strinfo, length, "bar", &result)); + g_assert_cmpint (result, ==, 2); + g_assert (!strinfo_enum_from_string (strinfo, length, "baz", &result)); + g_assert (!strinfo_enum_from_string (strinfo, length, "quux", &result)); + + g_assert_cmpstr (strinfo_string_from_enum (strinfo, length, 0), ==, NULL); + g_assert_cmpstr (strinfo_string_from_enum (strinfo, length, 1), ==, "foo"); + g_assert_cmpstr (strinfo_string_from_enum (strinfo, length, 2), ==, "bar"); + g_assert_cmpstr (strinfo_string_from_enum (strinfo, length, 3), ==, NULL); + + g_assert (strinfo_is_string_valid (strinfo, length, "foo")); + g_assert (strinfo_is_string_valid (strinfo, length, "bar")); + g_assert (!strinfo_is_string_valid (strinfo, length, "baz")); + g_assert (!strinfo_is_string_valid (strinfo, length, "quux")); +} + +static void +test_enums (void) +{ + GSettings *settings, *direct; + + settings = g_settings_new ("org.gtk.test.enums"); + direct = g_settings_new ("org.gtk.test.enums.direct"); + + if (!backend_set) + { + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR)) + g_settings_get_enum (direct, "test"); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*not associated with an enum*"); + + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR)) + g_settings_set_enum (settings, "test", 42); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*invalid enum value 42*"); + + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR)) + g_settings_set_string (settings, "test", "qux"); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*g_settings_range_check*"); + } + + g_assert_cmpstr (g_settings_get_string (settings, "test"), ==, "bar"); + g_settings_set_enum (settings, "test", TEST_ENUM_FOO); + g_assert_cmpstr (g_settings_get_string (settings, "test"), ==, "foo"); + g_assert_cmpint (g_settings_get_enum (settings, "test"), ==, TEST_ENUM_FOO); + g_settings_set_string (direct, "test", "qux"); + g_assert_cmpstr (g_settings_get_string (direct, "test"), ==, "qux"); + g_assert_cmpstr (g_settings_get_string (settings, "test"), ==, "quux"); + g_assert_cmpint (g_settings_get_enum (settings, "test"), ==, TEST_ENUM_QUUX); +} + +static void +test_range (void) +{ + GSettings *settings, *direct; + + settings = g_settings_new ("org.gtk.test.range"); + direct = g_settings_new ("org.gtk.test.range.direct"); + + if (!backend_set) + { + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR)) + g_settings_set_int (settings, "val", 45); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*g_settings_range_check*"); + + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR)) + g_settings_set_int (settings, "val", 1); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*g_settings_range_check*"); + } + + g_assert_cmpint (g_settings_get_int (settings, "val"), ==, 33); + g_settings_set_int (direct, "val", 22); + g_assert_cmpint (g_settings_get_int (direct, "val"), ==, 22); + g_assert_cmpint (g_settings_get_int (settings, "val"), ==, 22); + g_settings_set_int (direct, "val", 45); + g_assert_cmpint (g_settings_get_int (direct, "val"), ==, 45); + g_assert_cmpint (g_settings_get_int (settings, "val"), ==, 33); + g_settings_set_int (direct, "val", 1); + g_assert_cmpint (g_settings_get_int (direct, "val"), ==, 1); + g_assert_cmpint (g_settings_get_int (settings, "val"), ==, 33); +} + int main (int argc, char *argv[]) { + gchar *enums; gint result; setlocale (LC_ALL, ""); @@ -1203,9 +1333,18 @@ main (int argc, char *argv[]) g_type_init (); g_test_init (&argc, &argv, NULL); + g_remove ("org.gtk.test.enums.xml"); + g_assert (g_spawn_command_line_sync ("../../gobject/glib-mkenums " + "--template enums.xml.template " + SRCDIR "/testenum.h", + &enums, NULL, &result, NULL)); + g_assert (result == 0); + g_assert (g_file_set_contents ("org.gtk.test.enums.xml", enums, -1, NULL)); + g_remove ("gschemas.compiled"); g_assert (g_spawn_command_line_sync ("../glib-compile-schemas --targetdir=. " SRCDIR, - NULL, NULL, NULL, NULL)); + NULL, NULL, &result, NULL)); + g_assert (result == 0); g_test_add_func ("/gsettings/basic", test_basic); @@ -1243,6 +1382,9 @@ main (int argc, char *argv[]) g_test_add_func ("/gsettings/keyfile", test_keyfile); g_test_add_func ("/gsettings/child-schema", test_child_schema); + g_test_add_func ("/gsettings/strinfo", test_strinfo); + g_test_add_func ("/gsettings/enums", test_enums); + g_test_add_func ("/gsettings/range", test_range); result = g_test_run (); diff --git a/gio/tests/org.gtk.test.gschema.xml b/gio/tests/org.gtk.test.gschema.xml index 9d1a821..a7c6c13 100644 --- a/gio/tests/org.gtk.test.gschema.xml +++ b/gio/tests/org.gtk.test.gschema.xml @@ -92,4 +92,32 @@ + + + 'bar' + + + + + + + + + 'bar' + + + + + + 33 + + + + + + + 33 + + + diff --git a/gio/tests/testenum.h b/gio/tests/testenum.h new file mode 100644 index 0000000..4329860 --- /dev/null +++ b/gio/tests/testenum.h @@ -0,0 +1,7 @@ +typedef enum +{ + TEST_ENUM_FOO, + TEST_ENUM_BAR, + TEST_ENUM_BAZ, + TEST_ENUM_QUUX +} TestEnum; -- 2.7.4