[glib] Bug 622124 - implement flags for GSettings



commit 5383c7110f793bfa749370cec9d708a6a5018751
Author: Ryan Lortie <desrt desrt ca>
Date:   Thu Jul 1 18:58:56 2010 -0400

    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.

 docs/reference/gio/gio-sections.txt                |    2 +
 gio/gio.symbols                                    |    2 +
 gio/gschema-compile.c                              |  161 +++++++++++++----
 gio/gsettings-mapping.c                            |   65 +++++++-
 gio/gsettings.c                                    |  194 +++++++++++++++++++-
 gio/gsettings.h                                    |    5 +
 gio/strinfo.c                                      |    8 +
 gio/tests/Makefile.am                              |    7 +
 gio/tests/gschema-compile.c                        |   11 +-
 gio/tests/gsettings.c                              |   80 ++++++++-
 gio/tests/org.gtk.test.gschema.xml                 |    9 +
 .../enum-with-repeated-nick.gschema.xml            |   10 +
 .../enum-with-repeated-value.gschema.xml           |   10 +
 .../schema-tests/flags-aliased-default.gschema.xml |   19 ++
 .../schema-tests/flags-bad-default.gschema.xml     |   16 ++
 .../flags-more-than-one-bit.gschema.xml            |   10 +
 .../schema-tests/flags-with-enum-attr.gschema.xml  |   14 ++
 .../schema-tests/flags-with-enum-tag.gschema.xml   |   14 ++
 gio/tests/testenum.h                               |    9 +
 19 files changed, 594 insertions(+), 52 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 420111b..4e10c7f 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -2168,6 +2168,8 @@ g_settings_get_strv
 g_settings_set_strv
 g_settings_get_enum
 g_settings_set_enum
+g_settings_get_flags
+g_settings_set_flags
 
 <SUBSECTION MappedGet>
 GSettingsGetMapping
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 0dd8b79..8f43a54 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1477,6 +1477,8 @@ g_settings_get_boolean
 g_settings_set_boolean
 g_settings_get_enum
 g_settings_set_enum
+g_settings_get_flags
+g_settings_set_flags
 g_settings_sync
 g_settings_list_items
 g_settings_get_mapped
diff --git a/gio/gschema-compile.c b/gio/gschema-compile.c
index cc1e3e3..d7ae5f8 100644
--- a/gio/gschema-compile.c
+++ b/gio/gschema-compile.c
@@ -35,14 +35,32 @@
 #include "strinfo.c"
 
 /* Handling of <enum> {{{1 */
