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