]> Pileus Git - ~andy/gtk/blob - gtk/gtkiconcache.c
Bug 457086 - numpad does not work when the Thai-Lao input method is used
[~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 "gtkiconcachevalidator.h"
25 #include "gtkalias.h"
26
27 #include <glib/gstdio.h>
28 #include <gdk-pixbuf/gdk-pixdata.h>
29
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 #ifdef G_OS_WIN32
34 #include <io.h>
35 #endif
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <string.h>
40
41
42 #ifndef _O_BINARY
43 #define _O_BINARY 0
44 #endif
45
46 #define MAJOR_VERSION 1
47 #define MINOR_VERSION 0
48
49 #define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
50 #define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
51
52
53 struct _GtkIconCache {
54   gint ref_count;
55
56   GMappedFile *map;
57   gchar *buffer;
58
59   guint32 last_chain_offset;
60 };
61
62 GtkIconCache *
63 _gtk_icon_cache_ref (GtkIconCache *cache)
64 {
65   cache->ref_count++;
66   return cache;
67 }
68
69 void
70 _gtk_icon_cache_unref (GtkIconCache *cache)
71 {
72   cache->ref_count --;
73
74   if (cache->ref_count == 0)
75     {
76       GTK_NOTE (ICONTHEME, 
77                 g_print ("unmapping icon cache\n"));
78
79       if (cache->map)
80         g_mapped_file_free (cache->map);
81       g_free (cache);
82     }
83 }
84
85 GtkIconCache *
86 _gtk_icon_cache_new_for_path (const gchar *path)
87 {
88   GtkIconCache *cache = NULL;
89   GMappedFile *map;
90
91   gchar *cache_filename;
92   gint fd = -1;
93   struct stat st;
94   struct stat path_st;
95   CacheInfo info;
96
97    /* Check if we have a cache file */
98   cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
99
100   GTK_NOTE (ICONTHEME, 
101             g_print ("look for cache in %s\n", path));
102
103   if (g_stat (path, &path_st) < 0)
104     goto done;
105
106   /* Open the file and map it into memory */
107   fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0);
108
109   if (fd < 0)
110     goto done;
111   
112   if (fstat (fd, &st) < 0 || st.st_size < 4)
113     goto done;
114
115   /* Verify cache is uptodate */
116   if (st.st_mtime < path_st.st_mtime)
117     {
118       GTK_NOTE (ICONTHEME, 
119                 g_print ("cache outdated\n"));
120       goto done; 
121     }
122
123   map = g_mapped_file_new (cache_filename, FALSE, NULL);
124
125   if (!map)
126     goto done;
127
128   info.cache = g_mapped_file_get_contents (map);
129   info.cache_size = g_mapped_file_get_length (map);
130   info.n_directories = 0;
131   info.flags = CHECK_OFFSETS|CHECK_STRINGS;
132
133 #ifdef G_ENABLE_DEBUG
134   if (gtk_debug_flags & GTK_DEBUG_ICONTHEME)
135     {
136       if (!_gtk_icon_cache_validate (&info))
137         {
138           g_mapped_file_free (map);
139           g_warning ("Icon cache '%s' is invalid\n", cache_filename);
140
141           goto done;
142         }
143     }
144 #endif 
145
146   GTK_NOTE (ICONTHEME, g_print ("found cache for %s\n", path));
147
148   cache = g_new0 (GtkIconCache, 1);
149   cache->ref_count = 1;
150   cache->map = map;
151   cache->buffer = g_mapped_file_get_contents (map);
152
153  done:
154   g_free (cache_filename);  
155   if (fd >= 0)
156     close (fd);
157
158   return cache;
159 }
160
161 GtkIconCache *
162 _gtk_icon_cache_new (const gchar *data)
163 {
164   GtkIconCache *cache;
165
166   cache = g_new0 (GtkIconCache, 1);
167   cache->ref_count = 1;
168   cache->map = NULL;
169   cache->buffer = (gchar *)data;
170   
171   return cache;
172 }
173
174 static gint
175 get_directory_index (GtkIconCache *cache,
176                      const gchar *directory)
177 {
178   guint32 dir_list_offset;
179   gint n_dirs;
180   gint i;
181   
182   dir_list_offset = GET_UINT32 (cache->buffer, 8);
183
184   n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
185
186   for (i = 0; i < n_dirs; i++)
187     {
188       guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
189       gchar *name = cache->buffer + name_offset;
190       if (strcmp (name, directory) == 0)
191         return i;
192     }
193   
194   return -1;
195 }
196
197 gint
198 _gtk_icon_cache_get_directory_index (GtkIconCache *cache,
199                                      const gchar *directory)
200 {
201   return get_directory_index (cache, directory);
202 }
203
204 static guint
205 icon_name_hash (gconstpointer key)
206 {
207   const signed char *p = key;
208   guint32 h = *p;
209
210   if (h)
211     for (p += 1; *p != '\0'; p++)
212       h = (h << 5) - h + *p;
213
214   return h;
215 }
216
217 static gint
218 find_image_offset (GtkIconCache *cache,
219                    const gchar  *icon_name,
220                    gint          directory_index)
221 {
222   guint32 hash_offset;
223   guint32 n_buckets;
224   guint32 chain_offset;
225   int hash;
226   guint32 image_list_offset, n_images;
227   int i;
228
229   chain_offset = cache->last_chain_offset;
230   if (chain_offset)
231     {
232       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
233       gchar *name = cache->buffer + name_offset;
234
235       if (strcmp (name, icon_name) == 0)
236         goto find_dir;
237     }
238
239   hash_offset = GET_UINT32 (cache->buffer, 4);
240   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
241   hash = icon_name_hash (icon_name) % n_buckets;
242
243   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
244   while (chain_offset != 0xffffffff)
245     {
246       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
247       gchar *name = cache->buffer + name_offset;
248
249       if (strcmp (name, icon_name) == 0)
250         {
251           cache->last_chain_offset = chain_offset;
252           goto find_dir;
253         }
254   
255       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
256     }
257
258   cache->last_chain_offset = 0;
259   return 0;
260
261 find_dir:
262   /* We've found an icon list, now check if we have the right icon in it */
263   image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
264   n_images = GET_UINT32 (cache->buffer, image_list_offset);
265   
266   for (i = 0; i < n_images; i++)
267     {
268       if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
269           directory_index) 
270         return image_list_offset + 4 + 8 * i;
271     }
272
273   return 0;
274 }
275
276 gint
277 _gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
278                                 const gchar  *icon_name,
279                                 gint          directory_index)
280 {
281   guint32 image_offset;
282
283   image_offset = find_image_offset (cache, icon_name, directory_index);
284
285   if (!image_offset)
286     return 0;
287
288   return GET_UINT16 (cache->buffer, image_offset + 2);
289 }
290
291 void
292 _gtk_icon_cache_add_icons (GtkIconCache *cache,
293                            const gchar  *directory,
294                            GHashTable   *hash_table)
295 {
296   int directory_index;
297   guint32 hash_offset, n_buckets;
298   guint32 chain_offset;
299   guint32 image_list_offset, n_images;
300   int i, j;
301   
302   directory_index = get_directory_index (cache, directory);
303
304   if (directory_index == -1)
305     return;
306   
307   hash_offset = GET_UINT32 (cache->buffer, 4);
308   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
309
310   for (i = 0; i < n_buckets; i++)
311     {
312       chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
313       while (chain_offset != 0xffffffff)
314         {
315           guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
316           gchar *name = cache->buffer + name_offset;
317           
318           image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
319           n_images = GET_UINT32 (cache->buffer, image_list_offset);
320   
321           for (j = 0; j < n_images; j++)
322             {
323               if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
324                   directory_index)
325                 g_hash_table_insert (hash_table, name, NULL);
326             }
327
328           chain_offset = GET_UINT32 (cache->buffer, chain_offset);
329         }
330     }  
331 }
332
333 gboolean
334 _gtk_icon_cache_has_icon (GtkIconCache *cache,
335                           const gchar  *icon_name)
336 {
337   guint32 hash_offset;
338   guint32 n_buckets;
339   guint32 chain_offset;
340   gint hash;
341   
342   hash_offset = GET_UINT32 (cache->buffer, 4);
343   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
344
345   hash = icon_name_hash (icon_name) % n_buckets;
346
347   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
348   while (chain_offset != 0xffffffff)
349     {
350       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
351       gchar *name = cache->buffer + name_offset;
352
353       if (strcmp (name, icon_name) == 0)
354         return TRUE;
355           
356       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
357     }
358
359   return FALSE;
360 }
361
362 gboolean
363 _gtk_icon_cache_has_icon_in_directory (GtkIconCache *cache,
364                                        const gchar  *icon_name,
365                                        const gchar  *directory)
366 {
367   guint32 hash_offset;
368   guint32 n_buckets;
369   guint32 chain_offset;
370   gint hash;
371   gboolean found_icon = FALSE;
372   gint directory_index;
373
374   directory_index = get_directory_index (cache, directory);
375
376   if (directory_index == -1)
377     return FALSE;
378   
379   hash_offset = GET_UINT32 (cache->buffer, 4);
380   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
381
382   hash = icon_name_hash (icon_name) % n_buckets;
383
384   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
385   while (chain_offset != 0xffffffff)
386     {
387       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
388       gchar *name = cache->buffer + name_offset;
389
390       if (strcmp (name, icon_name) == 0)
391         {
392           found_icon = TRUE;
393           break;
394         }
395           
396       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
397     }
398
399   if (found_icon)
400     {
401       guint32 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
402       guint32 n_images =  GET_UINT32 (cache->buffer, image_list_offset);
403       guint32 image_offset = image_list_offset + 4;
404       gint i;
405       for (i = 0; i < n_images; i++)
406         {
407           guint16 index = GET_UINT16 (cache->buffer, image_offset);
408           
409           if (index == directory_index)
410             return TRUE;
411           image_offset += 8;
412         }
413     }
414
415   return FALSE;
416 }
417
418 static void
419 pixbuf_destroy_cb (guchar   *pixels, 
420                    gpointer  data)
421 {
422   GtkIconCache *cache = data;
423
424   _gtk_icon_cache_unref (cache);
425 }
426
427 GdkPixbuf *
428 _gtk_icon_cache_get_icon (GtkIconCache *cache,
429                           const gchar  *icon_name,
430                           gint          directory_index)
431 {
432   guint32 offset, image_data_offset, pixel_data_offset;
433   guint32 length, type;
434   GdkPixbuf *pixbuf;
435   GdkPixdata pixdata;
436   GError *error = NULL;
437
438   offset = find_image_offset (cache, icon_name, directory_index);
439   
440   image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
441   
442   if (!image_data_offset)
443     return NULL;
444
445   pixel_data_offset = GET_UINT32 (cache->buffer, image_data_offset);
446
447   type = GET_UINT32 (cache->buffer, pixel_data_offset);
448
449   if (type != 0)
450     {
451       GTK_NOTE (ICONTHEME,
452                 g_print ("invalid pixel data type %u\n", type));
453       return NULL;
454     }
455
456   length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
457   
458   if (!gdk_pixdata_deserialize (&pixdata, length, 
459                                 (guchar *)(cache->buffer + pixel_data_offset + 8),
460                                 &error))
461     {
462       GTK_NOTE (ICONTHEME,
463                 g_print ("could not deserialize data: %s\n", error->message));
464       g_error_free (error);
465
466       return NULL;
467     }
468
469   pixbuf = gdk_pixbuf_new_from_data (pixdata.pixel_data, GDK_COLORSPACE_RGB,
470                                      (pixdata.pixdata_type & GDK_PIXDATA_COLOR_TYPE_MASK) == GDK_PIXDATA_COLOR_TYPE_RGBA,
471                                      8, pixdata.width, pixdata.height, pixdata.rowstride,
472                                      (GdkPixbufDestroyNotify)pixbuf_destroy_cb, 
473                                      cache);
474   if (!pixbuf)
475     {
476       GTK_NOTE (ICONTHEME,
477                 g_print ("could not convert pixdata to pixbuf: %s\n", error->message));
478       g_error_free (error);
479
480       return NULL;
481     }
482
483   _gtk_icon_cache_ref (cache);
484
485   return pixbuf;
486 }
487
488 GtkIconData  *
489 _gtk_icon_cache_get_icon_data  (GtkIconCache *cache,
490                                 const gchar  *icon_name,
491                                 gint          directory_index)
492 {
493   guint32 offset, image_data_offset, meta_data_offset;
494   GtkIconData *data;
495   int i;
496
497   offset = find_image_offset (cache, icon_name, directory_index);
498   if (!offset)
499     return NULL;
500
501   image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
502   if (!image_data_offset)
503     return NULL;
504
505   meta_data_offset = GET_UINT32 (cache->buffer, image_data_offset + 4);
506   
507   if (!meta_data_offset)
508     return NULL;
509
510   data = g_slice_new0 (GtkIconData);
511
512   offset = GET_UINT32 (cache->buffer, meta_data_offset);
513   if (offset)
514     {
515       data->has_embedded_rect = TRUE;
516       data->x0 = GET_UINT16 (cache->buffer, offset);
517       data->y0 = GET_UINT16 (cache->buffer, offset + 2);
518       data->x1 = GET_UINT16 (cache->buffer, offset + 4);
519       data->y1 = GET_UINT16 (cache->buffer, offset + 6);
520     }
521
522   offset = GET_UINT32 (cache->buffer, meta_data_offset + 4);
523   if (offset)
524     {
525       data->n_attach_points = GET_UINT32 (cache->buffer, offset);
526       data->attach_points = g_new (GdkPoint, data->n_attach_points);
527       for (i = 0; i < data->n_attach_points; i++)
528         {
529           data->attach_points[i].x = GET_UINT16 (cache->buffer, offset + 4 + 4 * i); 
530           data->attach_points[i].y = GET_UINT16 (cache->buffer, offset + 4 + 4 * i + 2); 
531         }
532     }
533
534   offset = GET_UINT32 (cache->buffer, meta_data_offset + 8);
535   if (offset)
536     {
537       gint n_names;
538       gchar *lang, *name;
539       gchar **langs;
540       GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
541
542       n_names = GET_UINT32 (cache->buffer, offset);
543       
544       for (i = 0; i < n_names; i++)
545         {
546           lang = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i);
547           name = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i + 4);
548           
549           g_hash_table_insert (table, lang, name);
550         }
551       
552       langs = (gchar **)g_get_language_names ();
553       for (i = 0; langs[i]; i++)
554         {
555           name = g_hash_table_lookup (table, langs[i]);
556           if (name)
557             {
558               data->display_name = g_strdup (name);
559               break;
560             }
561         }
562
563       g_hash_table_destroy (table);
564     }
565
566   return data;
567 }
568