Coverage Report

Created: 2026-05-23 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/glib/gio/gthemedicon.c
Line
Count
Source
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
0
G_DEFINE_TYPE_WITH_CODE (GThemedIcon, g_themed_icon, G_TYPE_OBJECT,
74
0
       G_IMPLEMENT_INTERFACE (G_TYPE_ICON,
75
0
            g_themed_icon_icon_iface_init))
76
0
77
0
static void
78
0
g_themed_icon_get_property (GObject    *object,
79
0
                            guint       prop_id,
80
0
                            GValue     *value,
81
0
                            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
 *
237
 * - The first requested icon is first in priority.
238
 * - If "use-default-fallbacks" is #TRUE, then it is followed by all its
239
 *   fallbacks (starting from top to lower context levels).
240
 * - Then next requested icons, and optionally their fallbacks, follow.
241
 * - Finally all the style variants (symbolic or regular, opposite to whatever
242
 *   is the requested style) follow in the same order.
243
 *
244
 * An icon is not added twice in the list if it was previously added.
245
 *
246
 * For instance, if requested names are:
247
 * [ "some-icon-symbolic", "some-other-icon" ]
248
 * and use-default-fallbacks is TRUE, the final name list shall be:
249
 * [ "some-icon-symbolic", "some-symbolic", "some-other-icon",
250
 *   "some-other", "some", "some-icon", "some-other-icon-symbolic",
251
 *   "some-other-symbolic" ]
252
 *
253
 * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon
254
 **/
255
static void
256
g_themed_icon_update_names (GThemedIcon *themed)
257
0
{
258
0
  GList *names    = NULL;
259
0
  GList *variants = NULL;
260
0
  GList *iter;
261
0
  guint  i;
262
263
0
  g_return_if_fail (themed->init_names != NULL && themed->init_names[0] != NULL);
264
265
0
  for (i = 0; themed->init_names[i]; i++)
266
0
    {
267
0
      gchar    *name;
268
0
      gboolean  is_symbolic;
269
270
0
      is_symbolic = g_str_has_suffix (themed->init_names[i], "-symbolic");
271
0
      if (is_symbolic)
272
0
        name = g_strndup (themed->init_names[i], strlen (themed->init_names[i]) - 9);
273
0
      else
274
0
        name = g_strdup (themed->init_names[i]);
275
276
0
      if (g_list_find_custom (names, name, (GCompareFunc) g_strcmp0))
277
0
        {
278
0
          g_free (name);
279
0
          continue;
280
0
        }
281
282
0
      if (is_symbolic)
283
0
        names = g_list_prepend (names, g_strdup (themed->init_names[i]));
284
0
      else
285
0
        names = g_list_prepend (names, name);
286
287
0
      if (themed->use_default_fallbacks)
288
0
        {
289
0
          char *dashp;
290
0
          char *last;
291
292
0
          last = name;
293
294
0
          while ((dashp = strrchr (last, '-')) != NULL)
295
0
            {
296
0
              gchar *tmp = last;
297
0
              gchar *fallback;
298
299
0
              last = g_strndup (last, (size_t) (dashp - last));
300
0
              if (is_symbolic)
301
0
                {
302
0
                  g_free (tmp);
303
0
                  fallback = g_strdup_printf ("%s-symbolic", last);
304
0
                }
305
0
              else
306
0
                fallback = last;
307
0
              if (g_list_find_custom (names, fallback, (GCompareFunc) g_strcmp0))
308
0
                {
309
0
                  g_free (fallback);
310
0
                  break;
311
0
                }
312
0
              names = g_list_prepend (names, fallback);
313
0
            }
314
0
          if (is_symbolic)
315
0
            g_free (last);
316
0
        }
317
0
      else if (is_symbolic)
318
0
        g_free (name);
319
0
    }
320
0
  for (iter = names; iter; iter = iter->next)
321
0
    {
322
0
      gchar    *name = (gchar *) iter->data;
323
0
      gchar    *variant;
324
0
      gboolean  is_symbolic;
325
326
0
      is_symbolic = g_str_has_suffix (name, "-symbolic");
327
0
      if (is_symbolic)
328
0
        variant = g_strndup (name, strlen (name) - 9);
329
0
      else
330
0
        variant = g_strdup_printf ("%s-symbolic", name);
331
0
      if (g_list_find_custom (names, variant, (GCompareFunc) g_strcmp0) ||
332
0
          g_list_find_custom (variants, variant, (GCompareFunc) g_strcmp0))
333
0
        {
334
0
          g_free (variant);
335
0
          continue;
336
0
        }
337
338
0
      variants = g_list_prepend (variants, variant);
339
0
    }
340
0
  names = g_list_reverse (names);
341
342
0
  g_strfreev (themed->names);
343
0
  themed->names = g_new (char *, g_list_length (names) + g_list_length (variants) + 1);
344
345
0
  for (iter = names, i = 0; iter; iter = iter->next, i++)
346
0
    themed->names[i] = iter->data;
347
0
  for (iter = variants; iter; iter = iter->next, i++)
348
0
    themed->names[i] = iter->data;
349
0
  themed->names[i] = NULL;
350
351
0
  g_list_free (names);
352
0
  g_list_free (variants);
353
354
0
  g_object_notify (G_OBJECT (themed), "names");
355
0
}
356
357
/**
358
 * g_themed_icon_new:
359
 * @iconname: a string containing an icon name.
360
 * 
361
 * Creates a new themed icon for @iconname.
362
 * 
363
 * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon.
364
 **/
365
GIcon *
366
g_themed_icon_new (const char *iconname)
367
0
{
368
0
  g_return_val_if_fail (iconname != NULL, NULL);
369
370
0
  return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, NULL));
