Add gtk immodule main file
authorAlsor Zhou <alsor.zhou@gmail.com>
Thu, 22 Oct 2009 09:32:12 +0000 (17:32 +0800)
committerAlsor Zhou <alsor.zhou@gmail.com>
Thu, 22 Oct 2009 09:32:12 +0000 (17:32 +0800)
src/ui/mim-immodule.c [new file with mode: 0644]

diff --git a/src/ui/mim-immodule.c b/src/ui/mim-immodule.c
new file mode 100644 (file)
index 0000000..b2ef13e
--- /dev/null
@@ -0,0 +1,513 @@
+/*
+ * GTK+ MiM input module 
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <gtk/gtkimcontext.h>
+#include <gtk/gtkimmodule.h>
+#include <gdk/gdkkeysyms.h>
+#include <string.h>
+
+#include "im-extra-intl.h"
+
+
+GType type_mim = 0;
+
+enum { MIM_MAX_COMPOSE_LEN = 3 };
+
+typedef struct
+{
+  guint keys[MIM_MAX_COMPOSE_LEN + 1];  /* 0-terminated */
+  gchar *normal;
+  gchar *initial;
+  gchar *final;
+  gboolean is_terminator;  /* true if a preceding possible final form
+                              should be final */
+}
+ComposeSequence;
+
+/* like GtkIMContextSimple */
+static guint compose_buffer[MIM_MAX_COMPOSE_LEN + 1];
+static int n_compose = 0;
+static ComposeSequence *preceding_possible_final_form = NULL;
+
+static ComposeSequence mim_compose_seqs[] = 
+{
+  { { GDK_a,          0,              0,     0, }, "אַ",   NULL,  NULL, FALSE, },
+  { { GDK_A,          0,              0,     0, }, "א",   NULL,  NULL, FALSE, },
+  { { GDK_b,          0,              0,     0, }, "ב",   NULL,  NULL, FALSE, },
+  { { GDK_B,          0,              0,     0, }, "בֿ",   NULL,  NULL, FALSE, },
+  { { GDK_c,          0,              0,     0, }, "צ",   NULL,  "ץ",  FALSE, },
+  { { GDK_C,          0,              0,     0, }, "ץ",   NULL,  NULL, FALSE, },
+  { { GDK_g,          0,              0,     0, }, "ג",   NULL,  NULL, FALSE, },
+  { { GDK_d,          0,              0,     0, }, "ד",   NULL,  NULL, FALSE, },
+  { { GDK_h,          0,              0,     0, }, "ה",   NULL,  NULL, FALSE, },
+  { { GDK_i,          0,              0,     0, }, "י",   "אי",  NULL, FALSE, },
+  { { GDK_I,          0,              0,     0, }, "יִ",   "איִ",  NULL, FALSE, },
+  { { GDK_v,          0,              0,     0, }, "װ",   NULL,  NULL, FALSE, },
+  { { GDK_z,          0,              0,     0, }, "ז",   NULL,  NULL, FALSE, },
+  { { GDK_H,          0,              0,     0, }, "ח",   NULL,  NULL, FALSE, },
+  { { GDK_t,          0,              0,     0, }, "ט",   NULL,  NULL, FALSE, },
+  { { GDK_T,          0,              0,     0, }, "תּ",   NULL,  NULL, FALSE, },
+  { { GDK_y,          0,              0,     0, }, "י",   NULL,  NULL, FALSE, },
+  { { GDK_x,          0,              0,     0, }, "כ",   NULL,  "ך",  FALSE, },
+  { { GDK_X,          0,              0,     0, }, "ך",   NULL,  NULL, FALSE, },
+  { { GDK_l,          0,              0,     0, }, "ל",   NULL,  NULL, FALSE, },
+  { { GDK_m,          0,              0,     0, }, "מ",   NULL,  "ם",  FALSE, },
+  { { GDK_M,          0,              0,     0, }, "ם",   NULL,  NULL, FALSE, },
+  { { GDK_n,          0,              0,     0, }, "נ",   NULL,  "ן",  FALSE, },
+  { { GDK_N,          0,              0,     0, }, "ן",   NULL,  NULL, FALSE, },
+  { { GDK_s,          0,              0,     0, }, "ס",   NULL,  NULL, FALSE, },
+  { { GDK_S,          0,              0,     0, }, "ת",   NULL,  NULL, FALSE, },
+  { { GDK_e,          0,              0,     0, }, "ע",   NULL,  NULL, FALSE, },
+  { { GDK_E,          0,              0,     0, }, "ײ",   "אײ",  NULL, FALSE, },
+  { { GDK_o,          0,              0,     0, }, "אָ",   NULL,  NULL, FALSE, },
+  { { GDK_O,          0,              0,     0, }, "ױ",   "אױ",  NULL, FALSE, },
+  { { GDK_u,          0,              0,     0, }, "ו",   "או",  NULL, FALSE, },
+  { { GDK_U,          0,              0,     0, }, "וּ",   "אוּ",  NULL, FALSE, },
+  { { GDK_p,          0,              0,     0, }, "פּ",   NULL,  NULL, FALSE, },
+  { { GDK_P,          0,              0,     0, }, "פ",   NULL,  NULL, FALSE, },
+  { { GDK_w,          0,              0,     0, }, "ש",   NULL,  NULL, FALSE, },
+  { { GDK_W,          0,              0,     0, }, "שׂ",   NULL,  NULL, FALSE, },
+  { { GDK_f,          0,              0,     0, }, "פֿ",   NULL,  "ף",  FALSE, },
+  { { GDK_F,          0,              0,     0, }, "ף",   NULL,  NULL, FALSE, },
+  { { GDK_k,          0,              0,     0, }, "ק",   NULL,  NULL, FALSE, },
+  { { GDK_K,          0,              0,     0, }, "כּ",   NULL,  NULL, FALSE, },
+  { { GDK_r,          0,              0,     0, }, "ר",   NULL,  NULL, FALSE, },
+  { { GDK_Y,          0,              0,     0, }, "ײַ",   "אײַ",  NULL, FALSE, },
+  { { GDK_minus,      0,              0,     0, }, "־",   NULL,  NULL, TRUE,  },
+  { { GDK_apostrophe, 0,              0,     0, }, "'",   NULL,  NULL, TRUE,  },
+  { { GDK_comma,      0,              0,     0, }, ",",   NULL,  NULL, TRUE,  },
+  { { GDK_s,          GDK_h,          0,     0, }, "ש",   NULL,  NULL, FALSE, },
+  { { GDK_t,          GDK_s,          0,     0, }, "צ",   NULL,  "ץ",  FALSE, },
+  { { GDK_t,          GDK_z,          0,     0, }, "צ",   NULL,  "ץ",  FALSE, },
+  { { GDK_z,          GDK_h,          0,     0, }, "זש",  NULL,  NULL, FALSE, },
+  { { GDK_a,          GDK_y,          0,     0, }, "ײַ",   "אײַ",  NULL, FALSE, },
+  { { GDK_o,          GDK_y,          0,     0, }, "ױ",   "אױ",  NULL, FALSE, },
+  { { GDK_o,          GDK_i,          0,     0, }, "ױ",   "אױ",  NULL, FALSE, },
+  { { GDK_d,          GDK_j,          0,     0, }, "דזש", NULL,  NULL, FALSE, },
+  { { GDK_e,          GDK_y,          0,     0, }, "ײ",   "אײ",  NULL, FALSE, },
+  { { GDK_apostrophe, GDK_apostrophe, 0,     0, }, "“",   NULL,  NULL, TRUE,  },
+  { { GDK_comma,      GDK_comma,      0,     0, }, "„",   NULL,  NULL, TRUE,  },
+  { { GDK_k,          GDK_h,          0,     0, }, "כ",   NULL,  "ך",  FALSE, },
+  { { GDK_c,          GDK_h,          0,     0, }, "כ",   NULL,  "ך",  FALSE, },
+  { { GDK_u,          GDK_v,          0,     0, }, "וּװ",  "אוּװ", NULL, FALSE, },
+  { { GDK_u,          GDK_u,          0,     0, }, "וּו",  "אוּו", NULL, FALSE, },
+  { { GDK_u,          GDK_i,          0,     0, }, "ויִ",  "אויִ", NULL, FALSE, },
+  { { GDK_u,          GDK_y,          0,     0, }, "וּי",  "אוּי", NULL, FALSE, },
+  { { GDK_v,          GDK_u,          0,     0, }, "װוּ",  NULL,  NULL, FALSE, },
+  { { GDK_y,          GDK_i,          0,     0, }, "ייִ",  NULL,  NULL, FALSE, },
+  { { GDK_i,          GDK_i,          0,     0, }, "יִיִ",  "איִיִ", NULL, FALSE, },
+  { { GDK_i,          GDK_y,          0,     0, }, "יִי",  "איִי", NULL, FALSE, },
+  { { GDK_E,          GDK_i,          0,     0, }, "ײיִ",  "אײיִ", NULL, FALSE, },
+  { { GDK_i,          GDK_e,          0,     0, }, "יִע",  "איִע", NULL, FALSE, },
+  { { GDK_i,          GDK_a,          0,     0, }, "יִאַ",  "איִאַ", NULL, FALSE, },
+  { { GDK_i,          GDK_o,          0,     0, }, "יִאָ",  "איִאָ", NULL, FALSE, },
+  { { GDK_t,          GDK_s,          GDK_h, 0, }, "טש",  NULL,  NULL, FALSE, },
+};
+
+
+static const guint16 mim_compose_ignore[] = 
+{
+  GDK_Shift_L,
+  GDK_Shift_R,
+  GDK_Control_L,
+  GDK_Control_R,
+  GDK_Caps_Lock,
+  GDK_Shift_Lock,
+  GDK_Meta_L,
+  GDK_Meta_R,
+  GDK_Alt_L,
+  GDK_Alt_R,
+  GDK_Super_L,
+  GDK_Super_R,
+  GDK_Hyper_L,
+  GDK_Hyper_R,
+  GDK_Mode_switch,
+};
+
+
+static void
+clear_compose_buffer ()
+{
+  memset (compose_buffer, 0, sizeof (compose_buffer));
+  n_compose = 0;
+}
+
+
+/* handles null case */
+static void
+commit_preceding_possible_final_form (GtkIMContext *context, 
+                                      gboolean is_final)
+{
+  if (preceding_possible_final_form != NULL)
+    {
+      if (is_final) 
+        g_signal_emit_by_name (context, "commit", 
+                               preceding_possible_final_form->final);
+      else
+        g_signal_emit_by_name (context, "commit", 
+                               preceding_possible_final_form->normal);
+
+      preceding_possible_final_form = NULL;
+    }
+}
+
+/* returns the composed string iff keys exactly matches the compose
+ * sequence keys */
+static ComposeSequence *
+find_complete_compose_sequence (guint *keys)
+{
+  gint i, j;
+
+  for (i = 0;  i < G_N_ELEMENTS (mim_compose_seqs);  i++)
+    for (j = 0;  j <= MIM_MAX_COMPOSE_LEN;  j++)
+      {
+        if (keys[j] != mim_compose_seqs[i].keys[j])
+          break;
+        else if (keys[j] == 0 && keys[j] == mim_compose_seqs[i].keys[j])
+          return mim_compose_seqs + i;
+      }
+
+  return NULL;
+}
+
+
+/* returns the composed string iff keys is a substring thang of the compose
+ * sequence keys */
+static ComposeSequence *
+find_incomplete_compose_sequence (guint *keys)
+{
+  gint i, j;
+
+  for (i = 0;  i < G_N_ELEMENTS (mim_compose_seqs);  i++)
+    for (j = 0;  j <= MIM_MAX_COMPOSE_LEN;  j++)
+      {
+        if (keys[j] == 0 && mim_compose_seqs[i].keys[j] != 0)
+          return mim_compose_seqs + i;
+        else if (keys[j] != mim_compose_seqs[i].keys[j])
+          break;
+      }
+
+  return NULL;
+}
+
+
+static gchar *
+get_appropriate_string (ComposeSequence *comp_seq, gboolean is_initial)
+{
+  if (comp_seq == NULL)
+    return NULL;
+  else if (is_initial && comp_seq->initial != NULL)
+    return comp_seq->initial;
+  else
+    return comp_seq->normal;
+}
+
+
+/* is this a character that could appear in a mim word */
+static gboolean
+is_mim_word_character (gunichar uc)
+{
+  return (((uc >= 0x0590 && uc <= 0x5ff) || (uc >= 0xfb1d && uc <= 0xfb4f))
+          && g_unichar_isdefined (uc) && ! g_unichar_ispunct (uc));
+
+}
+
+
+static gboolean
+at_initial_position (GtkIMContext *context)
+{
+  gchar *text;
+  gchar *prevp;
+  gint cursor_index;
+  gunichar uc;
+
+  if (! gtk_im_context_get_surrounding (context, &text, &cursor_index))
+    return FALSE;
+
+  prevp = g_utf8_find_prev_char (text, text + cursor_index);
+  if (prevp == NULL)
+    return TRUE;
+
+  uc = g_utf8_get_char_validated (prevp, text + cursor_index - prevp);
+  g_return_val_if_fail (uc != (gunichar)(-1) && uc != (gunichar)(-2), 
+                        FALSE);
+
+  if (is_mim_word_character (uc))
+    return FALSE;
+  else
+    return TRUE;
+}
+
+
+static void     
+mim_get_preedit_string (GtkIMContext   *context,
+                            gchar         **str,
+                            PangoAttrList **attrs,
+                            gint           *cursor_pos)
+{
+  ComposeSequence *comp_seq;
+  gboolean is_initial;
+  gchar *string;
+  gint len;
+
+  is_initial = at_initial_position (context) 
+               && preceding_possible_final_form == NULL;
+
+  comp_seq = find_complete_compose_sequence (compose_buffer);
+  if (comp_seq == NULL)
+    string = "";
+  else
+    string = get_appropriate_string (comp_seq, is_initial);
+
+  if (preceding_possible_final_form != NULL)
+    *str = g_strdup_printf ("%s%s", preceding_possible_final_form->normal, 
+                            string);
+  else
+    *str = g_strdup (string);
+
+  len = strlen (*str);
+
+  if (attrs)
+    {
+      *attrs = pango_attr_list_new ();
+
+      if (len != 0)
+        {
+          PangoAttribute *attr;
+          attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+          attr->start_index = 0;
+          attr->end_index = len;
+          pango_attr_list_insert (*attrs, attr);
+        }
+    }
+
+  if (cursor_pos)
+    *cursor_pos = len;
+}
+
+
+static void
+mim_reset (GtkIMContext *context)
+{
+  clear_compose_buffer ();
+  preceding_possible_final_form = NULL;
+  g_signal_emit_by_name (context, "preedit-changed");
+}
+
+
+static gboolean
+no_sequence_matches (GtkIMContext *context, GdkEventKey *event)
+{
+  gunichar uc;
+  gchar buf[7];
+
+  uc = gdk_keyval_to_unicode (event->keyval);
+  if (uc != 0)
+    {
+      buf[g_unichar_to_utf8 (uc, buf)] = '\0';
+      g_signal_emit_by_name (context, "commit", buf);
+      g_signal_emit_by_name (context, "preedit-changed");
+
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
+
+static gboolean
+mim_filter_keypress (GtkIMContext *context,
+                         GdkEventKey  *event)
+{
+  ComposeSequence *comp_seq;
+  gboolean is_initial;
+  gint i;
+
+  if (event->type == GDK_KEY_RELEASE)
+    return FALSE;
+
+  /* don't filter key events with accelerator modifiers held down */
+  if (event->state 
+      & (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK))
+    return FALSE;
+
+  for (i = 0;  i < G_N_ELEMENTS (mim_compose_ignore);  i++)
+    if (event->keyval == mim_compose_ignore[i])
+      return FALSE;
+
+  is_initial = at_initial_position (context) 
+               && preceding_possible_final_form == NULL;
+
+  /* '|' commits what we have */
+  if (event->keyval == GDK_bar && (n_compose > 0 
+                                   || preceding_possible_final_form != NULL))
+    {
+      commit_preceding_possible_final_form (context, FALSE); /* non-final */
+
+      comp_seq = find_complete_compose_sequence (compose_buffer);
+      if (comp_seq != NULL)
+        {
+          g_signal_emit_by_name (context, "commit", 
+                                 get_appropriate_string (comp_seq, is_initial));
+          clear_compose_buffer ();
+        }
+
+      g_signal_emit_by_name (context, "preedit-changed");
+      return TRUE;
+    }
+
+  compose_buffer[n_compose] = event->keyval;
+  n_compose++;
+
+  if (find_incomplete_compose_sequence (compose_buffer) != NULL)
+    {
+      g_signal_emit_by_name (context, "preedit-changed");
+      return TRUE;
+    }
+
+  comp_seq = find_complete_compose_sequence (compose_buffer);
+  if (comp_seq != NULL)
+    {
+      if (comp_seq->final != NULL) /* has a final form, don't commit yet */
+        {
+          commit_preceding_possible_final_form (context, 
+                                                comp_seq->is_terminator);
+          clear_compose_buffer ();
+          preceding_possible_final_form = comp_seq;
+          g_signal_emit_by_name (context, "preedit-changed");
+          return TRUE;
+        }
+      else
+        {
+          commit_preceding_possible_final_form (context, 
+                                                comp_seq->is_terminator);
+          g_signal_emit_by_name (context, "commit", 
+                                 get_appropriate_string (comp_seq, is_initial));
+          clear_compose_buffer ();
+          g_signal_emit_by_name (context, "preedit-changed");
+          return TRUE;
+        }
+    }
+
+  /* if we reach this point, the sequence *with the key just pressed*
+   * cannot be a complete or incomplete match, so: commit the old sequence,
+   * then reprocess this key */
+
+  n_compose--;
+  compose_buffer[n_compose] = 0;
+
+  if (n_compose > 0)
+    {
+      comp_seq = find_complete_compose_sequence (compose_buffer);
+      commit_preceding_possible_final_form (context, comp_seq->is_terminator);
+      if (comp_seq->final != NULL)
+        {
+          clear_compose_buffer ();
+          preceding_possible_final_form = comp_seq;
+        }
+      else
+        {
+          g_signal_emit_by_name (context, "commit", 
+                                 get_appropriate_string (comp_seq, is_initial));
+          clear_compose_buffer ();
+          g_signal_emit_by_name (context, "preedit-changed");
+        }
+
+      return mim_filter_keypress (context, event);
+    }
+  else
+    commit_preceding_possible_final_form (context, TRUE);
+
+  return no_sequence_matches (context, event);
+}
+
+
+static void
+mim_class_init (GtkIMContextClass *clazz)
+{
+  clazz->filter_keypress = mim_filter_keypress;
+  clazz->get_preedit_string = mim_get_preedit_string;
+  clazz->reset = mim_reset;
+}
+
+static void
+mim_init (GtkIMContext *im_context)
+{
+}
+
+
+static void
+mim_register_type (GTypeModule *module)
+{
+  static const GTypeInfo object_info =
+  {
+    sizeof (GtkIMContextClass),
+    (GBaseInitFunc) NULL,
+    (GBaseFinalizeFunc) NULL,
+    (GClassInitFunc) mim_class_init,
+    NULL,           /* class_finalize */
+    NULL,           /* class_data */
+    sizeof (GtkIMContext),
+    0,
+    (GInstanceInitFunc) mim_init,
+  };
+
+  type_mim = 
+    g_type_module_register_type (module,
+                                 GTK_TYPE_IM_CONTEXT,
+                                 "GtkIMContextYiddishNoah",
+                                 &object_info, 0);
+}
+
+
+static const GtkIMContextInfo mim_info =
+{
+  "mim",              /* ID */
+  N_("MiM"),          /* Human readable name */
+  GETTEXT_PACKAGE,    /* Translation domain */
+  LOCALEDIR,          /* Dir for bindtextdomain */
+  "zh_CN",            /* Languages for which this module is the default */
+};
+
+
+static const GtkIMContextInfo *info_list[] = 
+{
+  &mim_info,
+};
+
+
+void
+im_module_init (GTypeModule *module)
+{
+  mim_register_type (module);
+}
+
+void
+im_module_exit ()
+{
+}
+
+void 
+im_module_list (const GtkIMContextInfo ***contexts, gint *n_contexts)
+{
+  *contexts = info_list;
+  *n_contexts = G_N_ELEMENTS (info_list);
+}
+
+
+GtkIMContext *
+im_module_create (const gchar *context_id)
+{
+  if (strcmp (context_id, "mim-n") == 0)
+    return GTK_IM_CONTEXT (g_object_new (type_mim, NULL));
+  else
+    return NULL;
+}
+
+
+