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