-typedef GString EnumState;
+typedef struct
+{
+  GString *strinfo;
+
+  gboolean is_flags;
+} EnumState;
 
 static void
 enum_state_free (gpointer 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
@@ -58,12 +76,14 @@ enum_state_add_value (EnumState    *state,
     {
       g_set_error (error, G_MARKUP_ERROR,
                    G_MARKUP_ERROR_INVALID_CONTENT,
-                   "enum nick must be a minimum of 2 characters");
+                   "nick must be a minimum of 2 characters");
       return;
     }
 
   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_MARKUP_ERROR_INVALID_CONTENT,
@@ -71,15 +91,38 @@ enum_state_add_value (EnumState    *state,
       return;
     }
 
-  if (strinfo_builder_contains (state, nick))
+  if (strinfo_builder_contains (state->strinfo, nick))
     {
       g_set_error (error, G_MARKUP_ERROR,
                    G_MARKUP_ERROR_INVALID_CONTENT,
-                   "<value nick='%s'> already specified", nick);
+                   "<value nick='%s'/> already specified", nick);
       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
@@ -91,7 +134,7 @@ enum_state_end (EnumState **state_ptr,
   state = *state_ptr;
   *state_ptr = NULL;
 
-  if (state->len == 0)
+  if (state->strinfo->len == 0)
     g_set_error_literal (error,
                          G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                          "<enum> must contain at least one <value>");
@@ -116,6 +159,7 @@ typedef struct
 
   GString      *strinfo;
   gboolean      is_enum;
+  gboolean      is_flags;
 
   GVariant     *minimum;
   GVariant     *maximum;
@@ -131,16 +175,20 @@ typedef struct
 static KeyState *
 key_state_new (const gchar *type_string,
                const gchar *gettext_domain,
-               EnumState   *enum_data)
+               gboolean     is_enum,
+               gboolean     is_flags,
+               GString     *strinfo)
 {
   KeyState *state;
 
   state = g_slice_new0 (KeyState);
   state->type = g_variant_type_new (type_string);
   state->have_gettext_domain = gettext_domain != NULL;
+  state->is_enum = is_enum;
+  state->is_flags = is_flags;
 
-  if ((state->is_enum = (enum_data != NULL)))
-    state->strinfo = g_string_new_len (enum_data->str, enum_data->len);
+  if (strinfo)
+    state->strinfo = g_string_new_len (strinfo->str, strinfo->len);
   else
     state->strinfo = g_string_new (NULL);
 
@@ -159,6 +207,7 @@ key_state_override (KeyState    *state,
   copy->strinfo = g_string_new_len (state->strinfo->str,
                                     state->strinfo->len);
   copy->is_enum = state->is_enum;
+  copy->is_flags = state->is_flags;
   copy->is_override = TRUE;
 
   if (state->minimum)
@@ -251,6 +300,12 @@ key_state_check_range (KeyState  *state,
                              "<%s> is not a valid member of "
                              "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
                 g_set_error (error, G_MARKUP_ERROR,
                              G_MARKUP_ERROR_INVALID_CONTENT,
@@ -442,11 +497,11 @@ key_state_start_aliases (KeyState  *state,
                          G_MARKUP_ERROR_INVALID_CONTENT,
                          "<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_MARKUP_ERROR_INVALID_CONTENT,
                          "<aliases> can only be specified for keys with "
-                         "enumerated types or after <choices>");
+                         "enumerated or flags types or after <choices>");
 }
 
 static void
@@ -582,6 +637,7 @@ key_state_serialise (KeyState *state)
               state->strinfo = NULL;
 
               g_variant_builder_add (&builder, "(y au)",
+                                     state->is_flags ? 'f' :
                                      state->is_enum ? 'e' : 'c',
                                      array);
             }
@@ -771,13 +827,15 @@ schema_state_add_child (SchemaState  *state,
 static KeyState *
 schema_state_add_key (SchemaState  *state,
                       GHashTable   *enum_table,
+                      GHashTable   *flags_table,
                       const gchar  *name,
                       const gchar  *type_string,
                       const gchar  *enum_type,
+                      const gchar  *flags_type,
                       GError      **error)
 {
-  GString *enum_data;
   SchemaState *node;
+  GString *strinfo;
   KeyState *key;
 
   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_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>");
       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_type)
+        enum_state = g_hash_table_lookup (enum_table, enum_type);
+      else
+        enum_state = g_hash_table_lookup (flags_table, flags_type);
 
-      if (enum_data == NULL)
+
+      if (enum_state == NULL)
         {
           g_set_error (error, G_MARKUP_ERROR,
                        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;
         }
 
-      g_assert (type_string == NULL);
-      type_string = "s";
+      type_string = flags_type ? "as" : "s";
+      strinfo = enum_state->strinfo;
     }
   else
     {
@@ -854,10 +920,11 @@ schema_state_add_key (SchemaState  *state,
           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);
 
   return key;
@@ -922,13 +989,14 @@ override_state_end (KeyState **key_state,
 typedef struct
 {
   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 */
 
   SchemaState *schema_state;            /* non-NULL when inside <schema> */
   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 */
 } ParseState;
@@ -1047,18 +1115,22 @@ parse_state_start_schema (ParseState  *state,
 static void
 parse_state_start_enum (ParseState   *state,
                         const gchar  *id,
+                        gboolean      is_flags,
                         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_MARKUP_ERROR_INVALID_CONTENT,
-                   "<enum id='%s'> already specified", id);
+                   "<%s id='%s'> already specified",
+                   is_flags ? "flags" : "enum", id);
       return;
     }
 
-  state->enum_state = g_string_new (NULL);
-  g_hash_table_insert (state->enum_table, g_strdup (id), state->enum_state);
+  state->enum_state = enum_state_new (is_flags);
+  g_hash_table_insert (table, g_strdup (id), state->enum_state);
 }
 
 /* GMarkup Parser Functions {{{1 */
@@ -1117,12 +1189,19 @@ start_element (GMarkupParseContext  *context,
           return;
         }
 
-      else if (strcmp (element_name, "enum") == 0 ||
-               strcmp (element_name, "flags") == 0)
+      else if (strcmp (element_name, "enum") == 0)
         {
           const gchar *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;
         }
     }
@@ -1133,16 +1212,19 @@ start_element (GMarkupParseContext  *context,
     {
       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,
-                       OPTIONAL | STRING, "type", &type_string,
-                       OPTIONAL | STRING, "enum", &enum_type))
+          if (COLLECT (STRING,            "name",  &name,
+                       OPTIONAL | STRING, "type",  &type_string,
+                       OPTIONAL | STRING, "enum",  &enum_type,
+                       OPTIONAL | STRING, "flags", &flags_type))
 
             state->key_state = schema_state_add_key (state->schema_state,
                                                      state->enum_table,
+                                                     state->flags_table,
                                                      name, type_string,
-                                                     enum_type, error);
+                                                     enum_type, flags_type,
+                                                     error);
           return;
         }
       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,
                                             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,
                                               g_free, schema_state_free);
 
