]> Pileus Git - ~andy/gtk/blob - gtk/gtkimmodule.c
Support "*" for all locales with least priority when to select default im
[~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 #include <errno.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <gmodule.h>
33 #include <pango/pango-utils.h>
34 #include "gtkimmodule.h"
35 #include "gtkimcontextsimple.h"
36 #include "gtkrc.h"
37 #include "config.h"
38 #include "gtkintl.h"
39
40 #define SIMPLE_ID "gtk-im-context-simple"
41
42 typedef struct _GtkIMModule      GtkIMModule;
43 typedef struct _GtkIMModuleClass GtkIMModuleClass;
44
45 #define GTK_TYPE_IM_MODULE          (gtk_im_module_get_type ())
46 #define GTK_IM_MODULE(im_module)    (G_TYPE_CHECK_INSTANCE_CAST ((im_module), GTK_TYPE_IM_MODULE, GtkIMModule))
47 #define GTK_IS_IM_MODULE(im_module) (G_TYPE_CHECK_INSTANCE_TYPE ((im_module), GTK_TYPE_IM_MODULE))
48
49 struct _GtkIMModule
50 {
51   GTypeModule parent_instance;
52   
53   GModule *library;
54
55   void          (*list)   (const GtkIMContextInfo ***contexts,
56                            guint                    *n_contexts);
57   void          (*init)   (GTypeModule              *module);
58   void          (*exit)   (void);
59   GtkIMContext *(*create) (const gchar              *context_id);
60
61   GtkIMContextInfo **contexts;
62   guint n_contexts;
63
64   gchar *path;
65 };
66
67 struct _GtkIMModuleClass 
68 {
69   GTypeModuleClass parent_class;
70 };
71
72 GType gtk_im_module_get_type (void);
73
74 gint n_loaded_contexts = 0;
75 static GHashTable *contexts_hash = NULL;
76 static GSList *modules_list = NULL;
77
78 static GObjectClass *parent_class = NULL;
79
80 static gboolean
81 gtk_im_module_load (GTypeModule *module)
82 {
83   GtkIMModule *im_module = GTK_IM_MODULE (module);
84   
85   im_module->library = g_module_open (im_module->path, 0);
86   if (!im_module->library)
87     {
88       g_warning (g_module_error());
89       return FALSE;
90     }
91   
92   /* extract symbols from the lib */
93   if (!g_module_symbol (im_module->library, "im_module_init",
94                         (gpointer *)&im_module->init) ||
95       !g_module_symbol (im_module->library, "im_module_exit", 
96                         (gpointer *)&im_module->exit) ||
97       !g_module_symbol (im_module->library, "im_module_list", 
98                         (gpointer *)&im_module->list) ||
99       !g_module_symbol (im_module->library, "im_module_create", 
100                         (gpointer *)&im_module->create))
101     {
102       g_warning (g_module_error());
103       g_module_close (im_module->library);
104       
105       return FALSE;
106     }
107             
108   /* call the theme's init (theme_init) function to let it */
109   /* setup anything it needs to set up. */
110   im_module->init (module);
111
112   return TRUE;
113 }
114
115 static void
116 gtk_im_module_unload (GTypeModule *module)
117 {
118   GtkIMModule *im_module = GTK_IM_MODULE (module);
119   
120   im_module->exit();
121
122   g_module_close (im_module->library);
123   im_module->library = NULL;
124
125   im_module->init = NULL;
126   im_module->exit = NULL;
127   im_module->list = NULL;
128   im_module->create = NULL;
129 }
130
131 /* This only will ever be called if an error occurs during
132  * initialization
133  */
134 static void
135 gtk_im_module_finalize (GObject *object)
136 {
137   GtkIMModule *module = GTK_IM_MODULE (object);
138
139   g_free (module->path);
140
141   parent_class->finalize (object);
142 }
143
144 static void
145 gtk_im_module_class_init (GtkIMModuleClass *class)
146 {
147   GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (class);
148   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
149
150   parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (class));
151   
152   module_class->load = gtk_im_module_load;
153   module_class->unload = gtk_im_module_unload;
154
155   gobject_class->finalize = gtk_im_module_finalize;
156 }
157
158 GType
159 gtk_im_module_get_type (void)
160 {
161   static GType im_module_type = 0;
162
163   if (!im_module_type)
164     {
165       static const GTypeInfo im_module_info = {
166         sizeof (GtkIMModuleClass),
167         NULL,           /* base_init */
168         NULL,           /* base_finalize */
169         (GClassInitFunc) gtk_im_module_class_init,
170         NULL,           /* class_finalize */
171         NULL,           /* class_data */
172         sizeof (GtkIMModule),
173         0,              /* n_preallocs */
174         NULL,           /* instance_init */
175       };
176
177       im_module_type = g_type_register_static (G_TYPE_TYPE_MODULE, "GtkIMModule", &im_module_info, 0);
178     }
179   
180   return im_module_type;
181 }
182
183 static void
184 free_info (GtkIMContextInfo *info)
185 {
186   g_free ((char *)info->context_id);
187   g_free ((char *)info->context_name);
188   g_free ((char *)info->domain);
189   g_free ((char *)info->domain_dirname);
190   g_free ((char *)info->default_locales);
191   g_free (info);
192 }
193
194 static void
195 add_module (GtkIMModule *module, GSList *infos)
196 {
197   GSList *tmp_list = infos;
198   gint i = 0;
199   gint n = g_slist_length (infos);
200   module->contexts = g_new (GtkIMContextInfo *, n);
201
202   while (tmp_list)
203     {
204       GtkIMContextInfo *info = tmp_list->data;
205   
206       if (g_hash_table_lookup (contexts_hash, info->context_id))
207         {
208           free_info (info);     /* Duplicate */
209         }
210       else
211         {
212           g_hash_table_insert (contexts_hash, (char *)info->context_id, module);
213           module->contexts[i++] = tmp_list->data;
214           n_loaded_contexts++;
215         }
216       
217       tmp_list = tmp_list->next;
218     }
219   g_slist_free (infos);
220   module->n_contexts = i;
221
222   modules_list = g_slist_prepend (modules_list, module);
223 }
224
225 static void
226 gtk_im_module_init ()
227 {
228   GString *line_buf = g_string_new (NULL);
229   GString *tmp_buf = g_string_new (NULL);
230   gchar *filename = gtk_rc_get_im_module_file();
231   FILE *file;
232   gboolean have_error = FALSE;
233
234   GtkIMModule *module = NULL;
235   GSList *infos = NULL;
236
237   contexts_hash = g_hash_table_new (g_str_hash, g_str_equal);
238
239   file = fopen (filename, "r");
240   if (!file)
241     {
242       g_warning ("Can not open Input Method module file '%s': %s",
243                  filename, g_strerror (errno));
244       /* We are leaking all kinds of memory here. */
245       return;
246     }
247
248   while (!have_error && pango_read_line (file, line_buf))
249     {
250       const char *p;
251       
252       p = line_buf->str;
253
254       if (!pango_skip_space (&p))
255         {
256           /* Blank line marking the end of a module
257            */
258           if (module && *p != '#')
259             {
260               add_module (module, infos);
261               module = NULL;
262               infos = NULL;
263             }
264           
265           continue;
266         }
267
268       if (!module)
269         {
270           /* Read a module location
271            */
272           module = g_object_new (GTK_TYPE_IM_MODULE, NULL);
273
274           if (!pango_scan_string (&p, tmp_buf) ||
275               pango_skip_space (&p))
276             {
277               g_warning ("Error parsing context info in '%s'\n  %s", 
278                          filename, line_buf->str);
279               have_error = TRUE;
280             }
281
282           module->path = g_strdup (tmp_buf->str);
283           g_type_module_set_name (G_TYPE_MODULE (module), module->path);
284         }
285       else
286         {
287           GtkIMContextInfo *info = g_new0 (GtkIMContextInfo, 1);
288           
289           /* Read information about a context type
290            */
291           if (!pango_scan_string (&p, tmp_buf))
292             goto context_error;
293           info->context_id = g_strdup (tmp_buf->str);
294
295           if (!pango_scan_string (&p, tmp_buf))
296             goto context_error;
297           info->context_name = g_strdup (tmp_buf->str);
298
299           if (!pango_scan_string (&p, tmp_buf))
300             goto context_error;
301           info->domain = g_strdup (tmp_buf->str);
302
303           if (!pango_scan_string (&p, tmp_buf))
304             goto context_error;
305           info->domain_dirname = g_strdup (tmp_buf->str);
306
307           if (!pango_scan_string (&p, tmp_buf))
308             goto context_error;
309           info->default_locales = g_strdup (tmp_buf->str);
310
311           if (pango_skip_space (&p))
312             goto context_error;
313
314           infos = g_slist_prepend (infos, info);
315           continue;
316
317         context_error:
318           g_warning ("Error parsing context info in '%s'\n  %s", 
319                      filename, line_buf->str);
320           have_error = TRUE;
321         }
322     }
323
324   if (have_error)
325     {
326       GSList *tmp_list = infos;
327       while (tmp_list)
328         {
329           free_info (tmp_list->data);
330           tmp_list = tmp_list->next;
331         }
332       g_slist_free (infos);
333
334       g_object_unref (G_OBJECT (module));
335     }
336   else if (module)
337     add_module (module, infos);
338
339   fclose (file);
340   g_string_free (line_buf, TRUE);
341   g_string_free (tmp_buf, TRUE);
342   g_free (filename);
343 }
344
345 /**
346  * _gtk_im_module_list:
347  * @contexts: location to store an array of pointers to #GtkIMContextInfo
348  *            this array should be freed with g_free() when you are finished.
349  *            The structures it points are statically allocated and should
350  *            not be modified or freed.
351  * @n_contexts: the length of the array stored in @contexts
352  * 
353  * List all available types of input method context
354  **/
355 void
356 _gtk_im_module_list (const GtkIMContextInfo ***contexts,
357                      guint                    *n_contexts)
358 {
359   int n = 0;
360
361   static const GtkIMContextInfo simple_context_info = {
362     SIMPLE_ID,
363     "Default",
364     "gtk+",
365     NULL,
366     ""
367   };
368
369   if (!contexts_hash)
370     gtk_im_module_init ();
371
372   if (n_contexts)
373     *n_contexts = (n_loaded_contexts + 1);
374
375   if (contexts)
376     {
377       GSList *tmp_list;
378       int i;
379       
380       *contexts = g_new (const GtkIMContextInfo *, n_loaded_contexts + 1);
381
382       (*contexts)[n++] = &simple_context_info;
383
384       tmp_list = modules_list;
385       while (tmp_list)
386         {
387           GtkIMModule *module = tmp_list->data;
388
389           for (i=0; i<module->n_contexts; i++)
390             (*contexts)[n++] = module->contexts[i];
391           
392           tmp_list = tmp_list->next;
393         }
394     }
395 }
396
397 /**
398  * _gtk_im_module_create:
399  * @context_id: the context ID for the context type to create
400  * 
401  * Create an IM context of a type specified by the string
402  * ID @context_id.
403  * 
404  * Return value: a newly created input context of or @context_id, or
405  * if that could not be created, a newly created GtkIMContextSimple.
406  **/
407 GtkIMContext *
408 _gtk_im_module_create (const gchar *context_id)
409 {
410   GtkIMModule *im_module;
411   GtkIMContext *context = NULL;
412   
413   if (!contexts_hash)
414     gtk_im_module_init ();
415
416   if (strcmp (context_id, SIMPLE_ID) != 0)
417     {
418       im_module = g_hash_table_lookup (contexts_hash, context_id);
419       if (!im_module)
420         {
421           g_warning ("Attempt to load unknown IM context type '%s'", context_id);
422         }
423       else
424         {
425           if (g_type_module_use (G_TYPE_MODULE (im_module)))
426             {
427               context = im_module->create (context_id);
428               g_type_module_unuse (G_TYPE_MODULE (im_module));
429             }
430           
431           if (!context)
432             g_warning ("Loading IM context type '%s' failed", context_id);
433         }
434     }
435   
436   if (!context)
437      return gtk_im_context_simple_new ();
438   else
439     return context;
440 }
441
442 /* Match @locale against @against.
443  * 
444  * 'en_US' against 'en_US'       => 4
445  * 'en_US' against 'en'          => 3
446  * 'en', 'en_UK' against 'en_US' => 2
447  *  all locales, against '*'     => 1
448  */
449 static gint
450 match_locale (const gchar *locale,
451               const gchar *against,
452               gint         against_len)
453 {
454   if (strcmp (against, "*") == 0)
455     return 1;
456
457   if (strcmp (locale, against) == 0)
458     return 4;
459
460   if (strncmp (locale, against, 2) == 0)
461     return (against_len == 2) ? 3 : 2;
462
463   return 0;
464 }
465
466 /**
467  * _gtk_im_module_get_default_context_id:
468  * @locale: a locale id in the form 'en_US'
469  * 
470  * Return the context_id of the best IM context type
471  * for the given locale ID.
472  * 
473  * Return value: the context ID (will never be %NULL)
474  *    the value is newly allocated and must be freed
475  *    with g_free().
476  **/
477 const gchar *
478 _gtk_im_module_get_default_context_id (const gchar *locale)
479 {
480   GSList *tmp_list;
481   const gchar *context_id = NULL;
482   gint best_goodness = 0;
483   gint i;
484   gchar *tmp_locale, *tmp;
485   gchar *envvar;
486       
487   if (!contexts_hash)
488     gtk_im_module_init ();
489
490   envvar = g_getenv ("GTK_IM_MODULE");
491   if (envvar && g_hash_table_lookup (contexts_hash, envvar))
492     return g_strdup (envvar);
493
494   /* Strip the locale code down to the essentials
495    */
496   tmp_locale = g_strdup (locale);
497   tmp = strchr (tmp_locale, '.');
498   if (tmp)
499     *tmp = '\0';
500   tmp = strchr (tmp_locale, '@');
501   if (tmp)
502     *tmp = '\0';
503   
504   tmp_list = modules_list;
505   while (tmp_list)
506     {
507       GtkIMModule *module = tmp_list->data;
508      
509       for (i=0; i<module->n_contexts; i++)
510         {
511           const gchar *p = module->contexts[i]->default_locales;
512           while (p)
513             {
514               const gchar *q = strchr (p, ':');
515               gint goodness = match_locale (tmp_locale, p, q ? q - p : strlen (p));
516
517               if (goodness > best_goodness)
518                 {
519                   context_id = module->contexts[i]->context_id;
520                   best_goodness = goodness;
521                 }
522
523               p = q ? q + 1 : NULL;
524             }
525         }
526       
527       tmp_list = tmp_list->next;
528     }
529
530   g_free (tmp_locale);
531   
532   return g_strdup (context_id ? context_id : SIMPLE_ID);
533 }