Coverage Report

Created: 2025-07-01 07:09

/src/glib/gio/gthemedicon.c
Line
Count
Source (jump to first uncovered line)
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
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
0
{
82
0
  GThemedIcon *icon = G_THEMED_ICON (object);
83
84
0
  switch (prop_id)
85
0
    {
86
0
      case PROP_NAMES:
87
0
        g_value_set_boxed (value, icon->init_names);
88
0
        break;
89
90
0
      case PROP_USE_DEFAULT_FALLBACKS:
91
0
        g_value_set_boolean (value, icon->use_default_fallbacks);
92
0
        break;
93
94
0
      default:
95
0
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96
0
    }
97
0
}
98
99
static void
100
g_themed_icon_set_property (GObject      *object,
101
                            guint         prop_id,
102
                            const GValue *value,
103
                            GParamSpec   *pspec)
104
0
{
105
0
  GThemedIcon *icon = G_THEMED_ICON (object);
106
0
  gchar **names;
107
0
  const gchar *name;
108
109
0
  switch (prop_id)
110
0
    {
111
0
      case PROP_NAME:
112
0
        name = g_value_get_string (value);
113
114
0
        if (!name)
115
0
          break;
116
117
0
        if (icon->init_names)
118
0
          g_strfreev (icon->init_names);
119
120
0
        icon->init_names = g_new (char *, 2);
121
0
        icon->init_names[0] = g_strdup (name);
122
0
        icon->init_names[1] = NULL;
123
0
        break;
124
125
0
      case PROP_NAMES:
126
0
        names = g_value_dup_boxed (value);
127
128
0
        if (!names)
129
0
          break;
130
131
0
        if (icon->init_names)
132
0
          g_strfreev (icon->init_names);
133
134
0
        icon->init_names = names;
135
0
        break;
136
137
0
      case PROP_USE_DEFAULT_FALLBACKS:
138
0
        icon->use_default_fallbacks = g_value_get_boolean (value);
139
0
        break;
140
141
0
      default:
142
0
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
143
0
    }
144
0
}
145
146
static void
147
g_themed_icon_constructed (GObject *object)
148
0
{
149
0
  g_themed_icon_update_names (G_THEMED_ICON (object));
150
0
}
151
152
static void
153
g_themed_icon_finalize (GObject *object)
154
0
{
155
0
  GThemedIcon *themed;
156
157
0
  themed = G_THEMED_ICON (object);
158
159
0
  g_strfreev (themed->init_names);
160
0
  g_strfreev (themed->names);
161
162
0
  G_OBJECT_CLASS (g_themed_icon_parent_class)->finalize (object);
163
0
}
164
165
static void
166
g_themed_icon_class_init (GThemedIconClass *klass)
167
0
{
168
0
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
169
  
170
0
  gobject_class->finalize = g_themed_icon_finalize;
171
0
  gobject_class->constructed = g_themed_icon_constructed;
172
0
  gobject_class->set_property = g_themed_icon_set_property;
173
0
  gobject_class->get_property = g_themed_icon_get_property;
174
175
  /**
176
   * GThemedIcon:name:
177
   *
178
   * The icon name.
179
   */
180
0
  g_object_class_install_property (gobject_class, PROP_NAME,
181
0
                                   g_param_spec_string ("name",
182
0
                                                        P_("name"),
183
0
                                                        P_("The name of the icon"),
184
0
                                                        NULL,
185
0
                                                        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
0
  g_object_class_install_property (gobject_class, PROP_NAMES,
193
0
                                   g_param_spec_boxed ("names",
194
0
                                                       P_("names"),
195
0
                                                       P_("An array containing the icon names"),
196
0
                                                       G_TYPE_STRV,
197
0
                                                       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
0
  g_object_class_install_property (gobject_class, PROP_USE_DEFAULT_FALLBACKS,
219
0
                                   g_param_spec_boolean ("use-default-fallbacks",
220
0
                                                         P_("use default fallbacks"),
221
0
                                                         P_("Whether to use default fallbacks found by shortening the name at “-” characters. Ignores names after the first if multiple names are given."),
222
0
                                                         FALSE,
223
0
                                                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
224
0
}
225
226
static void
227
g_themed_icon_init (GThemedIcon *themed)
228
0
{
229
0
  themed->init_names = NULL;
230
0
  themed->names      = NULL;
231
0
}
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
260
g_themed_icon_update_names (GThemedIcon *themed)
261
0
{
262
0
  GList *names    = NULL;
263
0
  GList *variants = NULL;
264
0
  GList *iter;
265
0
  guint  i;
266
267
0
  g_return_if_fail (themed->init_names != NULL && themed->init_names[0] != NULL);
268
269
0
  for (i = 0; themed->init_names[i]; i++)
270
0
    {
271
0
      gchar    *name;
272
0
      gboolean  is_symbolic;
273
274
0
      is_symbolic = g_str_has_suffix (themed->init_names[i], "-symbolic");
275
0
      if (is_symbolic)
276
0
        name = g_strndup (themed->init_names[i], strlen (themed->init_names[i]) - 9);
277
0
      else
278
0
        name = g_strdup (themed->init_names[i]);
279
280
0
      if (g_list_find_custom (names, name, (GCompareFunc) g_strcmp0))
281
0
        {
282
0
          g_free (name);
283
0
          continue;
284
0
        }
285
286
0
      if (is_symbolic)
287
0
        names = g_list_prepend (names, g_strdup (themed->init_names[i]));
288
0
      else
289
0
        names = g_list_prepend (names, name);
290
291
0
      if (themed->use_default_fallbacks)
292
0
        {
293
0
          char *dashp;
294
0
          char *last;
295
296
0
          last = name;
297
298
0
          while ((dashp = strrchr (last, '-')) != NULL)
299
0
            {
300
0
              gchar *tmp = last;
301
0
              gchar *fallback;
302
303
0
              last = g_strndup (last, dashp - last);
304
0
              if (is_symbolic)
305
0
                {
306
0
                  g_free (tmp);
307
0
                  fallback = g_strdup_printf ("%s-symbolic", last);
308
0
                }
309
0
              else
310
0
                fallback = last;
311
0
              if (g_list_find_custom (names, fallback, (GCompareFunc) g_strcmp0))
312
0
                {
313
0
                  g_free (fallback);
314
0
                  break;
315
0
                }
316
0
              names = g_list_prepend (names, fallback);
317
0
            }
318
0
          if (is_symbolic)
319
0
            g_free (last);
320
0
        }
321
0
      else if (is_symbolic)
322
0
        g_free (name);
323
0
    }
324
0
  for (iter = names; iter; iter = iter->next)
325
0
    {
326
0
      gchar    *name = (gchar *) iter->data;
327
0
      gchar    *variant;
328
0
      gboolean  is_symbolic;
329
330
0
      is_symbolic = g_str_has_suffix (name, "-symbolic");
331
0
      if (is_symbolic)
332
0
        variant = g_strndup (name, strlen (name) - 9);
333
0
      else
334
0
        variant = g_strdup_printf ("%s-symbolic", name);
335
0
      if (g_list_find_custom (names, variant, (GCompareFunc) g_strcmp0) ||
336
0
          g_list_find_custom (variants, variant, (GCompareFunc) g_strcmp0))
337
0
        {
338
0
          g_free (variant);
339
0
          continue;
340
0
        }
341
342
0
      variants = g_list_prepend (variants, variant);
343
0
    }
344
0
  names = g_list_reverse (names);
345
346
0
  g_strfreev (themed->names);
347
0
  themed->names = g_new (char *, g_list_length (names) + g_list_length (variants) + 1);
348
349
0
  for (iter = names, i = 0; iter; iter = iter->next, i++)
350
0
    themed->names[i] = iter->data;
351
0
  for (iter = variants; iter; iter = iter->next, i++)
352
0
    themed->names[i] = iter->data;
353
0
  themed->names[i] = NULL;
354
355
0
  g_list_free (names);
356
0
  g_list_free (variants);
357
358
0
  g_object_notify (G_OBJECT (themed), "names");
359
0
}
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 *
370
g_themed_icon_new (const char *iconname)
371
0
{
372
0
  g_return_val_if_fail (iconname != NULL, NULL);
373
374
0
  return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, NULL));
375
0
}
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 *
388
g_themed_icon_new_from_names (char **iconnames,
389
                              int    len)
390
0
{
391
0
  GIcon *icon;
392
393
0
  g_return_val_if_fail (iconnames != NULL, NULL);
394
395
0
  if (len >= 0)
396
0
    {
397
0
      char **names;
398
0
      int i;
399
400
0
      names = g_new (char *, len + 1);
401
402
0
      for (i = 0; i < len; i++)
403
0
        names[i] = iconnames[i];
404
405
0
      names[i] = NULL;
406
407
0
      icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", names, NULL));
408
409
0
      g_free (names);
410
0
    }
411
0
  else
412
0
    icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", iconnames, NULL));
413
414
0
  return icon;
415
0
}
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 *
440
g_themed_icon_new_with_default_fallbacks (const char *iconname)
441
0
{
442
0
  g_return_val_if_fail (iconname != NULL, NULL);
443
444
0
  return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, "use-default-fallbacks", TRUE, NULL));
445
0
}
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 *
457
g_themed_icon_get_names (GThemedIcon *icon)
458
0
{
459
0
  g_return_val_if_fail (G_IS_THEMED_ICON (icon), NULL);
460
0
  return (const char * const *)icon->names;
461
0
}
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
474
g_themed_icon_append_name (GThemedIcon *icon, 
475
                           const char  *iconname)
476
0
{
477
0
  guint num_names;
478
479
0
  g_return_if_fail (G_IS_THEMED_ICON (icon));
480
0
  g_return_if_fail (iconname != NULL);
481
482
0
  num_names = g_strv_length (icon->init_names);
483
0
  icon->init_names = g_realloc (icon->init_names, sizeof (char*) * (num_names + 2));
484
0
  icon->init_names[num_names] = g_strdup (iconname);
485
0
  icon->init_names[num_names + 1] = NULL;
486
487
0
  g_themed_icon_update_names (icon);
488
0
}
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
503
g_themed_icon_prepend_name (GThemedIcon *icon, 
504
                            const char  *iconname)
505
0
{
506
0
  guint num_names;
507
0
  gchar **names;
508
0
  gint i;
509
510
0
  g_return_if_fail (G_IS_THEMED_ICON (icon));
511
0
  g_return_if_fail (iconname != NULL);
512
513
0
  num_names = g_strv_length (icon->init_names);
514
0
  names = g_new (char*, num_names + 2);
515
0
  for (i = 0; icon->init_names[i]; i++)
516
0
    names[i + 1] = icon->init_names[i];
517
0
  names[0] = g_strdup (iconname);
518
0
  names[num_names + 1] = NULL;
519
520
0
  g_free (icon->init_names);
521
0
  icon->init_names = names;
522
523
0
  g_themed_icon_update_names (icon);
524
0
}
525
526
static guint
527
g_themed_icon_hash (GIcon *icon)
528
0
{
529
0
  GThemedIcon *themed = G_THEMED_ICON (icon);
530
0
  guint hash;
531
0
  int i;
532
533
0
  hash = 0;
534
535
0
  for (i = 0; themed->names[i] != NULL; i++)
536
0
    hash ^= g_str_hash (themed->names[i]);
537
  
538
0
  return hash;
539
0
}
540
541
static gboolean
542
g_themed_icon_equal (GIcon *icon1,
543
                     GIcon *icon2)
544
0
{
545
0
  GThemedIcon *themed1 = G_THEMED_ICON (icon1);
546
0
  GThemedIcon *themed2 = G_THEMED_ICON (icon2);
547
0
  int i;
548
549
0
  for (i = 0; themed1->names[i] != NULL && themed2->names[i] != NULL; i++)
550
0
    {
551
0
      if (!g_str_equal (themed1->names[i], themed2->names[i]))
552
0
  return FALSE;
553
0
    }
554
555
0
  return themed1->names[i] == NULL && themed2->names[i] == NULL;
556
0
}
557
558
559
static gboolean
560
g_themed_icon_to_tokens (GIcon *icon,
561
       GPtrArray *tokens,
562
                         gint  *out_version)
563
0
{
564
0
  GThemedIcon *themed_icon = G_THEMED_ICON (icon);
565
0
  int n;
566
567
0
  g_return_val_if_fail (out_version != NULL, FALSE);
568
569
0
  *out_version = 0;
570
571
0
  for (n = 0; themed_icon->names[n] != NULL; n++)
572
0
    g_ptr_array_add (tokens,
573
0
         g_strdup (themed_icon->names[n]));
574
  
575
0
  return TRUE;
576
0
}
577
578
static GIcon *
579
g_themed_icon_from_tokens (gchar  **tokens,
580
                           gint     num_tokens,
581
                           gint     version,
582
                           GError **error)
583
0
{
584
0
  GIcon *icon;
585
0
  gchar **names;
586
0
  int n;
587
588
0
  icon = NULL;
589
590
0
  if (version != 0)
591
0
    {
592
0
      g_set_error (error,
593
0
                   G_IO_ERROR,
594
0
                   G_IO_ERROR_INVALID_ARGUMENT,
595
0
                   _("Can’t handle version %d of GThemedIcon encoding"),
596
0
                   version);
597
0
      goto out;
598
0
    }
599
  
600
0
  names = g_new0 (gchar *, num_tokens + 1);
601
0
  for (n = 0; n < num_tokens; n++)
602
0
    names[n] = tokens[n];
603
0
  names[n] = NULL;
604
605
0
  icon = g_themed_icon_new_from_names (names, num_tokens);
606
0
  g_free (names);
607
608
0
 out:
609
0
  return icon;
610
0
}
611
612
static GVariant *
613
g_themed_icon_serialize (GIcon *icon)
614
0
{
615
0
  GThemedIcon *themed_icon = G_THEMED_ICON (icon);
616
617
0
  return g_variant_new ("(sv)", "themed", g_variant_new ("^as", themed_icon->names));
618
0
}
619
620
static void
621
g_themed_icon_icon_iface_init (GIconIface *iface)
622
0
{
623
0
  iface->hash = g_themed_icon_hash;
624
0
  iface->equal = g_themed_icon_equal;
625
0
  iface->to_tokens = g_themed_icon_to_tokens;
626
0
  iface->from_tokens = g_themed_icon_from_tokens;
627
0
  iface->serialize = g_themed_icon_serialize;
628
0
}