diff --git a/gio/gsettings-mapping.c b/gio/gsettings-mapping.c
index 52a9b21..d85fae1 100644
--- a/gio/gsettings-mapping.c
+++ b/gio/gsettings-mapping.c
@@ -392,6 +392,34 @@ g_settings_set_mapping (const GValue       *value,
         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);
   g_critical ("No GSettings bind handler for type \"%s\".", type_string);
   g_free (type_string);
@@ -447,8 +475,7 @@ g_settings_get_mapping (GValue   *value,
           return TRUE;
         }
 
-      else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING) &&
-               G_VALUE_HOLDS_ENUM (value))
+      else if (G_VALUE_HOLDS_ENUM (value))
         {
           GEnumClass *eclass;
           GEnumValue *evalue;
@@ -469,6 +496,38 @@ g_settings_get_mapping (GValue   *value,
           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")))
     {
       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));
   else if (G_TYPE_IS_ENUM (gvalue_type))
     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;
 }
diff --git a/gio/gsettings.c b/gio/gsettings.c
index 5a5ceed..e5ac148 100644
--- a/gio/gsettings.c
+++ b/gio/gsettings.c
@@ -831,7 +831,7 @@ g_settings_new_with_backend_and_path (const gchar      *schema,
                        NULL);
 }
 
-/* Internal read/write utilities, enum conversion, validation {{{1 */
+/* Internal read/write utilities, enum/flags conversion, validation {{{1 */
 typedef struct
 {
   GSettings *settings;
@@ -839,7 +839,9 @@ typedef struct
 
   GSettingsSchema *schema;
 
-  gboolean is_enum;
+  guint is_flags : 1;
+  guint is_enum  : 1;
+
   const guint32 *strinfo;
   gsize strinfo_length;
 
@@ -879,9 +881,16 @@ g_settings_get_key_info (GSettingsKeyInfo *info,
           break;
 
         case 'e':
-          /* enumerated types, ... */
+          /* enumerated types... */
           info->is_enum = TRUE;
-        case 'c':
+          goto choice;
+
+        case 'f':
+          /* flags... */
+          info->is_flags = TRUE;
+          goto choice;
+
+        choice: case 'c':
           /* ..., choices, aliases */
           info->strinfo = g_variant_get_fixed_array (data,
                                                      &info->strinfo_length,
@@ -956,7 +965,7 @@ g_settings_range_check (GSettingsKeyInfo *info,
       g_variant_iter_init (&iter, value);
       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);
         }
 
@@ -992,6 +1001,7 @@ g_settings_range_fixup (GSettingsKeyInfo *info,
       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)))
