Coverage Report

Created: 2025-07-23 08:13

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