b2ef13eb616e2708882393789bc67e23f24cd18a
[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
247 static void     
248 mim_get_preedit_string (GtkIMContext   *context,
249                             gchar         **str,
250                             PangoAttrList **attrs,
251                             gint           *cursor_pos)
252 {
253   ComposeSequence *comp_seq;
254   gboolean is_initial;
255   gchar *string;
256   gint len;
257
258   is_initial = at_initial_position (context) 
259                && preceding_possible_final_form == NULL;
260
261   comp_seq = find_complete_compose_sequence (compose_buffer);
262   if (comp_seq == NULL)
263     string = "";
264   else
265     string = get_appropriate_string (comp_seq, is_initial);
266
267   if (preceding_possible_final_form != NULL)
268     *str = g_strdup_printf ("%s%s", preceding_possible_final_form->normal, 
269                             string);
270   else
271     *str = g_strdup (string);
272
273   len = strlen (*str);
274
275   if (attrs)
276     {
277       *attrs = pango_attr_list_new ();
278
279       if (len != 0)
280         {
281           PangoAttribute *attr;
282           attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
283           attr->start_index = 0;
284           attr->end_index = len;
285           pango_attr_list_insert (*attrs, attr);
286         }
287     }
288
289   if (cursor_pos)
290     *cursor_pos = len;
291 }
292
293
294 static void
295 mim_reset (GtkIMContext *context)
296 {
297   clear_compose_buffer ();
298   preceding_possible_final_form = NULL;
299   g_signal_emit_by_name (context, "preedit-changed");
300 }
301
302
303 static gboolean
304 no_sequence_matches (GtkIMContext *context, GdkEventKey *event)
305 {
306   gunichar uc;
307   gchar buf[7];
308
309   uc = gdk_keyval_to_unicode (event->keyval);
310   if (uc != 0)
311     {
312       buf[g_unichar_to_utf8 (uc, buf)] = '\0';
313       g_signal_emit_by_name (context, "commit", buf);
314       g_signal_emit_by_name (context, "preedit-changed");
315
316       return TRUE;
317     }
318   else
319     return FALSE;
320 }
321
322
323 static gboolean
324 mim_filter_keypress (GtkIMContext *context,
325                          GdkEventKey  *event)
326 {
327   ComposeSequence *comp_seq;
328   gboolean is_initial;
329   gint i;
330
331   if (event->type == GDK_KEY_RELEASE)
332     return FALSE;
333
334   /* don't filter key events with accelerator modifiers held down */
335   if (event->state 
336       & (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK))
337     return FALSE;
338
339   for (i = 0;  i < G_N_ELEMENTS (mim_compose_ignore);  i++)
340     if (event->keyval == mim_compose_ignore[i])
341       return FALSE;
342
343   is_initial = at_initial_position (context) 
344                && preceding_possible_final_form == NULL;
345
346   /* '|' commits what we have */
347   if (event->keyval == GDK_bar && (n_compose > 0 
348                                    || preceding_possible_final_form != NULL))
349     {
350       commit_preceding_possible_final_form (context, FALSE); /* non-final */
351
352       comp_seq = find_complete_compose_sequence (compose_buffer);
353       if (comp_seq != NULL)
354         {
355           g_signal_emit_by_name (context, "commit", 
356                                  get_appropriate_string (comp_seq, is_initial));
357           clear_compose_buffer ();
358         }
359
360       g_signal_emit_by_name (context, "preedit-changed");
361       return TRUE;
362     }
363
364   compose_buffer[n_compose] = event->keyval;
365   n_compose++;
366
367   if (find_incomplete_compose_sequence (compose_buffer) != NULL)
368     {
369       g_signal_emit_by_name (context, "preedit-changed");
370       return TRUE;
371     }
372
373   comp_seq = find_complete_compose_sequence (compose_buffer);
374   if (comp_seq != NULL)
375     {
376       if (comp_seq->final != NULL) /* has a final form, don't commit yet */
377         {
378           commit_preceding_possible_final_form (context, 
379                                                 comp_seq->is_terminator);
380           clear_compose_buffer ();
381           preceding_possible_final_form = comp_seq;
382           g_signal_emit_by_name (context, "preedit-changed");
383           return TRUE;
384         }
385       else
386         {
387           commit_preceding_possible_final_form (context, 
388                                                 comp_seq->is_terminator);
389           g_signal_emit_by_name (context, "commit", 
390                                  get_appropriate_string (comp_seq, is_initial));
391           clear_compose_buffer ();
392           g_signal_emit_by_name (context, "preedit-changed");
393           return TRUE;
394         }
395     }
396
397   /* if we reach this point, the sequence *with the key just pressed*
398    * cannot be a complete or incomplete match, so: commit the old sequence,
399    * then reprocess this key */
400
401   n_compose--;
402   compose_buffer[n_compose] = 0;
403
404   if (n_compose > 0)
405     {
406       comp_seq = find_complete_compose_sequence (compose_buffer);
407       commit_preceding_possible_final_form (context, comp_seq->is_terminator);
408       if (comp_seq->final != NULL)
409         {
410           clear_compose_buffer ();
411           preceding_possible_final_form = comp_seq;
412         }
413       else
414         {
415           g_signal_emit_by_name (context, "commit", 
416                                  get_appropriate_string (comp_seq, is_initial));
417           clear_compose_buffer ();
418           g_signal_emit_by_name (context, "preedit-changed");
419         }
420
421       return mim_filter_keypress (context, event);
422     }
423   else
424     commit_preceding_possible_final_form (context, TRUE);
425
426   return no_sequence_matches (context, event);
427 }
428
429
430 static void
431 mim_class_init (GtkIMContextClass *clazz)
432 {
433   clazz->filter_keypress = mim_filter_keypress;
434   clazz->get_preedit_string = mim_get_preedit_string;
435   clazz->reset = mim_reset;
436 }
437
438 static void
439 mim_init (GtkIMContext *im_context)
440 {
441 }
442
443
444 static void
445 mim_register_type (GTypeModule *module)
446 {
447   static const GTypeInfo object_info =
448   {
449     sizeof (GtkIMContextClass),
450     (GBaseInitFunc) NULL,
451     (GBaseFinalizeFunc) NULL,
452     (GClassInitFunc) mim_class_init,
453     NULL,           /* class_finalize */
454     NULL,           /* class_data */
455     sizeof (GtkIMContext),
456     0,
457     (GInstanceInitFunc) mim_init,
458   };
459
460   type_mim = 
461     g_type_module_register_type (module,
462                                  GTK_TYPE_IM_CONTEXT,
463                                  "GtkIMContextYiddishNoah",
464                                  &object_info, 0);
465 }
466
467
468 static const GtkIMContextInfo mim_info =
469 {
470   "mim",              /* ID */
471   N_("MiM"),          /* Human readable name */
472   GETTEXT_PACKAGE,    /* Translation domain */
473   LOCALEDIR,          /* Dir for bindtextdomain */
474   "zh_CN",            /* Languages for which this module is the default */
475 };
476
477
478 static const GtkIMContextInfo *info_list[] = 
479 {
480   &mim_info,
481 };
482
483
484 void
485 im_module_init (GTypeModule *module)
486 {
487   mim_register_type (module);
488 }
489
490 void
491 im_module_exit ()
492 {
493 }
494
495 void 
496 im_module_list (const GtkIMContextInfo ***contexts, gint *n_contexts)
497 {
498   *contexts = info_list;
499   *n_contexts = G_N_ELEMENTS (info_list);
500 }
501
502
503 GtkIMContext *
504 im_module_create (const gchar *context_id)
505 {
506   if (strcmp (context_id, "mim-n") == 0)
507     return GTK_IM_CONTEXT (g_object_new (type_mim, NULL));
508   else
509     return NULL;
510 }
511
512
513