]> Pileus Git - ~andy/gtk/blob - gtk/gtkiconfactory.c
Move more text widget headers into the private header list
[~andy/gtk] / gtk / gtkiconfactory.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include "gtkiconfactory.h"
28 #include "stock-icons/gtkstockpixbufs.h"
29 #include "gtkstock.h"
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <ctype.h>
33 #include <string.h>
34
35 static gpointer parent_class = NULL;
36
37 static void gtk_icon_factory_init       (GtkIconFactory      *icon_factory);
38 static void gtk_icon_factory_class_init (GtkIconFactoryClass *klass);
39 static void gtk_icon_factory_finalize   (GObject             *object);
40 static void get_default_icons           (GtkIconFactory      *icon_factory);
41
42 GType
43 gtk_icon_factory_get_type (void)
44 {
45   static GType object_type = 0;
46
47   if (!object_type)
48     {
49       static const GTypeInfo object_info =
50       {
51         sizeof (GtkIconFactoryClass),
52         (GBaseInitFunc) NULL,
53         (GBaseFinalizeFunc) NULL,
54         (GClassInitFunc) gtk_icon_factory_class_init,
55         NULL,           /* class_finalize */
56         NULL,           /* class_data */
57         sizeof (GtkIconFactory),
58         0,              /* n_preallocs */
59         (GInstanceInitFunc) gtk_icon_factory_init,
60       };
61       
62       object_type = g_type_register_static (G_TYPE_OBJECT,
63                                             "GtkIconFactory",
64                                             &object_info);
65     }
66   
67   return object_type;
68 }
69
70 static void
71 gtk_icon_factory_init (GtkIconFactory *factory)
72 {
73   factory->icons = g_hash_table_new (g_str_hash, g_str_equal);
74 }
75
76 static void
77 gtk_icon_factory_class_init (GtkIconFactoryClass *klass)
78 {
79   GObjectClass *object_class = G_OBJECT_CLASS (klass);
80   
81   parent_class = g_type_class_peek_parent (klass);
82
83   object_class->finalize = gtk_icon_factory_finalize;
84 }
85
86 static void
87 free_icon_set (gpointer key, gpointer value, gpointer data)
88 {
89   g_free (key);
90   gtk_icon_set_unref (value);
91 }
92
93 static void
94 gtk_icon_factory_finalize (GObject *object)
95 {
96   GtkIconFactory *factory = GTK_ICON_FACTORY (object);
97
98   g_hash_table_foreach (factory->icons, free_icon_set, NULL);
99   
100   g_hash_table_destroy (factory->icons);
101   
102   G_OBJECT_CLASS (parent_class)->finalize (object);
103 }
104
105 GtkIconFactory*
106 gtk_icon_factory_new (void)
107 {
108   return GTK_ICON_FACTORY (g_object_new (GTK_TYPE_ICON_FACTORY, NULL));
109 }
110
111 void
112 gtk_icon_factory_add (GtkIconFactory *factory,
113                       const gchar    *stock_id,
114                       GtkIconSet     *icon_set)
115 {
116   gpointer old_key = NULL;
117   gpointer old_value = NULL;
118
119   g_return_if_fail (GTK_IS_ICON_FACTORY (factory));
120   g_return_if_fail (stock_id != NULL);
121   g_return_if_fail (icon_set != NULL);  
122
123   g_hash_table_lookup_extended (factory->icons, stock_id,
124                                 &old_key, &old_value);
125
126   if (old_value == icon_set)
127     return;
128   
129   gtk_icon_set_ref (icon_set);
130
131   /* GHashTable key memory management is so fantastically broken. */
132   if (old_key)
133     g_hash_table_insert (factory->icons, old_key, icon_set);
134   else
135     g_hash_table_insert (factory->icons, g_strdup (stock_id), icon_set);
136
137   if (old_value)
138     gtk_icon_set_unref (old_value);
139 }
140
141 GtkIconSet *
142 gtk_icon_factory_lookup (GtkIconFactory *factory,
143                          const gchar    *stock_id)
144 {
145   g_return_val_if_fail (GTK_IS_ICON_FACTORY (factory), NULL);
146   g_return_val_if_fail (stock_id != NULL, NULL);
147   
148   return g_hash_table_lookup (factory->icons, stock_id);
149 }
150
151 static GtkIconFactory *gtk_default_icons = NULL;
152 static GSList *default_factories = NULL;
153
154 void
155 gtk_icon_factory_add_default (GtkIconFactory *factory)
156 {
157   g_return_if_fail (GTK_IS_ICON_FACTORY (factory));
158
159   g_object_ref (G_OBJECT (factory));
160   
161   default_factories = g_slist_prepend (default_factories, factory);
162 }
163
164 void
165 gtk_icon_factory_remove_default (GtkIconFactory  *factory)
166 {
167   g_return_if_fail (GTK_IS_ICON_FACTORY (factory));
168
169   default_factories = g_slist_remove (default_factories, factory);
170
171   g_object_unref (G_OBJECT (factory));
172 }
173
174 GtkIconSet *
175 gtk_icon_factory_lookup_default (const gchar *stock_id)
176 {
177   GSList *tmp_list;
178
179   g_return_val_if_fail (stock_id != NULL, NULL);
180   
181   tmp_list = default_factories;
182   while (tmp_list != NULL)
183     {
184       GtkIconSet *icon_set =
185         gtk_icon_factory_lookup (GTK_ICON_FACTORY (tmp_list->data),
186                                  stock_id);
187
188       if (icon_set)
189         return icon_set;
190
191       tmp_list = g_slist_next (tmp_list);
192     }
193
194   if (gtk_default_icons == NULL)
195     {
196       gtk_default_icons = gtk_icon_factory_new ();
197
198       get_default_icons (gtk_default_icons);
199     }
200   
201   return gtk_icon_factory_lookup (gtk_default_icons, stock_id);
202 }
203
204 static GtkIconSet *
205 sized_icon_set_from_inline (const guchar *inline_data,
206                             const gchar  *size)
207 {
208   GtkIconSet *set;
209
210   GtkIconSource source = { NULL, NULL, 0, 0, NULL,
211                            TRUE, TRUE, FALSE };
212
213   source.size = size;
214
215   set = gtk_icon_set_new ();
216
217   source.pixbuf = gdk_pixbuf_new_from_inline (inline_data, FALSE, -1);
218
219   g_assert (source.pixbuf);
220
221   gtk_icon_set_add_source (set, &source);
222
223   g_object_unref (G_OBJECT (source.pixbuf));
224   
225   return set;
226 }
227
228 static GtkIconSet *
229 unsized_icon_set_from_inline (const guchar *inline_data)
230 {
231   GtkIconSet *set;
232
233   /* This icon can be used for any direction/state/size */
234   GtkIconSource source = { NULL, NULL, 0, 0, 0,
235                            TRUE, TRUE, TRUE };
236
237   set = gtk_icon_set_new ();
238
239   source.pixbuf = gdk_pixbuf_new_from_inline (inline_data, FALSE, -1);
240
241   g_assert (source.pixbuf);
242
243   gtk_icon_set_add_source (set, &source);
244
245   g_object_unref (G_OBJECT (source.pixbuf));
246   
247   return set;
248 }
249
250 static void
251 add_sized (GtkIconFactory *factory,
252            const guchar   *inline_data,
253            const gchar    *size,
254            const gchar    *stock_id)
255 {
256   GtkIconSet *set;
257   
258   set = sized_icon_set_from_inline (inline_data, size);
259   
260   gtk_icon_factory_add (factory, stock_id, set);
261
262   gtk_icon_set_unref (set);
263 }
264
265 static void
266 add_unsized (GtkIconFactory *factory,
267              const guchar   *inline_data,
268              const gchar    *stock_id)
269 {
270   GtkIconSet *set;
271   
272   set = unsized_icon_set_from_inline (inline_data);
273   
274   gtk_icon_factory_add (factory, stock_id, set);
275
276   gtk_icon_set_unref (set);
277 }
278
279 static void
280 get_default_icons (GtkIconFactory *factory)
281 {
282   /* KEEP IN SYNC with gtkstock.c */
283
284   add_sized (factory, dialog_error, GTK_ICON_SIZE_DIALOG, GTK_STOCK_DIALOG_ERROR);
285   add_sized (factory, dialog_info, GTK_ICON_SIZE_DIALOG, GTK_STOCK_DIALOG_INFO);
286   add_sized (factory, dialog_question, GTK_ICON_SIZE_DIALOG, GTK_STOCK_DIALOG_QUESTION);
287   add_sized (factory, dialog_warning, GTK_ICON_SIZE_DIALOG, GTK_STOCK_DIALOG_WARNING);
288
289   add_sized (factory, stock_button_apply, GTK_ICON_SIZE_BUTTON, GTK_STOCK_BUTTON_APPLY);
290   add_sized (factory, stock_button_ok, GTK_ICON_SIZE_BUTTON, GTK_STOCK_BUTTON_OK);
291   add_sized (factory, stock_button_cancel, GTK_ICON_SIZE_BUTTON, GTK_STOCK_BUTTON_CANCEL);
292   add_sized (factory, stock_button_close, GTK_ICON_SIZE_BUTTON, GTK_STOCK_BUTTON_CLOSE);
293   add_sized (factory, stock_button_yes, GTK_ICON_SIZE_BUTTON, GTK_STOCK_BUTTON_YES);
294   add_sized (factory, stock_button_no, GTK_ICON_SIZE_BUTTON, GTK_STOCK_BUTTON_NO);
295     
296   add_unsized (factory, stock_close, GTK_STOCK_CLOSE);
297   add_unsized (factory, stock_exit, GTK_STOCK_QUIT);
298   add_unsized (factory, stock_help, GTK_STOCK_HELP);
299   add_unsized (factory, stock_new, GTK_STOCK_NEW);
300   add_unsized (factory, stock_open, GTK_STOCK_OPEN);
301   add_unsized (factory, stock_save, GTK_STOCK_SAVE);
302 }
303
304 /* Sizes */
305
306 static GHashTable *icon_sizes = NULL;
307
308 typedef struct _IconSize IconSize;
309
310 struct _IconSize
311 {
312   gchar *name;
313   
314   gboolean is_alias;
315
316   union
317   {
318     gchar *target;
319     struct
320     {
321       gint width;
322       gint height;
323     } size;
324   } d;
325 };
326
327 static IconSize*
328 icon_size_new (const gchar *name)
329 {
330   IconSize *is;
331
332   is = g_new0 (IconSize, 1);
333
334   is->name = g_strdup (name);
335
336   return is;
337 }
338
339 static void
340 icon_size_free (IconSize *is)
341 {
342   g_free (is->name);
343   
344   if (is->is_alias)
345     g_free (is->d.target);
346
347   g_free (is);
348 }
349
350 static void
351 icon_size_insert (IconSize *is)
352 {
353   gpointer old_key, old_value;
354
355   /* Remove old ones */
356   if (g_hash_table_lookup_extended (icon_sizes,
357                                     is->name,
358                                     &old_key, &old_value))
359     {
360       g_hash_table_remove (icon_sizes, is->name);
361       icon_size_free (old_value);
362     }
363   
364   g_hash_table_insert (icon_sizes,
365                        is->name, is);
366
367 }
368
369 static IconSize*
370 icon_size_lookup (const gchar *name)
371 {
372   IconSize *is;
373
374   is = g_hash_table_lookup (icon_sizes,
375                             name);
376
377   while (is && is->is_alias)
378     {
379       is = g_hash_table_lookup (icon_sizes,
380                                 is->d.target);
381
382     }
383
384   return is;
385 }
386
387 static void
388 icon_size_add (const gchar *name,
389                gint         width,
390                gint         height)
391 {
392   IconSize *is;
393   
394   is = icon_size_new (name);
395   is->d.size.width = width;
396   is->d.size.height = height;
397   
398   icon_size_insert (is);
399 }
400
401 static void
402 icon_alias_add (const gchar *name,
403                 const gchar *target)
404 {
405   IconSize *is;
406   
407   is = icon_size_new (name);
408   is->is_alias = TRUE;
409
410   is->d.target = g_strdup (target);
411
412   icon_size_insert (is);
413 }
414
415 static void
416 init_icon_sizes (void)
417 {
418   if (icon_sizes == NULL)
419     {
420       IconSize *is;
421       
422       icon_sizes = g_hash_table_new (g_str_hash, g_str_equal);
423
424       icon_size_add (GTK_ICON_SIZE_MENU, 16, 16);
425       icon_size_add (GTK_ICON_SIZE_BUTTON, 24, 24);
426       icon_size_add (GTK_ICON_SIZE_SMALL_TOOLBAR, 18, 18);
427       icon_size_add (GTK_ICON_SIZE_LARGE_TOOLBAR, 24, 24);
428       icon_size_add (GTK_ICON_SIZE_DIALOG, 48, 48);
429     }
430 }
431
432 gboolean
433 gtk_icon_size_lookup (const gchar *alias,
434                       gint        *widthp,
435                       gint        *heightp)
436 {
437   IconSize *is;
438   
439   g_return_val_if_fail (alias != NULL, FALSE);
440   
441   init_icon_sizes ();
442   
443   is = icon_size_lookup (alias);
444
445   if (is == NULL)
446     return FALSE;
447
448   if (widthp)
449     *widthp = is->d.size.width;
450
451   if (heightp)
452     *heightp = is->d.size.height;
453
454   return TRUE;
455 }
456
457 void
458 gtk_icon_size_register (const gchar *alias,
459                         gint         width,
460                         gint         height)
461 {
462   gpointer old_key, old_value;
463   
464   g_return_if_fail (alias != NULL);
465   g_return_if_fail (width > 0);
466   g_return_if_fail (height > 0);
467   
468   init_icon_sizes ();
469
470   icon_size_add (alias, width, height);
471 }
472
473 void
474 gtk_icon_size_register_alias (const gchar *alias,
475                               const gchar *target)
476 {
477   g_return_if_fail (alias != NULL);
478   g_return_if_fail (target != NULL);
479
480   init_icon_sizes ();
481
482   icon_alias_add (alias, target);
483 }
484
485 /* Icon Set */
486
487
488 /* Clear icon set contents, drop references to all contained
489  * GdkPixbuf objects and forget all GtkIconSources. Used to
490  * recycle an icon set.
491  */
492 static void gtk_icon_set_clear      (GtkIconSet       *icon_set);
493
494 static GdkPixbuf *find_in_cache     (GtkIconSet       *icon_set,
495                                      GtkStyle         *style,
496                                      GtkTextDirection  direction,
497                                      GtkStateType      state,
498                                      const gchar      *size);
499 static void       add_to_cache      (GtkIconSet       *icon_set,
500                                      GtkStyle         *style,
501                                      GtkTextDirection  direction,
502                                      GtkStateType      state,
503                                      const gchar      *size,
504                                      GdkPixbuf        *pixbuf);
505 static void       clear_cache       (GtkIconSet       *icon_set,
506                                      gboolean          style_detach);
507 static GSList*    copy_cache        (GtkIconSet       *icon_set,
508                                      GtkIconSet       *copy_recipient);
509 static void       attach_to_style   (GtkIconSet       *icon_set,
510                                      GtkStyle         *style);
511 static void       detach_from_style (GtkIconSet       *icon_set,
512                                      GtkStyle         *style);
513 static void       style_dnotify     (gpointer          data);
514
515 struct _GtkIconSet
516 {
517   guint ref_count;
518
519   GSList *sources;
520
521   /* Cache of the last few rendered versions of the icon. */
522   GSList *cache;
523
524   guint cache_size;
525
526   guint cache_serial;
527 };
528
529 static guint cache_serial = 0;
530
531 GtkIconSet*
532 gtk_icon_set_new (void)
533 {
534   GtkIconSet *icon_set;
535
536   icon_set = g_new (GtkIconSet, 1);
537
538   icon_set->ref_count = 1;
539   icon_set->sources = NULL;
540   icon_set->cache = NULL;
541   icon_set->cache_size = 0;
542   icon_set->cache_serial = cache_serial;
543   
544   return icon_set;
545 }
546
547 GtkIconSet*
548 gtk_icon_set_ref (GtkIconSet *icon_set)
549 {
550   g_return_val_if_fail (icon_set != NULL, NULL);
551   g_return_val_if_fail (icon_set->ref_count > 0, NULL);
552
553   icon_set->ref_count += 1;
554
555   return icon_set;
556 }
557
558 void
559 gtk_icon_set_unref (GtkIconSet *icon_set)
560 {
561   g_return_if_fail (icon_set != NULL);
562   g_return_if_fail (icon_set->ref_count > 0);
563
564   icon_set->ref_count -= 1;
565
566   if (icon_set->ref_count == 0)
567     {
568       GSList *tmp_list = icon_set->sources;
569       while (tmp_list != NULL)
570         {
571           gtk_icon_source_free (tmp_list->data);
572
573           tmp_list = g_slist_next (tmp_list);
574         }
575
576       clear_cache (icon_set, TRUE);
577
578       g_free (icon_set);
579     }
580 }
581
582 GtkIconSet*
583 gtk_icon_set_copy (GtkIconSet *icon_set)
584 {
585   GtkIconSet *copy;
586   GSList *tmp_list;
587   
588   copy = gtk_icon_set_new ();
589
590   tmp_list = icon_set->sources;
591   while (tmp_list != NULL)
592     {
593       copy->sources = g_slist_prepend (copy->sources,
594                                        gtk_icon_source_copy (tmp_list->data));
595
596       tmp_list = g_slist_next (tmp_list);
597     }
598
599   copy->sources = g_slist_reverse (copy->sources);
600
601   copy->cache = copy_cache (icon_set, copy);
602   copy->cache_size = icon_set->cache_size;
603   copy->cache_serial = icon_set->cache_serial;
604   
605   return copy;
606 }
607
608
609 static gboolean
610 sizes_equivalent (const gchar *rhs, const gchar *lhs)
611 {
612   gint r_w, r_h, l_w, l_h;
613
614   gtk_icon_size_lookup (rhs, &r_w, &r_h);
615   gtk_icon_size_lookup (lhs, &l_w, &l_h);
616
617   return r_w == l_w && r_h == l_h;
618 }
619
620 static GtkIconSource*
621 find_and_prep_icon_source (GtkIconSet       *icon_set,
622                            GtkTextDirection  direction,
623                            GtkStateType      state,
624                            const gchar      *size)
625 {
626   GtkIconSource *source;
627   GSList *tmp_list;
628
629
630   /* We need to find the best icon source.  Direction matters more
631    * than state, state matters more than size. icon_set->sources
632    * is sorted according to wildness, so if we take the first
633    * match we find it will be the least-wild match (if there are
634    * multiple matches for a given "wildness" then the RC file contained
635    * dumb stuff, and we end up with an arbitrary matching source)
636    */
637   
638   source = NULL;
639   tmp_list = icon_set->sources;
640   while (tmp_list != NULL)
641     {
642       GtkIconSource *s = tmp_list->data;
643       
644       if ((s->any_direction || (s->direction == direction)) &&
645           (s->any_state || (s->state == state)) &&
646           (s->any_size || (sizes_equivalent (size, s->size))))
647         {
648           source = s;
649           break;
650         }
651       
652       tmp_list = g_slist_next (tmp_list);
653     }
654
655   if (source == NULL)
656     return NULL;
657   
658   if (source->pixbuf == NULL)
659     {
660       gchar *full;
661       
662       g_assert (source->filename);
663
664       if (*source->filename != G_DIR_SEPARATOR)
665         full = gtk_rc_find_pixmap_in_path (NULL, source->filename);
666       else
667         full = g_strdup (source->filename);
668       
669       source->pixbuf = gdk_pixbuf_new_from_file (full);
670
671       g_free (full);
672       
673       if (source->pixbuf == NULL)
674         {
675           /* Remove this icon source so we don't keep trying to
676            * load it.
677            */
678           g_warning ("Failed to load icon '%s'", source->filename);
679           
680           icon_set->sources = g_slist_remove (icon_set->sources, source);
681
682           gtk_icon_source_free (source);
683
684           /* Try to fall back to other sources */
685           if (icon_set->sources != NULL)
686             return find_and_prep_icon_source (icon_set,
687                                               direction,
688                                               state,
689                                               size);
690           else
691             return NULL;
692         }
693     }
694
695   return source;
696 }
697
698 GdkPixbuf*
699 gtk_icon_set_render_icon (GtkIconSet        *icon_set,
700                           GtkStyle          *style,
701                           GtkTextDirection   direction,
702                           GtkStateType       state,
703                           const gchar       *size,
704                           GtkWidget         *widget,
705                           const char        *detail)
706 {
707   GdkPixbuf *icon;
708   GtkIconSource *source;
709   
710   /* FIXME conceivably, everywhere this function
711    * returns NULL, we should return some default
712    * dummy icon like a question mark or the image icon
713    * from netscape
714    */
715   
716   g_return_val_if_fail (icon_set != NULL, NULL);
717   g_return_val_if_fail (GTK_IS_STYLE (style), NULL);
718
719   if (icon_set->sources == NULL)
720     return NULL;
721   
722   icon = find_in_cache (icon_set, style, direction,
723                         state, size);
724
725   if (icon)
726     {
727       g_object_ref (G_OBJECT (icon));
728       return icon;
729     }
730
731   
732   source = find_and_prep_icon_source (icon_set, direction, state, size);
733
734   if (source == NULL)
735     return NULL;
736
737   g_assert (source->pixbuf != NULL);
738   
739   icon = gtk_style_render_icon (style,
740                                 source,
741                                 direction,
742                                 state,
743                                 size,
744                                 widget,
745                                 detail);
746
747   if (icon == NULL)
748     {
749       g_warning ("Theme engine failed to render icon");
750       return NULL;
751     }
752   
753   add_to_cache (icon_set, style, direction, state, size, icon);
754   
755   return icon;
756 }
757
758 /* Order sources by their "wildness", so that "wilder" sources are
759  * greater than "specific" sources; for determining ordering,
760  * direction beats state beats size.
761  */
762
763 static int
764 icon_source_compare (gconstpointer ap, gconstpointer bp)
765 {
766   const GtkIconSource *a = ap;
767   const GtkIconSource *b = bp;
768
769   if (!a->any_direction && b->any_direction)
770     return -1;
771   else if (a->any_direction && !b->any_direction)
772     return 1;
773   else if (!a->any_state && b->any_state)
774     return -1;
775   else if (a->any_state && !b->any_state)
776     return 1;
777   else if (!a->any_size && b->any_size)
778     return -1;
779   else if (a->any_size && !b->any_size)
780     return 1;
781   else
782     return 0;
783 }
784
785 void
786 gtk_icon_set_add_source (GtkIconSet *icon_set,
787                          const GtkIconSource *source)
788 {
789   g_return_if_fail (icon_set != NULL);
790   g_return_if_fail (source != NULL);
791
792   if (source->pixbuf == NULL &&
793       source->filename == NULL)
794     {
795       g_warning ("Useless GtkIconSource contains NULL filename and pixbuf");
796       return;
797     }
798   
799   icon_set->sources = g_slist_insert_sorted (icon_set->sources,
800                                              gtk_icon_source_copy (source),
801                                              icon_source_compare);
802 }
803
804 GtkIconSource *
805 gtk_icon_source_copy (const GtkIconSource *source)
806 {
807   GtkIconSource *copy;
808   
809   g_return_val_if_fail (source != NULL, NULL);
810
811   copy = g_new (GtkIconSource, 1);
812
813   *copy = *source;
814   
815   copy->filename = g_strdup (source->filename);
816   copy->size = g_strdup (source->size);
817   if (copy->pixbuf)
818     g_object_ref (G_OBJECT (copy->pixbuf));
819
820   return copy;
821 }
822
823 void
824 gtk_icon_source_free (GtkIconSource *source)
825 {
826   g_return_if_fail (source != NULL);
827
828   g_free ((char*) source->filename);
829   g_free ((char*) source->size);
830   if (source->pixbuf)
831     g_object_unref (G_OBJECT (source->pixbuf));
832
833   g_free (source);
834 }
835
836 void
837 gtk_icon_set_clear (GtkIconSet *icon_set)
838 {
839   GSList *tmp_list;
840
841   g_return_if_fail (icon_set != NULL);
842   
843   tmp_list = icon_set->sources;
844   while (tmp_list != NULL)
845     {
846       gtk_icon_source_free (tmp_list->data);
847
848       tmp_list = g_slist_next (tmp_list);
849     }
850
851   clear_cache (icon_set, TRUE);
852 }
853
854 /* Note that the logical maximum is 20 per GtkTextDirection, so we could
855  * eventually set this to >20 to never throw anything out.
856  */
857 #define NUM_CACHED_ICONS 8
858
859 typedef struct _CachedIcon CachedIcon;
860
861 struct _CachedIcon
862 {
863   /* These must all match to use the cached pixbuf.
864    * If any don't match, we must re-render the pixbuf.
865    */
866   GtkStyle *style;
867   GtkTextDirection direction;
868   GtkStateType state;
869   gchar *size;
870
871   GdkPixbuf *pixbuf;
872 };
873
874 static void
875 ensure_cache_up_to_date (GtkIconSet *icon_set)
876 {
877   if (icon_set->cache_serial != cache_serial)
878     clear_cache (icon_set, TRUE);
879 }
880
881 static void
882 cached_icon_free (CachedIcon *icon)
883 {
884   g_free (icon->size);
885   g_object_unref (G_OBJECT (icon->pixbuf));
886
887   g_free (icon);
888 }
889
890 static GdkPixbuf *
891 find_in_cache (GtkIconSet      *icon_set,
892                GtkStyle        *style,
893                GtkTextDirection direction,
894                GtkStateType     state,
895                const gchar     *size)
896 {
897   GSList *tmp_list;
898   GSList *prev;
899
900   ensure_cache_up_to_date (icon_set);
901   
902   prev = NULL;
903   tmp_list = icon_set->cache;
904   while (tmp_list != NULL)
905     {
906       CachedIcon *icon = tmp_list->data;
907
908       if (icon->style == style &&
909           icon->direction == direction &&
910           icon->state == state &&
911           strcmp (icon->size, size) == 0)
912         {
913           if (prev)
914             {
915               /* Move this icon to the front of the list. */
916               prev->next = tmp_list->next;
917               tmp_list->next = icon_set->cache;
918               icon_set->cache = tmp_list;
919             }
920           
921           return icon->pixbuf;
922         }
923           
924       prev = tmp_list;
925       tmp_list = g_slist_next (tmp_list);
926     }
927
928   return NULL;
929 }
930
931 static void
932 add_to_cache (GtkIconSet      *icon_set,
933               GtkStyle        *style,
934               GtkTextDirection direction,
935               GtkStateType     state,
936               const gchar     *size,
937               GdkPixbuf       *pixbuf)
938 {
939   CachedIcon *icon;
940
941   ensure_cache_up_to_date (icon_set);
942   
943   g_object_ref (G_OBJECT (pixbuf));
944
945   /* We have to ref the style, since if the style was finalized
946    * its address could be reused by another style, creating a
947    * really weird bug
948    */
949   
950   if (style)
951     g_object_ref (G_OBJECT (style));
952   
953
954   icon = g_new (CachedIcon, 1);
955   icon_set->cache = g_slist_prepend (icon_set->cache, icon);
956
957   icon->style = style;
958   icon->direction = direction;
959   icon->state = state;
960   icon->size = g_strdup (size);
961   icon->pixbuf = pixbuf;
962
963   if (icon->style)
964     attach_to_style (icon_set, icon->style);
965   
966   if (icon_set->cache_size >= NUM_CACHED_ICONS)
967     {
968       /* Remove oldest item in the cache */
969       
970       GSList *tmp_list;
971       
972       tmp_list = icon_set->cache;
973
974       /* Find next-to-last link */
975       g_assert (NUM_CACHED_ICONS > 2);
976       while (tmp_list->next->next)
977         tmp_list = tmp_list->next;
978
979       g_assert (tmp_list != NULL);
980       g_assert (tmp_list->next != NULL);
981       g_assert (tmp_list->next->next == NULL);
982
983       /* Free the last icon */
984       icon = tmp_list->next->data;
985
986       g_slist_free (tmp_list->next);
987       tmp_list->next = NULL;
988
989       cached_icon_free (icon);
990     }
991 }
992
993 static void
994 clear_cache (GtkIconSet *icon_set,
995              gboolean    style_detach)
996 {
997   GSList *tmp_list;
998   GtkStyle *last_style = NULL;
999
1000   tmp_list = icon_set->cache;
1001   while (tmp_list != NULL)
1002     {
1003       CachedIcon *icon = tmp_list->data;
1004
1005       if (style_detach)
1006         {
1007           /* simple optimization for the case where the cache
1008            * contains contiguous icons from the same style.
1009            * it's safe to call detach_from_style more than
1010            * once on the same style though.
1011            */
1012           if (last_style != icon->style)
1013             {
1014               detach_from_style (icon_set, icon->style);
1015               last_style = icon->style;
1016             }
1017         }
1018       
1019       cached_icon_free (icon);      
1020       
1021       tmp_list = g_slist_next (tmp_list);
1022     }
1023
1024   g_slist_free (icon_set->cache);
1025   icon_set->cache = NULL;
1026   icon_set->cache_size = 0;
1027 }
1028
1029 static GSList*
1030 copy_cache (GtkIconSet *icon_set,
1031             GtkIconSet *copy_recipient)
1032 {
1033   GSList *tmp_list;
1034   GSList *copy = NULL;
1035
1036   ensure_cache_up_to_date (icon_set);
1037   
1038   tmp_list = icon_set->cache;
1039   while (tmp_list != NULL)
1040     {
1041       CachedIcon *icon = tmp_list->data;
1042       CachedIcon *icon_copy = g_new (CachedIcon, 1);
1043
1044       *icon_copy = *icon;
1045
1046       if (icon_copy->style)
1047         attach_to_style (copy_recipient, icon_copy->style);
1048         
1049       g_object_ref (G_OBJECT (icon_copy->pixbuf));
1050
1051       icon_copy->size = g_strdup (icon->size);
1052       
1053       copy = g_slist_prepend (copy, icon_copy);      
1054       
1055       tmp_list = g_slist_next (tmp_list);
1056     }
1057
1058   return g_slist_reverse (copy);
1059 }
1060
1061 static void
1062 attach_to_style (GtkIconSet *icon_set,
1063                  GtkStyle   *style)
1064 {
1065   GHashTable *table;
1066
1067   table = g_object_get_qdata (G_OBJECT (style),
1068                               g_quark_try_string ("gtk-style-icon-sets"));
1069
1070   if (table == NULL)
1071     {
1072       table = g_hash_table_new (NULL, NULL);
1073       g_object_set_qdata_full (G_OBJECT (style),
1074                                g_quark_from_static_string ("gtk-style-icon-sets"),
1075                                table,
1076                                style_dnotify);
1077     }
1078
1079   g_hash_table_insert (table, icon_set, icon_set);
1080 }
1081
1082 static void
1083 detach_from_style (GtkIconSet *icon_set,
1084                    GtkStyle   *style)
1085 {
1086   GHashTable *table;
1087
1088   table = g_object_get_qdata (G_OBJECT (style),
1089                               g_quark_try_string ("gtk-style-icon-sets"));
1090
1091   if (table != NULL)
1092     g_hash_table_remove (table, icon_set);
1093 }
1094
1095 static void
1096 iconsets_foreach (gpointer key,
1097                   gpointer value,
1098                   gpointer user_data)
1099 {
1100   GtkIconSet *icon_set = key;
1101
1102   /* We only need to remove cache entries for the given style;
1103    * but that complicates things because in destroy notify
1104    * we don't know which style got destroyed, and 95% of the
1105    * time all cache entries will have the same style,
1106    * so this is faster anyway.
1107    */
1108   
1109   clear_cache (icon_set, FALSE);
1110 }
1111
1112 static void
1113 style_dnotify (gpointer data)
1114 {
1115   GHashTable *table = data;
1116   
1117   g_hash_table_foreach (table, iconsets_foreach, NULL);
1118
1119   g_hash_table_destroy (table);
1120 }
1121
1122 /* This allows the icon set to detect that its cache is out of date. */
1123 void
1124 _gtk_icon_set_invalidate_caches (void)
1125 {
1126   ++cache_serial;
1127 }