@@ -1123,7 +1133,64 @@ g_settings_from_enum (GSettingsKeyInfo *info,
   if (string == 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) */
@@ -1235,7 +1302,7 @@ g_settings_get_enum (GSettings   *settings,
  * schema for @settings or is not marked as an enumerated type, or for
  * @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
  * @value.
  **/
@@ -1271,7 +1338,118 @@ g_settings_set_enum (GSettings   *settings,
 
   success = g_settings_write_to_backend (&info, variant);
   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;
 }
diff --git a/gio/gsettings.h b/gio/gsettings.h
index f0da640..70dbbd8 100644
--- a/gio/gsettings.h
+++ b/gio/gsettings.h
@@ -126,6 +126,11 @@ gint                    g_settings_get_enum                             (GSettin
 gboolean                g_settings_set_enum                             (GSettings          *settings,
                                                                          const gchar        *key,
                                                                          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,
                                                                          const gchar        *name);
 
diff --git a/gio/strinfo.c b/gio/strinfo.c
index f762fc5..84e4acf 100644
--- a/gio/strinfo.c
+++ b/gio/strinfo.c
@@ -309,3 +309,11 @@ strinfo_builder_contains (GString     *builder,
          strinfo_find_string ((const guint32 *) builder->str,
                               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;
+}
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index d95c618..9b3286a 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -300,8 +300,15 @@ schema_tests = \
 	schema-tests/enum-with-choice.gschema.xml			\
 	schema-tests/enum-with-invalid-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.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.gschema.xml			\
 	schema-tests/extend-missing.gschema.xml				\
diff --git a/gio/tests/gschema-compile.c b/gio/tests/gschema-compile.c
index 983a1c8..930d352 100644
--- a/gio/tests/gschema-compile.c
+++ b/gio/tests/gschema-compile.c
@@ -71,6 +71,8 @@ static const SchemaTest tests[] = {
   { "enum-with-aliases",            NULL, NULL                                                  },
   { "enum-with-invalid-alias",      NULL, "*'banger' is not in enumerated type*"                },
   { "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-shadow-alias",       NULL, "*'mash' is already a member of the enum*"            },
   { "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>*"        },
   { "choice-bad",                   NULL, "*<default> contains string not in <choices>*"        },
   { "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                                                  },
   { "default-in-aliases",           NULL, "*<default> contains string 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-then-key",            NULL, "*shadows <key name='foo'> in <schema id='base'>*"    },
   { "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
diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c
index cdfa9b8..0e43506 100644
--- a/gio/tests/gsettings.c
+++ b/gio/tests/gsettings.c
@@ -1249,8 +1249,6 @@ test_strinfo (void)
   g_assert (!strinfo_is_string_valid (strinfo, length, "quux"));
 }
 
-
-
 static void
 test_enums (void)
 {
@@ -1276,6 +1274,11 @@ test_enums (void)
         g_settings_set_string (settings, "test", "qux");
       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_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");
@@ -1304,6 +1307,78 @@ test_enums (void)
 }
 
 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
 test_range (void)
 {
   GSettings *settings, *direct;
@@ -1408,6 +1483,7 @@ main (int argc, char *argv[])
   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/flags", test_flags);
   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 a7c6c13..c7d3d97 100644
--- a/gio/tests/org.gtk.test.gschema.xml
+++ b/gio/tests/org.gtk.test.gschema.xml
@@ -99,9 +99,18 @@
         <alias value='qux' target='quux'/>
       </aliases>
     </key>
+    <key name='f-test' flags='org.gtk.test.TestFlags'>
+      <default>[]</default>
+      <aliases>
+        <alias value='speaking' target='talking'/>
+      </aliases>
+    </key>
   </schema>
 
   <schema id='org.gtk.test.enums.direct' path='/tests/enums/'>
+    <key name='f-test' type='as'>
+      <default>[]</default>
+    </key>
     <key name='test' type='s'>
       <default>'bar'</default>
     </key>
diff --git a/gio/tests/schema-tests/enum-with-repeated-nick.gschema.xml b/gio/tests/schema-tests/enum-with-repeated-nick.gschema.xml
new file mode 100644
index 0000000..8910711
--- /dev/null
+++ b/gio/tests/schema-tests/enum-with-repeated-nick.gschema.xml
@@ -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>
diff --git a/gio/tests/schema-tests/enum-with-repeated-value.gschema.xml b/gio/tests/schema-tests/enum-with-repeated-value.gschema.xml
new file mode 100644
index 0000000..a357d11
--- /dev/null
+++ b/gio/tests/schema-tests/enum-with-repeated-value.gschema.xml
@@ -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>
diff --git a/gio/tests/schema-tests/flags-aliased-default.gschema.xml b/gio/tests/schema-tests/flags-aliased-default.gschema.xml
new file mode 100644
index 0000000..c36bd62
--- /dev/null
+++ b/gio/tests/schema-tests/flags-aliased-default.gschema.xml
@@ -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>
+
diff --git a/gio/tests/schema-tests/flags-bad-default.gschema.xml b/gio/tests/schema-tests/flags-bad-default.gschema.xml
new file mode 100644
index 0000000..d412057
--- /dev/null
+++ b/gio/tests/schema-tests/flags-bad-default.gschema.xml
@@ -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>
+
diff --git a/gio/tests/schema-tests/flags-more-than-one-bit.gschema.xml b/gio/tests/schema-tests/flags-more-than-one-bit.gschema.xml
new file mode 100644
index 0000000..ce2faaf
--- /dev/null
+++ b/gio/tests/schema-tests/flags-more-than-one-bit.gschema.xml
@@ -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>
+
diff --git a/gio/tests/schema-tests/flags-with-enum-attr.gschema.xml b/gio/tests/schema-tests/flags-with-enum-attr.gschema.xml
new file mode 100644
index 0000000..a48547f
--- /dev/null
+++ b/gio/tests/schema-tests/flags-with-enum-attr.gschema.xml
@@ -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>
+
diff --git a/gio/tests/schema-tests/flags-with-enum-tag.gschema.xml b/gio/tests/schema-tests/flags-with-enum-tag.gschema.xml
new file mode 100644
index 0000000..4b2fb90
--- /dev/null
+++ b/gio/tests/schema-tests/flags-with-enum-tag.gschema.xml
@@ -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>
+
diff --git a/gio/tests/testenum.h b/gio/tests/testenum.h
index 4329860..def8713 100644
--- a/gio/tests/testenum.h
+++ b/gio/tests/testenum.h
@@ -5,3 +5,12 @@ typedef enum
   TEST_ENUM_BAZ,
   TEST_ENUM_QUUX
 } 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;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]