Coverage Report

Created: 2025-06-13 06:55

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