371
0
}
372
373
/**
374
 * g_themed_icon_new_from_names:
375
 * @iconnames: (array length=len): an array of strings containing icon names.
376
 * @len: the length of the @iconnames array, or -1 if @iconnames is 
377
 *     %NULL-terminated
378
 * 
379
 * Creates a new themed icon for @iconnames.
380
 * 
381
 * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon
382
 **/
383
GIcon *
384
g_themed_icon_new_from_names (char **iconnames,
385
                              int    len)
386
0
{
387
0
  GIcon *icon;
388
389
0
  g_return_val_if_fail (iconnames != NULL, NULL);
390
391
0
  if (len >= 0)
392
0
    {
393
0
      char **names;
394
0
      size_t i;
395
396
0
      names = g_new (char *, (size_t) len + 1);
397
398
0
      for (i = 0; i < (size_t) len; i++)
399
0
        names[i] = iconnames[i];
400
401
0
      names[i] = NULL;
402
403
0
      icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", names, NULL));
404
405
0
      g_free (names);
406
0
    }
407
0
  else
408
0
    icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", iconnames, NULL));
409
410
0
  return icon;
411
0
}
412
413
/**
414
 * g_themed_icon_new_with_default_fallbacks:
415
 * @iconname: a string containing an icon name
416
 *
417
 * Creates a new themed icon for @iconname, and all the names
418
 * that can be created by shortening @iconname at '-' characters.
419
 * 
420
 * In the following example, @icon1 and @icon2 are equivalent:
421
 * |[<!-- language="C" -->
422
 * const char *names[] = { 
423
 *   "gnome-dev-cdrom-audio",
424
 *   "gnome-dev-cdrom",
425
 *   "gnome-dev",
426
 *   "gnome"
427
 * };
428
 *
429
 * icon1 = g_themed_icon_new_from_names (names, 4);
430
 * icon2 = g_themed_icon_new_with_default_fallbacks ("gnome-dev-cdrom-audio");
431
 * ]|
432
 *
433
 * Returns: (transfer full) (type GThemedIcon): a new #GThemedIcon.
434
 */
435
GIcon *
436
g_themed_icon_new_with_default_fallbacks (const char *iconname)
437
0
{
438
0
  g_return_val_if_fail (iconname != NULL, NULL);
439
440
0
  return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, "use-default-fallbacks", TRUE, NULL));
441
0
}
442
443
444
/**
445
 * g_themed_icon_get_names:
446
 * @icon: a #GThemedIcon.
447
 *
448
 * Gets the names of icons from within @icon.
449
 *
450
 * Returns: (transfer none): a list of icon names.
451
 */
452
const char * const *
453
g_themed_icon_get_names (GThemedIcon *icon)
454
0
{
455
0
  g_return_val_if_fail (G_IS_THEMED_ICON (icon), NULL);
456
0
  return (const char * const *)icon->names;
457
0
}
458
459
/**
460
 * g_themed_icon_append_name:
461
 * @icon: a #GThemedIcon
462
 * @iconname: name of icon to append to list of icons from within @icon.
463
 *
464
 * Append a name to the list of icons from within @icon.
465
 *
466
 * Note that doing so invalidates the hash computed by prior calls
467
 * to g_icon_hash().
468
 */
469
void
470
g_themed_icon_append_name (GThemedIcon *icon, 
471
                           const char  *iconname)
472
0
{
473
0
  guint num_names;
474
475
0
  g_return_if_fail (G_IS_THEMED_ICON (icon));
476
0
  g_return_if_fail (iconname != NULL);
477
478
0
  num_names = g_strv_length (icon->init_names);
479
0
  icon->init_names = g_realloc (icon->init_names, sizeof (char*) * (num_names + 2));
480
0
  icon->init_names[num_names] = g_strdup (iconname);
481
0
  icon->init_names[num_names + 1] = NULL;
482
483
0
  g_themed_icon_update_names (icon);
484
0
}
485
486
/**
487
 * g_themed_icon_prepend_name:
488
 * @icon: a #GThemedIcon
489
 * @iconname: name of icon to prepend to list of icons from within @icon.
490
 *
491
 * Prepend a name to the list of icons from within @icon.
492
 *
493
 * Note that doing so invalidates the hash computed by prior calls
494
 * to g_icon_hash().
495
 *
496
 * Since: 2.18
497
 */
