+
+/**
+ * g_settings_schema_get_id:
+ * @schema: a #GSettingsSchema
+ *
+ * Get the ID of @schema.
+ *
+ * Returns: (transfer none): the ID
+ **/
+const gchar *
+g_settings_schema_get_id (GSettingsSchema *schema)
+{
+ return schema->id;
+}
+
+static inline void
+endian_fixup (GVariant **value)
+{
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+ GVariant *tmp;
+
+ tmp = g_variant_byteswap (*value);
+ g_variant_unref (*value);
+ *value = tmp;
+#endif
+}
+
+void
+g_settings_schema_key_init (GSettingsSchemaKey *key,
+ GSettingsSchema *schema,
+ const gchar *name)
+{
+ GVariantIter *iter;
+ GVariant *data;
+ guchar code;
+
+ memset (key, 0, sizeof *key);
+
+ iter = g_settings_schema_get_value (schema, name);
+
+ key->schema = g_settings_schema_ref (schema);
+ key->default_value = g_variant_iter_next_value (iter);
+ endian_fixup (&key->default_value);
+ key->type = g_variant_get_type (key->default_value);
+ key->name = g_intern_string (name);
+
+ while (g_variant_iter_next (iter, "(y*)", &code, &data))
+ {
+ switch (code)
+ {
+ case 'l':
+ /* translation requested */
+ g_variant_get (data, "(y&s)", &key->lc_char, &key->unparsed);
+ break;
+
+ case 'e':
+ /* enumerated types... */
+ key->is_enum = TRUE;
+ goto choice;
+
+ case 'f':
+ /* flags... */
+ key->is_flags = TRUE;
+ goto choice;
+
+ choice: case 'c':
+ /* ..., choices, aliases */
+ key->strinfo = g_variant_get_fixed_array (data, &key->strinfo_length, sizeof (guint32));
+ break;
+
+ case 'r':
+ g_variant_get (data, "(**)", &key->minimum, &key->maximum);
+ endian_fixup (&key->minimum);
+ endian_fixup (&key->maximum);
+ break;
+
+ default:
+ g_warning ("unknown schema extension '%c'", code);
+ break;
+ }
+
+ g_variant_unref (data);
+ }
+
+ g_variant_iter_free (iter);
+}
+
+void
+g_settings_schema_key_clear (GSettingsSchemaKey *key)
+{
+ if (key->minimum)
+ g_variant_unref (key->minimum);
+
+ if (key->maximum)
+ g_variant_unref (key->maximum);
+
+ g_variant_unref (key->default_value);
+
+ g_settings_schema_unref (key->schema);
+}
+
+gboolean
+g_settings_schema_key_type_check (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ return g_variant_is_of_type (value, key->type);
+}
+
+GVariant *
+g_settings_schema_key_range_fixup (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ const gchar *target;
+
+ if (g_settings_schema_key_range_check (key, value))
+ return g_variant_ref (value);
+
+ if (key->strinfo == NULL)
+ return NULL;
+
+ if (g_variant_is_container (value))
+ {
+ GVariantBuilder builder;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_iter_init (&iter, value);
+ g_variant_builder_init (&builder, g_variant_get_type (value));
+
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ GVariant *fixed;
+
+ fixed = g_settings_schema_key_range_fixup (key, child);
+ g_variant_unref (child);
+
+ if (fixed == NULL)
+ {
+ 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 (key->strinfo, key->strinfo_length,
+ g_variant_get_string (value, NULL));
+ return target ? g_variant_ref_sink (g_variant_new_string (target)) : NULL;
+}
+
+GVariant *
+g_settings_schema_key_get_translated_default (GSettingsSchemaKey *key)
+{
+ const gchar *translated;
+ GError *error = NULL;
+ const gchar *domain;
+ GVariant *value;
+
+ domain = g_settings_schema_get_gettext_domain (key->schema);
+
+ if (key->lc_char == '\0')
+ /* translation not requested for this key */
+ return NULL;
+
+ if (key->lc_char == 't')
+ translated = g_dcgettext (domain, key->unparsed, LC_TIME);
+ else
+ translated = g_dgettext (domain, key->unparsed);
+
+ if (translated == key->unparsed)
+ /* the default value was not translated */
+ return NULL;
+
+ /* try to parse the translation of the unparsed default */
+ value = g_variant_parse (key->type, translated, NULL, NULL, &error);
+
+ if (value == NULL)
+ {
+ g_warning ("Failed to parse translated string '%s' for "
+ "key '%s' in schema '%s': %s", translated, key->name,
+ g_settings_schema_get_id (key->schema), error->message);
+ g_warning ("Using untranslated default instead.");
+ g_error_free (error);
+ }
+
+ else if (!g_settings_schema_key_range_check (key, value))
+ {
+ g_warning ("Translated default '%s' for key '%s' in schema '%s' "
+ "is outside of valid range", key->unparsed, key->name,
+ g_settings_schema_get_id (key->schema));
+ g_variant_unref (value);
+ value = NULL;
+ }
+
+ return value;
+}
+
+gint
+g_settings_schema_key_to_enum (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ gboolean it_worked;
+ guint result;
+
+ it_worked = strinfo_enum_from_string (key->strinfo, key->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;
+}
+
+GVariant *
+g_settings_schema_key_from_enum (GSettingsSchemaKey *key,
+ gint value)
+{
+ const gchar *string;
+
+ string = strinfo_string_from_enum (key->strinfo, key->strinfo_length, value);
+
+ if (string == NULL)
+ return NULL;
+
+ return g_variant_new_string (string);
+}
+
+guint
+g_settings_schema_key_to_flags (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ GVariantIter iter;
+ const gchar *flag;
+ guint result;
+
+ result = 0;
+ g_variant_iter_init (&iter, value);
+ while (g_variant_iter_next (&iter, "&s", &flag))
+ {
+ gboolean it_worked;
+ guint flag_value;
+
+ it_worked = strinfo_enum_from_string (key->strinfo, key->strinfo_length, flag, &flag_value);
+ /* as in g_settings_to_enum() */
+ g_assert (it_worked);
+
+ result |= flag_value;
+ }
+
+ return result;
+}
+
+GVariant *
+g_settings_schema_key_from_flags (GSettingsSchemaKey *key,
+ guint value)
+{
+ GVariantBuilder builder;
+ gint i;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (i = 0; i < 32; i++)
+ if (value & (1u << i))
+ {
+ const gchar *string;
+
+ string = strinfo_string_from_enum (key->strinfo, key->strinfo_length, 1u << i);
+
+ if (string == NULL)
+ {
+ g_variant_builder_clear (&builder);
+ return NULL;
+ }
+
+ g_variant_builder_add (&builder, "s", string);
+ }
+
+ return g_variant_builder_end (&builder);
+}
+
+G_DEFINE_BOXED_TYPE (GSettingsSchemaKey, g_settings_schema_key, g_settings_schema_key_ref, g_settings_schema_key_unref)
+
+/**
+ * g_settings_schema_key_ref:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Increase the reference count of @key, returning a new reference.
+ *
+ * Returns: a new reference to @key
+ *
+ * Since: 2.40
+ **/
+GSettingsSchemaKey *
+g_settings_schema_key_ref (GSettingsSchemaKey *key)
+{
+ g_return_val_if_fail (key != NULL, NULL);
+
+ g_atomic_int_inc (&key->ref_count);
+
+ return key;
+}
+
+/**
+ * g_settings_schema_key_unref:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Decrease the reference count of @key, possibly freeing it.
+ *
+ * Since: 2.40
+ **/
+void
+g_settings_schema_key_unref (GSettingsSchemaKey *key)
+{
+ g_return_if_fail (key != NULL);
+
+ if (g_atomic_int_dec_and_test (&key->ref_count))
+ {
+ g_settings_schema_key_clear (key);
+
+ g_slice_free (GSettingsSchemaKey, key);
+ }
+}
+
+/**
+ * g_settings_schema_get_key:
+ * @schema: a #GSettingsSchema
+ * @name: the name of a key
+ *
+ * Gets the key named @name from @schema.
+ *
+ * It is a programmer error to request a key that does not exist. See
+ * g_settings_schema_list_keys().
+ *
+ * Returns: (transfer full): the #GSettingsSchemaKey for @name
+ *
+ * Since: 2.40
+ **/
+GSettingsSchemaKey *
+g_settings_schema_get_key (GSettingsSchema *schema,
+ const gchar *name)
+{
+ GSettingsSchemaKey *key;
+
+ g_return_val_if_fail (schema != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ key = g_slice_new (GSettingsSchemaKey);
+ g_settings_schema_key_init (key, schema, name);
+ key->ref_count = 1;
+
+ return key;
+}
+
+/**
+ * g_settings_schema_key_get_summary:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Gets the summary for @key.
+ *
+ * If no summary has been provided in the schema for @key, returns
+ * %NULL.
+ *
+ * The summary is a short description of the purpose of the key; usually
+ * one short sentence. Summaries can be translated and the value
+ * returned from this function is is the current locale.
+ *
+ * This function is slow. The summary and description information for
+ * the schemas is not stored in the compiled schema database so this
+ * function has to parse all of the source XML files in the schema
+ * directory.
+ *
+ * Returns: the summary for @key, or %NULL
+ *
+ * Since: 2.34
+ **/
+const gchar *
+g_settings_schema_key_get_summary (GSettingsSchemaKey *key)
+{
+ GHashTable **text_tables;
+ GHashTable *summaries;
+
+ text_tables = g_settings_schema_source_get_text_tables (key->schema->source);
+ summaries = g_hash_table_lookup (text_tables[0], key->schema->id);
+
+ return summaries ? g_hash_table_lookup (summaries, key->name) : NULL;
+}
+
+/**
+ * g_settings_schema_key_get_description:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Gets the description for @key.
+ *
+ * If no description has been provided in the schema for @key, returns
+ * %NULL.
+ *
+ * The description can be one sentence to several paragraphs in length.
+ * Paragraphs are delimited with a double newline. Descriptions can be
+ * translated and the value returned from this function is is the
+ * current locale.
+ *
+ * This function is slow. The summary and description information for
+ * the schemas is not stored in the compiled schema database so this
+ * function has to parse all of the source XML files in the schema
+ * directory.
+ *
+ * Returns: the description for @key, or %NULL
+ *
+ * Since: 2.34
+ **/
+const gchar *
+g_settings_schema_key_get_description (GSettingsSchemaKey *key)
+{
+ GHashTable **text_tables;
+ GHashTable *descriptions;
+
+ text_tables = g_settings_schema_source_get_text_tables (key->schema->source);
+ descriptions = g_hash_table_lookup (text_tables[1], key->schema->id);
+
+ return descriptions ? g_hash_table_lookup (descriptions, key->name) : NULL;
+}
+
+/**
+ * g_settings_schema_key_get_value_type:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Gets the #GVariantType of @key.
+ *
+ * Returns: (transfer none): the type of @key
+ *
+ * Since: 2.40
+ **/
+const GVariantType *
+g_settings_schema_key_get_value_type (GSettingsSchemaKey *key)
+{
+ g_return_val_if_fail (key, NULL);
+
+ return key->type;
+}
+
+/**
+ * g_settings_schema_key_get_default_value:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Gets the default value for @key.
+ *
+ * Note that this is the default value according to the schema. System
+ * administrator defaults and lockdown are not visible via this API.
+ *
+ * Returns: (transfer full): the default value for the key
+ *
+ * Since: 2.40
+ **/
+GVariant *
+g_settings_schema_key_get_default_value (GSettingsSchemaKey *key)
+{
+ GVariant *value;
+
+ g_return_val_if_fail (key, NULL);
+
+ value = g_settings_schema_key_get_translated_default (key);
+
+ if (!value)
+ value = g_variant_ref (key->default_value);
+
+ return value;
+}
+
+/**
+ * g_settings_schema_key_get_range:
+ * @key: a #GSettingsSchemaKey
+ *
+ * Queries the range of a key.
+ *
+ * This function will return a #GVariant that fully describes the range
+ * of values that are valid for @key.
+ *
+ * The type of #GVariant returned is `(sv)`. The string describes
+ * the type of range restriction in effect. The type and meaning of
+ * the value contained in the variant depends on the string.
+ *
+ * If the string is `'type'` then the variant contains an empty array.
+ * The element type of that empty array is the expected type of value
+ * and all values of that type are valid.
+ *
+ * If the string is `'enum'` then the variant contains an array
+ * enumerating the possible values. Each item in the array is
+ * a possible valid value and no other values are valid.
+ *
+ * If the string is `'flags'` then the variant contains an array. Each
+ * item in the array is a value that may appear zero or one times in an
+ * array to be used as the value for this key. For example, if the
+ * variant contained the array `['x', 'y']` then the valid values for
+ * the key would be `[]`, `['x']`, `['y']`, `['x', 'y']` and
+ * `['y', 'x']`.
+ *
+ * Finally, if the string is `'range'` then the variant contains a pair
+ * of like-typed values -- the minimum and maximum permissible values
+ * for this key.
+ *
+ * This information should not be used by normal programs. It is
+ * considered to be a hint for introspection purposes. Normal programs
+ * should already know what is permitted by their own schema. The
+ * format may change in any way in the future -- but particularly, new
+ * forms may be added to the possibilities described above.
+ *
+ * You should free the returned value with g_variant_unref() when it is
+ * no longer needed.
+ *
+ * Returns: (transfer full): a #GVariant describing the range
+ *
+ * Since: 2.40
+ **/
+GVariant *
+g_settings_schema_key_get_range (GSettingsSchemaKey *key)
+{
+ const gchar *type;
+ GVariant *range;
+
+ if (key->minimum)
+ {
+ range = g_variant_new ("(**)", key->minimum, key->maximum);
+ type = "range";
+ }
+ else if (key->strinfo)
+ {
+ range = strinfo_enumerate (key->strinfo, key->strinfo_length);
+ type = key->is_flags ? "flags" : "enum";
+ }
+ else
+ {
+ range = g_variant_new_array (key->type, NULL, 0);
+ type = "type";
+ }
+
+ return g_variant_ref_sink (g_variant_new ("(sv)", type, range));
+}
+
+/**
+ * g_settings_schema_key_range_check:
+ * @key: a #GSettingsSchemaKey
+ * @value: the value to check
+ *
+ * Checks if the given @value is of the correct type and within the
+ * permitted range for @key.
+ *
+ * It is a programmer error if @value is not of the correct type -- you
+ * must check for this first.
+ *
+ * Returns: %TRUE if @value is valid for @key
+ *
+ * Since: 2.40
+ **/
+gboolean
+g_settings_schema_key_range_check (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ if (key->minimum == NULL && key->strinfo == NULL)
+ return TRUE;
+
+ if (g_variant_is_container (value))
+ {
+ gboolean ok = TRUE;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_iter_init (&iter, value);
+ while (ok && (child = g_variant_iter_next_value (&iter)))
+ {
+ ok = g_settings_schema_key_range_check (key, child);
+ g_variant_unref (child);
+ }
+
+ return ok;
+ }
+
+ if (key->minimum)
+ {
+ return g_variant_compare (key->minimum, value) <= 0 &&
+ g_variant_compare (value, key->maximum) <= 0;
+ }
+
+ return strinfo_is_string_valid (key->strinfo, key->strinfo_length,
+ g_variant_get_string (value, NULL));
+}