]> Pileus Git - ~andy/gtk/blob - gtk/gtkiconcache.c
8faca64a64960ef0d8a9f6fbc5d3912d332915c0
[~andy/gtk] / gtk / gtkiconcache.c
1 /* gtkiconcache.c
2  * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library 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 #include <config.h>
21
22 #include "gtkdebug.h"
23 #include "gtkiconcache.h"
24 #include <glib/gstdio.h>
25 #include <gdk-pixbuf/gdk-pixdata.h>
26
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #ifdef G_OS_WIN32
31 #include <io.h>
32 #endif
33 #include <fcntl.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <string.h>
37
38
39 #ifndef _O_BINARY
40 #define _O_BINARY 0
41 #endif
42
43 #define MAJOR_VERSION 1
44 #define MINOR_VERSION 0
45
46 #define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
47 #define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
48
49 struct _GtkIconCache {
50   gint ref_count;
51
52   GMappedFile *map;
53   gchar *buffer;
54 };
55
56 GtkIconCache *
57 _gtk_icon_cache_ref (GtkIconCache *cache)
58 {
59   cache->ref_count ++;
60   return cache;
61 }
62
63 void
64 _gtk_icon_cache_unref (GtkIconCache *cache)
65 {
66   cache->ref_count --;
67
68   if (cache->ref_count == 0)
69     {
70       GTK_NOTE (ICONTHEME, 
71                 g_print ("unmapping icon cache\n"));
72
73       g_mapped_file_free (cache->map);
74       g_free (cache);
75     }
76 }
77
78 GtkIconCache *
79 _gtk_icon_cache_new_for_path (const gchar *path)
80 {
81   GtkIconCache *cache = NULL;
82   GMappedFile *map;
83
84   gchar *cache_filename;
85   gint fd = -1;
86   struct stat st;
87   struct stat path_st;
88   gchar *buffer = NULL;
89
90    /* Check if we have a cache file */
91   cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
92
93   GTK_NOTE (ICONTHEME, 
94             g_print ("look for cache in %s\n", path));
95
96   if (g_stat (path, &path_st) < 0)
97     goto done;
98
99   /* Open the file and map it into memory */
100   fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0);
101
102   if (fd < 0)
103     goto done;
104   
105   if (fstat (fd, &st) < 0 || st.st_size < 4)
106     goto done;
107
108   /* Verify cache is uptodate */
109   if (st.st_mtime < path_st.st_mtime)
110     {
111       GTK_NOTE (ICONTHEME, 
112                 g_print ("cache outdated\n"));
113       goto done; 
114     }
115
116   map = g_mapped_file_new (cache_filename, FALSE, NULL);
117
118   if (!map)
119     goto done;
120
121   /* Verify version */
122   buffer = g_mapped_file_get_contents (map);
123   if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
124       GET_UINT16 (buffer, 2) != MINOR_VERSION)
125     {
126       g_mapped_file_free (map);
127
128       GTK_NOTE (ICONTHEME, 
129                 g_print ("wrong cache version\n"));
130       goto done;
131     }
132   
133   GTK_NOTE (ICONTHEME, 
134             g_print ("found cache for %s\n", path));
135
136   cache = g_new0 (GtkIconCache, 1);
137   cache->ref_count = 1;
138   cache->map = map;
139   cache->buffer = buffer;
140
141  done:
142   g_free (cache_filename);  
143   if (fd >= 0)
144     close (fd);
145
146   return cache;
147 }
148
149 static int
150 get_directory_index (GtkIconCache *cache,
151                      const gchar *directory)
152 {
153   guint32 dir_list_offset;
154   int n_dirs;
155   int i;
156   
157   dir_list_offset = GET_UINT32 (cache->buffer, 8);
158
159   n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
160
161   for (i = 0; i < n_dirs; i++)
162     {
163       guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
164       gchar *name = cache->buffer + name_offset;
165       if (strcmp (name, directory) == 0)
166         return i;
167     }
168   
169   return -1;
170 }
171
172 gboolean
173 _gtk_icon_cache_has_directory (GtkIconCache *cache,
174                                const gchar *directory)
175 {
176   return get_directory_index (cache, directory) != -1;
177 }
178
179 static guint
180 icon_name_hash (gconstpointer key)
181 {
182   const signed char *p = key;
183   guint32 h = *p;
184
185   if (h)
186     for (p += 1; *p != '\0'; p++)
187       h = (h << 5) - h + *p;
188
189   return h;
190 }
191
192 static gint
193 find_image_offset (GtkIconCache *cache,
194                    const gchar  *icon_name,
195                    const gchar  *directory)
196 {
197   guint32 hash_offset;
198   guint32 n_buckets;
199   guint32 chain_offset;
200   int hash, directory_index;
201   guint32 image_list_offset, n_images;
202   gboolean found = FALSE;
203   int i;
204   
205   hash_offset = GET_UINT32 (cache->buffer, 4);
206   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
207
208   hash = icon_name_hash (icon_name) % n_buckets;
209
210   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
211   while (chain_offset != 0xffffffff)
212     {
213       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
214       gchar *name = cache->buffer + name_offset;
215
216       if (strcmp (name, icon_name) == 0)
217         {
218           found = TRUE;
219           break;
220         }
221           
222       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
223     }
224
225   if (!found) {
226     return 0;
227   }
228
229   /* We've found an icon list, now check if we have the right icon in it */
230   directory_index = get_directory_index (cache, directory);
231   image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
232   n_images = GET_UINT32 (cache->buffer, image_list_offset);
233   
234   for (i = 0; i < n_images; i++)
235     {
236       if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
237           directory_index) 
238         return image_list_offset + 4 + 8 * i;
239     }
240
241   return 0;
242 }
243
244 gint
245 _gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
246                                 const gchar  *icon_name,
247                                 const gchar  *directory)
248 {
249   guint32 image_offset;
250
251   image_offset = find_image_offset (cache, icon_name, directory);
252
253   if (!image_offset)
254     return 0;
255
256   return GET_UINT16 (cache->buffer, image_offset + 2);
257 }
258
259 void
260 _gtk_icon_cache_add_icons (GtkIconCache *cache,
261                            const gchar  *directory,
262                            GHashTable   *hash_table)
263 {
264   int directory_index;
265   guint32 hash_offset, n_buckets;
266   guint32 chain_offset;
267   guint32 image_list_offset, n_images;
268   int i, j;
269   
270   directory_index = get_directory_index (cache, directory);
271
272   if (directory_index == -1)
273     return;
274   
275   hash_offset = GET_UINT32 (cache->buffer, 4);
276   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
277
278   for (i = 0; i < n_buckets; i++)
279     {
280       chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
281       while (chain_offset != 0xffffffff)
282         {
283           guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
284           gchar *name = cache->buffer + name_offset;
285           
286           image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
287           n_images = GET_UINT32 (cache->buffer, image_list_offset);
288   
289           for (j = 0; j < n_images; j++)
290             {
291               if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
292                   directory_index)
293                 g_hash_table_insert (hash_table, name, NULL);
294             }
295
296           chain_offset = GET_UINT32 (cache->buffer, chain_offset);
297         }
298     }  
299 }
300
301 gboolean
302 _gtk_icon_cache_has_icon (GtkIconCache *cache,
303                           const gchar  *icon_name)
304 {
305   guint32 hash_offset;
306   guint32 n_buckets;
307   guint32 chain_offset;
308   gint hash;
309   
310   hash_offset = GET_UINT32 (cache->buffer, 4);
311   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
312
313   hash = icon_name_hash (icon_name) % n_buckets;
314
315   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
316   while (chain_offset != 0xffffffff)
317     {
318       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
319       gchar *name = cache->buffer + name_offset;
320
321       if (strcmp (name, icon_name) == 0)
322         return TRUE;
323           
324       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
325     }
326
327   return FALSE;
328 }
329                           
330 GdkPixbuf *
331 _gtk_icon_cache_get_icon (GtkIconCache *cache,
332                           const gchar  *icon_name,
333                           const gchar  *directory)
334 {
335   guint32 offset, image_data_offset, pixel_data_offset;
336   guint32 length, type;
337   GdkPixbuf *pixbuf;
338   GdkPixdata pixdata;
339   GError *error = NULL;
340
341   offset = find_image_offset (cache, icon_name, directory);
342   
343   image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
344   
345   if (!image_data_offset)
346     return NULL;
347
348   pixel_data_offset = GET_UINT32 (cache->buffer, image_data_offset);
349
350   type = GET_UINT32 (cache->buffer, pixel_data_offset);
351
352   if (type != 0)
353     {
354       GTK_NOTE (ICONTHEME,
355                 g_print ("invalid pixel data type %d\n", type));
356       return NULL;
357     }
358
359   length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
360   
361   if (!gdk_pixdata_deserialize (&pixdata, length, 
362                                 cache->buffer + pixel_data_offset + 8,
363                                 &error))
364     {
365       GTK_NOTE (ICONTHEME,
366                 g_print ("could not deserialize data: %s\n", error->message));
367       g_error_free (error);
368
369       return NULL;
370     }
371
372   pixbuf = gdk_pixbuf_from_pixdata (&pixdata, FALSE, &error);
373
374   if (!pixbuf)
375     {
376       GTK_NOTE (ICONTHEME,
377                 g_print ("could not convert pixdata to pixbuf: %s\n", error->message));
378       g_error_free (error);
379
380       return NULL;
381     }
382
383   return pixbuf;
384 }
385
386 GtkIconData  *
387 _gtk_icon_cache_get_icon_data  (GtkIconCache *cache,
388                                 const gchar  *icon_name,
389                                 const gchar  *directory)
390 {
391   guint32 offset, image_data_offset, meta_data_offset;
392   GtkIconData *data;
393   int i;
394
395   offset = find_image_offset (cache, icon_name, directory);
396   if (!offset)
397     return NULL;
398
399   image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
400   if (!image_data_offset)
401     return NULL;
402
403   meta_data_offset = GET_UINT32 (cache->buffer, image_data_offset + 4);
404   
405   if (!meta_data_offset)
406     return NULL;
407
408   data = g_new0 (GtkIconData, 1);
409
410   offset = GET_UINT32 (cache->buffer, meta_data_offset);
411   if (offset)
412     {
413       data->has_embedded_rect = TRUE;
414       data->x0 = GET_UINT16 (cache->buffer, offset);
415       data->y0 = GET_UINT16 (cache->buffer, offset + 2);
416       data->x1 = GET_UINT16 (cache->buffer, offset + 4);
417       data->y1 = GET_UINT16 (cache->buffer, offset + 6);
418     }
419
420   offset = GET_UINT32 (cache->buffer, meta_data_offset + 4);
421   if (offset)
422     {
423       data->n_attach_points = GET_UINT32 (cache->buffer, offset);
424       data->attach_points = g_new (GdkPoint, data->n_attach_points);
425       for (i = 0; i < data->n_attach_points; i++)
426         {
427           data->attach_points[i].x = GET_UINT16 (cache->buffer, offset + 4 + 4 * i); 
428           data->attach_points[i].y = GET_UINT16 (cache->buffer, offset + 4 + 4 * i + 2); 
429         }
430     }
431
432   offset = GET_UINT32 (cache->buffer, meta_data_offset + 8);
433   if (offset)
434     {
435       gint n_names;
436       gchar *lang, *name;
437       gchar **langs;
438       GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
439
440       n_names = GET_UINT32 (cache->buffer, offset);
441       
442       for (i = 0; i < n_names; i++)
443         {
444           lang = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i);
445           name = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i + 4);
446           
447           g_hash_table_insert (table, lang, name);
448         }
449       
450       langs = (gchar **)g_get_language_names ();
451       for (i = 0; langs[i]; i++)
452         {
453           name = g_hash_table_lookup (table, langs[i]);
454           if (name)
455             {
456               data->display_name = g_strdup (name);
457               break;
458             }
459         }
460
461       g_hash_table_destroy (table);
462     }
463
464   return data;
465 }
466