91c3b757cd1f476064a777905007b60e33c1cbaf
[mim] / src / ui / mim-immodule.c
1 /*
2  * GTK+ MiM input module 
3  */
4
5 #ifdef HAVE_CONFIG_H
6 # include <config.h>
7 #endif
8
9 #include <gtk/gtkimcontext.h>
10 #include <gtk/gtkimmodule.h>
11 #include <gdk/gdkkeysyms.h>
12 #include <string.h>
13
14 #include "im-extra-intl.h"
15
16
17 GType type_mim = 0;
18
19 enum { MIM_MAX_COMPOSE_LEN = 3 };
20
21 typedef struct
22 {
23   guint keys[MIM_MAX_COMPOSE_LEN + 1];  /* 0-terminated */
24   gchar *normal;
25   gchar *initial;
26   gchar *final;
27   gboolean is_terminator;  /* true if a preceding possible final form
28                               should be final */
29 }
30 ComposeSequence;
31
32 /* like GtkIMContextSimple */
33 static guint compose_buffer[MIM_MAX_COMPOSE_LEN + 1];
34 static int n_compose = 0;
35 static ComposeSequence *preceding_possible_final_form = NULL;
36
37 static ComposeSequence mim_compose_seqs[] = 
38 {
39   { { GDK_a,          0,              0,     0, }, "אַ",   NULL,  NULL, FALSE, },
40   { { GDK_A,          0,              0,     0, }, "א",   NULL,  NULL, FALSE, },
41   { { GDK_b,          0,              0,     0, }, "ב",   NULL,  NULL, FALSE, },
42   { { GDK_B,          0,              0,     0, }, "בֿ",   NULL,  NULL, FALSE, },
43   { { GDK_c,          0,              0,     0, }, "צ",   NULL,  "ץ",  FALSE, },
44   { { GDK_C,          0,              0,     0, }, "ץ",   NULL,  NULL, FALSE, },
45   { { GDK_g,          0,              0,     0, }, "ג",   NULL,  NULL, FALSE, },
46   { { GDK_d,          0,              0,     0, }, "ד",   NULL,  NULL, FALSE, },
47   { { GDK_h,          0,              0,     0, }, "ה",   NULL,  NULL, FALSE, },
48   { { GDK_i,          0,              0,     0, }, "י",   "אי",  NULL, FALSE, },
49   { { GDK_I,          0,              0,     0, }, "יִ",   "איִ",  NULL, FALSE, },
50   { { GDK_v,          0,              0,     0, }, "װ",   NULL,  NULL, FALSE, },
51   { { GDK_z,          0,              0,     0, }, "ז",   NULL,  NULL, FALSE, },
52   { { GDK_H,          0,              0,     0, }, "ח",   NULL,  NULL, FALSE, },
53   { { GDK_t,          0,              0,     0, }, "ט",   NULL,  NULL, FALSE, },
54   { { GDK_T,          0,              0,     0, }, "תּ",   NULL,  NULL, FALSE, },
55   { { GDK_y,          0,              0,     0, }, "י",   NULL,  NULL, FALSE, },
56   { { GDK_x,          0,              0,     0, }, "כ",   NULL,  "ך",  FALSE, },
57   { { GDK_X,          0,              0,     0, }, "ך",   NULL,  NULL, FALSE, },
58   { { GDK_l,          0,              0,     0, }, "ל",   NULL,  NULL, FALSE, },
59   { { GDK_m,          0,              0,     0, }, "מ",   NULL,  "ם",  FALSE, },
60   { { GDK_M,          0,              0,     0, }, "ם",   NULL,  NULL, FALSE, },
61   { { GDK_n,          0,              0,     0, }, "נ",   NULL,  "ן",  FALSE, },
62   { { GDK_N,          0,              0,     0, }, "ן",   NULL,  NULL, FALSE, },
63   { { GDK_s,          0,              0,     0, }, "ס",   NULL,  NULL, FALSE, },
64   { { GDK_S,          0,              0,     0, }, "ת",   NULL,  NULL, FALSE, },
65   { { GDK_e,          0,              0,     0, }, "ע",   NULL,  NULL, FALSE, },
66   { { GDK_E,          0,              0,     0, }, "ײ",   "אײ",  NULL, FALSE, },
67   { { GDK_o,          0,              0,     0, }, "אָ",   NULL,  NULL, FALSE, },
68   { { GDK_O,          0,              0,     0, }, "ױ",   "אױ",  NULL, FALSE, },
69   { { GDK_u,          0,              0,     0, }, "ו",   "או",  NULL, FALSE, },
70   { { GDK_U,          0,              0,     0, }, "וּ",   "אוּ",  NULL, FALSE, },
71   { { GDK_p,          0,              0,     0, }, "פּ",   NULL,  NULL, FALSE, },
72   { { GDK_P,          0,              0,     0, }, "פ",   NULL,  NULL, FALSE, },
73   { { GDK_w,          0,              0,     0, }, "ש",   NULL,  NULL, FALSE, },
74   { { GDK_W,          0,              0,     0, }, "שׂ",   NULL,  NULL, FALSE, },
75   { { GDK_f,          0,              0,     0, }, "פֿ",   NULL,  "ף",  FALSE, },
76   { { GDK_F,          0,              0,     0, }, "ף",   NULL,  NULL, FALSE, },
77   { { GDK_k,          0,              0,     0, }, "ק",   NULL,  NULL, FALSE, },
78   { { GDK_K,          0,              0,     0, }, "כּ",   NULL,  NULL, FALSE, },
79   { { GDK_r,          0,              0,     0, }, "ר",   NULL,  NULL, FALSE, },
80   { { GDK_Y,          0,              0,     0, }, "ײַ",   "אײַ",  NULL, FALSE, },
81   { { GDK_minus,      0,              0,     0, }, "־",   NULL,  NULL, TRUE,  },
82   { { GDK_apostrophe, 0,              0,     0, }, "'",   NULL,  NULL, TRUE,  },
83   { { GDK_comma,      0,              0,     0, }, ",",   NULL,  NULL, TRUE,  },
84   { { GDK_s,          GDK_h,          0,     0, }, "ש",   NULL,  NULL, FALSE, },
85   { { GDK_t,          GDK_s,          0,     0, }, "צ",   NULL,  "ץ",  FALSE, },
86   { { GDK_t,          GDK_z,          0,     0, }, "צ",   NULL,  "ץ",  FALSE, },
87   { { GDK_z,          GDK_h,          0,     0, }, "זש",  NULL,  NULL, FALSE, },
88   { { GDK_a,          GDK_y,          0,     0, }, "ײַ",   "אײַ",  NULL, FALSE, },
89   { { GDK_o,          GDK_y,          0,     0, }, "ױ",   "אױ",  NULL, FALSE, },
90   { { GDK_o,          GDK_i,          0,     0, }, "ױ",   "אױ",  NULL, FALSE, },
91   { { GDK_d,          GDK_j,          0,     0, }, "דזש", NULL,  NULL, FALSE, },
92   { { GDK_e,          GDK_y,          0,     0, }, "ײ",   "אײ",  NULL, FALSE, },
93   { { GDK_apostrophe, GDK_apostrophe, 0,     0, }, "“",   NULL,  NULL, TRUE,  },
94   { { GDK_comma,      GDK_comma,      0,     0, }, "„",   NULL,  NULL, TRUE,  },
95   { { GDK_k,          GDK_h,          0,     0, }, "כ",   NULL,  "ך",  FALSE, },
96   { { GDK_c,          GDK_h,          0,     0, }, "כ",   NULL,  "ך",  FALSE, },
97   { { GDK_u,          GDK_v,          0,     0, }, "וּװ",  "אוּװ", NULL, FALSE, },
98   { { GDK_u,          GDK_u,          0,     0, }, "וּו",  "אוּו", NULL, FALSE, },
99   { { GDK_u,          GDK_i,          0,     0, }, "ויִ",  "אויִ", NULL, FALSE, },
100   { { GDK_u,          GDK_y,          0,     0, }, "וּי",  "אוּי", NULL, FALSE, },
101   { { GDK_v,          GDK_u,          0,     0, }, "װוּ",  NULL,  NULL, FALSE, },
102   { { GDK_y,          GDK_i,          0,     0, }, "ייִ",  NULL,  NULL, FALSE, },
103   { { GDK_i,          GDK_i,          0,     0, }, "יִיִ",  "איִיִ", NULL, FALSE, },
104   { { GDK_i,          GDK_y,          0,     0, }, "יִי",  "איִי", NULL, FALSE, },
105   { { GDK_E,          GDK_i,          0,     0, }, "ײיִ",  "אײיִ", NULL, FALSE, },
106   { { GDK_i,          GDK_e,          0,     0, }, "יִע",  "איִע", NULL, FALSE, },
107   { { GDK_i,          GDK_a,          0,     0, }, "יִאַ",  "איִאַ", NULL, FALSE, },
108   { { GDK_i,          GDK_o,          0,     0, }, "יִאָ",  "איִאָ", NULL, FALSE, },
109   { { GDK_t,          GDK_s,          GDK_h, 0, }, "טש",  NULL,  NULL, FALSE, },
110 };
111
112
113 static const guint16 mim_compose_ignore[] = 
114 {
115   GDK_Shift_L,
116   GDK_Shift_R,
117   GDK_Control_L,
118   GDK_Control_R,
119   GDK_Caps_Lock,
120   GDK_Shift_Lock,
121   GDK_Meta_L,
122   GDK_Meta_R,
123   GDK_Alt_L,
124   GDK_Alt_R,
125   GDK_Super_L,
126   GDK_Super_R,
127   GDK_Hyper_L,
128   GDK_Hyper_R,
129   GDK_Mode_switch,
130 };
131
132
133 static void
134 clear_compose_buffer ()
135 {
136   memset (compose_buffer, 0, sizeof (compose_buffer));
137   n_compose = 0;
138 }
139
140
141 /* handles null case */
142 static void
143 commit_preceding_possible_final_form (GtkIMContext *context, 
144                                       gboolean is_final)
145 {
146   if (preceding_possible_final_form != NULL)
147     {
148       if (is_final) 
149         g_signal_emit_by_name (context, "commit", 
150                                preceding_possible_final_form->final);
151       else
152         g_signal_emit_by_name (context, "commit", 
153                                preceding_possible_final_form->normal);
154
155       preceding_possible_final_form = NULL;
156     }
157 }
158
159 /* returns the composed string iff keys exactly matches the compose
160  * sequence keys */
161 static ComposeSequence *
162 find_complete_compose_sequence (guint *keys)
163 {
164   gint i, j;
165
166   for (i = 0;  i < G_N_ELEMENTS (mim_compose_seqs);  i++)
167     for (j = 0;  j <= MIM_MAX_COMPOSE_LEN;  j++)
168       {
169         if (keys[j] != mim_compose_seqs[i].keys[j])
170           break;
171         else if (keys[j] == 0 && keys[j] == mim_compose_seqs[i].keys[j])
172           return mim_compose_seqs + i;
173       }
174
175   return NULL;
176 }
177
178
179 /* returns the composed string iff keys is a substring thang of the compose
180  * sequence keys */
181 static ComposeSequence *
182 find_incomplete_compose_sequence (guint *keys)
183 {
184   gint i, j;
185
186   for (i = 0;  i < G_N_ELEMENTS (mim_compose_seqs);  i++)
187     for (j = 0;  j <= MIM_MAX_COMPOSE_LEN;  j++)
188       {
189         if (keys[j] == 0 && mim_compose_seqs[i].keys[j] != 0)
190           return mim_compose_seqs + i;
191         else if (keys[j] != mim_compose_seqs[i].keys[j])
192           break;
193       }
194
195   return NULL;
196 }
197
198
199 static gchar *
200 get_appropriate_string (ComposeSequence *comp_seq, gboolean is_initial)
201 {
202   if (comp_seq == NULL)
203     return NULL;
204   else if (is_initial && comp_seq->initial != NULL)
205     return comp_seq->initial;
206   else
207     return comp_seq->normal;
208 }
209
210
211 /* is this a character that could appear in a mim word */
212 static gboolean
213 is_mim_word_character (gunichar uc)
214 {
215   return (((uc >= 0x0590 && uc <= 0x5ff) || (uc >= 0xfb1d && uc <= 0xfb4f))
216           && g_unichar_isdefined (uc) && ! g_unichar_ispunct (uc));
217
218 }
219
220
221 static gboolean
222 at_initial_position (GtkIMContext *context)
223 {
224   gchar *text;
225   gchar *prevp;
226   gint cursor_index;
227   gunichar uc;
228
229   if (! gtk_im_context_get_surrounding (context, &text, &cursor_index))
230     return FALSE;
231
232   prevp = g_utf8_find_prev_char (text, text + cursor_index);
233   if (prevp == NULL)
234     return TRUE;
235
236   uc = g_utf8_get_char_validated (prevp, text + cursor_index - prevp);
237   g_return_val_if_fail (uc != (gunichar)(-1) && uc != (gunichar)(-2), 
238                         FALSE);
239
240   if (is_mim_word_character (uc))
241     return FALSE;
242   else
243     return TRUE;
244 }
245
246 static void
247 mim_reset (GtkIMContext *context)
248 {
249   clear_compose_buffer ();
250   preceding_possible_final_form = NULL;
251 }
252
253
254 static gboolean
255 no_sequence_matches (GtkIMContext *context, GdkEventKey *event)
256 {
257   gunichar uc;
258   gchar buf[7];
259
260   uc = gdk_keyval_to_unicode (event->keyval);
261   if (uc != 0)
262     {
263       buf[g_unichar_to_utf8 (uc, buf)] = '\0';
264       g_signal_emit_by_name (context, "commit", buf);
265
266       return TRUE;
267     }
268   else
269     return FALSE;
270 }
271
272
273 static gboolean
274 mim_filter_keypress (GtkIMContext *context,
275                          GdkEventKey  *event)
276 {
277   ComposeSequence *comp_seq;
278   gboolean is_initial;
279   gint i;
280
281   if (event->type == GDK_KEY_RELEASE)
282     return FALSE;
283
284   /* don't filter key events with accelerator modifiers held down */
285   if (event->state 
286       & (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK))
287     return FALSE;
288
289   for (i = 0;  i < G_N_ELEMENTS (mim_compose_ignore);  i++)
290     if (event->keyval == mim_compose_ignore[i])
291       return FALSE;
292
293   is_initial = at_initial_position (context) 
294                && preceding_possible_final_form == NULL;
295
296   /* '|' commits what we have */
297   if (event->keyval == GDK_bar && (n_compose > 0 
298                                    || preceding_possible_final_form != NULL))
299     {
300       commit_preceding_possible_final_form (context, FALSE); /* non-final */
301
302       comp_seq = find_complete_compose_sequence (compose_buffer);
303       if (comp_seq != NULL)
304         {
305           g_signal_emit_by_name (context, "commit", 
306                                  get_appropriate_string (comp_seq, is_initial));
307           clear_compose_buffer ();
308         }
309
310       return TRUE;
311     }
312
313   compose_buffer[n_compose] = event->keyval;
314   n_compose++;
315
316   if (find_incomplete_compose_sequence (compose_buffer) != NULL)
317     {
318       return TRUE;
319     }
320
321   comp_seq = find_complete_compose_sequence (compose_buffer);
322   if (comp_seq != NULL)
323     {
324       if (comp_seq->final != NULL) /* has a final form, don't commit yet */
325         {
326           commit_preceding_possible_final_form (context, 
327                                                 comp_seq->is_terminator);
328           clear_compose_buffer ();
329           preceding_possible_final_form = comp_seq;
330           return TRUE;
331         }
332       else
333         {
334           commit_preceding_possible_final_form (context, 
335                                                 comp_seq->is_terminator);
336           g_signal_emit_by_name (context, "commit", 
337                                  get_appropriate_string (comp_seq, is_initial));
338           clear_compose_buffer ();
339           return TRUE;
340         }
341     }
342
343   /* if we reach this point, the sequence *with the key just pressed*
344    * cannot be a complete or incomplete match, so: commit the old sequence,
345    * then reprocess this key */
346
347   n_compose--;
348   compose_buffer[n_compose] = 0;
349
350   if (n_compose > 0)
351     {
352       comp_seq = find_complete_compose_sequence (compose_buffer);
353       commit_preceding_possible_final_form (context, comp_seq->is_terminator);
354       if (comp_seq->final != NULL)
355         {
356           clear_compose_buffer ();
357           preceding_possible_final_form = comp_seq;
358         }
359       else
360         {
361           g_signal_emit_by_name (context, "commit", 
362                                  get_appropriate_string (comp_seq, is_initial));
363           clear_compose_buffer ();
364         }
365
366       return mim_filter_keypress (context, event);
367     }
368   else
369     commit_preceding_possible_final_form (context, TRUE);
370
371   return no_sequence_matches (context, event);
372 }
373
374
375 static void
376 mim_class_init (GtkIMContextClass *clazz)
377 {
378   clazz->filter_keypress = mim_filter_keypress;
379   clazz->reset = mim_reset;
380 }
381
382 static void
383 mim_init (GtkIMContext *im_context)
384 {
385 }
386
387
388 static void
389 mim_register_type (GTypeModule *module)
390 {
391   static const GTypeInfo object_info =
392   {
393     sizeof (GtkIMContextClass),
394     (GBaseInitFunc) NULL,
395     (GBaseFinalizeFunc) NULL,
396     (GClassInitFunc) mim_class_init,
397     NULL,           /* class_finalize */
398     NULL,           /* class_data */
399     sizeof (GtkIMContext),
400     0,
401     (GInstanceInitFunc) mim_init,
402   };
403
404   type_mim = 
405     g_type_module_register_type (module,
406                                  GTK_TYPE_IM_CONTEXT,
407                                  "GtkIMContextYiddishNoah",
408                                  &object_info, 0);
409 }
410
411
412 static const GtkIMContextInfo mim_info =
413 {
414   "mim",              /* ID */
415   N_("MiM"),          /* Human readable name */
416   GETTEXT_PACKAGE,    /* Translation domain */
417   LOCALEDIR,          /* Dir for bindtextdomain */
418   "zh_CN",            /* Languages for which this module is the default */
419 };
420
421
422 static const GtkIMContextInfo *info_list[] = 
423 {
424   &mim_info,
425 };
426
427
428 void
429 im_module_init (GTypeModule *module)
430 {
431   g_debug("mim-immodule imported!");
432   mim_register_type (module);
433 }
434
435 void
436 im_module_exit ()
437 {
438   g_debug("mim-immodule released!");
439 }
440
441 void 
442 im_module_list (const GtkIMContextInfo ***contexts, gint *n_contexts)
443 {
444   *contexts = info_list;
445   *n_contexts = G_N_ELEMENTS (info_list);
446 }
447
448
449 GtkIMContext *
450 im_module_create (const gchar *context_id)
451 {
452   if (strcmp (context_id, "mim-n") == 0)
453     return GTK_IM_CONTEXT (g_object_new (type_mim, NULL));
454   else
455     return NULL;
456 }
457
458
459