Bug 622124 - implement flags for GSettings

Add a <flags> tag to the schema file format and a flags='' attribute to
go along with.  Add some extra test cases for those.

Add new g_settings_{get,set}_flags() calls and support binding to
GParamSpecFlags properties.  Add test cases.
This commit is contained in:
Ryan Lortie
2010-07-01 18:58:56 -04:00
parent 69fe50c116
commit 5383c7110f
19 changed files with 594 additions and 52 deletions

View File

@@ -2168,6 +2168,8 @@ g_settings_get_strv
g_settings_set_strv g_settings_set_strv
g_settings_get_enum g_settings_get_enum
g_settings_set_enum g_settings_set_enum
g_settings_get_flags
g_settings_set_flags
<SUBSECTION MappedGet> <SUBSECTION MappedGet>
GSettingsGetMapping GSettingsGetMapping

View File

@@ -1477,6 +1477,8 @@ g_settings_get_boolean
g_settings_set_boolean g_settings_set_boolean
g_settings_get_enum g_settings_get_enum
g_settings_set_enum g_settings_set_enum
g_settings_get_flags
g_settings_set_flags
g_settings_sync g_settings_sync
g_settings_list_items g_settings_list_items
g_settings_get_mapped g_settings_get_mapped

View File

@@ -35,14 +35,32 @@
#include "strinfo.c" #include "strinfo.c"
/* Handling of <enum> {{{1 */ /* Handling of <enum> {{{1 */
typedef GString EnumState; typedef struct
{
GString *strinfo;
gboolean is_flags;
} EnumState;
static void static void
enum_state_free (gpointer data) enum_state_free (gpointer data)
{ {
EnumState *state = data; EnumState *state = data;
g_string_free (state, TRUE); g_string_free (state->strinfo, TRUE);
g_slice_free (EnumState, state);
}
EnumState *
enum_state_new (gboolean is_flags)
{
EnumState *state;
state = g_slice_new (EnumState);
state->strinfo = g_string_new (NULL);
state->is_flags = is_flags;
return state;
} }
static void static void
@@ -58,12 +76,14 @@ enum_state_add_value (EnumState *state,
{ {
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
"enum nick must be a minimum of 2 characters"); "nick must be a minimum of 2 characters");
return; return;
} }
value = g_ascii_strtoll (valuestr, &end, 0); value = g_ascii_strtoll (valuestr, &end, 0);
if (*end || value > G_MAXINT32 || value < G_MININT32) if (*end || state->is_flags ?
(value > G_MAXUINT32 || value < 0) :
(value > G_MAXINT32 || value < G_MININT32))
{ {
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
@@ -71,15 +91,38 @@ enum_state_add_value (EnumState *state,
return; return;
} }
if (strinfo_builder_contains (state, nick)) if (strinfo_builder_contains (state->strinfo, nick))
{ {
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
"<value nick='%s'> already specified", nick); "<value nick='%s'/> already specified", nick);
return; return;
} }
strinfo_builder_append_item (state, nick, value); if (strinfo_builder_contains_value (state->strinfo, value))
{
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
"value='%s' already specified", valuestr);
return;
}
if (state->is_flags && (value & (value - 1)))
{
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
"flags values must have at most 1 bit set");
return;
}
/* Since we reject exact duplicates of value='' and we only allow one
* bit to be set, it's not possible to have overlaps.
*
* If we loosen the one-bit-set restriction we need an overlap check.
*/
strinfo_builder_append_item (state->strinfo, nick, value);
} }
static void static void
@@ -91,7 +134,7 @@ enum_state_end (EnumState **state_ptr,
state = *state_ptr; state = *state_ptr;
*state_ptr = NULL; *state_ptr = NULL;
if (state->len == 0) if (state->strinfo->len == 0)
g_set_error_literal (error, g_set_error_literal (error,
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
"<enum> must contain at least one <value>"); "<enum> must contain at least one <value>");
@@ -116,6 +159,7 @@ typedef struct
GString *strinfo; GString *strinfo;
gboolean is_enum; gboolean is_enum;
gboolean is_flags;
GVariant *minimum; GVariant *minimum;
GVariant *maximum; GVariant *maximum;
@@ -131,16 +175,20 @@ typedef struct
static KeyState * static KeyState *
key_state_new (const gchar *type_string, key_state_new (const gchar *type_string,
const gchar *gettext_domain, const gchar *gettext_domain,
EnumState *enum_data) gboolean is_enum,
gboolean is_flags,
GString *strinfo)
{ {
KeyState *state; KeyState *state;
state = g_slice_new0 (KeyState); state = g_slice_new0 (KeyState);
state->type = g_variant_type_new (type_string); state->type = g_variant_type_new (type_string);
state->have_gettext_domain = gettext_domain != NULL; state->have_gettext_domain = gettext_domain != NULL;
state->is_enum = is_enum;
state->is_flags = is_flags;
if ((state->is_enum = (enum_data != NULL))) if (strinfo)
state->strinfo = g_string_new_len (enum_data->str, enum_data->len); state->strinfo = g_string_new_len (strinfo->str, strinfo->len);
else else
state->strinfo = g_string_new (NULL); state->strinfo = g_string_new (NULL);
@@ -159,6 +207,7 @@ key_state_override (KeyState *state,
copy->strinfo = g_string_new_len (state->strinfo->str, copy->strinfo = g_string_new_len (state->strinfo->str,
state->strinfo->len); state->strinfo->len);
copy->is_enum = state->is_enum; copy->is_enum = state->is_enum;
copy->is_flags = state->is_flags;
copy->is_override = TRUE; copy->is_override = TRUE;
if (state->minimum) if (state->minimum)
@@ -251,6 +300,12 @@ key_state_check_range (KeyState *state,
"<%s> is not a valid member of " "<%s> is not a valid member of "
"the specified enumerated type", tag); "the specified enumerated type", tag);
else if (state->is_flags)
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
"<%s> contains string not in the "
"specified flags type", tag);
else else
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
@@ -442,11 +497,11 @@ key_state_start_aliases (KeyState *state,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
"<aliases> already specified for this key"); "<aliases> already specified for this key");
if (!state->is_enum && !state->has_choices) if (!state->is_flags && !state->is_enum && !state->has_choices)
g_set_error_literal (error, G_MARKUP_ERROR, g_set_error_literal (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
"<aliases> can only be specified for keys with " "<aliases> can only be specified for keys with "
"enumerated types or after <choices>"); "enumerated or flags types or after <choices>");
} }
static void static void
@@ -582,6 +637,7 @@ key_state_serialise (KeyState *state)
state->strinfo = NULL; state->strinfo = NULL;
g_variant_builder_add (&builder, "(y@au)", g_variant_builder_add (&builder, "(y@au)",
state->is_flags ? 'f' :
state->is_enum ? 'e' : 'c', state->is_enum ? 'e' : 'c',
array); array);
} }
@@ -771,13 +827,15 @@ schema_state_add_child (SchemaState *state,
static KeyState * static KeyState *
schema_state_add_key (SchemaState *state, schema_state_add_key (SchemaState *state,
GHashTable *enum_table, GHashTable *enum_table,
GHashTable *flags_table,
const gchar *name, const gchar *name,
const gchar *type_string, const gchar *type_string,
const gchar *enum_type, const gchar *enum_type,
const gchar *flags_type,
GError **error) GError **error)
{ {
GString *enum_data;
SchemaState *node; SchemaState *node;
GString *strinfo;
KeyState *key; KeyState *key;
if (state->list_of) if (state->list_of)
@@ -820,29 +878,37 @@ schema_state_add_key (SchemaState *state,
} }
} }
if ((type_string == NULL) == (enum_type == NULL)) if ((type_string != NULL) + (enum_type != NULL) + (flags_type != NULL) != 1)
{ {
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE, G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"exactly one of 'type' or 'enum' must " "exactly one of 'type', 'enum' or 'flags' must "
"be specified as an attribute to <key>"); "be specified as an attribute to <key>");
return NULL; return NULL;
} }
if (enum_type != NULL) if (type_string == NULL) /* flags or enums was specified */
{ {
enum_data = g_hash_table_lookup (enum_table, enum_type); EnumState *enum_state;
if (enum_data == NULL) if (enum_type)
enum_state = g_hash_table_lookup (enum_table, enum_type);
else
enum_state = g_hash_table_lookup (flags_table, flags_type);
if (enum_state == NULL)
{ {
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
"<enum id='%s'> not (yet) defined.", enum_type); "<%s id='%s'> not (yet) defined.",
flags_type ? "flags" : "enum",
flags_type ? flags_type : enum_type);
return NULL; return NULL;
} }
g_assert (type_string == NULL); type_string = flags_type ? "as" : "s";
type_string = "s"; strinfo = enum_state->strinfo;
} }
else else
{ {
@@ -854,10 +920,11 @@ schema_state_add_key (SchemaState *state,
return NULL; return NULL;
} }
enum_data = NULL; strinfo = NULL;
} }
key = key_state_new (type_string, state->gettext_domain, enum_data); key = key_state_new (type_string, state->gettext_domain,
enum_type != NULL, flags_type != NULL, strinfo);
g_hash_table_insert (state->keys, g_strdup (name), key); g_hash_table_insert (state->keys, g_strdup (name), key);
return key; return key;
@@ -922,13 +989,14 @@ override_state_end (KeyState **key_state,
typedef struct typedef struct
{ {
GHashTable *schema_table; /* string -> SchemaState */ GHashTable *schema_table; /* string -> SchemaState */
GHashTable *enum_table; /* string -> GString */ GHashTable *flags_table; /* string -> EnumState */
GHashTable *enum_table; /* string -> EnumState */
gchar *schemalist_domain; /* the <schemalist> gettext domain */ gchar *schemalist_domain; /* the <schemalist> gettext domain */
SchemaState *schema_state; /* non-NULL when inside <schema> */ SchemaState *schema_state; /* non-NULL when inside <schema> */
KeyState *key_state; /* non-NULL when inside <key> */ KeyState *key_state; /* non-NULL when inside <key> */
GString *enum_state; /* non-NULL when inside <enum> */ EnumState *enum_state; /* non-NULL when inside <enum> */
GString *string; /* non-NULL when accepting text */ GString *string; /* non-NULL when accepting text */
} ParseState; } ParseState;
@@ -1047,18 +1115,22 @@ parse_state_start_schema (ParseState *state,
static void static void
parse_state_start_enum (ParseState *state, parse_state_start_enum (ParseState *state,
const gchar *id, const gchar *id,
gboolean is_flags,
GError **error) GError **error)
{ {
if (g_hash_table_lookup (state->enum_table, id)) GHashTable *table = is_flags ? state->flags_table : state->enum_table;
if (g_hash_table_lookup (table, id))
{ {
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
"<enum id='%s'> already specified", id); "<%s id='%s'> already specified",
is_flags ? "flags" : "enum", id);
return; return;
} }
state->enum_state = g_string_new (NULL); state->enum_state = enum_state_new (is_flags);
g_hash_table_insert (state->enum_table, g_strdup (id), state->enum_state); g_hash_table_insert (table, g_strdup (id), state->enum_state);
} }
/* GMarkup Parser Functions {{{1 */ /* GMarkup Parser Functions {{{1 */
@@ -1117,12 +1189,19 @@ start_element (GMarkupParseContext *context,
return; return;
} }
else if (strcmp (element_name, "enum") == 0 || else if (strcmp (element_name, "enum") == 0)
strcmp (element_name, "flags") == 0)
{ {
const gchar *id; const gchar *id;
if (COLLECT (STRING, "id", &id)) if (COLLECT (STRING, "id", &id))
parse_state_start_enum (state, id, error); parse_state_start_enum (state, id, FALSE, error);
return;
}
else if (strcmp (element_name, "flags") == 0)
{
const gchar *id;
if (COLLECT (STRING, "id", &id))
parse_state_start_enum (state, id, TRUE, error);
return; return;
} }
} }
@@ -1133,16 +1212,19 @@ start_element (GMarkupParseContext *context,
{ {
if (strcmp (element_name, "key") == 0) if (strcmp (element_name, "key") == 0)
{ {
const gchar *name, *type_string, *enum_type; const gchar *name, *type_string, *enum_type, *flags_type;
if (COLLECT (STRING, "name", &name, if (COLLECT (STRING, "name", &name,
OPTIONAL | STRING, "type", &type_string, OPTIONAL | STRING, "type", &type_string,
OPTIONAL | STRING, "enum", &enum_type)) OPTIONAL | STRING, "enum", &enum_type,
OPTIONAL | STRING, "flags", &flags_type))
state->key_state = schema_state_add_key (state->schema_state, state->key_state = schema_state_add_key (state->schema_state,
state->enum_table, state->enum_table,
state->flags_table,
name, type_string, name, type_string,
enum_type, error); enum_type, flags_type,
error);
return; return;
} }
else if (strcmp (element_name, "child") == 0) else if (strcmp (element_name, "child") == 0)
@@ -1481,6 +1563,9 @@ parse_gschema_files (gchar **files,
state.enum_table = g_hash_table_new_full (g_str_hash, g_str_equal, state.enum_table = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, enum_state_free); g_free, enum_state_free);
state.flags_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, state.schema_table = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, schema_state_free); g_free, schema_state_free);

View File

@@ -392,6 +392,34 @@ g_settings_set_mapping (const GValue *value,
return NULL; return NULL;
} }
else if (G_VALUE_HOLDS_FLAGS (value))
{
GVariantBuilder builder;
GFlagsValue *flagsval;
GFlagsClass *fclass;
guint flags;
fclass = g_type_class_peek (G_VALUE_TYPE (value));
flags = g_value_get_flags (value);
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
while (flags)
{
flagsval = g_flags_get_first_value (fclass, flags);
if (flagsval == NULL)
{
g_variant_builder_clear (&builder);
return NULL;
}
g_variant_builder_add (&builder, "s", flagsval->value_nick);
flags &= ~flagsval->value;
}
return g_variant_builder_end (&builder);
}
type_string = g_variant_type_dup_string (expected_type); type_string = g_variant_type_dup_string (expected_type);
g_critical ("No GSettings bind handler for type \"%s\".", type_string); g_critical ("No GSettings bind handler for type \"%s\".", type_string);
g_free (type_string); g_free (type_string);
@@ -447,8 +475,7 @@ g_settings_get_mapping (GValue *value,
return TRUE; return TRUE;
} }
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING) && else if (G_VALUE_HOLDS_ENUM (value))
G_VALUE_HOLDS_ENUM (value))
{ {
GEnumClass *eclass; GEnumClass *eclass;
GEnumValue *evalue; GEnumValue *evalue;
@@ -469,6 +496,38 @@ g_settings_get_mapping (GValue *value,
return FALSE; return FALSE;
} }
} }
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("as")))
{
if (G_VALUE_HOLDS_FLAGS (value))
{
GFlagsClass *fclass;
GFlagsValue *fvalue;
const gchar *nick;
GVariantIter iter;
guint flags = 0;
fclass = g_type_class_peek (G_VALUE_TYPE (value));
g_variant_iter_init (&iter, variant);
while (g_variant_iter_next (&iter, "&s", &nick))
{
fvalue = g_flags_get_value_by_nick (fclass, nick);
if (fvalue)
flags |= fvalue->value;
else
{
g_warning ("Unable to lookup flags nick '%s' via GType\n",
nick);
return FALSE;
}
}
g_value_set_flags (value, flags);
return TRUE;
}
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) else if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay")))
{ {
g_value_set_string (value, g_variant_get_byte_array (variant, NULL)); g_value_set_string (value, g_variant_get_byte_array (variant, NULL));
@@ -512,6 +571,8 @@ g_settings_mapping_is_compatible (GType gvalue_type,
g_variant_type_equal (variant_type, G_VARIANT_TYPE_SIGNATURE)); g_variant_type_equal (variant_type, G_VARIANT_TYPE_SIGNATURE));
else if (G_TYPE_IS_ENUM (gvalue_type)) else if (G_TYPE_IS_ENUM (gvalue_type))
ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE_STRING); ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE_STRING);
else if (G_TYPE_IS_FLAGS (gvalue_type))
ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE ("as"));
return ok; return ok;
} }

