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 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, write to the
17 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18 * Boston, MA 02111-1307, USA.
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#include "gioalias.h"
33
34/**
35 * SECTION:gthemedicon
36 * @short_description: Icon theming support
37 * @include: gio/gio.h
38 * @see_also: #GIcon, #GLoadableIcon
39 *
40 * #GThemedIcon is an implementation of #GIcon that supports icon themes.
41 * #GThemedIcon contains a list of all of the icons present in an icon
42 * theme, so that icons can be looked up quickly. #GThemedIcon does
43 * not provide actual pixmaps for icons, just the icon names.
44 * Ideally something like gtk_icon_theme_choose_icon() should be used to
45 * resolve the list of names so that fallback icons work nicely with
46 * themes that inherit other themes.
47 **/
48
49static void g_themed_icon_icon_iface_init (GIconIface *iface);
50
51struct _GThemedIcon
52{
53  GObject parent_instance;
54
55  char     **names;
56  gboolean   use_default_fallbacks;
57};
58
59struct _GThemedIconClass
60{
61  GObjectClass parent_class;
62};
63
64enum
65{
66  PROP_0,
67  PROP_NAME,
68  PROP_NAMES,
69  PROP_USE_DEFAULT_FALLBACKS
70};
71
72G_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
76static void
77g_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, icon->names);
88        break;
89
90      case PROP_USE_DEFAULT_FALLBACKS:
91        g_value_set_boolean (value, icon->use_default_fallbacks);
92        break;
93
94      default:
95        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96    }
97}
98
99static void
100g_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->names)
118          g_strfreev (icon->names);
119
120        icon->names = g_new (char *, 2);
121        icon->names[0] = g_strdup (name);
122        icon->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->names)
132          g_strfreev (icon->names);
133
134        icon->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
146static void
147g_themed_icon_constructed (GObject *object)
148{
149  GThemedIcon *themed = G_THEMED_ICON (object);
150
151  g_return_if_fail (themed->names != NULL && themed->names[0] != NULL);
152
153  if (themed->use_default_fallbacks)
154    {
155      int i = 0, dashes = 0;
156      const char *p;
157      char *dashp;
158      char *last;
159
160      p = themed->names[0];
161      while (*p)
162        {
163          if (*p == '-')
164            dashes++;
165          p++;
166        }
167
168      last = g_strdup (themed->names[0]);
169
170      g_strfreev (themed->names);
171
172      themed->names = g_new (char *, dashes + 1 + 1);
173      themed->names[i++] = last;
174
175      while ((dashp = strrchr (last, '-')) != NULL)
176        themed->names[i++] = last = g_strndup (last, dashp - last);
177
178      themed->names[i++] = NULL;
179    }
180}
181
182static void
183g_themed_icon_finalize (GObject *object)
184{
185  GThemedIcon *themed;
186
187  themed = G_THEMED_ICON (object);
188
189  g_strfreev (themed->names);
190
191  G_OBJECT_CLASS (g_themed_icon_parent_class)->finalize (object);
192}
193
194static void
195g_themed_icon_class_init (GThemedIconClass *klass)
196{
197  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
198
199  gobject_class->finalize = g_themed_icon_finalize;
200  gobject_class->constructed = g_themed_icon_constructed;
201  gobject_class->set_property = g_themed_icon_set_property;
202  gobject_class->get_property = g_themed_icon_get_property;
203
204  /**
205   * GThemedIcon:name:
206   *
207   * The icon name.
208   */
209  g_object_class_install_property (gobject_class, PROP_NAME,
210                                   g_param_spec_string ("name",
211                                                        _("name"),
212                                                        _("The name of the icon"),
213                                                        NULL,
214                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
215
216  /**
217   * GThemedIcon:names:
218   *
219   * A %NULL-terminated array of icon names.
220   */
221  g_object_class_install_property (gobject_class, PROP_NAMES,
222                                   g_param_spec_boxed ("names",
223                                                       _("names"),
224                                                       _("An array containing the icon names"),
225                                                       G_TYPE_STRV,
226                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
227
228  /**
229   * GThemedIcon:use-default-fallbacks:
230   *
231   * Whether to use the default fallbacks found by shortening the icon name
232   * at '-' characters. If the "names" array has more than one element,
233   * ignores any past the first.
234   *
235   * For example, if the icon name was "gnome-dev-cdrom-audio", the array
236   * would become
237   * |[
238   * {
239   *   "gnome-dev-cdrom-audio",
240   *   "gnome-dev-cdrom",
241   *   "gnome-dev",
242   *   "gnome",
243   *   NULL
244   * };
245   * ]|
246   */
247  g_object_class_install_property (gobject_class, PROP_USE_DEFAULT_FALLBACKS,
248                                   g_param_spec_boolean ("use-default-fallbacks",
249                                                         _("use default fallbacks"),
250                                                         _("Whether to use default fallbacks found by shortening the name at '-' characters. Ignores names after the first if multiple names are given."),
251                                                         FALSE,
252                                                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
253}
254
255static void
256g_themed_icon_init (GThemedIcon *themed)
257{
258  themed->names = NULL;
259}
260
261/**
262 * g_themed_icon_new:
263 * @iconname: a string containing an icon name.
264 *
265 * Creates a new themed icon for @iconname.
266 *
267 * Returns: a new #GThemedIcon.
268 **/
269GIcon *
270g_themed_icon_new (const char *iconname)
271{
272  g_return_val_if_fail (iconname != NULL, NULL);
273
274  return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, NULL));
275}
276
277/**
278 * g_themed_icon_new_from_names:
279 * @iconnames: an array of strings containing icon names.
280 * @len: the length of the @iconnames array, or -1 if @iconnames is
281 *     %NULL-terminated
282 *
283 * Creates a new themed icon for @iconnames.
284 *
285 * Returns: a new #GThemedIcon
286 **/
287GIcon *
288g_themed_icon_new_from_names (char **iconnames,
289                              int    len)
290{
291  GIcon *icon = icon;
292
293  g_return_val_if_fail (iconnames != NULL, NULL);
294
295  if (len >= 0)
296    {
297      char **names;
298      int i;
299
300      names = g_new (char *, len + 1);
301
302      for (i = 0; i < len; i++)
303        names[i] = iconnames[i];
304
305      names[i] = NULL;
306
307      icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", names, NULL));
308
309      g_free (names);
310    }
311  else
312    icon = G_ICON (g_object_new (G_TYPE_THEMED_ICON, "names", iconnames, NULL));
313
314  return icon;
315}
316
317/**
318 * g_themed_icon_new_with_default_fallbacks:
319 * @iconname: a string containing an icon name
320 *
321 * Creates a new themed icon for @iconname, and all the names
322 * that can be created by shortening @iconname at '-' characters.
323 *
324 * In the following example, @icon1 and @icon2 are equivalent:
325 * |[
326 * const char *names[] = {
327 *   "gnome-dev-cdrom-audio",
328 *   "gnome-dev-cdrom",
329 *   "gnome-dev",
330 *   "gnome"
331 * };
332 *
333 * icon1 = g_themed_icon_new_from_names (names, 4);
334 * icon2 = g_themed_icon_new_with_default_fallbacks ("gnome-dev-cdrom-audio");
335 * ]|
336 *
337 * Returns: a new #GThemedIcon.
338 */
339GIcon *
340g_themed_icon_new_with_default_fallbacks (const char *iconname)
341{
342  g_return_val_if_fail (iconname != NULL, NULL);
343
344  return G_ICON (g_object_new (G_TYPE_THEMED_ICON, "name", iconname, "use-default-fallbacks", TRUE, NULL));
345}
346
347
348/**
349 * g_themed_icon_get_names:
350 * @icon: a #GThemedIcon.
351 *
352 * Gets the names of icons from within @icon.
353 *
354 * Returns: a list of icon names.
355 **/
356const char * const *
357g_themed_icon_get_names (GThemedIcon *icon)
358{
359  g_return_val_if_fail (G_IS_THEMED_ICON (icon), NULL);
360  return (const char * const *)icon->names;
361}
362
363/**
364 * g_themed_icon_append_name:
365 * @icon: a #GThemedIcon
366 * @iconname: name of icon to append to list of icons from within @icon.
367 *
368 * Append a name to the list of icons from within @icon.
369 *
370 * <note><para>
371 * Note that doing so invalidates the hash computed by prior calls
372 * to g_icon_hash().
373 * </para></note>
374 */
375void
376g_themed_icon_append_name (GThemedIcon *icon,
377                           const char  *iconname)
378{
379  guint num_names;
380
381  g_return_if_fail (G_IS_THEMED_ICON (icon));
382  g_return_if_fail (iconname != NULL);
383
384  num_names = g_strv_length (icon->names);
385  icon->names = g_realloc (icon->names, sizeof (char*) * (num_names + 2));
386  icon->names[num_names] = g_strdup (iconname);
387  icon->names[num_names + 1] = NULL;
388
389  g_object_notify (G_OBJECT (icon), "names");
390}
391
392/**
393 * g_themed_icon_prepend_name:
394 * @icon: a #GThemedIcon
395 * @iconname: name of icon to prepend to list of icons from within @icon.
396 *
397 * Prepend a name to the list of icons from within @icon.
398 *
399 * <note><para>
400 * Note that doing so invalidates the hash computed by prior calls
401 * to g_icon_hash().
402 * </para></note>
403 *
404 * Since: 2.18
405 */
406void
407g_themed_icon_prepend_name (GThemedIcon *icon,
408                            const char  *iconname)
409{
410  guint num_names;
411  gchar **names;
412  gint i;
413
414  g_return_if_fail (G_IS_THEMED_ICON (icon));
415  g_return_if_fail (iconname != NULL);
416
417  num_names = g_strv_length (icon->names);
418  names = g_new (char*, num_names + 2);
419  for (i = 0; icon->names[i]; i++)
420    names[i + 1] = icon->names[i];
421  names[0] = g_strdup (iconname);
422  names[num_names + 1] = NULL;
423
424  g_free (icon->names);
425  icon->names = names;
426
427  g_object_notify (G_OBJECT (icon), "names");
428}
429
430static guint
431g_themed_icon_hash (GIcon *icon)
432{
433  GThemedIcon *themed = G_THEMED_ICON (icon);
434  guint hash;
435  int i;
436
437  hash = 0;
438
439  for (i = 0; themed->names[i] != NULL; i++)
440    hash ^= g_str_hash (themed->names[i]);
441
442  return hash;
443}
444
445static gboolean
446g_themed_icon_equal (GIcon *icon1,
447                     GIcon *icon2)
448{
449  GThemedIcon *themed1 = G_THEMED_ICON (icon1);
450  GThemedIcon *themed2 = G_THEMED_ICON (icon2);
451  int i;
452
453  for (i = 0; themed1->names[i] != NULL && themed2->names[i] != NULL; i++)
454    {
455      if (!g_str_equal (themed1->names[i], themed2->names[i]))
456	return FALSE;
457    }
458
459  return themed1->names[i] == NULL && themed2->names[i] == NULL;
460}
461
462
463static gboolean
464g_themed_icon_to_tokens (GIcon *icon,
465			 GPtrArray *tokens,
466                         gint  *out_version)
467{
468  GThemedIcon *themed_icon = G_THEMED_ICON (icon);
469  int n;
470
471  g_return_val_if_fail (out_version != NULL, FALSE);
472
473  *out_version = 0;
474
475  for (n = 0; themed_icon->names[n] != NULL; n++)
476    g_ptr_array_add (tokens,
477		     g_strdup (themed_icon->names[n]));
478
479  return TRUE;
480}
481
482static GIcon *
483g_themed_icon_from_tokens (gchar  **tokens,
484                           gint     num_tokens,
485                           gint     version,
486                           GError **error)
487{
488  GIcon *icon;
489  gchar **names;
490  int n;
491
492  icon = NULL;
493
494  if (version != 0)
495    {
496      g_set_error (error,
497                   G_IO_ERROR,
498                   G_IO_ERROR_INVALID_ARGUMENT,
499                   _("Can't handle version %d of GThemedIcon encoding"),
500                   version);
501      goto out;
502    }
503
504  names = g_new0 (gchar *, num_tokens + 1);
505  for (n = 0; n < num_tokens; n++)
506    names[n] = tokens[n];
507  names[n] = NULL;
508
509  icon = g_themed_icon_new_from_names (names, num_tokens);
510  g_free (names);
511
512 out:
513  return icon;
514}
515
516static void
517g_themed_icon_icon_iface_init (GIconIface *iface)
518{
519  iface->hash = g_themed_icon_hash;
520  iface->equal = g_themed_icon_equal;
521  iface->to_tokens = g_themed_icon_to_tokens;
522  iface->from_tokens = g_themed_icon_from_tokens;
523}
524
525#define __G_THEMED_ICON_C__
526#include "gioaliasdef.c"
527