498
void
499
g_themed_icon_prepend_name (GThemedIcon *icon, 
500
                            const char  *iconname)
501
0
{
502
0
  guint num_names;
503
0
  gchar **names;
504
0
  gint i;
505
506
0
  g_return_if_fail (G_IS_THEMED_ICON (icon));
507
0
  g_return_if_fail (iconname != NULL);
508
509
0
  num_names = g_strv_length (icon->init_names);
510
0
  names = g_new (char*, num_names + 2);
511
0
  for (i = 0; icon->init_names[i]; i++)
512
0
    names[i + 1] = icon->init_names[i];
513
0
  names[0] = g_strdup (iconname);
514
0
  names[num_names + 1] = NULL;
515
516
0
  g_free (icon->init_names);
517
0
  icon->init_names = names;
518
519
0
  g_themed_icon_update_names (icon);
520
0
}
521
522
static guint
523
g_themed_icon_hash (GIcon *icon)
524
0
{
525
0
  GThemedIcon *themed = G_THEMED_ICON (icon);
526
0
  guint hash;
527
0
  int i;
528
529
0
  hash = 0;
530
531
0
  for (i = 0; themed->names[i] != NULL; i++)
532
0
    hash ^= g_str_hash (themed->names[i]);
533
  
534
0
  return hash;
535
0
}
536
537
static gboolean
538
g_themed_icon_equal (GIcon *icon1,
539
                     GIcon *icon2)
540
0
{
541
0
  GThemedIcon *themed1 = G_THEMED_ICON (icon1);
542
0
  GThemedIcon *themed2 = G_THEMED_ICON (icon2);
543
0
  int i;
544
545
0
  for (i = 0; themed1->names[i] != NULL && themed2->names[i] != NULL; i++)
546
0
    {
547
0
      if (!g_str_equal (themed1->names[i], themed2->names[i]))
548
0
  return FALSE;
549
0
    }
550
551
0
  return themed1->names[i] == NULL && themed2->names[i] == NULL;
552
0
}
553
554
555
static gboolean
556
g_themed_icon_to_tokens (GIcon *icon,
557
       GPtrArray *tokens,
558
                         gint  *out_version)
559
0
{
560
0
  GThemedIcon *themed_icon = G_THEMED_ICON (icon);
561
0
  int n;
562
563
0
  g_return_val_if_fail (out_version != NULL, FALSE);
564
565
0
  *out_version = 0;
566
567
0
  for (n = 0; themed_icon->names[n] != NULL; n++)
568
0
    g_ptr_array_add (tokens,
569
0
         g_strdup (themed_icon->names[n]));
570
  
571
0
  return TRUE;
572
0
}
573
574
static GIcon *
575
g_themed_icon_from_tokens (gchar  **tokens,
576
                           gint     num_tokens,
577
                           gint     version,
578
                           GError **error)
579
0
{
580
0
  GIcon *icon;
581
0
  gchar **names;
582
0
  size_t n;
583
584
  /* This is guaranteed by the GIcon interface */
585
0
  g_assert (num_tokens >= 0);
586
587
0
  icon = NULL;
588
589
0
  if (version != 0)
590
0
    {
591
0
      g_set_error (error,
592
0
                   G_IO_ERROR,
593
0
                   G_IO_ERROR_INVALID_ARGUMENT,
594
0
                   _("Can’t handle version %d of GThemedIcon encoding"),
595
0
                   version);
596
0
      goto out;
597
0
    }
598
  
599
0
  names = g_new0 (gchar *, (size_t) num_tokens + 1);
600
0
  for (n = 0; n < (size_t) num_tokens; n++)
601
0
    names[n] = tokens[n];
602
0
  names[n] = NULL;
603
604
0
  icon = g_themed_icon_new_from_names (names, num_tokens);
605
0
  g_free (names);
606
607
0
 out:
608
0
  return icon;
609
0
}
610
611
static GVariant *
612
g_themed_icon_serialize (GIcon *icon)
613
0
{
614
0
  GThemedIcon *themed_icon = G_THEMED_ICON (icon);
615
616
0
  return g_variant_new ("(sv)", "themed", g_variant_new ("^as", themed_icon->names));
617
0
}
618
619
static void
620
g_themed_icon_icon_iface_init (GIconIface *iface)
621
0
{
622
0
  iface->hash = g_themed_icon_hash;
623
0
  iface->equal = g_themed_icon_equal;
624
0
  iface->to_tokens = g_themed_icon_to_tokens;
625
0
  iface->from_tokens = g_themed_icon_from_tokens;
626
0
  iface->serialize = g_themed_icon_serialize;
627
0
}