View File

@@ -831,7 +831,7 @@ g_settings_new_with_backend_and_path (const gchar *schema,
NULL); NULL);
} }
/* Internal read/write utilities, enum conversion, validation {{{1 */ /* Internal read/write utilities, enum/flags conversion, validation {{{1 */
typedef struct typedef struct
{ {
GSettings *settings; GSettings *settings;
@@ -839,7 +839,9 @@ typedef struct
GSettingsSchema *schema; GSettingsSchema *schema;
gboolean is_enum; guint is_flags : 1;
guint is_enum : 1;
const guint32 *strinfo; const guint32 *strinfo;
gsize strinfo_length; gsize strinfo_length;
@@ -879,9 +881,16 @@ g_settings_get_key_info (GSettingsKeyInfo *info,
break; break;
case 'e': case 'e':
/* enumerated types, ... */ /* enumerated types... */
info->is_enum = TRUE; info->is_enum = TRUE;
case 'c': goto choice;
case 'f':
/* flags... */
info->is_flags = TRUE;
goto choice;
choice: case 'c':
/* ..., choices, aliases */ /* ..., choices, aliases */
info->strinfo = g_variant_get_fixed_array (data, info->strinfo = g_variant_get_fixed_array (data,
&info->strinfo_length, &info->strinfo_length,
@@ -956,7 +965,7 @@ g_settings_range_check (GSettingsKeyInfo *info,
g_variant_iter_init (&iter, value); g_variant_iter_init (&iter, value);
while (ok && (child = g_variant_iter_next_value (&iter))) while (ok && (child = g_variant_iter_next_value (&iter)))
{ {
ok = g_settings_range_check (info, value); ok = g_settings_range_check (info, child);
g_variant_unref (child); g_variant_unref (child);
} }
@@ -992,6 +1001,7 @@ g_settings_range_fixup (GSettingsKeyInfo *info,
GVariantIter iter; GVariantIter iter;
GVariant *child; GVariant *child;
g_variant_iter_init (&iter, value);
g_variant_builder_init (&builder, g_variant_get_type (value)); g_variant_builder_init (&builder, g_variant_get_type (value));
while ((child = g_variant_iter_next_value (&iter))) while ((child = g_variant_iter_next_value (&iter)))
@@ -1123,7 +1133,64 @@ g_settings_from_enum (GSettingsKeyInfo *info,
if (string == NULL) if (string == NULL)
return NULL; return NULL;
return g_variant_ref_sink (g_variant_new_string (string)); return g_variant_new_string (string);
}
static guint
g_settings_to_flags (GSettingsKeyInfo *info,
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 (info->strinfo,
info->strinfo_length,
flag, &flag_value);
/* as in g_settings_to_enum() */
g_assert (it_worked);
result |= flag_value;
}
return result;
}
static GVariant *
g_settings_from_flags (GSettingsKeyInfo *info,
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 (info->strinfo,
info->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);
} }
/* Public Get/Set API {{{1 (get, get_value, set, set_value, get_mapped) */ /* Public Get/Set API {{{1 (get, get_value, set, set_value, get_mapped) */
@@ -1235,7 +1302,7 @@ g_settings_get_enum (GSettings *settings,
* schema for @settings or is not marked as an enumerated type, or for * schema for @settings or is not marked as an enumerated type, or for
* @value not to be a valid value for the named type. * @value not to be a valid value for the named type.
* *
* After performing the write, accessing @key directly * After performing the write, accessing @key directly with
* g_settings_get_string() will return the 'nick' associated with * g_settings_get_string() will return the 'nick' associated with
* @value. * @value.
**/ **/
@@ -1271,7 +1338,118 @@ g_settings_set_enum (GSettings *settings,
success = g_settings_write_to_backend (&info, variant); success = g_settings_write_to_backend (&info, variant);
g_settings_free_key_info (&info); g_settings_free_key_info (&info);
g_variant_unref (variant);
return success;
}
/**
* g_settings_get_flags:
* @settings: a #GSettings object
* @key: the key to get the value for
* @returns: the flags value
*
* Gets the value that is stored in @settings for @key and converts it
* to the flags value that it represents.
*
* In order to use this function the type of the value must be an array
* of strings and it must be marked in the schema file as an flags type.
*
* It is a programmer error to give a @key that isn't contained in the
* schema for @settings or is not marked as a flags type.
*
* If the value stored in the configuration database is not a valid
* value for the flags type then this function will return the default
* value.
*
* Since: 2.26
**/
guint
g_settings_get_flags (GSettings *settings,
const gchar *key)
{
GSettingsKeyInfo info;
GVariant *value;
guint 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_flags)
{
g_critical ("g_settings_get_flags() called on key `%s' which is not "
"associated with a flags 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_flags (&info, value);
g_settings_free_key_info (&info);
g_variant_unref (value);
return result;
}
/**
* g_settings_set_flags:
* @settings: a #GSettings object
* @key: a key, within @settings
* @value: a flags value
* @returns: %TRUE, if the set succeeds
*
* Looks up the flags type nicks for the bits specified by @value, puts
* them in an array of strings and writes the array to @key, withing
* @settings.
*
* It is a programmer error to give a @key that isn't contained in the
* schema for @settings or is not marked as a flags type, or for @value
* to contain any bits that are not value for the named type.
*
* After performing the write, accessing @key directly with
* g_settings_get_strv() will return an array of 'nicks'; one for each
* bit in @value.
**/
gboolean
g_settings_set_flags (GSettings *settings,
const gchar *key,
guint 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_flags)
{
g_critical ("g_settings_set_flags() called on key `%s' which is not "
"associated with a flags type", info.key);
return FALSE;
}
if (!(variant = g_settings_from_flags (&info, value)))
{
g_critical ("g_settings_set_flags(): invalid flags value 0x%08x "
"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);
return success; return success;
} }

View File

@@ -126,6 +126,11 @@ gint g_settings_get_enum (GSettin
gboolean g_settings_set_enum (GSettings *settings, gboolean g_settings_set_enum (GSettings *settings,
const gchar *key, const gchar *key,
gint value); gint value);
guint g_settings_get_flags (GSettings *settings,
const gchar *key);
gboolean g_settings_set_flags (GSettings *settings,
const gchar *key,
guint value);
GSettings * g_settings_get_child (GSettings *settings, GSettings * g_settings_get_child (GSettings *settings,
const gchar *name); const gchar *name);

View File

@@ -309,3 +309,11 @@ strinfo_builder_contains (GString *builder,
strinfo_find_string ((const guint32 *) builder->str, strinfo_find_string ((const guint32 *) builder->str,
builder->len / 4, string, TRUE) != -1; builder->len / 4, string, TRUE) != -1;
} }
G_GNUC_UNUSED static gboolean
strinfo_builder_contains_value (GString *builder,
guint value)
{
return strinfo_string_from_enum ((const guint32 *) builder->str,
builder->len / 4, value) != NULL;
}

View File

@@ -300,8 +300,15 @@ schema_tests = \
schema-tests/enum-with-choice.gschema.xml \ schema-tests/enum-with-choice.gschema.xml \
schema-tests/enum-with-invalid-alias.gschema.xml \ schema-tests/enum-with-invalid-alias.gschema.xml \
schema-tests/enum-with-repeated-alias.gschema.xml \ schema-tests/enum-with-repeated-alias.gschema.xml \
schema-tests/enum-with-repeated-nick.gschema.xml \
schema-tests/enum-with-repeated-value.gschema.xml \
schema-tests/enum-with-shadow-alias.gschema.xml \ schema-tests/enum-with-shadow-alias.gschema.xml \
schema-tests/enum.gschema.xml \ schema-tests/enum.gschema.xml \
schema-tests/flags-aliased-default.gschema.xml \
schema-tests/flags-bad-default.gschema.xml \
schema-tests/flags-more-than-one-bit.gschema.xml \
schema-tests/flags-with-enum-attr.gschema.xml \
schema-tests/flags-with-enum-tag.gschema.xml \
schema-tests/extend-and-shadow-indirect.gschema.xml \ schema-tests/extend-and-shadow-indirect.gschema.xml \
schema-tests/extend-and-shadow.gschema.xml \ schema-tests/extend-and-shadow.gschema.xml \
schema-tests/extend-missing.gschema.xml \ schema-tests/extend-missing.gschema.xml \

View File

@@ -71,6 +71,8 @@ static const SchemaTest tests[] = {
{ "enum-with-aliases", NULL, NULL }, { "enum-with-aliases", NULL, NULL },
{ "enum-with-invalid-alias", NULL, "*'banger' is not in enumerated type*" }, { "enum-with-invalid-alias", NULL, "*'banger' is not in enumerated type*" },
{ "enum-with-repeated-alias", NULL, "*<alias value='sausages'/> already specified*" }, { "enum-with-repeated-alias", NULL, "*<alias value='sausages'/> already specified*" },
{ "enum-with-repeated-nick", NULL, "*<value nick='spam'/> already specified*" },
{ "enum-with-repeated-value", NULL, "*value='1' already specified*" },
{ "enum-with-chained-alias", NULL, "*'sausages' is not in enumerated type*" }, { "enum-with-chained-alias", NULL, "*'sausages' is not in enumerated type*" },
{ "enum-with-shadow-alias", NULL, "*'mash' is already a member of the enum*" }, { "enum-with-shadow-alias", NULL, "*'mash' is already a member of the enum*" },
{ "enum-with-choice", NULL, "*<choices> can not be specified*" }, { "enum-with-choice", NULL, "*<choices> can not be specified*" },
@@ -80,7 +82,7 @@ static const SchemaTest tests[] = {
{ "bad-choice", NULL, "*<default> contains string not in <choices>*" }, { "bad-choice", NULL, "*<default> contains string not in <choices>*" },
{ "choice-bad", NULL, "*<default> contains string not in <choices>*" }, { "choice-bad", NULL, "*<default> contains string not in <choices>*" },
{ "choice-badtype", NULL, "*<choices> not allowed for keys of type 'i'*" }, { "choice-badtype", NULL, "*<choices> not allowed for keys of type 'i'*" },
{ "bare-alias", NULL, "*enumerated types or after <choices>*" }, { "bare-alias", NULL, "*enumerated or flags types or after <choices>*" },
{ "choice-alias", NULL, NULL }, { "choice-alias", NULL, NULL },
{ "default-in-aliases", NULL, "*<default> contains string not in <choices>*" }, { "default-in-aliases", NULL, "*<default> contains string not in <choices>*" },
{ "choice-invalid-alias", NULL, "*'befor' is not in <choices>*" }, { "choice-invalid-alias", NULL, "*'befor' is not in <choices>*" },
@@ -109,7 +111,12 @@ static const SchemaTest tests[] = {
{ "override-range-error", NULL, "*<override> is not contained in the specified range*"}, { "override-range-error", NULL, "*<override> is not contained in the specified range*"},
{ "override-then-key", NULL, "*shadows <key name='foo'> in <schema id='base'>*" }, { "override-then-key", NULL, "*shadows <key name='foo'> in <schema id='base'>*" },
{ "override-twice", NULL, "*<override name='foo'> already specified*" }, { "override-twice", NULL, "*<override name='foo'> already specified*" },
{ "override-type-error", NULL, "*invalid character in number*" } { "override-type-error", NULL, "*invalid character in number*" },
{ "flags-aliased-default", NULL, "*<default> * not in the specified flags type*" },
{ "flags-bad-default", NULL, "*<default> * not in the specified flags type*" },
{ "flags-more-than-one-bit", NULL, "*flags values must have at most 1 bit set*" },
{ "flags-with-enum-attr", NULL, "*<enum id='flags'> not (yet) defined*" },
{ "flags-with-enum-tag", NULL, "*<flags id='flags'> not (yet) defined*" }
}; };
int int

View File

@@ -1249,8 +1249,6 @@ test_strinfo (void)
g_assert (!strinfo_is_string_valid (strinfo, length, "quux")); g_assert (!strinfo_is_string_valid (strinfo, length, "quux"));
} }
static void static void
test_enums (void) test_enums (void)
{ {
@@ -1276,6 +1274,11 @@ test_enums (void)
g_settings_set_string (settings, "test", "qux"); g_settings_set_string (settings, "test", "qux");
g_test_trap_assert_failed (); g_test_trap_assert_failed ();
g_test_trap_assert_stderr ("*g_settings_range_check*"); g_test_trap_assert_stderr ("*g_settings_range_check*");
if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
g_settings_get_flags (settings, "test");
g_test_trap_assert_failed ();
g_test_trap_assert_stderr ("*not associated with a flags*");
} }
str = g_settings_get_string (settings, "test"); str = g_settings_get_string (settings, "test");
@@ -1303,6 +1306,78 @@ test_enums (void)
g_assert_cmpint (g_settings_get_enum (settings, "test"), ==, TEST_ENUM_QUUX); g_assert_cmpint (g_settings_get_enum (settings, "test"), ==, TEST_ENUM_QUUX);
} }
static void
test_flags (void)
{
GSettings *settings, *direct;
gchar **strv;
gchar *str;
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_flags (direct, "test");
g_test_trap_assert_failed ();
g_test_trap_assert_stderr ("*not associated with a flags*");
if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
g_settings_set_flags (settings, "f-test", 0x42);
g_test_trap_assert_failed ();
g_test_trap_assert_stderr ("*invalid flags value 0x00000042*");
if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
g_settings_set_strv (settings, "f-test",
(const gchar **) g_strsplit ("rock", ",", 0));
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_get_enum (settings, "f-test");
g_test_trap_assert_failed ();
g_test_trap_assert_stderr ("*not associated with an enum*");
}
strv = g_settings_get_strv (settings, "f-test");
str = g_strjoinv (",", strv);
g_assert_cmpstr (str, ==, "");
g_strfreev (strv);
g_free (str);
g_settings_set_flags (settings, "f-test",
TEST_FLAGS_WALKING | TEST_FLAGS_TALKING);
strv = g_settings_get_strv (settings, "f-test");
str = g_strjoinv (",", strv);
g_assert_cmpstr (str, ==, "talking,walking");
g_strfreev (strv);
g_free (str);
g_assert_cmpint (g_settings_get_flags (settings, "f-test"), ==,
TEST_FLAGS_WALKING | TEST_FLAGS_TALKING);
strv = g_strsplit ("speaking,laughing", ",", 0);
g_settings_set_strv (direct, "f-test", (const gchar **) strv);
g_strfreev (strv);
strv = g_settings_get_strv (direct, "f-test");
str = g_strjoinv (",", strv);
g_assert_cmpstr (str, ==, "speaking,laughing");
g_strfreev (strv);
g_free (str);
strv = g_settings_get_strv (settings, "f-test");
str = g_strjoinv (",", strv);
g_assert_cmpstr (str, ==, "talking,laughing");
g_strfreev (strv);
g_free (str);
g_assert_cmpint (g_settings_get_flags (settings, "f-test"), ==,
TEST_FLAGS_TALKING | TEST_FLAGS_LAUGHING);
}
static void static void
test_range (void) test_range (void)
{ {
@@ -1408,6 +1483,7 @@ main (int argc, char *argv[])
g_test_add_func ("/gsettings/child-schema", test_child_schema); g_test_add_func ("/gsettings/child-schema", test_child_schema);
g_test_add_func ("/gsettings/strinfo", test_strinfo); g_test_add_func ("/gsettings/strinfo", test_strinfo);
g_test_add_func ("/gsettings/enums", test_enums); g_test_add_func ("/gsettings/enums", test_enums);
g_test_add_func ("/gsettings/flags", test_flags);
g_test_add_func ("/gsettings/range", test_range); g_test_add_func ("/gsettings/range", test_range);
result = g_test_run (); result = g_test_run ();

View File

@@ -99,9 +99,18 @@
<alias value='qux' target='quux'/> <alias value='qux' target='quux'/>
</aliases> </aliases>
</key> </key>
<key name='f-test' flags='org.gtk.test.TestFlags'>
<default>[]</default>
<aliases>
<alias value='speaking' target='talking'/>
</aliases>
</key>
</schema> </schema>
<schema id='org.gtk.test.enums.direct' path='/tests/enums/'> <schema id='org.gtk.test.enums.direct' path='/tests/enums/'>
<key name='f-test' type='as'>
<default>[]</default>
</key>
<key name='test' type='s'> <key name='test' type='s'>
<default>'bar'</default> <default>'bar'</default>
</key> </key>

View File

@@ -0,0 +1,10 @@
<schemalist>
<enum id='org.gtk.test.MyEnum'>
<value nick='nospam' value='0'/>
<value nick='spam' value='1'/>
<value nick='ham' value='2'/>
<value nick='eggs' value='3'/>
<value nick='bangers' value='4'/>
<value nick='spam' value='5'/>
</enum>
</schemalist>

View File

@@ -0,0 +1,10 @@
<schemalist>
<enum id='org.gtk.test.MyEnum'>
<value nick='nospam' value='0'/>
<value nick='spam' value='1'/>
<value nick='ham' value='2'/>
<value nick='eggs' value='3'/>
<value nick='bangers' value='4'/>
<value nick='mash' value='1'/>
</enum>
</schemalist>

View File

@@ -0,0 +1,19 @@
<schemalist>
<flags id='flags'>
<value nick='none' value='0'/>
<value nick='mourning' value='1'/>
<value nick='laughing' value='2'/>
<value nick='talking' value='4'/>
<value nick='walking' value='8'/>
</flags>
<schema id='xyz'>
<key name='abc' flags='flags'>
<aliases>
<alias value='speaking' target='talking'/>
</aliases>
<default>['speaking']</default>
</key>
</schema>
</schemalist>

View File

@@ -0,0 +1,16 @@
<schemalist>
<flags id='flags'>
<value nick='none' value='0'/>
<value nick='mourning' value='1'/>
<value nick='laughing' value='2'/>
<value nick='talking' value='4'/>
<value nick='walking' value='8'/>
</flags>
<schema id='xyz'>
<key name='abc' flags='flags'>
<default>['speaking']</default>
</key>
</schema>
</schemalist>

View File

@@ -0,0 +1,10 @@
<schemalist>
<flags id='flags'>
<value nick='none' value='0'/>
<value nick='mourning' value='1'/>
<value nick='laughing' value='2'/>
<value nick='talking' value='4'/>
<value nick='walking' value='24'/>
</flags>
</schemalist>

View File

@@ -0,0 +1,14 @@
<schemalist>
<flags id='flags'>
<value nick='none' value='0'/>
<value nick='mourning' value='1'/>
<value nick='laughing' value='2'/>
<value nick='talking' value='4'/>
<value nick='walking' value='8'/>
</flags>
<schema id='foo'>
<key name='xyz' enum='flags'/>
</schema>
</schemalist>

View File

@@ -0,0 +1,14 @@
<schemalist>
<enum id='flags'>
<value nick='none' value='0'/>
<value nick='mourning' value='1'/>
<value nick='laughing' value='2'/>
<value nick='talking' value='4'/>
<value nick='walking' value='8'/>
</enum>
<schema id='foo'>
<key name='xyz' flags='flags'/>
</schema>
</schemalist>

View File

@@ -5,3 +5,12 @@ typedef enum
TEST_ENUM_BAZ, TEST_ENUM_BAZ,
TEST_ENUM_QUUX TEST_ENUM_QUUX
} TestEnum; } TestEnum;
typedef enum
{
TEST_FLAGS_NONE = 0,
TEST_FLAGS_MOURNING = (1 << 0),
TEST_FLAGS_LAUGHING = (1 << 1),
TEST_FLAGS_TALKING = (1 << 2),
TEST_FLAGS_WALKING = (1 << 3)
} TestFlags;