]> Pileus Git - ~andy/gtk/blob - gtk/gtkimmodule.c
More of the same
[~andy/gtk] / gtk / gtkimmodule.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * Themes added by The Rasterman <raster@redhat.com>
5  * 
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free
18  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
26  */
27
28 #include <config.h>
29
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <glib/gstdio.h>
36 #include <gmodule.h>
37 #include <pango/pango-utils.h>
38 #include "gtkimmodule.h"
39 #include "gtkimcontextsimple.h"
40 #include "gtkrc.h"
41 #include "gtkintl.h"
42 #include "gtkalias.h"
43
44 /* Do *not* include "gtkprivate.h" in this file. If you do, the
45  * correct_libdir_prefix() function below will have to move somewhere
46  * else.
47  */
48
49 #ifdef __GTK_PRIVATE_H__
50 #error gtkprivate.h should not be included in this file
51 #endif
52
53 #define SIMPLE_ID "gtk-im-context-simple"
54
55 typedef struct _GtkIMModule      GtkIMModule;
56 typedef struct _GtkIMModuleClass GtkIMModuleClass;
57
58 #define GTK_TYPE_IM_MODULE          (gtk_im_module_get_type ())
59 #define GTK_IM_MODULE(im_module)    (G_TYPE_CHECK_INSTANCE_CAST ((im_module), GTK_TYPE_IM_MODULE, GtkIMModule))
60 #define GTK_IS_IM_MODULE(im_module) (G_TYPE_CHECK_INSTANCE_TYPE ((im_module), GTK_TYPE_IM_MODULE))
61
62 struct _GtkIMModule
63 {
64   GTypeModule parent_instance;
65   
66   GModule *library;
67
68   void          (*list)   (const GtkIMContextInfo ***contexts,
69                            guint                    *n_contexts);
70   void          (*init)   (GTypeModule              *module);
71   void          (*exit)   (void);
72   GtkIMContext *(*create) (const gchar              *context_id);
73
74   GtkIMContextInfo **contexts;
75   guint n_contexts;
76
77   gchar *path;
78 };
79
80 struct _GtkIMModuleClass 
81 {
82   GTypeModuleClass parent_class;
83 };
84
85 static GType gtk_im_module_get_type (void);
86
87 static gint n_loaded_contexts = 0;
88 static GHashTable *contexts_hash = NULL;
89 static GSList *modules_list = NULL;
90
91 static GObjectClass *parent_class = NULL;
92
93 static gboolean
94 gtk_im_module_load (GTypeModule *module)
95 {
96   GtkIMModule *im_module = GTK_IM_MODULE (module);
97   
98   im_module->library = g_module_open (im_module->path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
99   if (!im_module->library)
100     {
101       g_warning (g_module_error());
102       return FALSE;
103     }
104   
105   /* extract symbols from the lib */
106   if (!g_module_symbol (im_module->library, "im_module_init",
107                         (gpointer *)&im_module->init) ||
108       !g_module_symbol (im_module->library, "im_module_exit", 
109                         (gpointer *)&im_module->exit) ||
110       !g_module_symbol (im_module->library, "im_module_list", 
111                         (gpointer *)&im_module->list) ||
112       !g_module_symbol (im_module->library, "im_module_create", 
113                         (gpointer *)&im_module->create))
114     {
115       g_warning (g_module_error());
116       g_module_close (im_module->library);
117       
118       return FALSE;
119     }
120             
121   /* call the theme's init (theme_init) function to let it */
122   /* setup anything it needs to set up. */
123   im_module->init (module);
124
125   return TRUE;
126 }
127
128 static void
129 gtk_im_module_unload (GTypeModule *module)
130 {
131   GtkIMModule *im_module = GTK_IM_MODULE (module);
132   
133   im_module->exit();
134
135   g_module_close (im_module->library);
136   im_module->library = NULL;
137
138   im_module->init = NULL;
139   im_module->exit = NULL;
140   im_module->list = NULL;
141   im_module->create = NULL;
142 }
143
144 /* This only will ever be called if an error occurs during
145  * initialization
146  */
147 static void
148 gtk_im_module_finalize (GObject *object)
149 {
150   GtkIMModule *module = GTK_IM_MODULE (object);
151
152   g_free (module->path);
153
154   parent_class->finalize (object);
155 }
156
157 G_DEFINE_TYPE (GtkIMModule, gtk_im_module, G_TYPE_TYPE_MODULE)
158
159 static void
160 gtk_im_module_class_init (GtkIMModuleClass *class)
161 {
162   GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (class);
163   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
164
165   parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (class));
166   
167   module_class->load = gtk_im_module_load;
168   module_class->unload = gtk_im_module_unload;
169
170   gobject_class->finalize = gtk_im_module_finalize;
171 }
172
173 static void 
174 gtk_im_module_init (GtkIMModule* object)
175 {
176 }
177
178 static void
179 free_info (GtkIMContextInfo *info)
180 {
181   g_free ((char *)info->context_id);
182   g_free ((char *)info->context_name);
183   g_free ((char *)info->domain);
184   g_free ((char *)info->domain_dirname);
185   g_free ((char *)info->default_locales);
186   g_free (info);
187 }
188
189 static void
190 add_module (GtkIMModule *module, GSList *infos)
191 {
192   GSList *tmp_list = infos;
193   gint i = 0;
194   gint n = g_slist_length (infos);
195   module->contexts = g_new (GtkIMContextInfo *, n);
196
197   while (tmp_list)
198     {
199       GtkIMContextInfo *info = tmp_list->data;
200   
201       if (g_hash_table_lookup (contexts_hash, info->context_id))
202         {
203           free_info (info);     /* Duplicate */
204         }
205       else
206         {
207           g_hash_table_insert (contexts_hash, (char *)info->context_id, module);
208           module->contexts[i++] = tmp_list->data;
209           n_loaded_contexts++;
210         }
211       
212       tmp_list = tmp_list->next;
213     }
214   g_slist_free (infos);
215   module->n_contexts = i;
216
217   modules_list = g_slist_prepend (modules_list, module);
218 }
219
220 #if defined (G_OS_WIN32) && defined (GTK_LIBDIR)
221 /* This is needes on Win32, but not wanted when compiling with MSVC,
222  * as the makefile.msc doesn't define any GTK_LIBDIR value.
223  */
224
225 #define DO_CORRECT_LIBDIR_PREFIX /* Flag to check below whether to call this */
226
227 static void
228 correct_libdir_prefix (gchar **path)
229 {
230   /* GTK_LIBDIR here is supposed to still have the definition from
231    * Makefile.am, i.e. the build-time value. Do *not* include gtkprivate.h
232    * in this file.
233    */
234   if (strncmp (*path, GTK_LIBDIR, strlen (GTK_LIBDIR)) == 0)
235     {
236       /* This is an entry put there by make install on the
237        * packager's system. On Windows a prebuilt GTK+
238        * package can be installed in a random
239        * location. The gtk.immodules file distributed in
240        * such a package contains paths from the package
241        * builder's machine. Replace the path with the real
242        * one on this machine.
243        */
244       extern const gchar *_gtk_get_libdir ();
245       gchar *tem = *path;
246       *path = g_strconcat (_gtk_get_libdir (), tem + strlen (GTK_LIBDIR), NULL);
247       g_free (tem);
248     }
249 }
250 #endif
251
252
253 static void
254 gtk_im_module_initialize (void)
255 {
256   GString *line_buf = g_string_new (NULL);
257   GString *tmp_buf = g_string_new (NULL);
258   gchar *filename = gtk_rc_get_im_module_file();
259   FILE *file;
260   gboolean have_error = FALSE;
261
262   GtkIMModule *module = NULL;
263   GSList *infos = NULL;
264
265   contexts_hash = g_hash_table_new (g_str_hash, g_str_equal);
266
267   file = g_fopen (filename, "r");
268   if (!file)
269     {
270       /* In case someone wants only the default input method,
271        * we allow no file at all.
272        */
273       g_string_free (line_buf, TRUE);
274       g_string_free (tmp_buf, TRUE);
275       g_free (filename);
276       return;
277     }
278
279   while (!have_error && pango_read_line (file, line_buf))
280     {
281       const char *p;
282       
283       p = line_buf->str;
284
285       if (!pango_skip_space (&p))
286         {
287           /* Blank line marking the end of a module
288            */
289           if (module && *p != '#')
290             {
291               add_module (module, infos);
292               module = NULL;
293               infos = NULL;
294             }
295           
296           continue;
297         }
298
299       if (!module)
300         {
301           /* Read a module location
302            */
303           module = g_object_new (GTK_TYPE_IM_MODULE, NULL);
304
305           if (!pango_scan_string (&p, tmp_buf) ||
306               pango_skip_space (&p))
307             {
308               g_warning ("Error parsing context info in '%s'\n  %s", 
309                          filename, line_buf->str);
310               have_error = TRUE;
311             }
312
313           module->path = g_strdup (tmp_buf->str);
314 #ifdef DO_CORRECT_LIBDIR_PREFIX
315           correct_libdir_prefix (&module->path);
316 #endif
317           g_type_module_set_name (G_TYPE_MODULE (module), module->path);
318         }
319       else
320         {
321           GtkIMContextInfo *info = g_new0 (GtkIMContextInfo, 1);
322           
323           /* Read information about a context type
324            */
325           if (!pango_scan_string (&p, tmp_buf))
326             goto context_error;
327           info->context_id = g_strdup (tmp_buf->str);
328
329           if (!pango_scan_string (&p, tmp_buf))
330             goto context_error;
331           info->context_name = g_strdup (tmp_buf->str);
332
333           if (!pango_scan_string (&p, tmp_buf))
334             goto context_error;
335           info->domain = g_strdup (tmp_buf->str);
336
337           if (!pango_scan_string (&p, tmp_buf))
338             goto context_error;
339           info->domain_dirname = g_strdup (tmp_buf->str);
340 #ifdef DO_CORRECT_LIBDIR_PREFIX
341           correct_libdir_prefix ((char **) &info->domain_dirname);
342 #endif
343
344           if (!pango_scan_string (&p, tmp_buf))
345             goto context_error;
346           info->default_locales = g_strdup (tmp_buf->str);
347
348           if (pango_skip_space (&p))
349             goto context_error;
350
351           infos = g_slist_prepend (infos, info);
352           continue;
353
354         context_error:
355           g_warning ("Error parsing context info in '%s'\n  %s", 
356                      filename, line_buf->str);
357           have_error = TRUE;
358         }
359     }
360
361   if (have_error)
362     {
363       GSList *tmp_list = infos;
364       while (tmp_list)
365         {
366           free_info (tmp_list->data);
367           tmp_list = tmp_list->next;
368         }
369       g_slist_free (infos);
370
371       g_object_unref (module);
372     }
373   else if (module)
374     add_module (module, infos);
375
376   fclose (file);
377   g_string_free (line_buf, TRUE);
378   g_string_free (tmp_buf, TRUE);
379   g_free (filename);
380 }
381
382 static gint
383 compare_gtkimcontextinfo_name(const GtkIMContextInfo **a,
384                               const GtkIMContextInfo **b)
385 {
386   return g_utf8_collate ((*a)->context_name, (*b)->context_name);
387 }
388
389 /**
390  * _gtk_im_module_list:
391  * @contexts: location to store an array of pointers to #GtkIMContextInfo
392  *            this array should be freed with g_free() when you are finished.
393  *            The structures it points are statically allocated and should
394  *            not be modified or freed.
395  * @n_contexts: the length of the array stored in @contexts
396  * 
397  * List all available types of input method context
398  **/
399 void
400 _gtk_im_module_list (const GtkIMContextInfo ***contexts,
401                      guint                    *n_contexts)
402 {
403   int n = 0;
404
405   static const GtkIMContextInfo simple_context_info = {
406     SIMPLE_ID,
407     N_("Default"),
408     GETTEXT_PACKAGE,
409 #ifdef GTK_LOCALEDIR
410     GTK_LOCALEDIR,
411 #else
412     "",
413 #endif
414     ""
415   };
416
417   if (!contexts_hash)
418     gtk_im_module_initialize ();
419
420   if (n_contexts)
421     *n_contexts = (n_loaded_contexts + 1);
422
423   if (contexts)
424     {
425       GSList *tmp_list;
426       int i;
427       
428       *contexts = g_new (const GtkIMContextInfo *, n_loaded_contexts + 1);
429
430       (*contexts)[n++] = &simple_context_info;
431
432       tmp_list = modules_list;
433       while (tmp_list)
434         {
435           GtkIMModule *module = tmp_list->data;
436
437           for (i=0; i<module->n_contexts; i++)
438             (*contexts)[n++] = module->contexts[i];
439           
440           tmp_list = tmp_list->next;
441         }
442
443       /* fisrt element (Default) should always be at top */
444       qsort ((*contexts)+1, n-1, sizeof (GtkIMContextInfo *), (GCompareFunc)compare_gtkimcontextinfo_name);
445     }
446 }
447
448 /**
449  * _gtk_im_module_create:
450  * @context_id: the context ID for the context type to create
451  * 
452  * Create an IM context of a type specified by the string
453  * ID @context_id.
454  * 
455  * Return value: a newly created input context of or @context_id, or
456  * if that could not be created, a newly created GtkIMContextSimple.
457  **/
458 GtkIMContext *
459 _gtk_im_module_create (const gchar *context_id)
460 {
461   GtkIMModule *im_module;
462   GtkIMContext *context = NULL;
463   
464   if (!contexts_hash)
465     gtk_im_module_initialize ();
466
467   if (strcmp (context_id, SIMPLE_ID) != 0)
468     {
469       im_module = g_hash_table_lookup (contexts_hash, context_id);
470       if (!im_module)
471         {
472           g_warning ("Attempt to load unknown IM context type '%s'", context_id);
473         }
474       else
475         {
476           if (g_type_module_use (G_TYPE_MODULE (im_module)))
477             {
478               context = im_module->create (context_id);
479               g_type_module_unuse (G_TYPE_MODULE (im_module));
480             }
481           
482           if (!context)
483             g_warning ("Loading IM context type '%s' failed", context_id);
484         }
485     }
486   
487   if (!context)
488      return gtk_im_context_simple_new ();
489   else
490     return context;
491 }
492
493 /* Match @locale against @against.
494  * 
495  * 'en_US' against 'en_US'       => 4
496  * 'en_US' against 'en'          => 3
497  * 'en', 'en_UK' against 'en_US' => 2
498  *  all locales, against '*'     => 1
499  */
500 static gint
501 match_locale (const gchar *locale,
502               const gchar *against,
503               gint         against_len)
504 {
505   if (strcmp (against, "*") == 0)
506     return 1;
507
508   if (g_ascii_strcasecmp (locale, against) == 0)
509     return 4;
510
511   if (g_ascii_strncasecmp (locale, against, 2) == 0)
512     return (against_len == 2) ? 3 : 2;
513
514   return 0;
515 }
516
517 /**
518  * _gtk_im_module_get_default_context_id:
519  * @locale: a locale id in the form 'en_US'
520  * 
521  * Return the context_id of the best IM context type
522  * for the given locale ID.
523  * 
524  * Return value: the context ID (will never be %NULL)
525  *    the value is newly allocated and must be freed
526  *    with g_free().
527  **/
528 const gchar *
529 _gtk_im_module_get_default_context_id (const gchar *locale)
530 {
531   GSList *tmp_list;
532   const gchar *context_id = NULL;
533   gint best_goodness = 0;
534   gint i;
535   gchar *tmp_locale, *tmp;
536   const gchar *envvar;
537       
538   if (!contexts_hash)
539     gtk_im_module_initialize ();
540
541   envvar = g_getenv ("GTK_IM_MODULE");
542   if (envvar &&
543       (strcmp (envvar, SIMPLE_ID) == 0 ||
544        g_hash_table_lookup (contexts_hash, envvar)))
545     return g_strdup (envvar);
546
547   /* Strip the locale code down to the essentials
548    */
549   tmp_locale = g_strdup (locale);
550   tmp = strchr (tmp_locale, '.');
551   if (tmp)
552     *tmp = '\0';
553   tmp = strchr (tmp_locale, '@');
554   if (tmp)
555     *tmp = '\0';
556   
557   tmp_list = modules_list;
558   while (tmp_list)
559     {
560       GtkIMModule *module = tmp_list->data;
561      
562       for (i=0; i<module->n_contexts; i++)
563         {
564           const gchar *p = module->contexts[i]->default_locales;
565           while (p)
566             {
567               const gchar *q = strchr (p, ':');
568               gint goodness = match_locale (tmp_locale, p, q ? q - p : strlen (p));
569
570               if (goodness > best_goodness)
571                 {
572                   context_id = module->contexts[i]->context_id;
573                   best_goodness = goodness;
574                 }
575
576               p = q ? q + 1 : NULL;
577             }
578         }
579       
580       tmp_list = tmp_list->next;
581     }
582
583   g_free (tmp_locale);
584   
585   return g_strdup (context_id ? context_id : SIMPLE_ID);
586 }