1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Alexander Larsson <alexl@redhat.com>
19  */
20 
21 #include "config.h"
22 
23 #include <string.h>
24 
25 #include "gthemedicon.h"
26 #include "gicon.h"
27 #include "gioerror.h"
28 #include "glibintl.h"
29 
30 
31 /**
32  * SECTION:gthemedicon
33  * @short_description: Icon theming support
34  * @include: gio/gio.h
35  * @see_also: #GIcon, #GLoadableIcon
36  *
37  * #GThemedIcon is an implementation of #GIcon that supports icon themes.
38  * #GThemedIcon contains a list of all of the icons present in an icon
39  * theme, so that icons can be looked up quickly. #GThemedIcon does
40  * not provide actual pixmaps for icons, just the icon names.
41  * Ideally something like gtk_icon_theme_choose_icon() should be used to
42  * resolve the list of names so that fallback icons work nicely with
43  * themes that inherit other themes.
44  **/
45 
46 static void g_themed_icon_icon_iface_init (GIconIface *iface);
47 
48 struct _GThemedIcon
49 {
50   GObject parent_instance;
51 
52   char     **init_names;
53   char     **names;
54   gboolean   use_default_fallbacks;
55 };
56 
57 struct _GThemedIconClass
58 {
59   GObjectClass parent_class;
60 };
61 
62 enum
63 {
64   PROP_0,
65   PROP_NAME,
66   PROP_NAMES,
67   PROP_USE_DEFAULT_FALLBACKS
68 };
69 
70 static void g_themed_icon_update_names (GThemedIcon *themed);
71 
G_DEFINE_TYPE_WITH_CODE(GThemedIcon,g_themed_icon,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_ICON,g_themed_icon_icon_iface_init))72 G_DEFINE_TYPE_WITH_CODE (GThemedIcon, g_themed_icon, G_TYPE_OBJECT,
73 			 G_IMPLEMENT_INTERFACE (G_TYPE_ICON,
74 						g_themed_icon_icon_iface_init))
75 
76 static void
77 g_themed_icon_get_property (GObject    *object,
78                             guint       prop_id,
79                             GValue     *value,
80                             GParamSpec *pspec)
81 {
82   GThemedIcon *icon = G_THEMED_ICON (object);
83 
84   switch (prop_id)
85     {
86       case PROP_NAMES:
87         g_value_set_boxed (value, icon->init_names);
88         break;
89 
90       case PROP_USE_DEFAULT_FALLBACKS:
91         g_value_set_boolean (value, icon->use_default_fallbacks);
92         break;
93 
94       default:
95         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96     }
97 }
98 
99 static void
g_themed_icon_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)100 g_themed_icon_set_property (GObject      *object,
101                             guint         prop_id,
102                             const GValue *value,
103                             GParamSpec   *pspec)
104 {
105   GThemedIcon *icon = G_THEMED_ICON (object);
106   gchar **names;
107   const gchar *name;
108 
109   switch (prop_id)
110     {
111       case PROP_NAME:
112         name = g_value_get_string (value);
113 
114         if (!name)
115           break;
116 
117         if (icon->init_names)
118           g_strfreev (icon->init_names);
119 
120         icon->init_names = g_new (char *, 2);
121         icon->init_names[0] = g_strdup (name);
122         icon->init_names[1] = NULL;
123         break;
124 
125       case PROP_NAMES:
126         names = g_value_dup_boxed (value);
127 
128         if (!names)
129           break;
130 
131         if (icon->init_names)
132           g_strfreev (icon->init_names);
133 
134         icon->init_names = names;
135         break;
136 
137       case PROP_USE_DEFAULT_FALLBACKS:
138         icon->use_default_fallbacks = g_value_get_boolean (value);
139         break;
140 
141       default:
142         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
143     }
144 }
145 
146 static void
g_themed_icon_constructed(GObject * object)147 g_themed_icon_constructed (GObject *object)
148 {
149   g_themed_icon_update_names (G_THEMED_ICON (object));
150 }
151 
152 static void
g_themed_icon_finalize(GObject * object)153 g_themed_icon_finalize (GObject *object)
154 {
155   GThemedIcon *themed;
156 
157   themed = G_THEMED_ICON (object);
158 
159   g_strfreev (themed->init_names);
160   g_strfreev (themed->names);
161 
162   G_OBJECT_CLASS (g_themed_icon_parent_class)->finalize (object);
163 }
164 
165 static void
g_themed_icon_class_init(GThemedIconClass * klass)166 g_themed_icon_class_init (GThemedIconClass *klass)
167 {
168   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
169 
170   gobject_class->finalize = g_themed_icon_finalize;
171   gobject_class->constructed = g_themed_icon_constructed;
172   gobject_class->set_property = g_themed_icon_set_property;
173   gobject_class->get_property = g_themed_icon_get_property;
174 
175   /**
176    * GThemedIcon:name:
177    *
178    * The icon name.
179    */
180   g_object_class_install_property (gobject_class, PROP_NAME,
181                                    g_param_spec_string ("name",
182                                                         P_("name"),
183                                                         P_("The name of the icon"),
184                                                         NULL,
185                                                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
186 
187   /**
188    * GThemedIcon:names:
189    *
190    * A %NULL-terminated array of icon names.
191    */
192   g_object_class_install_property (gobject_class, PROP_NAMES,
193                                    g_param_spec_boxed ("names",
194                                                        P_("names"),
195                                                        P_("An array containing the icon names"),
196                                                        G_TYPE_STRV,
197                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
198 
199   /**
200    * GThemedIcon:use-default-fallbacks:
201    *
202    * Whether to use the default fallbacks found by shortening the icon name
203    * at '-' characters. If the "names" array has more than one element,
204    * ignores any past the first.
205    *
206    * For example, if the icon name was "gnome-dev-cdrom-audio", the array
207    * would become
208    * |[<!-- language="C" -->
209    * {
210    *   "gnome-dev-cdrom-audio",
211    *   "gnome-dev-cdrom",
212    *   "gnome-dev",
213    *   "gnome",
214    *   NULL
215    * };
216    * ]|
217    */
218   g_object_class_install_property (gobject_class, PROP_USE_DEFAULT_FALLBACKS,
219                                    g_param_spec_boolean ("use-default-fallbacks",
220                                                          P_("use default fallbacks"),
221                                                          P_("Whether to use default fallbacks found by shortening the name at “-” characters. Ignores names after the first if multiple names are given."),
222                                                          FALSE,
223                                                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
224 }
225 
226 static void
g_themed_icon_init(GThemedIcon * themed)227 g_themed_icon_init (GThemedIcon *themed)
228 {
229   themed->init_names = NULL;
230   themed->names      = NULL;
231 }
232 
233 /**
234  * g_themed_icon_update_names:
235  * @themed: a #GThemedIcon.
236  *
237  * Update the actual icon name list, based on the requested names (from
238  * construction, or later added with g_themed_icon_prepend_name() and
239  * g_themed_icon_append_name()).
240  * The order of the list matters, indicating priority:
241  * - The first requested icon is first in priority.
242  * - If "use-default-fallbacks" is #TRUE, then it is followed by all its
243  *   fallbacks (starting from top to lower context levels).
244  * - Then next requested icons, and optionally their fallbacks, follow.
245  * - Finally all the style variants (symbolic or regular, opposite to whatever
246  *   is the requested style) follow in the same order.
247  *
248  * An icon is not added twice in the list if it was previously added.
249  *
250  * For instance, if requested names are:
251  * [ "some-icon-symbolic", "some-other-icon" ]
252  * and use-default-fallbacks is TRUE, the final name list shall be:
253  * [ "some-icon-symbolic", "some-symbolic", "some-other-icon",
254  *   "some-other", "some", "some-icon", "some-other-icon-symbolic",
255  *   "some-other-symbolic" ]
256  *
257  * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon
258  **/
259 static void
g_themed_icon_update_names(GThemedIcon * themed)260 g_themed_icon_update_names (GThemedIcon *themed)
261 {
262   GList *names    = NULL;
263   GList *variants = NULL;
264   GList *iter;
265   guint  i;
266 
267   g_return_if_fail (themed->init_names != NULL && themed->init_names[0] != NULL);
268 
269   for (i = 0; themed->init_names[i]; i++)
270     {
271       gchar    *name;
272       gboolean  is_symbolic;
273 
274       is_symbolic = g_str_has_suffix (themed->init_names[i], "-symbolic");
275       if (is_symbolic)
276         name = g_strndup (themed->init_names[i], strlen (themed->init_names[i]) - 9);
277       else
278         name = g_strdup (themed->init_names[i]);
279 
280       if (g_list_find_custom (names, name, (GCompareFunc) g_strcmp0))
281         {
282           g_free (name);
283           continue;
284         }
285 
286       if (is_symbolic)
287         names = g_list_prepend (names, g_strdup (themed->init_names[i]));
288       else
289         names = g_list_prepend (names, name);
290 
291       if (themed->use_default_fallbacks)
292         {
293           char *dashp;
294           char *last;
295 
296           last = name;
297 
298           while ((dashp = strrchr (last, '-')) != NULL)
299             {
300               gchar *tmp = last;
301               gchar *fallback;
302 
303               last = g_strndup (last, dashp - last);
304               if (is_symbolic)
305                 {
306                   g_free (tmp);
307                   fallback = g_strdup_printf ("%s-symbolic", last);
308                 }
309               else
310                 fallback = last;
311               if (g_list_find_custom (names, fallback, (GCompareFunc) g_strcmp0))
312                 {
313                   g_free (fallback);
314                   break;
315                 }
316               names = g_list_prepend (names, fallback);
317             }
318           if (is_symbolic)
319             g_free (last);
320         }
321       else if (is_symbolic)
322         g_free (name);
323     }
324   for (iter = names; iter; iter = iter->next)
325     {
326       gchar    *name = (gchar *) iter->data;
327       gchar    *variant;
328       gboolean  is_symbolic;
329 
330       is_symbolic = g_str_has_suffix (name, "-symbolic");
331       if (is_symbolic)
332         variant = g_strndup (name, strlen (name) - 9);
333       else
334         variant = g_strdup_printf ("%s-symbolic", name);
335       if (g_list_find_custom (names, variant, (GCompareFunc) g_strcmp0) ||
336           g_list_find_custom (variants, variant, (GCompareFunc) g_strcmp0))
337         {
338           g_free (variant);
339           continue;
340         }
341 
342       variants = g_list_prepend (variants, variant);
343     }
344   names = g_list_reverse (names);
345 
346   g_strfreev (themed->names);
347   themed->names = g_new (char *, g_list_length (names) + g_list_length (variants) + 1);
348 
349   for (iter = names, i = 0; iter; iter = iter->next, i++)
350     themed->names[i] = iter->data;
351   for (iter = variants; iter; iter = iter->next, i++)
352     themed->names[i] = iter->data;
353   themed->names[i] = NULL;
354 
355   g_list_free (names);
356   g_list_free (variants);
357 
358   g_object_notify (G_OBJECT (themed), "names");
359 }
360 
361 /**
362  * g_themed_icon_new:
363  * @iconname: a string containing an icon name.
364  *
365  * Creates a new themed icon for @iconname.
366  *
367  * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon.
368  **/
369 GIcon *
g_themed_icon_new(const char * iconname)370 g_themed_icon_new (const char *iconname)
371 {
372   g_return_val_if_fail (iconname != NULL, NULL);
373 
374   return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, NULL));
375 }
376 
377 /**
378  * g_themed_icon_new_from_names:
379  * @iconnames: (array length=len): an array of strings containing icon names.
380  * @len: the length of the @iconnames array, or -1 if @iconnames is
381  *     %NULL-terminated
382  *
383  * Creates a new themed icon for @iconnames.
384  *
385  * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon
386  **/
387 GIcon *
g_themed_icon_new_from_names(char ** iconnames,int len)388 g_themed_icon_new_from_names (char **iconnames,
389                               int    len)
390 {
391   GIcon *icon;
392 
393   g_return_val_if_fail (iconnames != NULL, NULL);
394 
395   if (len >= 0)
396     {
397       char **names;
398       int i;
399 
400       names = g_new (char *, len + 1);
401 
402       for (i = 0; i < len; i++)
403         names[i] = iconnames[i];
404 
405       names[i] = NULL;
406 
407       icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", names, NULL));
408 
409       g_free (names);
410     }
411   else
412     icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", iconnames, NULL));
413 
414   return icon;
415 }
416 
417 /**
418  * g_themed_icon_new_with_default_fallbacks:
419  * @iconname: a string containing an icon name
420  *
421  * Creates a new themed icon for @iconname, and all the names
422  * that can be created by shortening @iconname at '-' characters.
423  *
424  * In the following example, @icon1 and @icon2 are equivalent:
425  * |[<!-- language="C" -->
426  * const char *names[] = {
427  *   "gnome-dev-cdrom-audio",
428  *   "gnome-dev-cdrom",
429  *   "gnome-dev",
430  *   "gnome"
431  * };
432  *
433  * icon1 = g_themed_icon_new_from_names (names, 4);
434  * icon2 = g_themed_icon_new_with_default_fallbacks ("gnome-dev-cdrom-audio");
435  * ]|
436  *
437  * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon.
438  */
439 GIcon *
g_themed_icon_new_with_default_fallbacks(const char * iconname)440 g_themed_icon_new_with_default_fallbacks (const char *iconname)
441 {
442   g_return_val_if_fail (iconname != NULL, NULL);
443 
444   return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, "use-default-fallbacks", TRUE, NULL));
445 }
446 
447 
448 /**
449  * g_themed_icon_get_names:
450  * @icon: a #GThemedIcon.
451  *
452  * Gets the names of icons from within @icon.
453  *
454  * Returns: (transfer none): a list of icon names.
455  */
456 const char * const *
g_themed_icon_get_names(GThemedIcon * icon)457 g_themed_icon_get_names (GThemedIcon *icon)
458 {
459   g_return_val_if_fail (G_IS_THEMED_ICON (icon), NULL);
460   return (const char * const *)icon->names;
461 }
462 
463 /**
464  * g_themed_icon_append_name:
465  * @icon: a #GThemedIcon
466  * @iconname: name of icon to append to list of icons from within @icon.
467  *
468  * Append a name to the list of icons from within @icon.
469  *
470  * Note that doing so invalidates the hash computed by prior calls
471  * to g_icon_hash().
472  */
473 void
g_themed_icon_append_name(GThemedIcon * icon,const char * iconname)474 g_themed_icon_append_name (GThemedIcon *icon,
475                            const char  *iconname)
476 {
477   guint num_names;
478 
479   g_return_if_fail (G_IS_THEMED_ICON (icon));
480   g_return_if_fail (iconname != NULL);
481 
482   num_names = g_strv_length (icon->init_names);
483   icon->init_names = g_realloc (icon->init_names, sizeof (char*) * (num_names + 2));
484   icon->init_names[num_names] = g_strdup (iconname);
485   icon->init_names[num_names + 1] = NULL;
486 
487   g_themed_icon_update_names (icon);
488 }
489 
490 /**
491  * g_themed_icon_prepend_name:
492  * @icon: a #GThemedIcon
493  * @iconname: name of icon to prepend to list of icons from within @icon.
494  *
495  * Prepend a name to the list of icons from within @icon.
496  *
497  * Note that doing so invalidates the hash computed by prior calls
498  * to g_icon_hash().
499  *
500  * Since: 2.18
501  */
502 void
g_themed_icon_prepend_name(GThemedIcon * icon,const char * iconname)503 g_themed_icon_prepend_name (GThemedIcon *icon,
504                             const char  *iconname)
505 {
506   guint num_names;
507   gchar **names;
508   gint i;
509 
510   g_return_if_fail (G_IS_THEMED_ICON (icon));
511   g_return_if_fail (iconname != NULL);
512 
513   num_names = g_strv_length (icon->init_names);
514   names = g_new (char*, num_names + 2);
515   for (i = 0; icon->init_names[i]; i++)
516     names[i + 1] = icon->init_names[i];
517   names[0] = g_strdup (iconname);
518   names[num_names + 1] = NULL;
519 
520   g_free (icon->init_names);
521   icon->init_names = names;
522 
523   g_themed_icon_update_names (icon);
524 }
525 
526 static guint
g_themed_icon_hash(GIcon * icon)527 g_themed_icon_hash (GIcon *icon)
528 {
529   GThemedIcon *themed = G_THEMED_ICON (icon);
530   guint hash;
531   int i;
532 
533   hash = 0;
534 
535   for (i = 0; themed->names[i] != NULL; i++)
536     hash ^= g_str_hash (themed->names[i]);
537 
538   return hash;
539 }
540 
541 static gboolean
g_themed_icon_equal(GIcon * icon1,GIcon * icon2)542 g_themed_icon_equal (GIcon *icon1,
543                      GIcon *icon2)
544 {
545   GThemedIcon *themed1 = G_THEMED_ICON (icon1);
546   GThemedIcon *themed2 = G_THEMED_ICON (icon2);
547   int i;
548 
549   for (i = 0; themed1->names[i] != NULL && themed2->names[i] != NULL; i++)
550     {
551       if (!g_str_equal (themed1->names[i], themed2->names[i]))
552 	return FALSE;
553     }
554 
555   return themed1->names[i] == NULL && themed2->names[i] == NULL;
556 }
557 
558 
559 static gboolean
g_themed_icon_to_tokens(GIcon * icon,GPtrArray * tokens,gint * out_version)560 g_themed_icon_to_tokens (GIcon *icon,
561 			 GPtrArray *tokens,
562                          gint  *out_version)
563 {
564   GThemedIcon *themed_icon = G_THEMED_ICON (icon);
565   int n;
566 
567   g_return_val_if_fail (out_version != NULL, FALSE);
568 
569   *out_version = 0;
570 
571   for (n = 0; themed_icon->names[n] != NULL; n++)
572     g_ptr_array_add (tokens,
573 		     g_strdup (themed_icon->names[n]));
574 
575   return TRUE;
576 }
577 
578 static GIcon *
g_themed_icon_from_tokens(gchar ** tokens,gint num_tokens,gint version,GError ** error)579 g_themed_icon_from_tokens (gchar  **tokens,
580                            gint     num_tokens,
581                            gint     version,
582                            GError **error)
583 {
584   GIcon *icon;
585   gchar **names;
586   int n;
587 
588   icon = NULL;
589 
590   if (version != 0)
591     {
592       g_set_error (error,
593                    G_IO_ERROR,
594                    G_IO_ERROR_INVALID_ARGUMENT,
595                    _("Can’t handle version %d of GThemedIcon encoding"),
596                    version);
597       goto out;
598     }
599 
600   names = g_new0 (gchar *, num_tokens + 1);
601   for (n = 0; n < num_tokens; n++)
602     names[n] = tokens[n];
603   names[n] = NULL;
604 
605   icon = g_themed_icon_new_from_names (names, num_tokens);
606   g_free (names);
607 
608  out:
609   return icon;
610 }
611 
612 static GVariant *
g_themed_icon_serialize(GIcon * icon)613 g_themed_icon_serialize (GIcon *icon)
614 {
615   GThemedIcon *themed_icon = G_THEMED_ICON (icon);
616 
617   return g_variant_new ("(sv)", "themed", g_variant_new ("^as", themed_icon->names));
618 }
619 
620 static void
g_themed_icon_icon_iface_init(GIconIface * iface)621 g_themed_icon_icon_iface_init (GIconIface *iface)
622 {
623   iface->hash = g_themed_icon_hash;
624   iface->equal = g_themed_icon_equal;
625   iface->to_tokens = g_themed_icon_to_tokens;
626   iface->from_tokens = g_themed_icon_from_tokens;
627   iface->serialize = g_themed_icon_serialize;
628 }
629