Merge branch 'master' into 'master'

gio: icons should fallback to non-preferred style appropriately.

See merge request GNOME/glib!72
diff --git a/gio/gthemedicon.c b/gio/gthemedicon.c
index 3ada77b..35970de 100644
--- a/gio/gthemedicon.c
+++ b/gio/gthemedicon.c
@@ -49,6 +49,7 @@
 {
   GObject parent_instance;
   
+  char     **init_names;
   char     **names;
   gboolean   use_default_fallbacks;
 };
@@ -66,6 +67,8 @@
   PROP_USE_DEFAULT_FALLBACKS
 };
 
+static void g_themed_icon_update_names (GThemedIcon *themed);
+
 G_DEFINE_TYPE_WITH_CODE (GThemedIcon, g_themed_icon, G_TYPE_OBJECT,
 			 G_IMPLEMENT_INTERFACE (G_TYPE_ICON,
 						g_themed_icon_icon_iface_init))
@@ -81,7 +84,7 @@
   switch (prop_id)
     {
       case PROP_NAMES:
-        g_value_set_boxed (value, icon->names);
+        g_value_set_boxed (value, icon->init_names);
         break;
 
       case PROP_USE_DEFAULT_FALLBACKS:
@@ -111,12 +114,12 @@
         if (!name)
           break;
 
-        if (icon->names)
-          g_strfreev (icon->names);
+        if (icon->init_names)
+          g_strfreev (icon->init_names);
 
-        icon->names = g_new (char *, 2);
-        icon->names[0] = g_strdup (name);
-        icon->names[1] = NULL;
+        icon->init_names = g_new (char *, 2);
+        icon->init_names[0] = g_strdup (name);
+        icon->init_names[1] = NULL;
         break;
 
       case PROP_NAMES:
@@ -125,10 +128,10 @@
         if (!names)
           break;
 
-        if (icon->names)
-          g_strfreev (icon->names);
+        if (icon->init_names)
+          g_strfreev (icon->init_names);
 
-        icon->names = names;
+        icon->init_names = names;
         break;
 
       case PROP_USE_DEFAULT_FALLBACKS:
@@ -143,63 +146,7 @@
 static void
 g_themed_icon_constructed (GObject *object)
 {
-  GThemedIcon *themed = G_THEMED_ICON (object);
-
-  g_return_if_fail (themed->names != NULL && themed->names[0] != NULL);
-
-  if (themed->use_default_fallbacks)
-    {
-      int i = 0, dashes = 0;
-      const char *p;
-      char *dashp;
-      char *last;
-      gboolean is_symbolic;
-      char *name;
-      char **names;
-
-      is_symbolic = g_str_has_suffix (themed->names[0], "-symbolic");
-      if (is_symbolic)
-        name = g_strndup (themed->names[0], strlen (themed->names[0]) - 9);
-      else
-        name = g_strdup (themed->names[0]);
-
-      p = name;
-      while (*p)
-        {
-          if (*p == '-')
-            dashes++;
-          p++;
-        }
-
-      last = name;
-
-      g_strfreev (themed->names);
-
-      names = g_new (char *, dashes + 1 + 1);
-      names[i++] = last;
-
-      while ((dashp = strrchr (last, '-')) != NULL)
-        names[i++] = last = g_strndup (last, dashp - last);
-
-      names[i++] = NULL;
-
-      if (is_symbolic)
-        {
-          themed->names = g_new (char *, 2 * dashes + 3);
-          for (i = 0; names[i] != NULL; i++)
-            {
-              themed->names[i] = g_strconcat (names[i], "-symbolic", NULL);
-              themed->names[dashes + 1 + i] = names[i];
-            }
-
-          themed->names[dashes + 1 + i] = NULL;
-          g_free (names);
-        }
-      else
-        {
-          themed->names = names;
-        }
-    }
+  g_themed_icon_update_names (G_THEMED_ICON (object));
 }
 
 static void
@@ -209,6 +156,7 @@
 
   themed = G_THEMED_ICON (object);
 
+  g_strfreev (themed->init_names);
   g_strfreev (themed->names);
 
   G_OBJECT_CLASS (g_themed_icon_parent_class)->finalize (object);
@@ -278,7 +226,136 @@
 static void
 g_themed_icon_init (GThemedIcon *themed)
 {
-  themed->names = NULL;
+  themed->init_names = NULL;
+  themed->names      = NULL;
+}
+
+/**
+ * g_themed_icon_update_names:
+ * @themed: a #GThemedIcon.
+ *
+ * Update the actual icon name list, based on the requested names (from
+ * construction, or later added with g_themed_icon_prepend_name() and
+ * g_themed_icon_append_name()).
+ * The order of the list matters, indicating priority:
+ * - The first requested icon is first in priority.
+ * - If "use-default-fallbacks" is #TRUE, then it is followed by all its
+ *   fallbacks (starting from top to lower context levels).
+ * - Then next requested icons, and optionally their fallbacks, follow.
+ * - Finally all the style variants (symbolic or regular, opposite to whatever
+ *   is the requested style) follow in the same order.
+ *
+ * An icon is not added twice in the list if it was previously added.
+ *
+ * For instance, if requested names are:
+ * [ "some-icon-symbolic", "some-other-icon" ]
+ * and use-default-fallbacks is TRUE, the final name list shall be:
+ * [ "some-icon-symbolic", "some-symbolic", "some-other-icon",
+ *   "some-other", "some", "some-icon", "some-other-icon-symbolic",
+ *   "some-other-symbolic" ]
+ *
+ * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon
+ **/
+static void
+g_themed_icon_update_names (GThemedIcon *themed)
+{
+  GList *names    = NULL;
+  GList *variants = NULL;
+  GList *iter;
+  guint  i;
+
+  g_return_if_fail (themed->init_names != NULL && themed->init_names[0] != NULL);
+
+  for (i = 0; themed->init_names[i]; i++)
+    {
+      gchar    *name;
+      gboolean  is_symbolic;
+
+      is_symbolic = g_str_has_suffix (themed->init_names[i], "-symbolic");
+      if (is_symbolic)
+        name = g_strndup (themed->init_names[i], strlen (themed->init_names[i]) - 9);
+      else
+        name = g_strdup (themed->init_names[i]);
+
+      if (g_list_find_custom (names, name, (GCompareFunc) g_strcmp0))
+        {
+          g_free (name);
+          continue;
+        }
+
+      if (is_symbolic)
+        names = g_list_prepend (names, g_strdup (themed->init_names[i]));
+      else
+        names = g_list_prepend (names, name);
+
+      if (themed->use_default_fallbacks)
+        {
+          char *dashp;
+          char *last;
+
+          last = name;
+
+          while ((dashp = strrchr (last, '-')) != NULL)
+            {
+              gchar *tmp = last;
+              gchar *fallback;
+
+              last = g_strndup (last, dashp - last);
+              if (is_symbolic)
+                {
+                  g_free (tmp);
+                  fallback = g_strdup_printf ("%s-symbolic", last);
+                }
+              else
+                fallback = last;
+              if (g_list_find_custom (names, fallback, (GCompareFunc) g_strcmp0))
+                {
+                  g_free (fallback);
+                  break;
+                }
+              names = g_list_prepend (names, fallback);
+            }
+          if (is_symbolic)
+            g_free (last);
+        }
+      else if (is_symbolic)
+        g_free (name);
+    }
+  for (iter = names; iter; iter = iter->next)
+    {
+      gchar    *name = (gchar *) iter->data;
+      gchar    *variant;
+      gboolean  is_symbolic;
+
+      is_symbolic = g_str_has_suffix (name, "-symbolic");
+      if (is_symbolic)
+        variant = g_strndup (name, strlen (name) - 9);
+      else
+        variant = g_strdup_printf ("%s-symbolic", name);
+      if (g_list_find_custom (names, variant, (GCompareFunc) g_strcmp0) ||
+          g_list_find_custom (variants, variant, (GCompareFunc) g_strcmp0))
+        {
+          g_free (variant);
+          continue;
+        }
+
+      variants = g_list_prepend (variants, variant);
+    }
+  names = g_list_reverse (names);
+
+  g_strfreev (themed->names);
+  themed->names = g_new (char *, g_list_length (names) + g_list_length (variants) + 1);
+
+  for (iter = names, i = 0; iter; iter = iter->next, i++)
+    themed->names[i] = iter->data;
+  for (iter = variants; iter; iter = iter->next, i++)
+    themed->names[i] = iter->data;
+  themed->names[i] = NULL;
+
+  g_list_free (names);
+  g_list_free (variants);
+
+  g_object_notify (G_OBJECT (themed), "names");
 }
 
 /**
@@ -402,12 +479,12 @@
   g_return_if_fail (G_IS_THEMED_ICON (icon));
   g_return_if_fail (iconname != NULL);
 
-  num_names = g_strv_length (icon->names);
-  icon->names = g_realloc (icon->names, sizeof (char*) * (num_names + 2));
-  icon->names[num_names] = g_strdup (iconname);
-  icon->names[num_names + 1] = NULL;
+  num_names = g_strv_length (icon->init_names);
+  icon->init_names = g_realloc (icon->init_names, sizeof (char*) * (num_names + 2));
+  icon->init_names[num_names] = g_strdup (iconname);
+  icon->init_names[num_names + 1] = NULL;
 
-  g_object_notify (G_OBJECT (icon), "names");
+  g_themed_icon_update_names (icon);
 }
 
 /**
@@ -433,17 +510,17 @@
   g_return_if_fail (G_IS_THEMED_ICON (icon));
   g_return_if_fail (iconname != NULL);
 
-  num_names = g_strv_length (icon->names);
+  num_names = g_strv_length (icon->init_names);
   names = g_new (char*, num_names + 2);
-  for (i = 0; icon->names[i]; i++)
-    names[i + 1] = icon->names[i];
+  for (i = 0; icon->init_names[i]; i++)
+    names[i + 1] = icon->init_names[i];
   names[0] = g_strdup (iconname);
   names[num_names + 1] = NULL;
 
-  g_free (icon->names);
-  icon->names = names;
+  g_free (icon->init_names);
+  icon->init_names = names;
 
-  g_object_notify (G_OBJECT (icon), "names");
+  g_themed_icon_update_names (icon);
 }
 
 static guint
diff --git a/gio/tests/g-icon.c b/gio/tests/g-icon.c
index 598ae94..7f87e49 100644
--- a/gio/tests/g-icon.c
+++ b/gio/tests/g-icon.c
@@ -108,9 +108,18 @@
   g_object_unref (location);
 #endif
 
+  icon = g_themed_icon_new_with_default_fallbacks ("some-icon-symbolic");
+  g_themed_icon_append_name (G_THEMED_ICON (icon), "some-other-icon");
+  data = g_icon_to_string (icon);
+  g_assert_cmpstr (data, ==, ". GThemedIcon "
+                             "some-icon-symbolic some-symbolic some-other-icon some-other some "
+                             "some-icon some-other-icon-symbolic some-other-symbolic");
+  g_free (data);
+  g_object_unref (icon);
+
   icon = g_themed_icon_new ("network-server");
   data = g_icon_to_string (icon);
-  g_assert_cmpstr (data, ==, "network-server");
+  g_assert_cmpstr (data, ==, ". GThemedIcon network-server network-server-symbolic");
   icon2 = g_icon_new_for_string (data, &error);
   g_assert_no_error (error);
   g_assert (g_icon_equal (icon, icon2));
@@ -371,7 +380,7 @@
 {
   GIcon *icon1, *icon2, *icon3, *icon4;
   const gchar *const *names;
-  const gchar *names2[] = { "first", "testicon", "last", NULL };
+  const gchar *names2[] = { "first-symbolic", "testicon", "last", NULL };
   gchar *str;
   gboolean fallbacks;
   GVariant *variant;
@@ -382,17 +391,21 @@
   g_assert (!fallbacks);
 
   names = g_themed_icon_get_names (G_THEMED_ICON (icon1));
-  g_assert_cmpint (g_strv_length ((gchar **)names), ==, 1);
+  g_assert_cmpint (g_strv_length ((gchar **)names), ==, 2);
   g_assert_cmpstr (names[0], ==, "testicon");
+  g_assert_cmpstr (names[1], ==, "testicon-symbolic");
 
-  g_themed_icon_prepend_name (G_THEMED_ICON (icon1), "first");
+  g_themed_icon_prepend_name (G_THEMED_ICON (icon1), "first-symbolic");
   g_themed_icon_append_name (G_THEMED_ICON (icon1), "last");
   names = g_themed_icon_get_names (G_THEMED_ICON (icon1));
-  g_assert_cmpint (g_strv_length ((gchar **)names), ==, 3);
-  g_assert_cmpstr (names[0], ==, "first");
+  g_assert_cmpint (g_strv_length ((gchar **)names), ==, 6);
+  g_assert_cmpstr (names[0], ==, "first-symbolic");
   g_assert_cmpstr (names[1], ==, "testicon");
   g_assert_cmpstr (names[2], ==, "last");
-  g_assert_cmpuint (g_icon_hash (icon1), ==, 2400773466U);
+  g_assert_cmpstr (names[3], ==, "first");
+  g_assert_cmpstr (names[4], ==, "testicon-symbolic");
+  g_assert_cmpstr (names[5], ==, "last-symbolic");
+  g_assert_cmpuint (g_icon_hash (icon1), ==, 1812785139);
 
   icon2 = g_themed_icon_new_from_names ((gchar**)names2, -1);
   g_assert (g_icon_equal (icon1, icon2));
@@ -448,11 +461,11 @@
 
   emblem = emblems->data;
   g_assert (g_emblem_get_icon (emblem) == icon2);
-  g_assert (g_emblem_get_origin (emblem) == G_EMBLEM_ORIGIN_TAG);
+  g_assert (g_emblem_get_origin (emblem) == G_EMBLEM_ORIGIN_UNKNOWN);
 
   emblem = emblems->next->data;
   g_assert (g_emblem_get_icon (emblem) == icon2);
-  g_assert (g_emblem_get_origin (emblem) == G_EMBLEM_ORIGIN_UNKNOWN);
+  g_assert (g_emblem_get_origin (emblem) == G_EMBLEM_ORIGIN_TAG);
 
   g_emblemed_icon_clear_emblems (G_EMBLEMED_ICON (icon4));
   g_assert (g_emblemed_icon_get_emblems (G_EMBLEMED_ICON (icon4)) == NULL);